[Asterisk-code-review] loader: support for permanent dlopen() (...asterisk[master])

Sebastian Kemper asteriskteam at digium.com
Tue Apr 2 16:26:13 CDT 2019


Sebastian Kemper has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/11217


Change subject: loader: support for permanent dlopen()
......................................................................

loader: support for permanent dlopen()

Asterisk assumes that dlopen() will always run the constructor of a
shared library and every dlclose() will run its destructor. But dlopen()
may be permanent, meaning the constructor will only be run once, as is
the case with musl libc.

With a permanent dlopen() the Asterisk module loader does not work
correctly, because it's expectations regarding when the constructors and
destructors are run are not met. In fact a segmentation fault will occur
when the first module is "re-opened" that has AST_MODFLAG_GLOBAL_SYMBOLS
set (the dlopen() does not call the constructor, resource_being_loaded
is not set to NULL, then strlen is called with NULL instead of a string,
see issue ASTERISK-28319).

This commit adds code to the loader that will manually run the
constructors/destructors of the (non-builtin) modules where needed. To
achieve this a new ao2 container (linked list) is started and filled
with objects that contain the names of the modules and the pointers to
their respective info structs.

This behavior can be activated when configuring Asterisk
(--enable-permanent-dlopen()). By default this is disabled, of course.

ASTERISK-28319 #close

Signed-off-by: Sebastian Kemper <sebastian_ml at gmx.net>
Change-Id: I86693a0ecf25d5ba81c73773a03df4abc3426875
---
M configure.ac
M main/loader.c
2 files changed, 165 insertions(+), 0 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/17/11217/1

diff --git a/configure.ac b/configure.ac
index 1ec7765..e65f159 100644
--- a/configure.ac
+++ b/configure.ac
@@ -729,6 +729,20 @@
 
 fi
 
+AC_ARG_ENABLE([permanent-dlopen],
+	[AS_HELP_STRING([--enable-permanent-dlopen],
+		[Enable when your libc has a permanent dlopen like musl])],
+	[case "${enableval}" in
+		y|ye|yes) PERMANENT_DLOPEN=yes ;;
+		n|no)  PERMANENT_DLOPEN=no ;;
+		*) AC_MSG_ERROR(bad value ${enableval} for --enable-permanent-dlopen)  ;;
+	esac], [PERMANENT_DLOPEN=no])
+
+AC_SUBST([PERMANENT_DLOPEN])
+if test "${PERMANENT_DLOPEN}" == "yes"; then
+	AC_DEFINE([HAVE_PERMANENT_DLOPEN], 1, [Define to support libc with permanent dlopen.])
+fi
+
 # some embedded systems omit internationalization (locale) support
 AC_CHECK_HEADERS([xlocale.h])
 
diff --git a/main/loader.c b/main/loader.c
index 3749c95..b3e25a1 100644
--- a/main/loader.c
+++ b/main/loader.c
@@ -153,6 +153,38 @@
 static struct ast_vector_string startup_errors;
 static struct ast_str *startup_error_builder;
 
+#if defined(HAVE_PERMANENT_DLOPEN)
+#define FIRST_DLOPEN 999
+
+struct ao2_container *info_list = NULL;
+
+struct info_list_obj {
+	const char *name;
+	const struct ast_module_info *info;
+	int dlopened;
+};
+
+static void info_list_obj_dtor(void *obj)
+{
+	struct info_list_obj *remove_entry = obj;
+
+	ast_free((char *)remove_entry->name);
+}
+
+static struct info_list_obj *info_list_obj_alloc(const char *name,
+	const struct ast_module_info *info)
+{
+	struct info_list_obj *new_entry;
+
+	new_entry = ao2_alloc(sizeof(*new_entry), info_list_obj_dtor);
+	new_entry->name = ast_strdup(name);
+	new_entry->info = info;
+	new_entry->dlopened = FIRST_DLOPEN;
+
+	return new_entry;
+}
+#endif
+
 static __attribute__((format(printf, 1, 2))) void module_load_error(const char *fmt, ...)
 {
 	char *copy = NULL;
@@ -250,6 +282,94 @@
  */
 static struct module_list builtin_module_list;
 
+#if defined(HAVE_PERMANENT_DLOPEN)
+static int info_list_obj_cmp_fn(void *obj1, void *obj2, int flags)
+{
+	struct info_list_obj *entry1 = obj1;
+	struct info_list_obj *entry2 = obj2;
+	const char *entry_name = (flags & OBJ_KEY) ? obj2 : entry2->name;
+
+	return strcasecmp(entry1->name, entry_name) ? 0 : CMP_MATCH | CMP_STOP;
+}
+
+static char *get_name_from_resource(const char *resource)
+{
+	int len;
+	const char *last_three;
+	char *mod_name;
+
+	if (!resource) {
+		return NULL;
+	}
+
+	len = strlen(resource);
+	if (len > 3) {
+		last_three = &resource[len-3];
+		if (!strcasecmp(last_three, ".so")) {
+			mod_name = ast_calloc(1, len - 2);
+			if (mod_name) {
+				ast_copy_string(mod_name, resource, len - 2);
+				return mod_name;
+			} else {
+				/* Unable to allocate memory. */
+				return NULL;
+			}
+		}
+	}
+
+	/* Resource is the name - happens when manually unloading a module. */
+	mod_name = ast_calloc(1, len + 1);
+	if (mod_name) {
+		ast_copy_string(mod_name, resource, len + 1);
+		return mod_name;
+	}
+
+	/* Unable to allocate memory. */
+	return NULL;
+}
+
+static void manual_mod_reg(const void *lib, const char *resource)
+{
+	struct info_list_obj *obj_tmp;
+	char *mod_name;
+
+	if (lib) {
+		mod_name = get_name_from_resource(resource);
+		if (mod_name) {
+			obj_tmp = ao2_find(info_list, mod_name, OBJ_SEARCH_KEY);
+			if (obj_tmp) {
+				if (obj_tmp->dlopened == FIRST_DLOPEN) {
+					obj_tmp->dlopened = 1;
+				} else {
+					ast_module_register(obj_tmp->info);
+				}
+			}
+			ast_free(mod_name);
+		}
+	}
+}
+
+static void manual_mod_unreg(const char *resource)
+{
+	const struct info_list_obj *obj_tmp;
+	char *mod_name;
+
+	/* When Asterisk shuts down the destructor is called automatically. */
+	if (ast_shutdown_final()) {
+		return;
+	}
+
+	mod_name = get_name_from_resource(resource);
+	if (mod_name) {
+		obj_tmp = ao2_find(info_list, mod_name, OBJ_SEARCH_KEY);
+		if (obj_tmp) {
+			ast_module_unregister(obj_tmp->info);
+		}
+		ast_free(mod_name);
+	}
+}
+#endif
+
 static int module_vector_strcasecmp(struct ast_module *a, struct ast_module *b)
 {
 	return strcasecmp(a->resource, b->resource);
@@ -597,6 +717,27 @@
 
 	/* give the module a copy of its own handle, for later use in registrations and the like */
 	*((struct ast_module **) &(info->self)) = mod;
+
+#if defined(HAVE_PERMANENT_DLOPEN)
+	if (!info_list) {
+		info_list = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL,
+			info_list_obj_cmp_fn);
+	}
+
+	if (info_list && mod->flags.builtin != 1) {
+		struct info_list_obj *obj_tmp = ao2_find(info_list, info->name,
+			OBJ_SEARCH_KEY);
+
+		if (!obj_tmp) {
+			ao2_link(info_list, info_list_obj_alloc(info->name, info));
+		}
+	}
+
+	if (!info_list) {
+		fprintf(stderr, "Module info list allocation failure during startup.\n");
+		exit(2);
+	}
+#endif
 }
 
 static int module_post_register(struct ast_module *mod)
@@ -843,6 +984,10 @@
 		error = dlerror();
 		ast_log(AST_LOG_ERROR, "Failure in dlclose for module '%s': %s\n",
 			S_OR(name, "unknown"), S_OR(error, "Unknown error"));
+#if defined(HAVE_PERMANENT_DLOPEN)
+	} else {
+		manual_mod_unreg(name);
+#endif
 	}
 }
 
@@ -949,6 +1094,9 @@
 
 	resource_being_loaded = mod;
 	mod->lib = dlopen(filename, flags);
+#if defined(HAVE_PERMANENT_DLOPEN)
+	manual_mod_reg(mod->lib, mod->resource);
+#endif
 	if (resource_being_loaded) {
 		struct ast_str *list;
 		int c = 0;
@@ -968,6 +1116,9 @@
 
 		resource_being_loaded = mod;
 		mod->lib = dlopen(filename, RTLD_LAZY | RTLD_LOCAL);
+#if defined(HAVE_PERMANENT_DLOPEN)
+		manual_mod_reg(mod->lib, mod->resource);
+#endif
 		if (resource_being_loaded) {
 			resource_being_loaded = NULL;
 

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/11217
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Change-Id: I86693a0ecf25d5ba81c73773a03df4abc3426875
Gerrit-Change-Number: 11217
Gerrit-PatchSet: 1
Gerrit-Owner: Sebastian Kemper <sebastian_ml at gmx.net>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20190402/2dd97492/attachment.html>


More information about the asterisk-code-review mailing list