1 /* Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; version 2 of the License.
6 
7    This program is distributed in the hope that it will be useful,
8    but WITHOUT ANY WARRANTY; without even the implied warranty of
9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10    GNU General Public License for more details.
11 
12    You should have received a copy of the GNU General Public License
13    along with this program; if not, write to the Free Software
14    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA */
15 
16 #include "mariadb.h"
17 #include "sql_priv.h"
18 #include "mysqld.h"
19 #include "sql_audit.h"
20 
21 extern int initialize_audit_plugin(st_plugin_int *plugin);
22 extern int finalize_audit_plugin(st_plugin_int *plugin);
23 
24 #ifndef EMBEDDED_LIBRARY
25 
26 struct st_mysql_event_generic
27 {
28   unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
29   unsigned int event_class;
30   const void *event;
31 };
32 
33 unsigned long mysql_global_audit_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
34 
35 static mysql_mutex_t LOCK_audit_mask;
36 
37 
38 static inline
set_audit_mask(unsigned long * mask,uint event_class)39 void set_audit_mask(unsigned long *mask, uint event_class)
40 {
41   mask[0]= 1;
42   mask[0]<<= event_class;
43 }
44 
45 static inline
add_audit_mask(unsigned long * mask,const unsigned long * rhs)46 void add_audit_mask(unsigned long *mask, const unsigned long *rhs)
47 {
48   mask[0]|= rhs[0];
49 }
50 
51 static inline
check_audit_mask(const unsigned long * lhs,const unsigned long * rhs)52 bool check_audit_mask(const unsigned long *lhs,
53                       const unsigned long *rhs)
54 {
55   return !(lhs[0] & rhs[0]);
56 }
57 
58 
59 /**
60   Acquire and lock any additional audit plugins as required
61 
62   @param[in] thd
63   @param[in] plugin
64   @param[in] arg
65 
66   @retval FALSE Always
67 */
68 
acquire_plugins(THD * thd,plugin_ref plugin,void * arg)69 static my_bool acquire_plugins(THD *thd, plugin_ref plugin, void *arg)
70 {
71   ulong *event_class_mask= (ulong*) arg;
72   st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
73 
74   /* Check if this plugin is interested in the event */
75   if (check_audit_mask(data->class_mask, event_class_mask))
76     return 0;
77 
78   /*
79     Check if this plugin may already be registered. This will fail to
80     acquire a newly installed plugin on a specific corner case where
81     one or more event classes already in use by the calling thread
82     are an event class of which the audit plugin has interest.
83   */
84   if (!check_audit_mask(data->class_mask, thd->audit_class_mask))
85     return 0;
86 
87   /* Check if we need to initialize the array of acquired plugins */
88   if (unlikely(!thd->audit_class_plugins.buffer))
89   {
90     /* specify some reasonable initialization defaults */
91     my_init_dynamic_array(&thd->audit_class_plugins,
92                           sizeof(plugin_ref), 16, 16, MYF(0));
93   }
94 
95   /* lock the plugin and add it to the list */
96   plugin= my_plugin_lock(NULL, plugin);
97   insert_dynamic(&thd->audit_class_plugins, (uchar*) &plugin);
98 
99   return 0;
100 }
101 
102 
103 /**
104   @brief Acquire audit plugins
105 
106   @param[in]   thd              MySQL thread handle
107   @param[in]   event_class      Audit event class
108 
109   @details Ensure that audit plugins interested in given event
110   class are locked by current thread.
111 */
mysql_audit_acquire_plugins(THD * thd,ulong * event_class_mask)112 void mysql_audit_acquire_plugins(THD *thd, ulong *event_class_mask)
113 {
114   DBUG_ENTER("mysql_audit_acquire_plugins");
115   DBUG_ASSERT(thd);
116   DBUG_ASSERT(!check_audit_mask(mysql_global_audit_mask, event_class_mask));
117 
118   if (check_audit_mask(thd->audit_class_mask, event_class_mask))
119   {
120     plugin_foreach(thd, acquire_plugins, MYSQL_AUDIT_PLUGIN, event_class_mask);
121     add_audit_mask(thd->audit_class_mask, event_class_mask);
122     thd->audit_plugin_version= global_plugin_version;
123   }
124   DBUG_VOID_RETURN;
125 }
126 
127 
128 /**
129   Check if there were changes in the state of plugins
130   so we need to do the mysql_audit_release asap.
131 
132   @param[in] thd
133 
134 */
135 
mysql_audit_release_required(THD * thd)136 my_bool mysql_audit_release_required(THD *thd)
137 {
138   return thd && (thd->audit_plugin_version != global_plugin_version);
139 }
140 
141 
142 /**
143   Release any resources associated with the current thd.
144 
145   @param[in] thd
146 
147 */
148 
mysql_audit_release(THD * thd)149 void mysql_audit_release(THD *thd)
150 {
151   plugin_ref *plugins, *plugins_last;
152 
153   if (!thd || !(thd->audit_class_plugins.elements))
154     return;
155 
156   plugins= (plugin_ref*) thd->audit_class_plugins.buffer;
157   plugins_last= plugins + thd->audit_class_plugins.elements;
158   for (; plugins < plugins_last; plugins++)
159   {
160     st_mysql_audit *data= plugin_data(*plugins, struct st_mysql_audit *);
161 
162     /* Check to see if the plugin has a release method */
163     if (!(data->release_thd))
164       continue;
165 
166     /* Tell the plugin to release its resources */
167     data->release_thd(thd);
168   }
169 
170   /* Now we actually unlock the plugins */
171   plugin_unlock_list(NULL, (plugin_ref*) thd->audit_class_plugins.buffer,
172                      thd->audit_class_plugins.elements);
173 
174   /* Reset the state of thread values */
175   reset_dynamic(&thd->audit_class_plugins);
176   bzero(thd->audit_class_mask, sizeof(thd->audit_class_mask));
177   thd->audit_plugin_version= -1;
178 }
179 
180 
181 /**
182   Initialize thd variables used by Audit
183 
184   @param[in] thd
185 
186 */
187 
mysql_audit_init_thd(THD * thd)188 void mysql_audit_init_thd(THD *thd)
189 {
190   bzero(&thd->audit_class_plugins, sizeof(thd->audit_class_plugins));
191   bzero(thd->audit_class_mask, sizeof(thd->audit_class_mask));
192 }
193 
194 
195 /**
196   Free thd variables used by Audit
197 
198   @param[in] thd
199   @param[in] plugin
200   @param[in] arg
201 
202   @retval FALSE Always
203 */
204 
mysql_audit_free_thd(THD * thd)205 void mysql_audit_free_thd(THD *thd)
206 {
207   mysql_audit_release(thd);
208   DBUG_ASSERT(thd->audit_class_plugins.elements == 0);
209   delete_dynamic(&thd->audit_class_plugins);
210 }
211 
212 #ifdef HAVE_PSI_INTERFACE
213 static PSI_mutex_key key_LOCK_audit_mask;
214 
215 static PSI_mutex_info all_audit_mutexes[]=
216 {
217   { &key_LOCK_audit_mask, "LOCK_audit_mask", PSI_FLAG_GLOBAL}
218 };
219 
init_audit_psi_keys(void)220 static void init_audit_psi_keys(void)
221 {
222   const char* category= "sql";
223   int count;
224 
225   if (PSI_server == NULL)
226     return;
227 
228   count= array_elements(all_audit_mutexes);
229   PSI_server->register_mutex(category, all_audit_mutexes, count);
230 }
231 #endif /* HAVE_PSI_INTERFACE */
232 
233 /**
234   Initialize Audit global variables
235 */
236 
mysql_audit_initialize()237 void mysql_audit_initialize()
238 {
239 #ifdef HAVE_PSI_INTERFACE
240   init_audit_psi_keys();
241 #endif
242 
243   mysql_mutex_init(key_LOCK_audit_mask, &LOCK_audit_mask, MY_MUTEX_INIT_FAST);
244   bzero(mysql_global_audit_mask, sizeof(mysql_global_audit_mask));
245 }
246 
247 
248 /**
249   Finalize Audit global variables
250 */
251 
mysql_audit_finalize()252 void mysql_audit_finalize()
253 {
254   mysql_mutex_destroy(&LOCK_audit_mask);
255 }
256 
257 
258 /**
259   Initialize an Audit plug-in
260 
261   @param[in] plugin
262 
263   @retval FALSE  OK
264   @retval TRUE   There was an error.
265 */
266 
initialize_audit_plugin(st_plugin_int * plugin)267 int initialize_audit_plugin(st_plugin_int *plugin)
268 {
269   st_mysql_audit *data= (st_mysql_audit*) plugin->plugin->info;
270 
271   if (!data->event_notify || !data->class_mask[0])
272   {
273     sql_print_error("Plugin '%s' has invalid data.",
274                     plugin->name.str);
275     return 1;
276   }
277 
278   if (plugin->plugin->init && plugin->plugin->init(NULL))
279   {
280     sql_print_error("Plugin '%s' init function returned error.",
281                     plugin->name.str);
282     return 1;
283   }
284 
285   /* Make the interface info more easily accessible */
286   plugin->data= plugin->plugin->info;
287 
288   /* Add the bits the plugin is interested in to the global mask */
289   mysql_mutex_lock(&LOCK_audit_mask);
290   add_audit_mask(mysql_global_audit_mask, data->class_mask);
291   mysql_mutex_unlock(&LOCK_audit_mask);
292 
293   /*
294     Pre-acquire the newly inslalled audit plugin for events that
295     may potentially occur further during INSTALL PLUGIN.
296 
297     When audit event is triggered, audit subsystem acquires interested
298     plugins by walking through plugin list. Evidently plugin list
299     iterator protects plugin list by acquiring LOCK_plugin, see
300     plugin_foreach_with_mask().
301 
302     On the other hand [UN]INSTALL PLUGIN is acquiring LOCK_plugin
303     rather for a long time.
304 
305     When audit event is triggered during [UN]INSTALL PLUGIN, plugin
306     list iterator acquires the same lock (within the same thread)
307     second time.
308 
309     This hack should be removed when LOCK_plugin is fixed so it
310     protects only what it supposed to protect.
311 
312     See also mysql_install_plugin() and mysql_uninstall_plugin()
313   */
314   THD *thd= current_thd;
315   if (thd)
316   {
317     acquire_plugins(thd, plugin_int_to_ref(plugin), data->class_mask);
318     add_audit_mask(thd->audit_class_mask, data->class_mask);
319   }
320 
321   return 0;
322 }
323 
324 
325 /**
326   Performs a bitwise OR of the installed plugins event class masks
327 
328   @param[in] thd
329   @param[in] plugin
330   @param[in] arg
331 
332   @retval FALSE  always
333 */
calc_class_mask(THD * thd,plugin_ref plugin,void * arg)334 static my_bool calc_class_mask(THD *thd, plugin_ref plugin, void *arg)
335 {
336   st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
337   if ((data= plugin_data(plugin, struct st_mysql_audit *)))
338     add_audit_mask((unsigned long *) arg, data->class_mask);
339   return 0;
340 }
341 
342 
343 /**
344   Finalize an Audit plug-in
345 
346   @param[in] plugin
347 
348   @retval FALSE  OK
349   @retval TRUE   There was an error.
350 */
finalize_audit_plugin(st_plugin_int * plugin)351 int finalize_audit_plugin(st_plugin_int *plugin)
352 {
353   unsigned long event_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
354 
355   if (plugin->plugin->deinit && plugin->plugin->deinit(NULL))
356   {
357     DBUG_PRINT("warning", ("Plugin '%s' deinit function returned error.",
358                             plugin->name.str));
359     DBUG_EXECUTE("finalize_audit_plugin", return 1; );
360   }
361 
362   plugin->data= NULL;
363   bzero(&event_class_mask, sizeof(event_class_mask));
364 
365   /* Iterate through all the installed plugins to create new mask */
366 
367   /*
368     LOCK_audit_mask/LOCK_plugin order is not fixed, but serialized with table
369     lock on mysql.plugin.
370   */
371   mysql_mutex_lock(&LOCK_audit_mask);
372   plugin_foreach(current_thd, calc_class_mask, MYSQL_AUDIT_PLUGIN,
373                  &event_class_mask);
374 
375   /* Set the global audit mask */
376   bmove(mysql_global_audit_mask, event_class_mask, sizeof(event_class_mask));
377   mysql_mutex_unlock(&LOCK_audit_mask);
378 
379   return 0;
380 }
381 
382 
383 /**
384   Dispatches an event by invoking the plugin's event_notify method.
385 
386   @param[in] thd
387   @param[in] plugin
388   @param[in] arg
389 
390   @retval FALSE  always
391 */
392 
plugins_dispatch(THD * thd,plugin_ref plugin,void * arg)393 static my_bool plugins_dispatch(THD *thd, plugin_ref plugin, void *arg)
394 {
395   const struct st_mysql_event_generic *event_generic=
396     (const struct st_mysql_event_generic *) arg;
397   st_mysql_audit *data= plugin_data(plugin, struct st_mysql_audit *);
398 
399   /* Check to see if the plugin is interested in this event */
400   if (!check_audit_mask(data->class_mask, event_generic->event_class_mask))
401     data->event_notify(thd, event_generic->event_class, event_generic->event);
402 
403   return 0;
404 }
405 
406 
407 /**
408   Distributes an audit event to plug-ins
409 
410   @param[in] thd
411   @param[in] event_class
412   @param[in] event
413 */
414 
mysql_audit_notify(THD * thd,uint event_class,const void * event)415 void mysql_audit_notify(THD *thd, uint event_class, const void *event)
416 {
417   struct st_mysql_event_generic event_generic;
418   event_generic.event_class= event_class;
419   event_generic.event= event;
420   set_audit_mask(event_generic.event_class_mask, event_class);
421   /*
422     Check if we are doing a slow global dispatch. This event occurs when
423     thd == NULL as it is not associated with any particular thread.
424   */
425   if (unlikely(!thd))
426   {
427     plugin_foreach(thd, plugins_dispatch, MYSQL_AUDIT_PLUGIN, &event_generic);
428   }
429   else
430   {
431     plugin_ref *plugins, *plugins_last;
432 
433     mysql_audit_acquire_plugins(thd, event_generic.event_class_mask);
434 
435     /* Use the cached set of audit plugins */
436     plugins= (plugin_ref*) thd->audit_class_plugins.buffer;
437     plugins_last= plugins + thd->audit_class_plugins.elements;
438 
439     for (; plugins < plugins_last; plugins++)
440       plugins_dispatch(thd, *plugins, &event_generic);
441   }
442 }
443 
444 
445 #else /* EMBEDDED_LIBRARY */
446 
447 
mysql_audit_acquire_plugins(THD * thd,ulong * event_class_mask)448 void mysql_audit_acquire_plugins(THD *thd, ulong *event_class_mask)
449 {
450 }
451 
452 
mysql_audit_initialize()453 void mysql_audit_initialize()
454 {
455 }
456 
457 
mysql_audit_finalize()458 void mysql_audit_finalize()
459 {
460 }
461 
462 
initialize_audit_plugin(st_plugin_int * plugin)463 int initialize_audit_plugin(st_plugin_int *plugin)
464 {
465   return 1;
466 }
467 
468 
finalize_audit_plugin(st_plugin_int * plugin)469 int finalize_audit_plugin(st_plugin_int *plugin)
470 {
471   return 0;
472 }
473 
474 
mysql_audit_release(THD * thd)475 void mysql_audit_release(THD *thd)
476 {
477 }
478 
mysql_audit_init_thd(THD * thd)479 void mysql_audit_init_thd(THD *thd)
480 {
481 }
482 
mysql_audit_free_thd(THD * thd)483 void mysql_audit_free_thd(THD *thd)
484 {
485 }
486 
487 #endif /* EMBEDDED_LIBRARY */
488