mirror of
https://github.com/MariaDB/server.git
synced 2025-01-30 18:41:56 +01:00
95963dd20a
The types mysql_event_general/mysql_event_connection are being cast to the incompatible type mysql_event. The way mysql_event and the other types are designed are prone to strict aliasing violations and can break things depending on how compilers optimizes this code. This patch fixes audit interface, so it confirms to strict- aliasing rules. It introduces incompatible changes to audit interface: - mysql_event type has been removed; - event_class has been removed from mysql_event_generic and mysql_event_connection types; - st_mysql_audit::event_notify() second argument is event_class; - st_mysql_audit::event_notify() third argument is event of type (const void *). "Writing Audit Plugins" section of manual should be updated: http://dev.mysql.com/doc/refman/5.5/en/writing-audit-plugins.html include/mysql/plugin_audit.h: event_class has been moved out of mysql_event types. include/mysql/plugin_audit.h.pp: event_class has been moved out of mysql_event types. plugin/audit_null/audit_null.c: event_class has been moved out of mysql_event types. sql/sql_audit.cc: event_class has been moved out of mysql_event types.
529 lines
13 KiB
C++
529 lines
13 KiB
C++
/* Copyright (C) 2007 MySQL AB, 2008-2009 Sun Microsystems, Inc
|
|
|
|
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
|
|
|
#include "sql_priv.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 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 void event_class_dispatch(THD *thd, unsigned int event_class,
|
|
const void *event);
|
|
|
|
|
|
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]);
|
|
}
|
|
|
|
|
|
typedef void (*audit_handler_t)(THD *thd, uint event_subtype, va_list ap);
|
|
|
|
/**
|
|
MYSQL_AUDIT_GENERAL_CLASS handler
|
|
|
|
@param[in] thd
|
|
@param[in] event_subtype
|
|
@param[in] error_code
|
|
@param[in] ap
|
|
|
|
*/
|
|
|
|
static void general_class_handler(THD *thd, uint event_subtype, va_list ap)
|
|
{
|
|
mysql_event_general event;
|
|
event.event_subclass= event_subtype;
|
|
event.general_error_code= va_arg(ap, int);
|
|
event.general_thread_id= thd ? thd->thread_id : 0;
|
|
event.general_time= va_arg(ap, time_t);
|
|
event.general_user= va_arg(ap, const char *);
|
|
event.general_user_length= va_arg(ap, unsigned int);
|
|
event.general_command= va_arg(ap, const char *);
|
|
event.general_command_length= va_arg(ap, unsigned int);
|
|
event.general_query= va_arg(ap, const char *);
|
|
event.general_query_length= va_arg(ap, unsigned int);
|
|
event.general_charset= va_arg(ap, struct charset_info_st *);
|
|
event.general_rows= (unsigned long long) va_arg(ap, ha_rows);
|
|
event_class_dispatch(thd, MYSQL_AUDIT_GENERAL_CLASS, &event);
|
|
}
|
|
|
|
|
|
static void connection_class_handler(THD *thd, uint event_subclass, va_list ap)
|
|
{
|
|
mysql_event_connection event;
|
|
event.event_subclass= event_subclass;
|
|
event.status= va_arg(ap, int);
|
|
event.thread_id= va_arg(ap, unsigned long);
|
|
event.user= va_arg(ap, const char *);
|
|
event.user_length= va_arg(ap, unsigned int);
|
|
event.priv_user= va_arg(ap, const char *);
|
|
event.priv_user_length= va_arg(ap, unsigned int);
|
|
event.external_user= va_arg(ap, const char *);
|
|
event.external_user_length= va_arg(ap, unsigned int);
|
|
event.proxy_user= va_arg(ap, const char *);
|
|
event.proxy_user_length= va_arg(ap, unsigned int);
|
|
event.host= va_arg(ap, const char *);
|
|
event.host_length= va_arg(ap, unsigned int);
|
|
event.ip= va_arg(ap, const char *);
|
|
event.ip_length= va_arg(ap, unsigned int);
|
|
event.database= va_arg(ap, const char *);
|
|
event.database_length= va_arg(ap, unsigned int);
|
|
event_class_dispatch(thd, MYSQL_AUDIT_CONNECTION_CLASS, &event);
|
|
}
|
|
|
|
|
|
static audit_handler_t audit_handlers[] =
|
|
{
|
|
general_class_handler, connection_class_handler
|
|
};
|
|
|
|
static const uint audit_handlers_count=
|
|
(sizeof(audit_handlers) / sizeof(audit_handler_t));
|
|
|
|
|
|
/**
|
|
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)
|
|
{
|
|
uint event_class= *(uint*) arg;
|
|
unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
|
|
st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
|
|
|
|
set_audit_mask(event_class_mask, event_class);
|
|
|
|
/* 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(&thd->audit_class_plugins,
|
|
sizeof(plugin_ref), 16, 16);
|
|
}
|
|
|
|
/* 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, uint event_class)
|
|
{
|
|
unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
|
|
DBUG_ENTER("mysql_audit_acquire_plugins");
|
|
set_audit_mask(event_class_mask, event_class);
|
|
if (thd && !check_audit_mask(mysql_global_audit_mask, event_class_mask) &&
|
|
check_audit_mask(thd->audit_class_mask, event_class_mask))
|
|
{
|
|
plugin_foreach(thd, acquire_plugins, MYSQL_AUDIT_PLUGIN, &event_class);
|
|
add_audit_mask(thd->audit_class_mask, event_class_mask);
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/**
|
|
Notify the audit system of an event
|
|
|
|
@param[in] thd
|
|
@param[in] event_class
|
|
@param[in] event_subtype
|
|
@param[in] error_code
|
|
|
|
*/
|
|
|
|
void mysql_audit_notify(THD *thd, uint event_class, uint event_subtype, ...)
|
|
{
|
|
va_list ap;
|
|
audit_handler_t *handlers= audit_handlers + event_class;
|
|
DBUG_ASSERT(event_class < audit_handlers_count);
|
|
mysql_audit_acquire_plugins(thd, event_class);
|
|
va_start(ap, event_subtype);
|
|
(*handlers)(thd, event_subtype, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
|
|
/**
|
|
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));
|
|
}
|
|
|
|
|
|
/**
|
|
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->class_mask || !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);
|
|
|
|
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;
|
|
unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
|
|
st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
|
|
|
|
set_audit_mask(event_class_mask, event_generic->event_class);
|
|
|
|
/* Check to see if the plugin is interested in this event */
|
|
if (check_audit_mask(data->class_mask, event_class_mask))
|
|
return 0;
|
|
|
|
/* Actually notify the plugin */
|
|
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
|
|
*/
|
|
|
|
static void event_class_dispatch(THD *thd, unsigned int event_class,
|
|
const void *event)
|
|
{
|
|
struct st_mysql_event_generic event_generic;
|
|
event_generic.event_class= event_class;
|
|
event_generic.event= event;
|
|
/*
|
|
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;
|
|
|
|
/* 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, uint event_class)
|
|
{
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
}
|
|
|
|
|
|
#endif /* EMBEDDED_LIBRARY */
|