Skip to content

Commit

Permalink
lift child restrictions after multi-threaded fork
Browse files Browse the repository at this point in the history
as the outcome of Austin Group tracker issue #62, future editions of
POSIX have dropped the requirement that fork be AS-safe. this allows
but does not require implementations to synchronize fork with internal
locks and give forked children of multithreaded parents a partly or
fully unrestricted execution environment where they can continue to
use the standard library (per POSIX, they can only portably use
AS-safe functions).

up until recently, taking this allowance did not seem desirable.
however, commit 8ed2bd8 exposed the
extent to which applications and libraries are depending on the
ability to use malloc and other non-AS-safe interfaces in MT-forked
children, by converting latent very-low-probability catastrophic state
corruption into predictable deadlock. dealing with the fallout has
been a huge burden for users/distros.

while it looks like most of the non-portable usage in applications
could be fixed given sufficient effort, at least some of it seems to
occur in language runtimes which are exposing the ability to run
unrestricted code in the child as part of the contract with the
programmer. any attempt at fixing such contracts is not just a
technical problem but a social one, and is probably not tractable.

this patch extends the fork function to take locks for all libc
singletons in the parent, and release or reset those locks in the
child, so that when the underlying fork operation takes place, the
state protected by these locks is consistent and ready for the child
to use. locking is skipped in the case where the parent is
single-threaded so as not to interfere with legacy AS-safety property
of fork in single-threaded programs. lock order is mostly arbitrary,
but the malloc locks (including bump allocator in case it's used) must
be taken after the locks on any subsystems that might use malloc, and
non-AS-safe locks cannot be taken while the thread list lock is held,
imposing a requirement that it be taken last.
  • Loading branch information
richfelker committed Nov 11, 2020
1 parent 34952fe commit 167390f
Show file tree
Hide file tree
Showing 17 changed files with 170 additions and 4 deletions.
19 changes: 19 additions & 0 deletions ldso/dynlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <semaphore.h>
#include <sys/membarrier.h>
#include "pthread_impl.h"
#include "fork_impl.h"
#include "libc.h"
#include "dynlink.h"

Expand Down Expand Up @@ -1426,6 +1427,17 @@ void __libc_exit_fini()
}
}

void __ldso_atfork(int who)
{
if (who<0) {
pthread_rwlock_wrlock(&lock);
pthread_mutex_lock(&init_fini_lock);
} else {
pthread_mutex_unlock(&init_fini_lock);
pthread_rwlock_unlock(&lock);
}
}

static struct dso **queue_ctors(struct dso *dso)
{
size_t cnt, qpos, spos, i;
Expand Down Expand Up @@ -1484,6 +1496,13 @@ static struct dso **queue_ctors(struct dso *dso)
}
queue[qpos] = 0;
for (i=0; i<qpos; i++) queue[i]->mark = 0;
for (i=0; i<qpos; i++)
if (queue[i]->ctor_visitor && queue[i]->ctor_visitor->tid < 0) {
error("State of %s is inconsistent due to multithreaded fork\n",
queue[i]->name);
free(queue);
if (runtime) longjmp(*rtld_fail, 1);
}

return queue;
}
Expand Down
2 changes: 2 additions & 0 deletions src/exit/at_quick_exit.c
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#include <stdlib.h>
#include "libc.h"
#include "lock.h"
#include "fork_impl.h"

#define COUNT 32

static void (*funcs[COUNT])(void);
static int count;
static volatile int lock[1];
volatile int *const __at_quick_exit_lockptr = lock;

void __funcs_on_quick_exit()
{
Expand Down
2 changes: 2 additions & 0 deletions src/exit/atexit.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <stdint.h>
#include "libc.h"
#include "lock.h"
#include "fork_impl.h"

#define malloc __libc_malloc
#define calloc __libc_calloc
Expand All @@ -20,6 +21,7 @@ static struct fl

static int slot;
static volatile int lock[1];
volatile int *const __atexit_lockptr = lock;

void __funcs_on_exit()
{
Expand Down
19 changes: 19 additions & 0 deletions src/internal/fork_impl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include <features.h>

extern hidden volatile int *const __at_quick_exit_lockptr;
extern hidden volatile int *const __atexit_lockptr;
extern hidden volatile int *const __dlerror_lockptr;
extern hidden volatile int *const __gettext_lockptr;
extern hidden volatile int *const __locale_lockptr;
extern hidden volatile int *const __random_lockptr;
extern hidden volatile int *const __sem_open_lockptr;
extern hidden volatile int *const __stdio_ofl_lockptr;
extern hidden volatile int *const __syslog_lockptr;
extern hidden volatile int *const __timezone_lockptr;

extern hidden volatile int *const __bump_lockptr;

extern hidden volatile int *const __vmlock_lockptr;

hidden void __malloc_atfork(int);
hidden void __ldso_atfork(int);
2 changes: 2 additions & 0 deletions src/ldso/dlerror.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "pthread_impl.h"
#include "dynlink.h"
#include "lock.h"
#include "fork_impl.h"

#define malloc __libc_malloc
#define calloc __libc_calloc
Expand All @@ -24,6 +25,7 @@ char *dlerror()

static volatile int freebuf_queue_lock[1];
static void **freebuf_queue;
volatile int *const __dlerror_lockptr = freebuf_queue_lock;

void __dl_thread_cleanup(void)
{
Expand Down
5 changes: 4 additions & 1 deletion src/locale/dcngettext.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "atomic.h"
#include "pleval.h"
#include "lock.h"
#include "fork_impl.h"

#define malloc __libc_malloc
#define calloc __libc_calloc
Expand Down Expand Up @@ -39,9 +40,11 @@ static char *gettextdir(const char *domainname, size_t *dirlen)
return 0;
}

static volatile int lock[1];
volatile int *const __gettext_lockptr = lock;

char *bindtextdomain(const char *domainname, const char *dirname)
{
static volatile int lock[1];
struct binding *p, *q;

if (!domainname) return 0;
Expand Down
5 changes: 4 additions & 1 deletion src/locale/locale_map.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "locale_impl.h"
#include "libc.h"
#include "lock.h"
#include "fork_impl.h"

#define malloc __libc_malloc
#define calloc undef
Expand All @@ -27,9 +28,11 @@ static const char envvars[][12] = {
"LC_MESSAGES",
};

static volatile int lock[1];
volatile int *const __locale_lockptr = lock;

const struct __locale_map *__get_locale(int cat, const char *val)
{
static volatile int lock[1];
static void *volatile loc_head;
const struct __locale_map *p;
struct __locale_map *new = 0;
Expand Down
5 changes: 4 additions & 1 deletion src/malloc/lite_malloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "libc.h"
#include "lock.h"
#include "syscall.h"
#include "fork_impl.h"

#define ALIGN 16

Expand All @@ -31,10 +32,12 @@ static int traverses_stack_p(uintptr_t old, uintptr_t new)
return 0;
}

static volatile int lock[1];
volatile int *const __bump_lockptr = lock;

static void *__simple_malloc(size_t n)
{
static uintptr_t brk, cur, end;
static volatile int lock[1];
static unsigned mmap_step;
size_t align=1;
void *p;
Expand Down
14 changes: 13 additions & 1 deletion src/malloc/mallocng/glue.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ __attribute__((__visibility__("hidden")))
extern int __malloc_lock[1];

#define LOCK_OBJ_DEF \
int __malloc_lock[1];
int __malloc_lock[1]; \
void __malloc_atfork(int who) { malloc_atfork(who); }

static inline void rdlock()
{
Expand All @@ -77,5 +78,16 @@ static inline void unlock()
static inline void upgradelock()
{
}
static inline void resetlock()
{
__malloc_lock[0] = 0;
}

static inline void malloc_atfork(int who)
{
if (who<0) rdlock();
else if (who>0) resetlock();
else unlock();
}

#endif
19 changes: 19 additions & 0 deletions src/malloc/oldmalloc/malloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "atomic.h"
#include "pthread_impl.h"
#include "malloc_impl.h"
#include "fork_impl.h"

#define malloc __libc_malloc
#define realloc __libc_realloc
Expand Down Expand Up @@ -531,3 +532,21 @@ void __malloc_donate(char *start, char *end)
c->csize = n->psize = C_INUSE | (end-start);
__bin_chunk(c);
}

void __malloc_atfork(int who)
{
if (who<0) {
lock(mal.split_merge_lock);
for (int i=0; i<64; i++)
lock(mal.bins[i].lock);
} else if (!who) {
for (int i=0; i<64; i++)
unlock(mal.bins[i].lock);
unlock(mal.split_merge_lock);
} else {
for (int i=0; i<64; i++)
mal.bins[i].lock[0] = mal.bins[i].lock[1] = 0;
mal.split_merge_lock[1] = 0;
mal.split_merge_lock[0] = 0;
}
}
2 changes: 2 additions & 0 deletions src/misc/syslog.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
#include <errno.h>
#include <fcntl.h>
#include "lock.h"
#include "fork_impl.h"

static volatile int lock[1];
static char log_ident[32];
static int log_opt;
static int log_facility = LOG_USER;
static int log_mask = 0xff;
static int log_fd = -1;
volatile int *const __syslog_lockptr = lock;

int setlogmask(int maskpri)
{
Expand Down
2 changes: 2 additions & 0 deletions src/prng/random.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <stdlib.h>
#include <stdint.h>
#include "lock.h"
#include "fork_impl.h"

/*
this code uses the same lagged fibonacci generator as the
Expand All @@ -23,6 +24,7 @@ static int i = 3;
static int j = 0;
static uint32_t *x = init+1;
static volatile int lock[1];
volatile int *const __random_lockptr = lock;

static uint32_t lcg31(uint32_t x) {
return (1103515245*x + 12345) & 0x7fffffff;
Expand Down
70 changes: 70 additions & 0 deletions src/process/fork.c
Original file line number Diff line number Diff line change
@@ -1,15 +1,85 @@
#include <unistd.h>
#include <errno.h>
#include "libc.h"
#include "lock.h"
#include "pthread_impl.h"
#include "fork_impl.h"

static volatile int *const dummy_lockptr = 0;

weak_alias(dummy_lockptr, __at_quick_exit_lockptr);
weak_alias(dummy_lockptr, __atexit_lockptr);
weak_alias(dummy_lockptr, __dlerror_lockptr);
weak_alias(dummy_lockptr, __gettext_lockptr);
weak_alias(dummy_lockptr, __locale_lockptr);
weak_alias(dummy_lockptr, __random_lockptr);
weak_alias(dummy_lockptr, __sem_open_lockptr);
weak_alias(dummy_lockptr, __stdio_ofl_lockptr);
weak_alias(dummy_lockptr, __syslog_lockptr);
weak_alias(dummy_lockptr, __timezone_lockptr);
weak_alias(dummy_lockptr, __bump_lockptr);

weak_alias(dummy_lockptr, __vmlock_lockptr);

static volatile int *const *const atfork_locks[] = {
&__at_quick_exit_lockptr,
&__atexit_lockptr,
&__dlerror_lockptr,
&__gettext_lockptr,
&__locale_lockptr,
&__random_lockptr,
&__sem_open_lockptr,
&__stdio_ofl_lockptr,
&__syslog_lockptr,
&__timezone_lockptr,
&__bump_lockptr,
};

static void dummy(int x) { }
weak_alias(dummy, __fork_handler);
weak_alias(dummy, __malloc_atfork);
weak_alias(dummy, __ldso_atfork);

static void dummy_0(void) { }
weak_alias(dummy_0, __tl_lock);
weak_alias(dummy_0, __tl_unlock);

pid_t fork(void)
{
sigset_t set;
__fork_handler(-1);
__block_app_sigs(&set);
int need_locks = libc.need_locks > 0;
if (need_locks) {
__ldso_atfork(-1);
__inhibit_ptc();
for (int i=0; i<sizeof atfork_locks/sizeof *atfork_locks; i++)
if (*atfork_locks[i]) LOCK(*atfork_locks[i]);
__malloc_atfork(-1);
__tl_lock();
}
pthread_t self=__pthread_self(), next=self->next;
pid_t ret = _Fork();
int errno_save = errno;
if (need_locks) {
if (!ret) {
for (pthread_t td=next; td!=self; td=td->next)
td->tid = -1;
if (__vmlock_lockptr) {
__vmlock_lockptr[0] = 0;
__vmlock_lockptr[1] = 0;
}
}
__tl_unlock();
__malloc_atfork(!ret);
for (int i=0; i<sizeof atfork_locks/sizeof *atfork_locks; i++)
if (*atfork_locks[i])
if (ret) UNLOCK(*atfork_locks[i]);
else **atfork_locks[i] = 0;
__release_ptc();
__ldso_atfork(!ret);
}
__restore_sigs(&set);
__fork_handler(!ret);
if (ret<0) errno = errno_save;
return ret;
Expand Down
2 changes: 2 additions & 0 deletions src/stdio/ofl.c
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include "stdio_impl.h"
#include "lock.h"
#include "fork_impl.h"

static FILE *ofl_head;
static volatile int ofl_lock[1];
volatile int *const __stdio_ofl_lockptr = ofl_lock;

FILE **__ofl_lock()
{
Expand Down
2 changes: 2 additions & 0 deletions src/thread/sem_open.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <stdlib.h>
#include <pthread.h>
#include "lock.h"
#include "fork_impl.h"

#define malloc __libc_malloc
#define calloc __libc_calloc
Expand All @@ -24,6 +25,7 @@ static struct {
int refcnt;
} *semtab;
static volatile int lock[1];
volatile int *const __sem_open_lockptr = lock;

#define FLAGS (O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NONBLOCK)

Expand Down
2 changes: 2 additions & 0 deletions src/thread/vmlock.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "pthread_impl.h"
#include "fork_impl.h"

static volatile int vmlock[2];
volatile int *const __vmlock_lockptr = vmlock;

void __vm_wait()
{
Expand Down
2 changes: 2 additions & 0 deletions src/time/__tz.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <sys/mman.h>
#include "libc.h"
#include "lock.h"
#include "fork_impl.h"

#define malloc __libc_malloc
#define calloc undef
Expand Down Expand Up @@ -35,6 +36,7 @@ static char *old_tz = old_tz_buf;
static size_t old_tz_size = sizeof old_tz_buf;

static volatile int lock[1];
volatile int *const __timezone_lockptr = lock;

static int getint(const char **p)
{
Expand Down

0 comments on commit 167390f

Please sign in to comment.