Bug 1435 - dlopen/dlclose with ctors/dtors and on_exit handler -> using uninitialized memory
Summary: dlopen/dlclose with ctors/dtors and on_exit handler -> using uninitialized me...
Status: REOPENED
Alias: None
Product: uClibc
Classification: Unclassified
Component: Shared Library Support (show other bugs)
Version: 0.9.31
Hardware: PC Linux
: P3 normal
Target Milestone: ---
Assignee: Carmelo Amoroso
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-03-27 21:55 UTC by Ronald Wahl
Modified: 2011-02-09 11:46 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:


Attachments
Better patch fixing the uninitialized value issue. (567 bytes, patch)
2010-03-28 00:02 UTC, Ronald Wahl
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Ronald Wahl 2010-03-27 21:55:59 UTC
The appended test program registers an exit_handler with on_exit() and calls dlopen and dlclose on a plugin (can be compiled using an empty source file). It only needs to be compiled with a toolchain supporting ctors/dtors using __cxa_exit. Valgrind will report:

==1264== Conditional jump or move depends on uninitialised value(s)
==1264==    at 0x405675F: __cxa_finalize (_atexit.c:203)
==1264==    by 0x4463354: (within /lib/test_plugin.so)
==1264==    by 0x446344B: (within /lib/test_plugin.so)
==1264==    by 0x40149F4: do_dlclose (libdl.c:545)
==1264==    by 0x8048522: main (dlclose_atexit_test_main.c:17)

This is because on_exit does not initialize the dso_handle to NULL in on_exit(). The memory of the structure containing dso_handle is allocated with realloc() and is uninitialized. Maybe this can crash the program on dlclose() or even be used to call arbitrary code. Current uClibc seem to be affected as well - at least the code looks the same. This patch fixes this:

Index: libc/stdlib/_atexit.c
===================================================================
--- libc/stdlib/_atexit.c       (revision 198509)
+++ libc/stdlib/_atexit.c       (revision 198510)
@@ -145,6 +145,7 @@
 
     efp->funcs.on_exit.func = func;
     efp->funcs.on_exit.arg = arg;
+    efp->funcs.cxa_atexit.dso_handle = NULL;
     /* assign last for thread safety, since we're now unlocked */
     efp->type = ef_on_exit;


--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--

#include <assert.h>
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

static void exit_handler(int i, void* arg)
{
    (void)i;
    (void)arg;
}

int main(void)
{
    on_exit(exit_handler, NULL);
    void* dl_handle = dlopen("test_plugin.so", RTLD_LAZY);
    assert(dl_handle);
    dlclose(dl_handle);
    return 0;
}

--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--8<--
Comment 1 Ronald Wahl 2010-03-28 00:02:40 UTC
Created attachment 1399 [details]
Better patch fixing the uninitialized value issue.

Just noticed that my original patch was probably not optimal. This one is better I think.
Comment 2 Bernhard Reutner-Fischer 2010-03-31 16:28:25 UTC
I cannot reproduce this with 0.9.31-rc1 nor current master. Please reopen the bug if you can reproduce it with 0.9.30.3
thanks,
Comment 3 Ronald Wahl 2010-03-31 20:32:53 UTC
Well unfortunately I cannot test this quickly. But probably you'll at least see the logical bug (or show me that I'm wrong):

In the following I talk about libc/stdlib/_atexit.c

(1) When registering an exit handler with on_exit or __cxa_atexit a new exit
    slot is acquired in __new_exitfn - in the end it is allocated with realloc
    there and this memory is actually not initialized.

(2) When a handler is registered with __cxa_atexit we initialize
    efp->funcs.cxa_atexit.dso_handle (note efp->funcs is actually a union!)

(3) When a handler is registered with on_exit the
    efp->funcs.cxa_atexit.dso_handle field is not initialized because it is in
    the wrong part of the union which is perfecly ok.

(4) Now look at the __cxa_finalize function: We iterate over the exit handlers
    and happily assume that efp->funcs.cxa_atexit.dso_handle is initialized. But
    this is only true for slots of type ef_cxa_atexit. If one registered an
    on_exit handler it will access uninitialized memory.

I'm not sure why you can't reproduce this. Either the uClibc has this __cxa_atexit stuff disabled, the toolchain does not support it, valgrind surpresses the output or whatever.

So can you tell me if I missed something or is this really a bug?
Comment 4 Ronald Wahl 2010-04-21 13:37:36 UTC
As I already suspected - the bug is still present in 0.9.31.

Maybe this is relevant as well: uClibc must be configured with UCLIBC_DYNAMIC_ATEXIT=y and the compiler must be compiled to use cxa_atexit via --enable-__cxa_atexit configure option.