diff options
Diffstat (limited to 'sql/sql_audit.cc')
-rw-r--r-- | sql/sql_audit.cc | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/sql/sql_audit.cc b/sql/sql_audit.cc new file mode 100644 index 00000000..3e9379eb --- /dev/null +++ b/sql/sql_audit.cc @@ -0,0 +1,487 @@ +/* Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +#include "mariadb.h" +#include "sql_priv.h" +#include "mysqld.h" +#include "sql_audit.h" + +extern int initialize_audit_plugin(st_plugin_int *plugin); +extern int finalize_audit_plugin(st_plugin_int *plugin); + +#ifndef EMBEDDED_LIBRARY + +struct st_mysql_event_generic +{ + unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE]; + unsigned int event_class; + const void *event; +}; + +unsigned long mysql_global_audit_mask[MYSQL_AUDIT_CLASS_MASK_SIZE]; + +static mysql_mutex_t LOCK_audit_mask; + + +static inline +void set_audit_mask(unsigned long *mask, uint event_class) +{ + mask[0]= 1; + mask[0]<<= event_class; +} + +static inline +void add_audit_mask(unsigned long *mask, const unsigned long *rhs) +{ + mask[0]|= rhs[0]; +} + +static inline +bool check_audit_mask(const unsigned long *lhs, + const unsigned long *rhs) +{ + return !(lhs[0] & rhs[0]); +} + + +/** + Acquire and lock any additional audit plugins as required + + @param[in] thd + @param[in] plugin + @param[in] arg + + @retval FALSE Always +*/ + +static my_bool acquire_plugins(THD *thd, plugin_ref plugin, void *arg) +{ + ulong *event_class_mask= (ulong*) arg; + st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *); + + /* Check if this plugin is interested in the event */ + if (check_audit_mask(data->class_mask, event_class_mask)) + return 0; + + /* + Check if this plugin may already be registered. This will fail to + acquire a newly installed plugin on a specific corner case where + one or more event classes already in use by the calling thread + are an event class of which the audit plugin has interest. + */ + if (!check_audit_mask(data->class_mask, thd->audit_class_mask)) + return 0; + + /* Check if we need to initialize the array of acquired plugins */ + if (unlikely(!thd->audit_class_plugins.buffer)) + { + /* specify some reasonable initialization defaults */ + my_init_dynamic_array(PSI_INSTRUMENT_ME, &thd->audit_class_plugins, + sizeof(plugin_ref), 16, 16, MYF(0)); + } + + /* lock the plugin and add it to the list */ + plugin= my_plugin_lock(NULL, plugin); + insert_dynamic(&thd->audit_class_plugins, (uchar*) &plugin); + + return 0; +} + + +/** + @brief Acquire audit plugins + + @param[in] thd MySQL thread handle + @param[in] event_class Audit event class + + @details Ensure that audit plugins interested in given event + class are locked by current thread. +*/ +void mysql_audit_acquire_plugins(THD *thd, ulong *event_class_mask) +{ + DBUG_ENTER("mysql_audit_acquire_plugins"); + DBUG_ASSERT(thd); + DBUG_ASSERT(!check_audit_mask(mysql_global_audit_mask, event_class_mask)); + + if (check_audit_mask(thd->audit_class_mask, event_class_mask)) + { + plugin_foreach(thd, acquire_plugins, MYSQL_AUDIT_PLUGIN, event_class_mask); + add_audit_mask(thd->audit_class_mask, event_class_mask); + thd->audit_plugin_version= global_plugin_version; + } + DBUG_VOID_RETURN; +} + + +/** + Check if there were changes in the state of plugins + so we need to do the mysql_audit_release asap. + + @param[in] thd + +*/ + +my_bool mysql_audit_release_required(THD *thd) +{ + return thd && (thd->audit_plugin_version != global_plugin_version); +} + + +/** + Release any resources associated with the current thd. + + @param[in] thd + +*/ + +void mysql_audit_release(THD *thd) +{ + plugin_ref *plugins, *plugins_last; + + if (!thd || !(thd->audit_class_plugins.elements)) + return; + + plugins= (plugin_ref*) thd->audit_class_plugins.buffer; + plugins_last= plugins + thd->audit_class_plugins.elements; + for (; plugins < plugins_last; plugins++) + { + st_mysql_audit *data= plugin_data(*plugins, struct st_mysql_audit *); + + /* Check to see if the plugin has a release method */ + if (!(data->release_thd)) + continue; + + /* Tell the plugin to release its resources */ + data->release_thd(thd); + } + + /* Now we actually unlock the plugins */ + plugin_unlock_list(NULL, (plugin_ref*) thd->audit_class_plugins.buffer, + thd->audit_class_plugins.elements); + + /* Reset the state of thread values */ + reset_dynamic(&thd->audit_class_plugins); + bzero(thd->audit_class_mask, sizeof(thd->audit_class_mask)); + thd->audit_plugin_version= -1; +} + + +/** + Initialize thd variables used by Audit + + @param[in] thd + +*/ + +void mysql_audit_init_thd(THD *thd) +{ + bzero(&thd->audit_class_plugins, sizeof(thd->audit_class_plugins)); + bzero(thd->audit_class_mask, sizeof(thd->audit_class_mask)); +} + + +/** + Free thd variables used by Audit + + @param[in] thd + @param[in] plugin + @param[in] arg + + @retval FALSE Always +*/ + +void mysql_audit_free_thd(THD *thd) +{ + mysql_audit_release(thd); + DBUG_ASSERT(thd->audit_class_plugins.elements == 0); + delete_dynamic(&thd->audit_class_plugins); +} + +#ifdef HAVE_PSI_INTERFACE +static PSI_mutex_key key_LOCK_audit_mask; + +static PSI_mutex_info all_audit_mutexes[]= +{ + { &key_LOCK_audit_mask, "LOCK_audit_mask", PSI_FLAG_GLOBAL} +}; + +static void init_audit_psi_keys(void) +{ + const char* category= "sql"; + int count; + + if (PSI_server == NULL) + return; + + count= array_elements(all_audit_mutexes); + PSI_server->register_mutex(category, all_audit_mutexes, count); +} +#endif /* HAVE_PSI_INTERFACE */ + +/** + Initialize Audit global variables +*/ + +void mysql_audit_initialize() +{ +#ifdef HAVE_PSI_INTERFACE + init_audit_psi_keys(); +#endif + + mysql_mutex_init(key_LOCK_audit_mask, &LOCK_audit_mask, MY_MUTEX_INIT_FAST); + bzero(mysql_global_audit_mask, sizeof(mysql_global_audit_mask)); +} + + +/** + Finalize Audit global variables +*/ + +void mysql_audit_finalize() +{ + mysql_mutex_destroy(&LOCK_audit_mask); +} + + +/** + Initialize an Audit plug-in + + @param[in] plugin + + @retval FALSE OK + @retval TRUE There was an error. +*/ + +int initialize_audit_plugin(st_plugin_int *plugin) +{ + st_mysql_audit *data= (st_mysql_audit*) plugin->plugin->info; + + if (!data->event_notify || !data->class_mask[0]) + { + sql_print_error("Plugin '%s' has invalid data.", + plugin->name.str); + return 1; + } + + if (plugin->plugin->init && plugin->plugin->init(NULL)) + { + sql_print_error("Plugin '%s' init function returned error.", + plugin->name.str); + return 1; + } + + /* Make the interface info more easily accessible */ + plugin->data= plugin->plugin->info; + + /* Add the bits the plugin is interested in to the global mask */ + mysql_mutex_lock(&LOCK_audit_mask); + add_audit_mask(mysql_global_audit_mask, data->class_mask); + mysql_mutex_unlock(&LOCK_audit_mask); + + /* + Pre-acquire the newly inslalled audit plugin for events that + may potentially occur further during INSTALL PLUGIN. + + When audit event is triggered, audit subsystem acquires interested + plugins by walking through plugin list. Evidently plugin list + iterator protects plugin list by acquiring LOCK_plugin, see + plugin_foreach_with_mask(). + + On the other hand [UN]INSTALL PLUGIN is acquiring LOCK_plugin + rather for a long time. + + When audit event is triggered during [UN]INSTALL PLUGIN, plugin + list iterator acquires the same lock (within the same thread) + second time. + + This hack should be removed when LOCK_plugin is fixed so it + protects only what it supposed to protect. + + See also mysql_install_plugin() and mysql_uninstall_plugin() + */ + THD *thd= current_thd; + if (thd) + { + acquire_plugins(thd, plugin_int_to_ref(plugin), data->class_mask); + add_audit_mask(thd->audit_class_mask, data->class_mask); + } + + return 0; +} + + +/** + Performs a bitwise OR of the installed plugins event class masks + + @param[in] thd + @param[in] plugin + @param[in] arg + + @retval FALSE always +*/ +static my_bool calc_class_mask(THD *thd, plugin_ref plugin, void *arg) +{ + st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *); + if ((data= plugin_data(plugin, struct st_mysql_audit *))) + add_audit_mask((unsigned long *) arg, data->class_mask); + return 0; +} + + +/** + Finalize an Audit plug-in + + @param[in] plugin + + @retval FALSE OK + @retval TRUE There was an error. +*/ +int finalize_audit_plugin(st_plugin_int *plugin) +{ + unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE]; + + if (plugin->plugin->deinit && plugin->plugin->deinit(NULL)) + { + DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.", + plugin->name.str)); + DBUG_EXECUTE("finalize_audit_plugin", return 1; ); + } + + plugin->data= NULL; + bzero(&event_class_mask, sizeof(event_class_mask)); + + /* Iterate through all the installed plugins to create new mask */ + + /* + LOCK_audit_mask/LOCK_plugin order is not fixed, but serialized with table + lock on mysql.plugin. + */ + mysql_mutex_lock(&LOCK_audit_mask); + plugin_foreach(current_thd, calc_class_mask, MYSQL_AUDIT_PLUGIN, + &event_class_mask); + + /* Set the global audit mask */ + bmove(mysql_global_audit_mask, event_class_mask, sizeof(event_class_mask)); + mysql_mutex_unlock(&LOCK_audit_mask); + + return 0; +} + + +/** + Dispatches an event by invoking the plugin's event_notify method. + + @param[in] thd + @param[in] plugin + @param[in] arg + + @retval FALSE always +*/ + +static my_bool plugins_dispatch(THD *thd, plugin_ref plugin, void *arg) +{ + const struct st_mysql_event_generic *event_generic= + (const struct st_mysql_event_generic *) arg; + st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *); + + /* Check to see if the plugin is interested in this event */ + if (!check_audit_mask(data->class_mask, event_generic->event_class_mask)) + data->event_notify(thd, event_generic->event_class, event_generic->event); + + return 0; +} + + +/** + Distributes an audit event to plug-ins + + @param[in] thd + @param[in] event_class + @param[in] event +*/ + +void mysql_audit_notify(THD *thd, uint event_class, const void *event) +{ + struct st_mysql_event_generic event_generic; + event_generic.event_class= event_class; + event_generic.event= event; + set_audit_mask(event_generic.event_class_mask, event_class); + /* + Check if we are doing a slow global dispatch. This event occurs when + thd == NULL as it is not associated with any particular thread. + */ + if (unlikely(!thd)) + { + plugin_foreach(thd, plugins_dispatch, MYSQL_AUDIT_PLUGIN, &event_generic); + } + else + { + plugin_ref *plugins, *plugins_last; + + mysql_audit_acquire_plugins(thd, event_generic.event_class_mask); + + /* Use the cached set of audit plugins */ + plugins= (plugin_ref*) thd->audit_class_plugins.buffer; + plugins_last= plugins + thd->audit_class_plugins.elements; + + for (; plugins < plugins_last; plugins++) + plugins_dispatch(thd, *plugins, &event_generic); + } +} + + +#else /* EMBEDDED_LIBRARY */ + + +void mysql_audit_acquire_plugins(THD *thd, ulong *event_class_mask) +{ +} + + +void mysql_audit_initialize() +{ +} + + +void mysql_audit_finalize() +{ +} + + +int initialize_audit_plugin(st_plugin_int *plugin) +{ + return 1; +} + + +int finalize_audit_plugin(st_plugin_int *plugin) +{ + return 0; +} + + +void mysql_audit_release(THD *thd) +{ +} + +void mysql_audit_init_thd(THD *thd) +{ +} + +void mysql_audit_free_thd(THD *thd) +{ +} + +#endif /* EMBEDDED_LIBRARY */ |