commit 02fda2b478f98cf3b8a1df76f772bf0be73bddd5 Author: Sebastian Kemper Date: Tue Apr 2 22:49:52 2019 +0200 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 Change-Id: I86693a0ecf25d5ba81c73773a03df4abc3426875 --- a/configure.ac +++ b/configure.ac @@ -727,6 +727,20 @@ if test "${DISABLE_XMLDOC}" != "yes"; th 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]) --- a/main/loader.c +++ b/main/loader.c @@ -153,6 +153,117 @@ static unsigned int loader_ready; 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 struct ast_module_info *info; + int dlopened; + char name[0]; +}; + +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) + strlen(name) + 1, NULL); + + if (!new_entry) { + return NULL; + } + + strcpy(new_entry->name, name); /* SAFE */ + new_entry->info = info; + new_entry->dlopened = FIRST_DLOPEN; + + return new_entry; +} + +AO2_STRING_FIELD_CMP_FN(info_list_obj, name) + +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); + } + ao2_ref(obj_tmp, -1); + } + ast_free(mod_name); + } + } +} + +static void manual_mod_unreg(const char *resource) +{ + 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); + ao2_ref(obj_tmp, -1); + } + ast_free(mod_name); + } +} +#endif + static __attribute__((format(printf, 1, 2))) void module_load_error(const char *fmt, ...) { char *copy = NULL; @@ -597,6 +708,23 @@ void ast_module_register(const struct as /* 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 (mod->flags.builtin != 1) { + struct info_list_obj *obj_tmp = ao2_find(info_list, info->name, + OBJ_SEARCH_KEY); + + if (!obj_tmp) { + obj_tmp = info_list_obj_alloc(info->name, info); + if (obj_tmp) { + ao2_link(info_list, obj_tmp); + ao2_ref(obj_tmp, -1); + } + } else { + ao2_ref(obj_tmp, -1); + } + } +#endif } static int module_post_register(struct ast_module *mod) @@ -843,6 +971,10 @@ static void logged_dlclose(const char *n 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 +1081,9 @@ static struct ast_module *load_dlopen(co 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 +1103,9 @@ static struct ast_module *load_dlopen(co 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; @@ -2206,6 +2344,15 @@ int load_modules(void) ast_verb(1, "Asterisk Dynamic Loader Starting:\n"); +#if defined(HAVE_PERMANENT_DLOPEN) + info_list = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, + info_list_obj_cmp_fn); /* must not be cleaned at shutdown */ + if (!info_list) { + fprintf(stderr, "Module info list allocation failure.\n"); + return 1; + } +#endif + AST_LIST_HEAD_INIT_NOLOCK(&load_order); AST_DLLIST_LOCK(&module_list);