Boost logo

Boost :

From: Maxim Yegorushkin (maxim.yegorushkin_at_[hidden])
Date: 2008-03-26 20:50:49


Good evening gentlemen,

Boost::gregorian::date streaming operators currently alter the locale of the
stream by inserting a custom_date_facet allocated on the heap into the
locale. The facet never gets explicitly destroyed, rather its ownership is
passed to the locale. Locale's destructor destroys the facet eventually.

As the code of the streaming operators, the constructor and the (compiler
generated) virtual destructor of the facet belong to header files, the vtable
for the facet ends up in the object file of the translation unit that first
calls a streaming operator and creates the facet.

The problem arises when using dynamic linking. It manifests itself most
apparently when locale's destructor is invoked. Locale's destructor accesses the
vtables of the facets when destroying them. This requires the vtables to be
accessible. If the module which contained the vtable of custom_date_facet has
already been unloaded, the facet on the heap is left with a dangling vtable
pointer. If the virtual address space range of the unloaded module is not
accessible any more (has been unmapped), accessing the dangling vtable pointer
leads to a crash.

The crash was first encountered on Windoze XP SP2 + MSVC 2005 SP1 + boost-1.33.x
and reproduced on Linux Fedora 8 + gcc-4.1.4 + boost-1.34.1 on the same day.

Here are the steps to reproduce:

1) A (C-language) application without C++ run-time dependencies explicitly loads
    at run-time two shared libraries (Foo, then Bar) linked dynamically against
    the same C++ run-time shared library. Loading the first library causes the
    C++ run-time library to be loaded and initialised.

2) A call is made to library Foo. The library outputs a boost::gregorian::date
    into std::cout, creating the facet. Another call is made to Bar just in case,
    which outputs just text into std::cout.

3) The libraries unloaded in the same order (Foo, the Bar). Unloading the second
    library decrements the reference counter of the C++ run-time dynamic library
    to zero and causes it do deinitialise. Deinitialising involves destroying the
    standard streams and eventually the locale the facet belongs to. The vtable
    pointer of the facet object refers to the address in Foo, which has already
    been unloaded. The application crashes.

Following is output of Linux crush. Please note, in the backtrace in stack frame
0 offending address 0x00135f20 belongs to unloaded foo.so, which can be deduced
from the process memory map.

A fix might be to avoid passing the ownership of the facet to the locale of the
stream (i.e. avoid calling imbue()), rather have the facet allocated statically
elsewhere and use it if the stream does not have one already (attached
explicitly by the user if desired).

Max

[max_at_zen boost_custom_date_facet]$ cat main.c
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define CHECK(expr, ...) if(expr) {} else { \
     fprintf(stderr, "%s:%d: ", __FILE__, __LINE__); \
     fprintf(stderr, __VA_ARGS__); \
     fprintf(stderr, "\n"); \
     exit(EXIT_FAILURE); \
}

typedef void fun_t();

int main()
{
     void *foo_lib, *bar_lib;
     fun_t *foo, *bar;

     printf("---- loading foo.so\n");
     CHECK((foo_lib = dlopen("./foo.so", RTLD_NOW)), "%s", dlerror());
     CHECK((foo = (fun_t*)dlsym(foo_lib, "foo")), "%s", dlerror());
     printf("---- foo.so loaded\n");

     printf("---- loading bar.so\n");
     CHECK((bar_lib = dlopen("./bar.so", RTLD_NOW)), "%s", dlerror());
     CHECK((bar = (fun_t*)dlsym(bar_lib, "bar")), "%s", dlerror());
     printf("---- bar.so loaded\n");

     printf("---- invoking foo()\n");
     foo();
     printf("---- invoking bar()\n");
     bar();

     printf("---- unloading foo.so\n");
     CHECK(!dlclose(foo_lib), "%s", dlerror());
     printf("---- foo.so unloaded\n");

     printf("---- unloading bar.so\n");
     CHECK(!dlclose(bar_lib), "%s", dlerror()); // <---- crashes here
     printf("---- bar.so unloaded\n");

     return EXIT_SUCCESS;
}
[max_at_zen boost_custom_date_facet]$ cat foo.cc
#include <iostream>
#include <fstream>
#include <boost/date_time/gregorian/gregorian.hpp>

extern "C" void foo()
{
     boost::gregorian::date d(2008, 3, 26);
     std::cout
         << "foo\n"
         << d << '\n'
         << std::ifstream("/proc/self/maps").rdbuf()
         << std::endl
         ;
}

[max_at_zen boost_custom_date_facet]$ cat bar.cc
#include <iostream>

extern "C" void bar()
{
     std::cout << "bar" << std::endl;
}
[max_at_zen boost_custom_date_facet]$ cat test.sh
#!/bin/bash -x

uname -a
rpm -q gcc gcc-c++ boost

# exit on compiler errors
set -e
gcc -o main -g -Wall -Wextra -ldl main.c
g++ -o foo.so -D_GLIBCXX_DEBUG -fpic -g -Wall -Wextra -shared foo.cc
g++ -o bar.so -D_GLIBCXX_DEBUG -fpic -g -Wall -Wextra -shared bar.cc
ls -l main foo.so bar.so

# don't exit on main crash
set +e
ulimit -c unlimited
rm -f core*
LD_DEBUG=libs ./main
gdb -silent main core* <<EOF
bt
EOF
[max_at_zen boost_custom_date_facet]$ ./test.sh
+ uname -a
Linux zen 2.6.24.3-34.fc8 #1 SMP Wed Mar 12 18:17:20 EDT 2008 i686 i686 i386
GNU/Linux
+ rpm -q gcc gcc-c++ boost
gcc-4.1.2-33
gcc-c++-4.1.2-33
boost-1.34.1-7.fc8
+ set -e
+ gcc -o main -g -Wall -Wextra -ldl main.c
+ g++ -o foo.so -D_GLIBCXX_DEBUG -fpic -g -Wall -Wextra -shared foo.cc
+ g++ -o bar.so -D_GLIBCXX_DEBUG -fpic -g -Wall -Wextra -shared bar.cc
+ ls -l main foo.so bar.so
-rwxrwxr-x 1 max max 63878 2008-03-27 00:01 bar.so
-rwxrwxr-x 1 max max 835145 2008-03-27 00:01 foo.so
-rwxrwxr-x 1 max max 9298 2008-03-27 00:01 main
+ set +e
+ ulimit -c unlimited
+ rm -f core.25031
+ LD_DEBUG=libs
+ ./main
      25064: find library=libdl.so.2 [0]; searching
      25064: search cache=/etc/ld.so.cache
      25064: trying file=/lib/libdl.so.2
      25064:
      25064: find library=libc.so.6 [0]; searching
      25064: search cache=/etc/ld.so.cache
      25064: trying file=/lib/libc.so.6
      25064:
      25064:
      25064: calling init: /lib/libc.so.6
      25064:
      25064:
      25064: calling init: /lib/libdl.so.2
      25064:
      25064:
      25064: initialize program: ./main
      25064:
      25064:
      25064: transferring control: ./main
      25064:
---- loading foo.so
      25064: find library=libstdc++.so.6 [0]; searching
      25064: search cache=/etc/ld.so.cache
      25064: trying file=/usr/lib/libstdc++.so.6
      25064:
      25064: find library=libm.so.6 [0]; searching
      25064: search cache=/etc/ld.so.cache
      25064: trying file=/lib/libm.so.6
      25064:
      25064: find library=libgcc_s.so.1 [0]; searching
      25064: search cache=/etc/ld.so.cache
      25064: trying file=/lib/libgcc_s.so.1
      25064:
      25064:
      25064: calling init: /lib/libgcc_s.so.1
      25064:
      25064:
      25064: calling init: /lib/libm.so.6
      25064:
      25064:
      25064: calling init: /usr/lib/libstdc++.so.6
      25064:
      25064:
      25064: calling init: ./foo.so
      25064:
---- foo.so loaded
---- loading bar.so
      25064:
      25064: calling init: ./bar.so
      25064:
---- bar.so loaded
---- invoking foo()
foo
2008-Mar-26
00110000-00111000 r-xp 00110000 00:00 0 [vdso]
00111000-00147000 r-xp 00000000 08:05 17178033
/home/max/src/boost_custom_date_facet/foo.so
00147000-00149000 rwxp 00036000 08:05 17178033
/home/max/src/boost_custom_date_facet/foo.so
00149000-0014c000 r-xp 00000000 08:05 17178034
/home/max/src/boost_custom_date_facet/bar.so
0014c000-0014d000 rwxp 00002000 08:05 17178034
/home/max/src/boost_custom_date_facet/bar.so
00700000-0070b000 r-xp 00000000 08:01 3649841 /lib/libgcc_s-4.1.2-20070925.so.1
0070b000-0070c000 rwxp 0000a000 08:01 3649841 /lib/libgcc_s-4.1.2-20070925.so.1
007b8000-007d3000 r-xp 00000000 08:01 3647869 /lib/ld-2.7.so
007d3000-007d4000 r-xp 0001a000 08:01 3647869 /lib/ld-2.7.so
007d4000-007d5000 rwxp 0001b000 08:01 3647869 /lib/ld-2.7.so
007d7000-0092a000 r-xp 00000000 08:01 3647870 /lib/libc-2.7.so
0092a000-0092c000 r-xp 00153000 08:01 3647870 /lib/libc-2.7.so
0092c000-0092d000 rwxp 00155000 08:01 3647870 /lib/libc-2.7.so
0092d000-00930000 rwxp 0092d000 00:00 0
00932000-00959000 r-xp 00000000 08:01 3649817 /lib/libm-2.7.so
00959000-0095a000 r-xp 00026000 08:01 3649817 /lib/libm-2.7.so
0095a000-0095b000 rwxp 00027000 08:01 3649817 /lib/libm-2.7.so
0095d000-00960000 r-xp 00000000 08:01 3649815 /lib/libdl-2.7.so
00960000-00961000 r-xp 00002000 08:01 3649815 /lib/libdl-2.7.so
00961000-00962000 rwxp 00003000 08:01 3649815 /lib/libdl-2.7.so
0616a000-0624a000 r-xp 00000000 08:01 4058038 /usr/lib/libstdc++.so.6.0.8
0624a000-0624e000 r-xp 000df000 08:01 4058038 /usr/lib/libstdc++.so.6.0.8
0624e000-0624f000 rwxp 000e3000 08:01 4058038 /usr/lib/libstdc++.so.6.0.8
0624f000-06255000 rwxp 0624f000 00:00 0
08048000-08049000 r-xp 00000000 08:05 17178019
/home/max/src/boost_custom_date_facet/main
08049000-0804a000 rw-p 00000000 08:05 17178019
/home/max/src/boost_custom_date_facet/main
09597000-095b8000 rw-p 09597000 00:00 0
b7f99000-b7f9b000 rw-p b7f99000 00:00 0
b7fb3000-b7fb4000 rw-p b7fb3000 00:00 0
bfec6000-bfedb000 rw-p bffea000 00:00 0 [stack]

---- invoking bar()
bar
---- unloading foo.so
      25064:
      25064: calling fini: ./foo.so [0]
      25064:
---- foo.so unloaded
---- unloading bar.so
      25064:
      25064: calling fini: ./bar.so [0]
      25064:
      25064:
      25064: calling fini: /usr/lib/libstdc++.so.6 [0]
      25064:
./test.sh: line 17: 25064 Segmentation fault (core dumped) LD_DEBUG=libs ./main
+ gdb -silent main core.25064
Using host libthread_db library "/lib/libthread_db.so.1".

warning: Can't read pathname for load map: Input/output error.
Missing separate debuginfo for ./bar.so
Try: yum --enablerepo='*-debuginfo' install
/usr/lib/debug/.build-id/63/a5b3dd1b5bb0cac7f2460e64314ba4bb271ba1
Reading symbols from /lib/libdl-2.7.so...Reading symbols from
/usr/lib/debug/lib/libdl-2.7.so.debug...done.
done.
Loaded symbols for /lib/libdl-2.7.so
Reading symbols from /lib/libc-2.7.so...Reading symbols from
/usr/lib/debug/lib/libc-2.7.so.debug...done.
done.
Loaded symbols for /lib/libc-2.7.so
Reading symbols from /lib/ld-2.7.so...Reading symbols from
/usr/lib/debug/lib/ld-2.7.so.debug...done.
done.
Loaded symbols for /lib/ld-2.7.so
Reading symbols from /usr/lib/libstdc++.so.6.0.8...Reading symbols from
/usr/lib/debug/usr/lib/libstdc++.so.6.0.8.debug...done.
done.
Loaded symbols for /usr/lib/libstdc++.so.6.0.8
Reading symbols from /lib/libm-2.7.so...Reading symbols from
/usr/lib/debug/lib/libm-2.7.so.debug...done.
done.
Loaded symbols for /lib/libm-2.7.so
Reading symbols from /lib/libgcc_s-4.1.2-20070925.so.1...Reading symbols from
/usr/lib/debug/lib/libgcc_s-4.1.2-20070925.so.1.debug...done.
done.
Loaded symbols for /lib/libgcc_s-4.1.2-20070925.so.1
Reading symbols from /home/max/src/boost_custom_date_facet/bar.so...done.
Loaded symbols for ./bar.so
Core was generated by `./main'.
Program terminated with signal 11, Segmentation fault.
#0 0x00135f20 in ?? ()
(gdb) #0 0x00135f20 in ?? ()
#1 0x061b5b51 in std::ios_base::Init::~Init ()
     at
/usr/src/debug/gcc-4.1.2-20070925/obj-i386-redhat-linux/i386-redhat-linux/libstdc++-v3/include/bits/basic_string.h:274
#2 0x061cb0f0 in __tcf_0 ()
     at
/usr/src/debug/gcc-4.1.2-20070925/obj-i386-redhat-linux/i386-redhat-linux/libstdc++-v3/include/iostream:76
#3 0x00803907 in __cxa_finalize (d=0x624b588) at cxa_finalize.c:56
#4 0x061a9c54 in __do_global_dtors_aux () from /usr/lib/libstdc++.so.6.0.8
#5 0x062251dc in _fini () from /usr/lib/libstdc++.so.6.0.8
#6 0x007cb0c2 in _dl_close_worker (map=0x9597f48) at dl-close.c:271
#7 0x007cb877 in _dl_close (_map=0x9597f48) at dl-close.c:729
#8 0x0095dd24 in dlclose_doit () from /lib/libdl-2.7.so
#9 0x007c61b6 in _dl_catch_error (objname=0x96105c, errstring=0x961060,
mallocedp=0x961058,
     operate=0x95dd00 <dlclose_doit>, args=0x9597f48) at dl-error.c:178
#10 0x0095e30c in _dlerror_run () from /lib/libdl-2.7.so
#11 0x0095dd5a in dlclose () from /lib/libdl-2.7.so
#12 0x080488b9 in main () at main.c:41
(gdb) quit


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk