This is the mail archive of the glibc-bugs@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[Bug nptl/19951] New: Use after free in pthread_detach


https://sourceware.org/bugzilla/show_bug.cgi?id=19951

            Bug ID: 19951
           Summary: Use after free in pthread_detach
           Product: glibc
           Version: 2.19
            Status: NEW
          Severity: critical
          Priority: P2
         Component: nptl
          Assignee: unassigned at sourceware dot org
          Reporter: labath at google dot com
                CC: drepper.fsp at gmail dot com
  Target Milestone: ---

Created attachment 9195
  --> https://sourceware.org/bugzilla/attachment.cgi?id=9195&action=edit
Source file reproducing the bug.

Summary: A race in pthread_detach can trigger a read from deallocated memory
(=>SEGV) or can corrupt the state of another thread, if that memory has been
reused.

When a detached thread exits, it's descriptor and stack does not get freed
immediately, but it gets put into a cache. Memory from this cache can later be
reused to create new threads, or freed (via munmap) if the cache gets too big.
However, it is possible for this reuse/unmap to happen before the actual
pthread_detach call returns, while it's still accessing the memory via the
descriptor of the now-exited thread.

I attach a small test file (a.c) which demonstrates problem. Note that I am
running the program under gdb, but I am doing this only to control the relative
timings of individual threads, I am messing in no way with the internal state
of the library.

$ gdb ./a.out 
(gdb) set non-stop on
(gdb) dir /etc/apt/eglibc-2.19/nptl
Source directories searched: /etc/apt/eglibc-2.19/nptl:$cdir:$cwd
(gdb) b 21
Breakpoint 1 at 0x4008d2: file a.c, line 21.
(gdb) b start
Breakpoint 2 at 0x4007c5: file a.c, line 6.
(gdb) r
Starting program: /tmp/a.out 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/grte/v4/lib64/libthread_db.so.1".
[New Thread 0x7ffff77f6700 (LWP 52987)]
[New Thread 0x7ffff45f5700 (LWP 52988)]

Breakpoint 2, start (arg=0x0) at a.c:6
6           return 0;
(gdb) 
Breakpoint 1, main () at a.c:21
21          assert(pthread_detach(handle2) == 0);

Breakpoint 2, start (arg=0x0) at a.c:6
6           return 0;
####
Main thread has created two new threads (which we have stopped in the start
function). Thread 2 has already been detached, and we are now about to detach
Thread 3.
####
s
pthread_detach (th=140737293276928) at pthread_detach.c:31
31        if (INVALID_NOT_TERMINATED_TD_P (pd))
(gdb) n
38        if (atomic_compare_and_exchange_bool_acq (&pd->joinid, pd, NULL))
(gdb) 
50          if ((pd->cancelhandling & EXITING_BITMASK) != 0)
(gdb) p pd
$1 = (struct pthread *) 0x7ffff45f5700
#####
The main thread is now in pthread_detach. It has already marked the thread as
"detached", but it will still access it's memory: pd->cancelhandling and
__free_tcb(pd). Before we let it do that, we will let other threads complete.
#####
(gdb) info th
  Id   Target Id         Frame 
  3    Thread 0x7ffff45f5700 (LWP 52988) "a.out" start (arg=0x0) at a.c:6
  2    Thread 0x7ffff77f6700 (LWP 52987) "a.out" start (arg=0x0) at a.c:6
* 1    Thread 0x7ffff7fd8740 (LWP 52983) "a.out" pthread_detach
(th=140737293276928)
    at pthread_detach.c:50
(gdb) thread 3
[Switching to thread 3 (Thread 0x7ffff45f5700 (LWP 52988))]
#0  start (arg=0x0) at a.c:6
6           return 0;
(gdb) c
Continuing.
[Thread 0x7ffff45f5700 (LWP 52988) exited]
No unwaited-for children left.
#####
Thread 3 has exited, it's memory has been put into the stack_cache
(allocatestack.c).
#####
(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff77f6700 (LWP 52987))]
#0  start (arg=0x0) at a.c:6
6           return 0;
(gdb) c
Continuing.
[Thread 0x7ffff77f6700 (LWP 52987) exited]
No unwaited-for children left.
#####
Thread 2 has exited as well. It's exit has triggered a purge of the cache,
which unmapped the memory used by Thread 3. Now we let the main thread finish,
which will trigger a SIGSEGV. If that memory had been reallocated, or if
pthread had reused the cache entry for another thread, it could mess with
random memory.
#####
(gdb) thread 1
[Switching to thread 1 (Thread 0x7ffff7fd8740 (LWP 52983))]
#0  pthread_detach (th=140737293276928) at pthread_detach.c:50
50          if ((pd->cancelhandling & EXITING_BITMASK) != 0)
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
pthread_detach (th=140737293276928) at pthread_detach.c:50
50          if ((pd->cancelhandling & EXITING_BITMASK) != 0)
(gdb) q



Note that even though I have reproduced this bug in gdb, I have observed this
happening in the wild (which led me to start investigating).

-- 
You are receiving this mail because:
You are on the CC list for the bug.

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]