Quellcode durchsuchen

exit: add back lock to make concurrent calls to exit safe

per the C and POSIX standards, calling exit "more than once",
including via return from main, produces undefined behavior. this
language predates threads, and at the time it was written, could only
have applied to recursive calls to exit via atexit handlers. C++
likewise makes calls to exit from global dtors undefined. nonetheless,
by the present specification as written, concurrent calls to exit by
multiple threads also have undefined behavior.

originally, our implementation of exit did have locking to handle
concurrent calls safely, but that was changed in commit
2e55da911896a91e95b24ab5dc8a9d9b0718f4de based on it being undefined.
from a standpoint of both hardening and quality of implementation,
that change seems to have been a mistake.

this change adds back locking, but with awareness of the lock owner so
that recursive calls to exit can be trapped rather than deadlocking.
this also opens up the possibility of allowing recursive calls to
succeed, if future consensus ends up being in favor of that.

prior to this change, exit already behaved partly as if protected by a
lock as long as atexit was linked, but multiple threads calling exit
could concurrently "pop off" atexit handlers and execute them in
parallel with one another rather than serialized in the reverse order
of registration. this was a likely unnoticed but potentially very
dangerous manifestation of the undefined behavior. if on the other
hand atexit was not linked, multiple threads calling exit concurrently
could each run their own instance of global dtors, if any, likely
producing double-free situations.

now, if multiple threads call exit concurrently, all but the first
will permanently block (in SYS_pause) until the process terminates,
and all atexit handlers, global dtors, and stdio flushing/position
consistency will be handled in the thread that arrived first. this is
really the only reasonable way to define concurrent calls to exit. it
is not recommended usage, but may become so in the future if there is
consensus/standardization, as there is a push from the rust language
community (and potentially other languages interoperating with the C
runtime) to make concurrent calls to the language's exit interfaces
safe even when multiple languages are involved in a program, and this
is only possible by having the locking in the underlying C exit.
Rich Felker vor 4 Monaten
Ursprung
Commit
8cca79a72c
1 geänderte Dateien mit 14 neuen und 0 gelöschten Zeilen
  1. 14 0
      src/exit/exit.c

+ 14 - 0
src/exit/exit.c

@@ -1,6 +1,9 @@
 #include <stdlib.h>
 #include <stdint.h>
 #include "libc.h"
+#include "pthread_impl.h"
+#include "atomic.h"
+#include "syscall.h"
 
 static void dummy()
 {
@@ -26,6 +29,17 @@ weak_alias(libc_exit_fini, __libc_exit_fini);
 
 _Noreturn void exit(int code)
 {
+	/* Handle potentially concurrent or recursive calls to exit,
+	 * whose behaviors have traditionally been undefined by the
+	 * standards. Using a custom lock here avoids pulling in lock
+	 * machinery and lets us trap recursive calls while supporting
+	 * multiple threads contending to be the one to exit(). */
+	static volatile int exit_lock[1];
+	int tid =  __pthread_self()->tid;
+	int prev = a_cas(exit_lock, 0, tid);
+	if (prev == tid) a_crash();
+	else if (prev) for (;;) __syscall(SYS_pause);
+
 	__funcs_on_exit();
 	__libc_exit_fini();
 	__stdio_exit();