diff --git a/README.txt b/README.txt index 7f89ded..68d3fa1 100644 --- a/README.txt +++ b/README.txt @@ -1,104 +1,52 @@ -Audit Plugin +MySQL AUDIT Plugin +=================== -This is a release of Audit Plugin for MySQL 5.1 and 5.5. - -Audit Plugin is brought to you by McAfee, Inc (www.mcafee.com). +MySQL AUDIT Plugin is a MySQL plugin from McAfee providing audit capabilities for MySQL, +designed with an emphasis on security and audit requirements. The plugin may be used +as a stand alone audit solution or configured to feed data to external monitoring tools. -==== INSTALLATION ===== +Installation and Configuration +------------------------------ -Make sure to download the proper binary distribution. There are separate binaries for MySQL 5.1 and 5.5 according -to platform (32 or 64 bit). +Please check out our wiki on github for detailed installation and configuration instructions: -Audit Plugin is available in the binary distribution under the lib dir. File name: libaudit_plugin.so. -To install Audit Plugin, copy libaudit_plugin.so to the plugin_dir (for example /usr/lib/mysql/plugin or /usr/lib64/mysql/plugin) of MySQL. - -To see the configured plugin dir login to MySQL and issue the following command: - -show global variables like 'plugin_dir'; - -There are 2 options for installing the plugin via plugin-load configuration option or by issuing the -INSTALL PLUGIN statement. - -* Installing via: plugin-load - -Add to the MySQL option file (my.cnf) at the [mysqld] section the option: -plugin-load=AUDIT=libaudit_plugin.so - -Restart the mysqld server for the changes to take effect. - -* Installing via: INSTALL PLUGIN - -You will need to issue the following sql command to install the plugin: - -INSTALL PLUGIN AUDIT SONAME 'libaudit_plugin.so'; - -A restart to the mysqld server is not necessary. - -Note: On production systems, McAfee recommends using the plugin-load option for installing -the audit plugin. - -More info on installing MySQL plugins is available at: -http://dev.mysql.com/doc/refman/5.1/en/plugin-installing-uninstalling.html - -===== VERIFICATION ===== - -To check if the plugin is installed successfully you can issue the following command, which will show all installed plugins: - -show plugins; - -The Audit plugin will show up with the name AUDIT. - -Additionally you can verify the version of the Audit Plugin by running the following command: - -show global status like 'AUDIT_version'; +https://github.com/mcafee/mysql-audit/wiki -===== CONFIGURATION ===== +Reporting Bugs +------------------------------ -By default, after installation the Audit Plugin doesn't log activity. You must explicitly enable -the type of logging desired. Configuration is done through the use of MySQL system variables. -Audit Plugin system variables can be set at server startup using options on the command line or in an option file. -Additionally, the Audit Plugin system variables can be changed dynamically while the server is running -by means of the SET statement. - -Available Audit Plugin command line options: - ---audit-json-file AUDIT plugin json log file Enable|Disable ---audit-json-file-sync=# - AUDIT plugin json log file sync period. If the value of - this variable is greater than 0, audit log will sync to - disk after every audit_json_file_sync writes. ---audit-json-log-file=name - AUDIT plugin json log file name ---audit-json-socket AUDIT plugin json log unix socket Enable|Disable ---audit-json-socket-name=name - AUDIT plugin json log unix socket name ---audit-uninstall-plugin - AUDIT uninstall plugin Enable|Disable. - If disabled attempts to uninstall the AUDIT plugin via the sql UNINSTALL command will fail. - Provides added security from uninstalling the plugin. Also protection from - CVE-2010-1621 (http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-1621) - affecting versions upto 5.1.46. - - -===== REPORTING BUGS ===== +Please report bugs to: https://github.com/mcafee/mysql-audit/issues Please describe the problem verbosely. Try to see if it reproduces and include a detailed description on how to reproduce. Make sure to include your MySQL Server version and Audit Plugin version. -To print MySQL Server version log into MySQL and execute the command: status. +To print MySQL Server version: log into MySQL and execute the command: -Please include with the bug the log files: - -* mysql-audit.log -* MySQL error log: log file location can be queried by running the following - command: show global variables like 'log_error' + status + +Please include with the bug the MySQL error log. +Log file location can be queried by running the following command: + + show global variables like 'log_error' + + +Source Code +------------------------------- +Source code of AUDIT plugin is available at: https://github.com/mcafee/mysql-audit -===== LICENSE ===== +License +------------------------------- +Copyright (C) 2012 McAfee, Inc. -The software included in this product contains copyrighted software that is licensed under the GPL Version 2. -See COPYING file for a copy of the GPL Version 2 license. -Source code is available at: https://github.com/mcafee/mysql-audit +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. + +See COPYING file for a copy of the GPL Version 2 license. diff --git a/configure.ac b/configure.ac index 18febc8..8aa3da4 100644 --- a/configure.ac +++ b/configure.ac @@ -95,6 +95,9 @@ CXXFLAGS="-fno-implicit-templates -fno-exceptions -fno-rtti " #add pthread libs LIBS="$LIBS -lpthread" + + + #make sure we have const AC_C_CONST AC_TYPE_SIZE_T @@ -113,11 +116,13 @@ echo "Version: $MYSQL_AUDIT_PLUGIN_VERSION-$MYSQL_AUDIT_PLUGIN_REVISION" CPPFLAGS="$CPPFLAGS -DMYSQL_AUDIT_PLUGIN_VERSION='\"$MYSQL_AUDIT_PLUGIN_VERSION\"'" CPPFLAGS="$CPPFLAGS -DMYSQL_AUDIT_PLUGIN_REVISION='\"$MYSQL_AUDIT_PLUGIN_REVISION\"'" + #subst the relevant variables AC_SUBST(CPPFLAGS) AC_SUBST(CXXLAGS) AC_SUBST(CLAGS) + AC_CONFIG_FILES([Makefile src/Makefile yajl/Makefile diff --git a/include/audit_handler.h b/include/audit_handler.h index 9249845..65ee0fd 100644 --- a/include/audit_handler.h +++ b/include/audit_handler.h @@ -33,6 +33,7 @@ typedef struct _THDPRINTED { const char * retrieve_command (THD * thd); typedef size_t OFFSET; +#define MAX_COM_STATUS_VARS_RECORDS 512 /** * The struct usd to hold offsets. We should have one per version. @@ -158,7 +159,7 @@ public: return thd->query; #endif } - //virtual char * retrieve_command (THD * thd); + }; diff --git a/src/Makefile.am b/src/Makefile.am index cb05d4d..b084ea2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -18,11 +18,11 @@ INCLUDES = $(MYSQL_INC) $(YAJL_INC) $(UDIS_INC) pkgplugindir = $(MYSQL_PLUGIN_DIR) pkgplugin_LTLIBRARIES = libaudit_plugin.la -libaudit_plugin_la_LDFLAGS = -module +libaudit_plugin_la_LDFLAGS = -module -Wl,--version-script=MySQLPlugin.map libaudit_plugin_la_SOURCES = hot_patch.cc audit_plugin.cc audit_handler.cc -libaudit_plugin_la_LIBADD = $(top_srcdir)/yajl/src/libyajl.la $(top_srcdir)/udis86/libudis86/libudis86.la $(MYSQL_LIBSERVICES) +libaudit_plugin_la_LIBADD = $(top_srcdir)/yajl/src/libyajl.la $(top_srcdir)/udis86/libudis86/libudis86.la $(MYSQL_LIBSERVICES) diff --git a/src/MySQLPlugin.map b/src/MySQLPlugin.map new file mode 100644 index 0000000..ec12f27 --- /dev/null +++ b/src/MySQLPlugin.map @@ -0,0 +1,10 @@ +{ + global: + _mysql_plugin_declarations_; + _mysql_plugin_interface_version_; + _mysql_sizeof_struct_st_plugin_; + audit_plugin_so_init; + thd_alloc_service; + + local: *; +}; diff --git a/src/audit_handler.cc b/src/audit_handler.cc index 19493e8..3308cd3 100644 --- a/src/audit_handler.cc +++ b/src/audit_handler.cc @@ -78,52 +78,6 @@ const char * Audit_formatter::retrive_object_type (TABLE_LIST *pObj) return "TABLE"; } -const char * retrieve_command (THD * thd) -{ - const char *cmd; - SHOW_VAR *com_status_vars; - int sv_idx =0; - int command = Audit_formatter::thd_inst_command(thd); - if (command < 0 || command > COM_END) - { - command = COM_END; - } - const int sql_command = thd_sql_command(thd); - while (strcmp (status_vars[sv_idx].name,"Com") !=0 && status_vars[sv_idx].name != NullS) - { - sv_idx ++; - } - if (strcmp (status_vars[sv_idx].name,"Com")==0) - { - int status_vars_index =0; - com_status_vars = (SHOW_VAR*)status_vars[sv_idx].value; - size_t initial_offset = (size_t) com_status_vars[0].value; - while (com_status_vars[status_vars_index].value != (char*) (sizeof (ulong) * (sql_command + 1) + initial_offset) - && com_status_vars[status_vars_index].name != NullS) - { - status_vars_index ++; - } - if (com_status_vars[status_vars_index].value != NullS) - { - cmd = com_status_vars[status_vars_index].name; - } - else - { - cmd = command_name[command].str; - } - } - else - { - cmd = "UNKNOWN"; - } - Security_context * sctx = Audit_formatter::thd_inst_main_security_ctx(thd); - if (strcmp (cmd, "Connect") ==0 && (sctx->priv_user == NULL || *sctx->priv_user ==0x0)) - { - cmd = "Failed Login"; - } - return cmd; -} - void Audit_handler::stop_all() { @@ -363,6 +317,15 @@ static inline void yajl_add_uint64(yajl_gen gen, const char * name, uint64 num) snprintf(buf, max_int64_str_len, "%llu", num); yajl_add_string_val(gen, name, buf); } +static inline void yajl_add_obj( yajl_gen gen, const char *db,const char* ptype,const char * name =NULL) +{ + yajl_add_string_val(gen, "db", db); + if (name) + { + yajl_add_string_val(gen, "name", name); + } + yajl_add_string_val(gen, "obj_type",ptype); +} //void Audit_file_handler::print_sleep (THD *thd, int delay_ms) //{ @@ -388,7 +351,7 @@ ssize_t Audit_json_formatter::event_format(ThdSesData* pThdData, IWriter * write unsigned long thdid = thd_get_thread_id(pThdData->getTHD()); query_id_t qid = thd_inst_query_id(pThdData->getTHD()); int command = thd_inst_command(pThdData->getTHD()); - const char *cmd = pThdData->getCmdName(); + Security_context * sctx = thd_inst_main_security_ctx(pThdData->getTHD()); @@ -407,7 +370,31 @@ ssize_t Audit_json_formatter::event_format(ThdSesData* pThdData, IWriter * write yajl_add_string_val(gen, "priv_user", sctx->priv_user); yajl_add_string_val(gen, "host", sctx->host); yajl_add_string_val(gen, "ip", sctx->ip); - yajl_add_string_val(gen, "cmd", cmd); + const char *cmd = pThdData->getCmdName(); + //only print tables if lex is not null and it is not a quit command + LEX * pLex = Audit_formatter::thd_lex(pThdData->getTHD()); + QueryTableInf *pQuery_cache_table_list = getQueryCacheTableList1 (pThdData->getTHD()); + if (pLex && command != COM_QUIT && pLex->query_tables == NULL && pQuery_cache_table_list) + { + yajl_add_string_val(gen, "cmd", "select"); + yajl_add_string(gen, "objects"); + yajl_gen_array_open(gen); + for (int i=0;inum_of_elem && i < MAX_NUM_QUERY_TABLE_ELEM && pQuery_cache_table_list->num_of_elem >=0;i++) + { + yajl_gen_map_open(gen); + yajl_add_obj (gen, pQuery_cache_table_list->db[i],pQuery_cache_table_list->object_type[i],pQuery_cache_table_list->table_name[i] ); + yajl_gen_map_close(gen); + + } + yajl_gen_array_close(gen); + + } + else + { + + yajl_add_string_val(gen, "cmd", cmd); + } + if (strcmp (cmd,"Init DB") ==0 || strcmp (cmd, "SHOW TABLES")== 0 || strcmp (cmd, "SHOW TABLE")==0) { @@ -415,14 +402,12 @@ ssize_t Audit_json_formatter::event_format(ThdSesData* pThdData, IWriter * write { yajl_add_string(gen, "objects"); yajl_gen_array_open(gen); - yajl_add_string_val(gen, "db",(pThdData->getTHD())->db); - yajl_add_string_val(gen, "obj_type","database"); + yajl_add_obj (gen,(pThdData->getTHD())->db,"database", NULL); yajl_gen_array_close(gen); } } - //only print tables if lex is not null and it is not a quit command - LEX * pLex = Audit_formatter::thd_lex(pThdData->getTHD()); + if (pLex && command != COM_QUIT && pLex->query_tables) { yajl_add_string(gen, "objects"); @@ -433,16 +418,14 @@ ssize_t Audit_json_formatter::event_format(ThdSesData* pThdData, IWriter * write while (table) { yajl_gen_map_open(gen); - yajl_add_string_val(gen, "db", table->get_db_name()); - yajl_add_string_val(gen, "name", table->get_table_name()); if (isFirstElementInView && strstr (cmd,"_view")!=NULL ) { - yajl_add_string_val(gen, "obj_type","view"); + yajl_add_obj (gen,table->get_db_name(), "view",table->get_table_name()); isFirstElementInView = false; } else { - yajl_add_string_val(gen, "obj_type",retrive_object_type(table)); + yajl_add_obj (gen,table->get_db_name(), retrive_object_type(table),table->get_table_name()); } yajl_gen_map_close(gen); table = table->next_global; @@ -450,25 +433,6 @@ ssize_t Audit_json_formatter::event_format(ThdSesData* pThdData, IWriter * write yajl_gen_array_close(gen); } - QueryTableInf *pQuery_cache_table_list = getQueryCacheTableList1 (pThdData->getTHD()); - - if (pLex && command != COM_QUIT && pLex->query_tables == NULL && pQuery_cache_table_list) - { - yajl_add_string(gen, "objects"); - yajl_gen_array_open(gen); - - for (int i=0;inum_of_elem && i < MAX_NUM_QUERY_TABLE_ELEM && pQuery_cache_table_list->num_of_elem >=0;i++) - { - yajl_gen_map_open(gen); - yajl_add_string_val(gen, "db", pQuery_cache_table_list->db[i]); - yajl_add_string_val(gen, "name", pQuery_cache_table_list->table_name[i]); - yajl_add_string_val(gen,"type",pQuery_cache_table_list->object_type[i]); - yajl_gen_map_close(gen); - - } - - yajl_gen_array_close(gen); - } size_t qlen = 0; diff --git a/src/audit_plugin.cc b/src/audit_plugin.cc index 080a916..94b3eb4 100644 --- a/src/audit_plugin.cc +++ b/src/audit_plugin.cc @@ -132,7 +132,8 @@ static const ThdOffsets thd_offsets_arr[] = {"5.5.19","0765dadb23315bb076bc6e21cfb2de40", 6048, 6096, 3800, 4224, 88, 2560}, //offsets for: /mysqlrpm/5.5.20/usr/sbin/mysqld (5.5.20) {"5.5.20","9f6122576930c5d09ca9244094c83f24", 6048, 6096, 3800, 4224, 88, 2560}, - + //offsets for: mysqlrpm/5.5.21/usr/sbin/mysqld (5.5.21) + {"5.5.21","4a03ad064ed393dabdde175f3ea05ff2", 6048, 6096, 3800, 4224, 88, 2560}, //DISTRIBUTION: tar.gz //offsets for: /mysql/5.1.30/bin/mysqld (5.1.30) @@ -228,9 +229,9 @@ static const ThdOffsets thd_offsets_arr[] = {"5.5.18","099d31c0cd0754934b84c17f683d019e", 6040, 6088, 3792, 4216, 88, 2560}, {"5.5.19","f000f941c4e4f7b84e66d7b8c115ca8f", 6048, 6096, 3800, 4224, 88, 2560}, //offsets for: /mysql/5.5.20/bin/mysqld (5.5.20) - {"5.5.20","8b68e84332b442d58a46ae4299380a99", 6048, 6096, 3800, 4224, 88, 2560} - - + {"5.5.20","8b68e84332b442d58a46ae4299380a99", 6048, 6096, 3800, 4224, 88, 2560}, + //offsets for: mysql/5.5.21/bin/mysqld (5.5.21) + {"5.5.21","66d23cb577e2bcfe29da08833f5e7d8b", 6048, 6096, 3800, 4224, 88, 2560} }; @@ -332,7 +333,8 @@ static const ThdOffsets thd_offsets_arr[] = {"5.5.19","f3c31e2a5d95d3511b7106441f38929e", 3808, 3836, 2360, 2692, 44, 1640}, //offsets for: /mysqlrpm/5.5.20/usr/sbin/mysqld (5.5.20) {"5.5.20","c73100bcb0d967b627cad72e66503194", 3808, 3836, 2360, 2692, 44, 1640}, - + //offsets for: mysqlrpm/5.5.21/usr/sbin/mysqld (5.5.21) + {"5.5.21","18d78ced97227b83e62e9b43ba5b3883", 3808, 3836, 2360, 2692, 44, 1640}, //DISTRIBUTION: tar.gz //offsets for: mysql/5.1.30/bin/mysqld (5.1.30) @@ -428,8 +430,9 @@ static const ThdOffsets thd_offsets_arr[] = //offsets for: /mysql/5.5.19/bin/mysqld (5.5.19) {"5.5.19","b407d678b9b855bfd29ba3c9f014d4b0", 3808, 3836, 2360, 2692, 44, 1640}, //offsets for: /mysql/5.5.20/bin/mysqld (5.5.20) - {"5.5.20","cb9b6887ea525fe9965121d357163fe4", 3808, 3836, 2360, 2692, 44, 1640} - + {"5.5.20","cb9b6887ea525fe9965121d357163fe4", 3808, 3836, 2360, 2692, 44, 1640}, + //offsets for: mysql/5.5.21/bin/mysqld (5.5.21) + {"5.5.21","a0762cee3ad5d4e77480956144900213", 3808, 3836, 2360, 2692, 44, 1640} }; #endif @@ -453,7 +456,7 @@ static int delay_ms_val =0; static char *delay_cmds_string = NULL; static char delay_cmds_array [SQLCOM_END + 2][MAX_COMMAND_CHAR_NUMBERS]; - +static SHOW_VAR com_status_vars_array [MAX_COM_STATUS_VARS_RECORDS] = {0}; /** * The trampoline function we use. Define it via a macro which simply fills it with nops. */ @@ -477,7 +480,7 @@ static int trampoline_check_user(THD *thd, enum enum_server_command command, con } static unsigned int trampoline_check_user_size =0; -bool trampoline_acl_authenticate(THD *thd, uint connect_errors, uint com_change_user_pkt_len){ +static bool trampoline_acl_authenticate(THD *thd, uint connect_errors, uint com_change_user_pkt_len){ TRAMPOLINE_NOP_DEF; return 0; //dummy return as this does a jump. } @@ -552,7 +555,7 @@ static int trampoline_send_result_to_client(Query_cache *pthis, THD *thd, char } #if MYSQL_VERSION_ID > 50505 -bool trampoline_open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, +static bool trampoline_open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags, Prelocking_strategy *prelocking_strategy) { TRAMPOLINE_NOP_DEF; @@ -1090,6 +1093,74 @@ static int setup_offsets() DBUG_RETURN(1); } + +const char * retrieve_command (THD * thd) +{ + const char *cmd = NULL; + + int command = Audit_formatter::thd_inst_command(thd); + if (command < 0 || command > COM_END) + { + command = COM_END; + } + const int sql_command = thd_sql_command(thd); + if (sql_command >=0 && sql_command <= (MAX_COM_STATUS_VARS_RECORDS -1) ) + { + cmd = com_status_vars_array[sql_command + 1].name; + } + if(!cmd) + { + cmd = command_name[command].str; + } + Security_context * sctx = Audit_formatter::thd_inst_main_security_ctx(thd); + if (strcmp (cmd, "Connect") ==0 && (sctx->priv_user == NULL || *sctx->priv_user == 0x0)) + { + cmd = "Failed Login"; + } + return cmd; +} + +static int set_com_status_vars_array () +{ + DBUG_ENTER("set_com_status_vars_array"); + SHOW_VAR *com_status_vars; + int sv_idx =0; + while (strcmp (status_vars[sv_idx].name,"Com") !=0 && status_vars[sv_idx].name != NullS) + { + sv_idx ++; + } + if (strcmp (status_vars[sv_idx].name,"Com")==0) + { + int status_vars_index =0; + com_status_vars = (SHOW_VAR*)status_vars[sv_idx].value; + size_t initial_offset = (size_t) com_status_vars[0].value; + while (com_status_vars[status_vars_index].name != NullS) + { + int sql_command_idx = (com_status_vars[status_vars_index].value - (char*) (initial_offset)) / sizeof (ulong); + if (sql_command_idx >=0 && sql_command_idx < MAX_COM_STATUS_VARS_RECORDS) + { + com_status_vars_array [sql_command_idx].name = com_status_vars[status_vars_index].name; + com_status_vars_array [sql_command_idx].type = com_status_vars[status_vars_index].type; + com_status_vars_array [sql_command_idx].value = com_status_vars[status_vars_index].value; + } + else + { + sql_print_error("%s Failed sql_command_idx [%d] is out of bounds. Plugin Init failed.", + log_prefix, sql_command_idx); + DBUG_RETURN (1); + } + status_vars_index ++; + } + + } + else + { + sql_print_error("%s Failed looking up 'Com' entry in status_vars. Plugin Init failed.", + log_prefix); + DBUG_RETURN (1); + } + DBUG_RETURN (0); +} /* Initialize the daemon plugin installation. @@ -1116,6 +1187,7 @@ static int audit_plugin_init(void *p) { DBUG_RETURN(1); } + //setup audit handlers (initially disabled) int res = json_file_handler.init(&json_formatter); if (res != 0) @@ -1276,7 +1348,10 @@ static int audit_plugin_init(void *p) log_prefix, *(bool (*)(THD *thd, TABLE_LIST **start, uint *counter, uint flags)) &open_tables, trampoline_open_tables_size); #endif - + if (set_com_status_vars_array () !=0) + { + DBUG_RETURN(1); + } DBUG_RETURN(0); } @@ -1485,7 +1560,7 @@ mysql_declare_plugin_end; * We set here the audit plugin version to the same as the first built in plugin. * This is so we can have a single lib for all versions (needed in 5.1) */ -void __attribute__ ((constructor)) audit_plugin_so_init(void) +extern "C" void __attribute__ ((constructor)) audit_plugin_so_init(void) { if (mysqld_builtins && mysqld_builtins[0]) { @@ -1503,7 +1578,7 @@ void __attribute__ ((constructor)) audit_plugin_so_init(void) } #else extern struct st_mysql_plugin *mysql_mandatory_plugins[]; -void __attribute__ ((constructor)) audit_plugin_so_init(void) +extern "C" void __attribute__ ((constructor)) audit_plugin_so_init(void) {