Files
browser/src/core_dump.zig
T
Scott Taylor 02a0a33498 Record Linux fatal signals without blocking on stderr
ReleaseFast faults otherwise leave only an exit status. Record the signal,
original fault registers and build identity without entering panic reporting,
the IO backend, an allocator-backed unwinder or telemetry.

For pipe stderr, prepare an independent nonblocking procfs descriptor before
threads start; never change the inherited descriptor's shared flags. For
sockets, use per-call nonblocking send flags. Drop output for unsupported
sinks or backpressure, then re-raise the original signal. Other platforms
retain their existing signal handling.

Core limits still apply, but cores capture the re-raise context rather than
the original fault; document that distinction. Subprocess tests cover full
pipes and sockets with undrained readers, unavailable/read-only/file stderr,
unchanged flags, repeated attachment, held panic locks and hardware faults.
2026-09-15 15:30:11 -04:00

84 lines
3.2 KiB
Zig

// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Opt-in core-dump suppression.
//!
//! On Linux, fatal signals are re-raised after nonblocking diagnostics.
//! Core limits still apply, but the core captures the re-raise context;
//! the diagnostic record holds the original fault PC. Other platforms keep
//! their existing signal handling. Signals and panics can produce cores. When many
//! instances run under a shared `core_pattern` crash reporter — e.g. a
//! containerized crawl fleet — those dumps become pure storage and alert
//! noise, and a browser core can capture the contents of arbitrary pages.
//! `LIGHTPANDA_DISABLE_CORE_DUMP` lets an operator drop the cores while
//! leaving the default behavior (and local debugging) untouched.
const std = @import("std");
const builtin = @import("builtin");
const lp = @import("lightpanda.zig");
const log = lp.log;
pub fn disableIfRequested() void {
if (!shouldDisable()) return;
disable() catch |err| {
log.warn(.app, "could not disable core dumps", .{ .err = err });
};
}
fn shouldDisable() bool {
if (builtin.os.tag == .windows) return false;
return std.c.getenv("LIGHTPANDA_DISABLE_CORE_DUMP") != null;
}
// Zeroes only the soft limit; that is what the kernel consults when deciding
// whether to dump (including the piped-`core_pattern` opt-out), and keeping the
// hard limit lets the process raise it again if it ever needs to.
fn disable() !void {
var limit = try std.posix.getrlimit(.CORE);
limit.cur = 0;
try std.posix.setrlimit(.CORE, limit);
}
const testing = @import("testing.zig");
extern fn setenv(name: [*:0]u8, value: [*:0]u8, override: c_int) c_int;
extern fn unsetenv(name: [*:0]u8) c_int;
test "core_dump: disabled only when the env var is set" {
_ = unsetenv(@constCast("LIGHTPANDA_DISABLE_CORE_DUMP"));
try testing.expectEqual(false, shouldDisable());
_ = setenv(@constCast("LIGHTPANDA_DISABLE_CORE_DUMP"), @constCast(""), 1);
defer _ = unsetenv(@constCast("LIGHTPANDA_DISABLE_CORE_DUMP"));
try testing.expectEqual(true, shouldDisable());
}
test "core_dump: disable zeroes the soft RLIMIT_CORE" {
if (builtin.os.tag == .windows) return;
const original = try std.posix.getrlimit(.CORE);
defer std.posix.setrlimit(.CORE, original) catch {};
try disable();
const after = try std.posix.getrlimit(.CORE);
try testing.expectEqual(@as(@TypeOf(after.cur), 0), after.cur);
try testing.expectEqual(original.max, after.max);
}