If dlclose() is called recursively from a _fini() function, the inner

dlclose() call may unload the object of the outer call prematurely
because objects are unreferenced before _fini() calls.

Fix this by unreferencing objects after calling objlist_call_fini() in
dlclose(). Therefore objlist_call_fini() now calls the fini function if
the reference count of an object is 1. In addition we must restart the
list_fini traversal after every _fini() call because another dlclose()
call might have modified the reference counts.

Add an XXX comment to objlist_call_fini() about possible race with
dlopen().

PR:		133246, 149464
Reviewed by:	kan, kib
This commit is contained in:
Jaakko Heinonen 2010-12-16 16:56:44 +00:00
parent 5e66b8e231
commit 4ef75f147a
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=216489

View File

@ -110,7 +110,7 @@ static int load_needed_objects(Obj_Entry *, int);
static int load_preload_objects(void);
static Obj_Entry *load_object(const char *, const Obj_Entry *, int);
static Obj_Entry *obj_from_addr(const void *);
static void objlist_call_fini(Objlist *, bool, int *);
static void objlist_call_fini(Objlist *, Obj_Entry *, int *);
static void objlist_call_init(Objlist *, int *);
static void objlist_clear(Objlist *);
static Objlist_Entry *objlist_find(Objlist *, const Obj_Entry *);
@ -1609,36 +1609,56 @@ obj_from_addr(const void *addr)
/*
* Call the finalization functions for each of the objects in "list"
* which are unreferenced. All of the objects are expected to have
* non-NULL fini functions.
* belonging to the DAG of "root" and referenced once. If NULL "root"
* is specified, every finalization function will be called regardless
* of the reference count and the list elements won't be freed. All of
* the objects are expected to have non-NULL fini functions.
*/
static void
objlist_call_fini(Objlist *list, bool force, int *lockstate)
objlist_call_fini(Objlist *list, Obj_Entry *root, int *lockstate)
{
Objlist_Entry *elm, *elm_tmp;
Objlist_Entry *elm;
char *saved_msg;
assert(root == NULL || root->refcount == 1);
/*
* Preserve the current error message since a fini function might
* call into the dynamic linker and overwrite it.
*/
saved_msg = errmsg_save();
STAILQ_FOREACH_SAFE(elm, list, link, elm_tmp) {
if (elm->obj->refcount == 0 || force) {
do {
STAILQ_FOREACH(elm, list, link) {
if (root != NULL && (elm->obj->refcount != 1 ||
objlist_find(&root->dagmembers, elm->obj) == NULL))
continue;
dbg("calling fini function for %s at %p", elm->obj->path,
(void *)elm->obj->fini);
LD_UTRACE(UTRACE_FINI_CALL, elm->obj, (void *)elm->obj->fini, 0, 0,
elm->obj->path);
/* Remove object from fini list to prevent recursive invocation. */
STAILQ_REMOVE(list, elm, Struct_Objlist_Entry, link);
/*
* XXX: If a dlopen() call references an object while the
* fini function is in progress, we might end up trying to
* unload the referenced object in dlclose() or the object
* won't be unloaded although its fini function has been
* called.
*/
wlock_release(rtld_bind_lock, *lockstate);
call_initfini_pointer(elm->obj, elm->obj->fini);
*lockstate = wlock_acquire(rtld_bind_lock);
/* No need to free anything if process is going down. */
if (!force)
if (root != NULL)
free(elm);
/*
* We must restart the list traversal after every fini call
* because a dlclose() call from the fini function or from
* another thread might have modified the reference counts.
*/
break;
}
}
} while (elm != NULL);
errmsg_restore(saved_msg);
}
@ -1826,7 +1846,7 @@ rtld_exit(void)
lockstate = wlock_acquire(rtld_bind_lock);
dbg("rtld_exit()");
objlist_call_fini(&list_fini, true, &lockstate);
objlist_call_fini(&list_fini, NULL, &lockstate);
/* No need to remove the items from the list, since we are exiting. */
if (!libmap_disable)
lm_fini();
@ -1939,20 +1959,22 @@ dlclose(void *handle)
/* Unreference the object and its dependencies. */
root->dl_refcount--;
unref_dag(root);
if (root->refcount == 0) {
if (root->refcount == 1) {
/*
* The object is no longer referenced, so we must unload it.
* The object will be no longer referenced, so we must unload it.
* First, call the fini functions.
*/
objlist_call_fini(&list_fini, false, &lockstate);
objlist_call_fini(&list_fini, root, &lockstate);
unref_dag(root);
/* Finish cleaning up the newly-unreferenced objects. */
GDB_STATE(RT_DELETE,&root->linkmap);
unload_object(root);
GDB_STATE(RT_CONSISTENT,NULL);
}
} else
unref_dag(root);
LD_UTRACE(UTRACE_DLCLOSE_STOP, handle, NULL, 0, 0, NULL);
wlock_release(rtld_bind_lock, lockstate);
return 0;