From 9c6b87510e854c39b4a4e53e21325bb7ce5b6693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Thu, 27 May 2010 12:36:10 +0100 Subject: [PATCH] pluginloading: add support for whitelisting based on plugin or source module name and path This feature is primarily intended for use in plugin modules' unit tests. Consider the following situation: gst-plugins-good is built against an installed GStreamer core. An older version of gst-plugins-good is also installed in that prefix, along with random other plugin modules. Now, when doing 'make check' in the just-built gst-plugins-good tree, we want to only load plugins from GStreamer core, gst-plugins-base, and gst-plugins-good, but not random other modules (we don't want any unit tests to fail just because some module in gst-plugins-bad has a broken plugin_init, for example). Also, we want to only load gst-plugins-good modules from the locally-built source tree, but not any of the older gst-plugins-good modules installed. This is usually assured by loading the ones in the source tree first (by adding that path first to the right environment variables), but it gets tricky when plugins are moved, removed, merged, or renamed, or the plugin filename changes. Note that 'make check' should really work right without doing 'make install' or uninstalling the old gst-plugins-good package (or any other gst-plugins-foo package) first. Enter GST_PLUGIN_LOADING_WHITELIST. This environment variable may contain source-package@path-prefix pairs separated by the platform search path separator (G_SEARCHPATH_SEPARATOR_S). The source package and path prefix are separated by the '@' character. The path prefix is entirely optional, as is the '@' separator if no path is given. It is also possible to filter based on plugin names instead of the name of the source-package by specifying one or more plugin names separated by commas before the optional path prefix. In short, the following match patterns are possible: plugin1,plugin2@pathprefix or plugin1,plugin2@* or just plugin1,plugin2 or source-package@pathprefix or source-package@* or just source-package So for our gst-plugins-good unit test example above, we would set the environment variable on *nix to something like this (will likely be a relative path in practice): gstreamer:gst-plugins-base:gst-plugins-good@/path/to/src/gst-plugins-good Fixes #619815 and #619717. --- gst/gst_private.h | 5 ++ gst/gstplugin.c | 146 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 141 insertions(+), 10 deletions(-) diff --git a/gst/gst_private.h b/gst/gst_private.h index 423e937540..7ca941001c 100644 --- a/gst/gst_private.h +++ b/gst/gst_private.h @@ -73,6 +73,11 @@ struct _GstPluginPrivate { GstStructure *cache_data; }; +gboolean priv_gst_plugin_loading_have_whitelist (void); + +gboolean priv_gst_plugin_desc_is_whitelisted (GstPluginDesc * desc, + const gchar * filename); + gboolean _priv_plugin_deps_env_vars_changed (GstPlugin * plugin); gboolean _priv_plugin_deps_files_changed (GstPlugin * plugin); diff --git a/gst/gstplugin.c b/gst/gstplugin.c index 325a5b3be6..8a5b196857 100644 --- a/gst/gstplugin.c +++ b/gst/gstplugin.c @@ -72,6 +72,7 @@ static guint _num_static_plugins; /* 0 */ static GstPluginDesc *_static_plugins; /* NULL */ static gboolean _gst_plugin_inited; +static gchar **_plugin_loading_whitelist; /* NULL */ /* static variables for segfault handling of plugin loading */ static char *_gst_plugin_fault_handler_filename = NULL; @@ -329,10 +330,20 @@ gst_plugin_register_static_full (gint major_version, gint minor_version, void _gst_plugin_initialize (void) { + const gchar *whitelist; guint i; _gst_plugin_inited = TRUE; + whitelist = g_getenv ("GST_PLUGIN_LOADING_WHITELIST"); + if (whitelist != NULL && *whitelist != '\0') { + _plugin_loading_whitelist = g_strsplit (whitelist, + G_SEARCHPATH_SEPARATOR_S, -1); + for (i = 0; _plugin_loading_whitelist[i] != NULL; ++i) { + GST_INFO ("plugins whitelist entry: %s", _plugin_loading_whitelist[i]); + } + } + /* now register all static plugins */ GST_INFO ("registering %u static plugins", _num_static_plugins); for (i = 0; i < _num_static_plugins; ++i) { @@ -351,6 +362,107 @@ _gst_plugin_initialize (void) } } +/* Whitelist entry format: + * + * plugin1,plugin2@pathprefix or + * plugin1,plugin2@* or just + * plugin1,plugin2 or + * source-package@pathprefix or + * source-package@* or just + * source-package + * + * ie. the bit before the path will be checked against both the plugin + * name and the plugin's source package name, to keep the format simple. + */ +static gboolean +gst_plugin_desc_matches_whitelist_entry (GstPluginDesc * desc, + const gchar * filename, const gchar * pattern) +{ + const gchar *sep; + gboolean ret = FALSE; + gchar *name; + + GST_LOG ("Whitelist pattern '%s', plugin: %s of %s@%s", pattern, desc->name, + desc->source, GST_STR_NULL (filename)); + + /* do we have a path prefix? */ + sep = strchr (pattern, '@'); + if (sep != NULL && strcmp (sep, "@*") != 0 && strcmp (sep, "@") != 0) { + /* paths are not canonicalised or treated with realpath() here. This + * should be good enough for our use case, since we just use the paths + * autotools uses, and those will be constructed from the same prefix. */ + if (filename != NULL && !g_str_has_prefix (filename, sep + 1)) + return FALSE; + + GST_LOG ("%s matches path prefix %s", GST_STR_NULL (filename), sep + 1); + } + + if (sep != NULL) { + name = g_strndup (pattern, (gsize) (sep - pattern)); + } else { + name = g_strdup (pattern); + } + + g_strstrip (name); + if (!g_ascii_isalnum (*name)) { + GST_WARNING ("Invalid whitelist pattern: %s", pattern); + goto done; + } + + /* now check plugin names / source package name */ + if (strchr (name, ',') == NULL) { + /* only a single name: either a plugin name or the source package name */ + ret = (strcmp (desc->source, name) == 0 || strcmp (desc->name, name) == 0); + } else { + gchar **n, **names; + + /* multiple names: assume these are plugin names */ + names = g_strsplit (name, ",", -1); + for (n = names; n != NULL && *n != NULL; ++n) { + g_strstrip (*n); + if (strcmp (desc->name, *n) == 0) { + ret = TRUE; + break; + } + } + g_strfreev (names); + } + + GST_LOG ("plugin / source package name match: %d", ret); + +done: + + g_free (name); + return ret; +} + +gboolean +priv_gst_plugin_desc_is_whitelisted (GstPluginDesc * desc, + const gchar * filename) +{ + gchar **entry; + + if (_plugin_loading_whitelist == NULL) + return TRUE; + + for (entry = _plugin_loading_whitelist; *entry != NULL; ++entry) { + if (gst_plugin_desc_matches_whitelist_entry (desc, filename, *entry)) { + GST_LOG ("Plugin %s is in whitelist", filename); + return TRUE; + } + } + + GST_LOG ("Plugin %s (package %s, file %s) not in whitelist", desc->name, + desc->source, filename); + return FALSE; +} + +gboolean +priv_gst_plugin_loading_have_whitelist (void) +{ + return (_plugin_loading_whitelist != NULL); +} + /* this function could be extended to check if the plugin license matches the * applications license (would require the app to register its license somehow). * We'll wait for someone who's interested in it to code it :) @@ -538,6 +650,7 @@ static GStaticMutex gst_plugin_loading_mutex = G_STATIC_MUTEX_INIT; GstPlugin * gst_plugin_load_file (const gchar * filename, GError ** error) { + GstPluginDesc *desc; GstPlugin *plugin; GModule *module; gboolean ret; @@ -597,15 +710,6 @@ gst_plugin_load_file (const gchar * filename, GError ** error) goto return_error; } - if (new_plugin) { - plugin = g_object_newv (GST_TYPE_PLUGIN, 0, NULL); - plugin->file_mtime = file_status.st_mtime; - plugin->file_size = file_status.st_size; - plugin->filename = g_strdup (filename); - plugin->basename = g_path_get_basename (filename); - } - plugin->module = module; - ret = g_module_symbol (module, "gst_plugin_desc", &ptr); if (!ret) { GST_DEBUG ("Could not find plugin entry point in \"%s\"", filename); @@ -616,7 +720,29 @@ gst_plugin_load_file (const gchar * filename, GError ** error) g_module_close (module); goto return_error; } - plugin->orig_desc = (GstPluginDesc *) ptr; + + desc = (GstPluginDesc *) ptr; + + if (priv_gst_plugin_loading_have_whitelist () && + !priv_gst_plugin_desc_is_whitelisted (desc, filename)) { + GST_INFO ("Whitelist specified and plugin not in whitelist, not loading: " + "name=%s, package=%s, file=%s", desc->name, desc->source, filename); + g_set_error (error, GST_PLUGIN_ERROR, GST_PLUGIN_ERROR_MODULE, + "Not loading plugin file \"%s\", not in whitelist", filename); + g_module_close (module); + goto return_error; + } + + if (new_plugin) { + plugin = g_object_newv (GST_TYPE_PLUGIN, 0, NULL); + plugin->file_mtime = file_status.st_mtime; + plugin->file_size = file_status.st_size; + plugin->filename = g_strdup (filename); + plugin->basename = g_path_get_basename (filename); + } + + plugin->module = module; + plugin->orig_desc = desc; if (new_plugin) { /* check plugin description: complain about bad values but accept them, to