From nobody@FreeBSD.org  Tue Mar 31 17:34:45 2009
Return-Path: <nobody@FreeBSD.org>
Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34])
	by hub.freebsd.org (Postfix) with ESMTP id 97A46106566C
	for <freebsd-gnats-submit@FreeBSD.org>; Tue, 31 Mar 2009 17:34:45 +0000 (UTC)
	(envelope-from nobody@FreeBSD.org)
Received: from www.freebsd.org (www.freebsd.org [IPv6:2001:4f8:fff6::21])
	by mx1.freebsd.org (Postfix) with ESMTP id 84CFD8FC20
	for <freebsd-gnats-submit@FreeBSD.org>; Tue, 31 Mar 2009 17:34:45 +0000 (UTC)
	(envelope-from nobody@FreeBSD.org)
Received: from www.freebsd.org (localhost [127.0.0.1])
	by www.freebsd.org (8.14.3/8.14.3) with ESMTP id n2VHYjxx017492
	for <freebsd-gnats-submit@FreeBSD.org>; Tue, 31 Mar 2009 17:34:45 GMT
	(envelope-from nobody@www.freebsd.org)
Received: (from nobody@localhost)
	by www.freebsd.org (8.14.3/8.14.3/Submit) id n2VHYjv1017491;
	Tue, 31 Mar 2009 17:34:45 GMT
	(envelope-from nobody)
Message-Id: <200903311734.n2VHYjv1017491@www.freebsd.org>
Date: Tue, 31 Mar 2009 17:34:45 GMT
From: Michael Moller <moller.michael10@yahoo.com>
To: freebsd-gnats-submit@FreeBSD.org
Subject: dlclose gives segfault when called in the fini function of another module
X-Send-Pr-Version: www-3.1
X-GNATS-Notify:

>Number:         133246
>Category:       kern
>Synopsis:       [libc] dlclose(3) gives segfault when called in the fini function of another module
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    freebsd-bugs
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Tue Mar 31 17:40:01 UTC 2009
>Closed-Date:    Sat Dec 18 18:02:35 UTC 2010
>Last-Modified:  Thu Mar  3 17:20:12 UTC 2011
>Originator:     Michael Moller
>Release:        7.1-RELEASE
>Organization:
Sybase, Inc.
>Environment:
FreeBSD xxxx 7.1-RELEASE FreeBSD 7.1-RELEASE #0: Thu Jan  1 14:37:25 UTC 2009     root@logan.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC  i386

>Description:
Dlopened ELF module A dlopens ELF module B and keeps a handle to it in a
static C++ object. When the process exits, rtld_exit is called and the fini
function for A is called. This function calls the object static destructor
and it tries to dlclose the handle to B. This gives a segfault, and stack corruption.

>How-To-Repeat:
See attachment for a repro.
The attachment contains 5 files, separated by === cut here ===.
Separate the files, and run "build.sh" to create the shared objects and executable "prog" which will repro the problem.
>Fix:


Patch attached with submission follows:

There are 5 files included below:
build.sh
ctest.h
ctest1.c
ctest2.c
prog.c

To compile, enter:
    ./build.sh
To run the repro, enter:
    ./prog
Result:
    Segmentation fault: 11 (core dumped)

==================== cut here ==================== 
#!/bin/bash

rm *.o
rm *.so
rm prog

g++ -g -Wall -fPIC -c ctest2.c
g++ -g -shared -Wl,-soname,libctest2.so -o libctest2.so ctest2.o
g++ -g -Wall -fPIC -c ctest1.c
g++ -g -shared -Wl,-soname,libctest1.so -o libctest1.so ctest1.o

g++ -g -Wall -I. prog.c -o prog

==================== cut here ==================== 

// *****************************************************
// ctest.h
// *****************************************************

#ifndef CTEST_H
#define CTEST_H

#include <stdio.h>

#ifdef __cplusplus
extern "C" {
#endif

void LibInit_1(void);
void LibFini_1(void);
void LibInit_2(void);
void LibFini_2(void);

void ctest1(int *);
void ctest2(int *);

#ifdef __cplusplus
}
#endif

class an_init_fini_execute_me_1 {
    public:
        an_init_fini_execute_me_1 () {
            LibInit_1();
        }
        ~an_init_fini_execute_me_1 () {
            LibFini_1();
        }
};

class an_init_fini_execute_me_2 {
    public:
        an_init_fini_execute_me_2 () {
            LibInit_2();
        }
        ~an_init_fini_execute_me_2 () {
            LibFini_2();
        }
};

#endif

==================== cut here ==================== 

// *****************************************************
// ctest1.c
// *****************************************************

#include <stdlib.h>
#include <dlfcn.h>
#include "ctest.h"

static int init_fini_call_count_1 = 0 ;
static class an_init_fini_execute_me_1 execute_me_1;
static void *lib_handle = NULL;

void LibInit_1( void ) {
    init_fini_call_count_1++ ;
    if( init_fini_call_count_1 == 1 ) {
        fprintf( stderr, "LibInit/Fini - %s of %s\n", "Beg Init", "1" );
        lib_handle = dlopen("libctest2.so", RTLD_LAZY);
        fprintf( stderr, "LibInit/Fini - %s of %s\n", "End Init", "1" );
    }
}

void LibFini_1( void ) {
    init_fini_call_count_1-- ;
    if( init_fini_call_count_1 == 0 )
    {
        fprintf( stderr, "LibInit/Fini - %s of %s\n", "Beg Fini", "1" );
        if( lib_handle != NULL )
        {
            dlclose(lib_handle);
            lib_handle = NULL;
        }
        fprintf( stderr, "LibInit/Fini - %s of %s\n", "End Fini", "1" );
    }
}

void ctest1(int *i)
{
   *i = 1;
}

==================== cut here ==================== 

// *****************************************************
// ctest2.c
// *****************************************************

#include "ctest.h"

static int init_fini_call_count_2 = 0 ;
static class an_init_fini_execute_me_2 execute_me_2;

void LibInit_2( void ) {
    init_fini_call_count_2++ ;
    if( init_fini_call_count_2 == 1 ) {
        fprintf( stderr, "LibInit/Fini - %s of %s\n", "Beg Init", "2" );
        fprintf( stderr, "LibInit/Fini - %s of %s\n", "End Init", "2" );
    }
}

void LibFini_2( void ) {
    init_fini_call_count_2-- ;
    if( init_fini_call_count_2 == 0 )
    {
        fprintf( stderr, "LibInit/Fini - %s of %s\n", "Beg Fini", "2" );
        fprintf( stderr, "LibInit/Fini - %s of %s\n", "End Fini", "2" );
    }
}

void ctest2(int *i)
{
   *i = 2;
}
==================== cut here ==================== 

// *****************************************************
// prog.c
// *****************************************************

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include "ctest.h"

int main(int argc, char **argv) 
{
   void *lib_handle;

   lib_handle = dlopen("libctest1.so", RTLD_LAZY);
   if (!lib_handle) 
   {
      fprintf(stderr, "%s\n", dlerror());
      exit(1);
   }

   dlclose(lib_handle);
   return 0;
}



>Release-Note:
>Audit-Trail:

From: Bruce Cran <bruce@cran.org.uk>
To: bug-followup@freebsd.org,
 moller.michael10@yahoo.com
Cc:  
Subject: Re: kern/133246: [libc] dlclose(3) gives segfault when called in the fini function of another module
Date: Thu, 4 Mar 2010 10:19:15 +0000

 This appears to be the same problem as reported in kern/42956.
 
 -- 
 Bruce Cran

From: dfilter@FreeBSD.ORG (dfilter service)
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: kern/133246: commit references a PR
Date: Thu, 16 Dec 2010 16:56:49 +0000 (UTC)

 Author: jh
 Date: Thu Dec 16 16:56:44 2010
 New Revision: 216489
 URL: http://svn.freebsd.org/changeset/base/216489
 
 Log:
   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
 
 Modified:
   head/libexec/rtld-elf/rtld.c
 
 Modified: head/libexec/rtld-elf/rtld.c
 ==============================================================================
 --- head/libexec/rtld-elf/rtld.c	Thu Dec 16 16:55:22 2010	(r216488)
 +++ head/libexec/rtld-elf/rtld.c	Thu Dec 16 16:56:44 2010	(r216489)
 @@ -110,7 +110,7 @@ static int load_needed_objects(Obj_Entry
  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;
 _______________________________________________
 svn-src-all@freebsd.org mailing list
 http://lists.freebsd.org/mailman/listinfo/svn-src-all
 To unsubscribe, send any mail to "svn-src-all-unsubscribe@freebsd.org"
 
State-Changed-From-To: open->closed 
State-Changed-By: jh 
State-Changed-When: Sat Dec 18 17:59:22 UTC 2010 
State-Changed-Why:  
Duplicate of bin/149464. Fixed in head (r216489). 

http://www.freebsd.org/cgi/query-pr.cgi?pr=133246 

From: dfilter@FreeBSD.ORG (dfilter service)
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: kern/133246: commit references a PR
Date: Thu,  3 Mar 2011 17:12:43 +0000 (UTC)

 Author: jh
 Date: Thu Mar  3 17:12:24 2011
 New Revision: 219237
 URL: http://svn.freebsd.org/changeset/base/219237
 
 Log:
   MFC r216489:
   
   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
 
 Modified:
   stable/8/libexec/rtld-elf/rtld.c
 Directory Properties:
   stable/8/libexec/rtld-elf/   (props changed)
 
 Modified: stable/8/libexec/rtld-elf/rtld.c
 ==============================================================================
 --- stable/8/libexec/rtld-elf/rtld.c	Thu Mar  3 17:11:11 2011	(r219236)
 +++ stable/8/libexec/rtld-elf/rtld.c	Thu Mar  3 17:12:24 2011	(r219237)
 @@ -107,7 +107,7 @@ static int load_needed_objects(Obj_Entry
  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 *);
 @@ -1616,36 +1616,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);
  }
  
 @@ -1833,7 +1853,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();
 @@ -1946,20 +1966,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;
 _______________________________________________
 svn-src-all@freebsd.org mailing list
 http://lists.freebsd.org/mailman/listinfo/svn-src-all
 To unsubscribe, send any mail to "svn-src-all-unsubscribe@freebsd.org"
 
>Unformatted:
