Skip to content

Commit da6e76a

Browse files
Jan Philipp Haferandrewrk
Jan Philipp Hafer
authored andcommitted
std.os.abort: ported signal handling from musl
* Document deviation from Linux man page, which is identical to musl. Man page wants always enabled user-provided abort handlers. Worst case logic bug, which this can introduce: + user disables SIGABRT handler to prevent tear down to last safe state + abort() gets called and enables user-provided SIGABRT handler + SIGABRT tears down to supposed last safe state instead of crash + Application, instead of crashing, continues * Pid 1 within containers needs special handling. - fatal signals are not transmitted without privileges, so use exit as fallback * Fix some signaling bits * Add checks in Debug and ReleaseSafe for wrong sigprocmask
1 parent f671942 commit da6e76a

File tree

1 file changed

+61
-3
lines changed

1 file changed

+61
-3
lines changed

lib/std/os.zig

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,15 @@ pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (builtin.
250250
else => undefined,
251251
};
252252

253+
/// Atomic to guard correct program teardown in abort()
254+
var abort_entered = impl: {
255+
if (builtin.single_threaded) {
256+
break :impl {};
257+
} else {
258+
break :impl std.atomic.Atomic(bool).init(false);
259+
}
260+
};
261+
253262
/// To obtain errno, call this function with the return value of the
254263
/// system function call. For some systems this will obtain the value directly
255264
/// from the return code; for others it will use a thread-local errno variable.
@@ -442,6 +451,7 @@ fn getRandomBytesDevURandom(buf: []u8) !void {
442451
/// Causes abnormal process termination.
443452
/// If linking against libc, this calls the abort() libc function. Otherwise
444453
/// it raises SIGABRT followed by SIGKILL and finally lo
454+
/// assume: Current signal handler for SIGABRT does **not call abort**.
445455
pub fn abort() noreturn {
446456
@setCold(true);
447457
// MSVCRT abort() sometimes opens a popup window which is undesirable, so
@@ -454,11 +464,48 @@ pub fn abort() noreturn {
454464
windows.kernel32.ExitProcess(3);
455465
}
456466
if (!builtin.link_libc and builtin.os.tag == .linux) {
467+
// Linux man page wants to first "unblock SIG.ABRT", but this is a footgun
468+
// for user-defined signal handlers that want to restore some state in
469+
// some program sections and crash in others
470+
471+
// user installed SIGABRT handler is run, if installed
457472
raise(SIG.ABRT) catch {};
458473

459-
// TODO the rest of the implementation of abort() from musl libc here
474+
// disable all signal handlers
475+
sigprocmask(SIG.BLOCK, &linux.all_mask, null);
476+
477+
// ensure teardown by one thread
478+
if (!builtin.single_threaded) {
479+
while (abort_entered.compareAndSwap(false, true, .SeqCst, .SeqCst)) |_| {}
480+
}
481+
482+
// install default handler to terminate
483+
const sigact = Sigaction{
484+
.handler = .{ .sigaction = SIG.DFL },
485+
.mask = undefined,
486+
.flags = undefined,
487+
.restorer = undefined,
488+
};
489+
sigaction(SIG.ABRT, &sigact, null) catch unreachable;
490+
491+
// make sure we have a pending SIGABRT queued
492+
const tid = std.Thread.getCurrentId();
493+
_ = linux.tkill(@intCast(i32, tid), SIG.ABRT);
460494

495+
// SIG.ABRT signal will run default handler
496+
const sigabrtmask: linux.sigset_t = [_]u32{0} ** 31 ++ [_]u32{1 << (SIG.ABRT - 1)};
497+
sigprocmask(SIG.UNBLOCK, &sigabrtmask, null); // [32]u32
498+
499+
// Beyond this point should be unreachable
500+
501+
// abnormal termination without using signal handler
502+
const nullptr: *allowzero volatile u8 = @intToPtr(*allowzero volatile u8, 0);
503+
nullptr.* = 0;
504+
505+
// try SIGKILL, which is no abnormal termination as defined by POSIX and ISO C
461506
raise(SIG.KILL) catch {};
507+
508+
// pid 1 might not be signalled in some containers
462509
exit(127);
463510
}
464511
if (builtin.os.tag == .uefi) {
@@ -485,13 +532,13 @@ pub fn raise(sig: u8) RaiseError!void {
485532
if (builtin.os.tag == .linux) {
486533
var set: sigset_t = undefined;
487534
// block application signals
488-
_ = linux.sigprocmask(SIG.BLOCK, &linux.app_mask, &set);
535+
sigprocmask(SIG.BLOCK, &linux.app_mask, &set);
489536

490537
const tid = linux.gettid();
491538
const rc = linux.tkill(tid, sig);
492539

493540
// restore signal mask
494-
_ = linux.sigprocmask(SIG.SETMASK, &set, null);
541+
sigprocmask(SIG.SETMASK, &set, null);
495542

496543
switch (errno(rc)) {
497544
.SUCCESS => return,
@@ -5441,6 +5488,17 @@ pub fn sigaction(sig: u6, noalias act: ?*const Sigaction, noalias oact: ?*Sigact
54415488
}
54425489
}
54435490

5491+
/// Set the thread signal mask
5492+
/// Invalid masks are checked in Debug and ReleaseFast
5493+
pub fn sigprocmask(flags: u32, noalias set: ?*const sigset_t, noalias oldset: ?*sigset_t) void {
5494+
switch (errno(system.sigprocmask(flags, set, oldset))) {
5495+
.SUCCESS => return,
5496+
.FAULT => unreachable,
5497+
.INVAL => unreachable, // main purpose: debug InvalidValue error
5498+
else => unreachable,
5499+
}
5500+
}
5501+
54445502
pub const FutimensError = error{
54455503
/// times is NULL, or both tv_nsec values are UTIME_NOW, and either:
54465504
/// * the effective user ID of the caller does not match the owner

0 commit comments

Comments
 (0)