Bug 12310 - pthread_exit() in main thread segfaults when statically-linked
Summary: pthread_exit() in main thread segfaults when statically-linked
Status: RESOLVED FIXED
Alias: None
Product: glibc
Classification: Unclassified
Component: nptl (show other bugs)
Version: 2.11
: P2 minor
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-12-11 23:15 UTC by Mark Seaborn
Modified: 2014-06-30 06:20 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Mark Seaborn 2010-12-11 23:15:09 UTC
Calling pthread_exit() in the main thread segfaults in a
statically-linked executable.

$ cat pthread-exit-main.c 
#include <pthread.h>
int main() {
  pthread_exit(NULL);
  return 1;
}

$ gcc -Wall pthread-exit-main.c -o pthread-exit-main
$ ./pthread-exit-main 
$ echo $?
0
$ gcc -Wall pthread-exit-main.c -o pthread-exit-main -static -lpthread
$ ./pthread-exit-main 
Segmentation fault

$ gdb ./pthread-exit-main 
(gdb) run
Starting program: /home/mseaborn/test/pthread-exit-main 

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()

This was with glibc 2.11.1 on Ubuntu Lucid.  I have also seen this
with glibc 2.9.
Comment 1 Siddhesh Poyarekar 2012-05-08 17:31:47 UTC
I am not able to reproduce this on trunk.
Comment 2 Vladimir Nikulichev 2013-05-17 14:14:23 UTC
I reproduced it with glibc 2.12.2, gcc 4.4.6, binutils 2.20.1.
It is a linking issue. Look at a file csu/libc-start.c:

//=======================================================

  extern unsigned int __nptl_nthreads __attribute ((weak));
  unsigned int *const ptr = &__nptl_nthreads;

    if (! atomic_decrement_and_test (ptr))
  /* Not much left to do but to exit the thread, not the process.  */
  __exit_thread (0);

//=======================================================

This atomic_decrement_and_test finally looks like this:
0x0000000000400629 <+489>:	lock decl -0x400630(%rip)        # 0x0
0x0000000000400630 <+496>:	sete   %dl

So it seems that address of __nptl_nthreads became NULL after linking. The variable is stored in file nptl/pthread_create.c. The program above will work fine if you add dummy call to pthread_create():

$ cat exit.c 
#include <pthread.h>

void foo() {
  pthread_create(NULL, NULL, NULL, NULL);
}

int main() {
  pthread_exit(NULL);
  return 1;
}
$ gcc exit.c -static -lpthread
$ ./a.out
$ echo $?
0
$ objdump -d ./a.out
# .................
409e09:       f0 ff 0d 40 62 29 00    lock decl 0x296240(%rip)        # 6a0050 <__nptl_nthreads>
409e10:       0f 94 c2                sete   %dl
# .................
Comment 3 Vladimir Nikulichev 2013-05-17 14:29:13 UTC
I guess nobody expected static program working with threads but not invoking pthread_create() anywhere. So in real cases weak reference doesn't make problems
Obvious solution could be adding reference to __nptl_nthreads in pthread_exit.c
Comment 4 Carlos O'Donell 2013-05-23 14:02:19 UTC
(In reply to comment #3)
> I guess nobody expected static program working with threads but not invoking
> pthread_create() anywhere. So in real cases weak reference doesn't make
> problems
> Obvious solution could be adding reference to __nptl_nthreads in pthread_exit.c

The fact that this doesn't crash in master is sheer luck.

In the non-static case the forwarder calls exit() when you ask for pthread_exit() and everything works correctly. In the static case you actually call pthread_exit() and it breaks because nothing included __nptl_nthreads from pthread_create.os and that means it has a zero value.

Static linking should work for all reasonable cases.

This case is reasonable because the main thread is a real thread and you should be able to exit from it using pthread_exit.

We need a reference to __nptl_nthreads as you suggest. Doing so will pull in all of pthread_exit.os into the application image, so it might grow a little in size. I don't see that as a problem.

Please post a patch to libc-alpha@sourceware.org to fix this.

I suggest building glibc master, and testing the patch with that, and making sure `make -k check' doesn't show any regressions.

See:
http://sourceware.org/glibc/wiki/Testing/Builds
and
http://sourceware.org/glibc/wiki/Contribution%20checklist

Your change is sufficiently minimal that it probably falls below the legally significant barrier and doesn't require a copyright assignment. However, if you plan to contribute more changes then we strongly recommend signing a copyright assignment for glibc to the FSF (or whatever you want to do e.g. individual disclaimer).
Comment 5 Vladimir Nikulichev 2013-06-16 13:27:48 UTC
There are two another solutions:

1) Move __nptl_nthreads to separate object file and don't mark the reference as weak. I think it is better semantically.
2) Check if &__nptl_nthreads == NULL in libc-start.

What do you think about it?
Comment 6 Carlos O'Donell 2013-06-17 15:31:51 UTC
(In reply to Vladimir Nikulichev from comment #5)
> There are two another solutions:
> 
> 1) Move __nptl_nthreads to separate object file and don't mark the reference
> as weak. I think it is better semantically.

Please investigate having pthread_exit reference __nptl_nthreads. We don't want to unconditionally include __nptl_nthreads if we can avoid it.

> 2) Check if &__nptl_nthreads == NULL in libc-start.

This isn't needed if pthread_exit correctly references all of the data that it needs. Keeping the code simpler is better for maintenance.
Comment 7 Vladimir Nikulichev 2013-06-22 14:08:47 UTC
I sent a patch to libc-alpha@sourceware.org
Comment 8 Vladimir Nikulichev 2013-06-23 14:56:33 UTC
Obviously better fix (sent another patch):

http://sourceware.org/ml/libc-alpha/2013-06/msg00862.html
Comment 9 Carlos O'Donell 2013-06-24 17:24:50 UTC
~~~ Vladimir Nikulichev 2013-06-23 14:56:33 UTC ~~~
Obviously better fix (sent another patch):

http://sourceware.org/ml/libc-alpha/2013-06/msg00862.html.
~~~
Comment 10 Carlos O'Donell 2013-06-24 21:12:07 UTC
Fixed.

commit 963509c045d192b5a27891617b907fb857042f36
Author: Vladimir Nikulichev <v.nikulichev@gmail.com>
Date:   Mon Jun 24 17:08:07 2013 -0400

    BZ #12310: pthread_exit in static app. segfaults
    
    Static applications that call pthread_exit on the main
    thread segfault. This is because after a thread terminates
    __libc_start_main decrements __nptl_nthreads which is only
    defined in pthread_create. Therefore the right solution is
    to add a requirement to pthread_create from pthread_exit.
    
    ~~~
    nptl/
    
    2013-06-24  Vladimir Nikulichev  <v.nikulichev@gmail.com>
    
        [BZ #12310]
        * pthread_exit.c: Add reference to pthread_create.

---