Add crash proofing to ipv4.games server

This commit is contained in:
Justine Tunney 2024-08-26 12:36:45 -07:00
parent e7b586e7f8
commit ebe1cbb1e3
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
5 changed files with 256 additions and 33 deletions

View file

@ -27,6 +27,7 @@
#include "libc/elf/tinyelf.internal.h"
#include "libc/errno.h"
#include "libc/intrin/directmap.h"
#include "libc/intrin/promises.h"
#include "libc/nt/memory.h"
#include "libc/nt/runtime.h"
#include "libc/runtime/runtime.h"
@ -37,7 +38,6 @@
#include "libc/sysv/consts/prot.h"
static struct {
atomic_uint once;
const char *res;
char buf[PATH_MAX];
} g_comdbg;
@ -69,35 +69,26 @@ static int GetElfMachine(void) {
}
static bool IsMyDebugBinary(const char *path) {
void *addr;
int64_t size;
uintptr_t value;
bool res = false;
int fd, e = errno;
struct DirectMap dm;
BLOCK_CANCELATION;
if ((fd = open(path, O_RDONLY | O_CLOEXEC, 0)) != -1) {
// sanity test that this .com.dbg file (1) is an elf image, and (2)
// contains the same number of bytes of code as our .com executable
// which is currently running in memory.
if ((size = lseek(fd, 0, SEEK_END)) != -1 &&
(dm = sys_mmap((void *)0x12345000000, size, PROT_READ, MAP_SHARED, fd,
0))
.addr != MAP_FAILED) {
if (READ32LE((char *)dm.addr) == READ32LE("\177ELF") &&
((Elf64_Ehdr *)dm.addr)->e_machine == GetElfMachine() &&
GetElfSymbolValue(dm.addr, "_etext", &value)) {
(addr = mmap(0, size, PROT_READ, MAP_SHARED, fd, 0)) != MAP_FAILED) {
if (READ32LE((char *)addr) == READ32LE("\177ELF") &&
((Elf64_Ehdr *)addr)->e_machine == GetElfMachine() &&
GetElfSymbolValue(addr, "_etext", &value)) {
res = !_etext || value == (uintptr_t)_etext;
}
if (!IsWindows()) {
sys_munmap(dm.addr, size);
} else {
CloseHandle(dm.maphandle);
UnmapViewOfFile(dm.addr);
}
munmap(addr, size);
}
close(fd);
}
ALLOW_CANCELATION;
errno = e;
return res;
}
@ -106,7 +97,7 @@ static void FindDebugBinaryInit(void) {
const char *comdbg;
if (issetugid())
return;
if ((comdbg = getenv("COMDBG")) && IsMyDebugBinary(comdbg)) {
if ((comdbg = getenv("COMDBG"))) {
g_comdbg.res = comdbg;
return;
}
@ -125,9 +116,18 @@ static void FindDebugBinaryInit(void) {
/**
* Returns path of binary with the debug information, or null.
*
* @return path to debug binary, or NULL
* You can specify the COMDBG environment variable, with the path of the
* debug binary, in case the automatic heuristics fail. What we look for
* is GetProgramExecutableName() with ".dbg", ".com.dbg", etc. appended.
*
* @return path to debug binary, or NULL if we couldn't find it
* @asyncsignalsafe
*/
const char *FindDebugBinary(void) {
cosmo_once(&g_comdbg.once, FindDebugBinaryInit);
return g_comdbg.res;
}
// pay startup cost to make this signal safe from the user's perspective
__attribute__((__constructor__(10))) static void FindDebugBinaryCtor(void) {
FindDebugBinaryInit();
}

View file

@ -232,6 +232,21 @@
* option might not be a good idea if you're pledging `exec` because
* subprocesses can't inherit the `SIGSYS` handler this installs.
*
* If you experience crashes during startup when execve'ing a cosmo
* binary that's had permissions like rpath pledged away, then try doing
* this before calling execve. This prevents special startup checks.
*
* putenv("COMDBG=program.dbg");
*
* If having pledge() security is mission critical, then add this code
* to the start of your main() function to ensure your program fails
* with an error if it isn't available.
*
* if (pledge(0, 0)) {
* fprintf(stderr, "error: OS doesn't support pledge() security\n");
* exit(1);
* }
*
* @return 0 on success, or -1 w/ errno
* @raise ENOSYS if `pledge(0, 0)` was used and security is not possible
* @raise EINVAL if `execpromises` on Linux isn't a subset of `promises`

View file

@ -405,6 +405,15 @@ int sys_unveil_linux(const char *path, const char *permissions) {
* - `c` allows `path` to be created and removed, corresponding to
* the pledge promise "cpath".
*
* If having unveil() security is mission critical, then add this code
* to the start of your main() function to ensure your program fails
* with an error if it isn't available.
*
* if (unveil("", 0) >= 0) {
* fprintf(stderr, "error: OS doesn't support unveil() security\n");
* exit(1);
* }
*
* @return 0 on success, or -1 w/ errno; note: if `unveil("",0)` is used
* to perform a feature check, then on Linux a value greater than 0
* shall be returned which is the supported Landlock ABI version

View file

@ -27,6 +27,7 @@
#include "libc/calls/struct/sysinfo.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timeval.h"
#include "libc/calls/ucontext.h"
#include "libc/ctype.h"
#include "libc/dce.h"
#include "libc/errno.h"
@ -35,6 +36,7 @@
#include "libc/intrin/atomic.h"
#include "libc/intrin/bsr.h"
#include "libc/intrin/hilbert.h"
#include "libc/intrin/iscall.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.h"
#include "libc/log/check.h"
@ -44,10 +46,12 @@
#include "libc/mem/mem.h"
#include "libc/mem/sortedints.internal.h"
#include "libc/nexgen32e/crc32.h"
#include "libc/nexgen32e/stackframe.h"
#include "libc/paths.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/runtime/symbols.internal.h"
#include "libc/runtime/sysconf.h"
#include "libc/serialize.h"
#include "libc/sock/sock.h"
@ -64,6 +68,7 @@
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/consts/rusage.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/so.h"
#include "libc/sysv/consts/sock.h"
@ -71,6 +76,7 @@
#include "libc/sysv/consts/tcp.h"
#include "libc/thread/thread.h"
#include "libc/thread/thread2.h"
#include "libc/thread/threads.h"
#include "libc/time.h"
#include "libc/x/x.h"
#include "libc/x/xasprintf.h"
@ -256,10 +262,12 @@ struct Blackhole {
// cli flags
bool g_integrity;
bool g_daemonize;
int g_crash_fd;
int g_port = PORT;
int g_workers = WORKERS;
int g_keepalive = KEEPALIVE_MS;
struct SortedInts g_whitelisted;
thread_local char last_message[INBUF_SIZE];
// lifecycle vars
pthread_t g_listener;
@ -694,6 +702,14 @@ void FreeSafeBuffer(void *p) {
void BlockSignals(void) {
sigset_t mask;
sigfillset(&mask);
sigdelset(&mask, SIGABRT);
sigdelset(&mask, SIGTRAP);
sigdelset(&mask, SIGFPE);
sigdelset(&mask, SIGBUS);
sigdelset(&mask, SIGSEGV);
sigdelset(&mask, SIGILL);
sigdelset(&mask, SIGXCPU);
sigdelset(&mask, SIGXFSZ);
sigprocmask(SIG_SETMASK, &mask, 0);
}
@ -872,7 +888,12 @@ void *HttpWorker(void *arg) {
DestroyHttpMessage(msg);
InitHttpMessage(msg, kHttpRequest);
g_worker[id].startread = timespec_real();
if ((got = read(client.sock, inbuf, INBUF_SIZE)) <= 0) {
got = read(client.sock, inbuf, INBUF_SIZE - 1);
if (got >= 0) {
memcpy(last_message, inbuf, got);
last_message[got] = 0;
}
if (got <= 0) {
++g_readfails;
break;
}
@ -1930,8 +1951,171 @@ OnError:
exit(1);
}
#ifdef __aarch64__
#define PC pc
#define BP regs[29]
#else
#define PC gregs[REG_RIP]
#define BP gregs[REG_RBP]
#endif
char *hexcpy(char *p, unsigned long x) {
int k = x ? (__builtin_clzl(x) ^ 63) + 1 : 1;
k = (k + 3) & -4;
while (k > 0)
*p++ = "0123456789abcdef"[(x >> (k -= 4)) & 15];
*p = '\0';
return p;
}
char *describe_backtrace(char *p, size_t len, const struct StackFrame *sf) {
char *pe = p + len;
bool gotsome = false;
// show address of each function
while (sf) {
if (kisdangerous(sf)) {
if (p + 1 + 9 + 1 < pe) {
if (gotsome)
*p++ = ' ';
p = stpcpy(p, "DANGEROUS");
if (p + 16 + 1 < pe) {
*p++ = ' ';
p = hexcpy(p, (long)sf);
}
}
break;
}
if (p + 16 + 1 < pe) {
unsigned char *ip = (unsigned char *)sf->addr;
#ifdef __x86_64__
// x86 advances the progrem counter before an instruction
// begins executing. return addresses in backtraces shall
// point to code after the call, which means addr2line is
// going to print unrelated code unless we fixup the addr
if (!kisdangerous(ip))
ip -= __is_call(ip);
#endif
if (gotsome)
*p++ = ' ';
else
gotsome = true;
p = hexcpy(p, (long)ip);
} else {
break;
}
sf = sf->next;
}
// terminate string
if (p < pe)
*p = '\0';
return p;
}
// abashed the devil stood
// and felt how awful goodness is
char *describe_crash(char *buf, size_t len, int sig, siginfo_t *si, void *arg) {
char *p = buf;
// check minimum length
if (len < 64)
return p;
// describe crash
char signame[21];
p = stpcpy(p, strsignal_r(sig, signame));
if (si && //
(sig == SIGFPE || //
sig == SIGILL || //
sig == SIGBUS || //
sig == SIGSEGV || //
sig == SIGTRAP)) {
p = stpcpy(p, " at ");
p = hexcpy(p, (long)si->si_addr);
}
// get stack frame daisy chain
struct StackFrame pc;
struct StackFrame *sf;
ucontext_t *ctx;
if ((ctx = (ucontext_t *)arg)) {
pc.addr = ctx->uc_mcontext.PC;
pc.next = (struct StackFrame *)ctx->uc_mcontext.BP;
sf = &pc;
} else {
sf = (struct StackFrame *)__builtin_frame_address(0);
}
// describe backtrace
p = stpcpy(p, " bt ");
p = describe_backtrace(p, len - (p - buf), sf);
return p;
}
void on_crash_signal(int sig, siginfo_t *si, void *arg) {
char *p;
char message[512];
write(2, "crash!\n", 7);
p = describe_crash(message, sizeof(message), sig, si, arg);
write(g_crash_fd, "crash: ", 7);
write(g_crash_fd, message, p - message);
write(g_crash_fd, "\n", 1);
write(g_crash_fd, last_message, strlen(last_message));
write(g_crash_fd, "\n", 1);
pthread_exit(PTHREAD_CANCELED);
}
static void show_crash_reports(void) {
const char *path = "crash.log";
if ((g_crash_fd = open(path, O_CREAT | O_WRONLY | O_APPEND, 0644)) == -1) {
fprintf(stderr, "%s: %s\n", path, strerror(errno));
exit(1);
}
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = on_crash_signal;
sigaddset(&sa.sa_mask, SIGABRT);
sigaddset(&sa.sa_mask, SIGTRAP);
sigaddset(&sa.sa_mask, SIGFPE);
sigaddset(&sa.sa_mask, SIGBUS);
sigaddset(&sa.sa_mask, SIGSEGV);
sigaddset(&sa.sa_mask, SIGILL);
sigaddset(&sa.sa_mask, SIGXCPU);
sigaddset(&sa.sa_mask, SIGXFSZ);
sigaction(SIGABRT, &sa, 0);
sigaction(SIGTRAP, &sa, 0);
sigaction(SIGFPE, &sa, 0);
sigaction(SIGILL, &sa, 0);
sigaction(SIGXCPU, &sa, 0);
sigaction(SIGXFSZ, &sa, 0);
sa.sa_flags |= SA_ONSTACK;
sigaction(SIGBUS, &sa, 0);
sigaction(SIGSEGV, &sa, 0);
}
int main(int argc, char *argv[]) {
// ShowCrashReports();
FindDebugBinary();
show_crash_reports();
unassert(false);
if (pledge(0, 0)) {
fprintf(stderr, "%s: this OS doesn't support pledge() security\n", argv[0]);
exit(1);
}
if (unveil("", 0) < 2) {
fprintf(stderr, "%s: need OpenBSD or Landlock LSM v3+\n", argv[0]);
exit(1);
}
if (IsLinux()) {
Write(2, "Enabling TCP_FASTOPEN for server sockets...\n");
@ -2026,20 +2210,26 @@ int main(int argc, char *argv[]) {
sa.sa_handler = IgnoreSignal;
sigaction(SIGUSR1, &sa, 0);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 128 * 1024);
pthread_attr_setguardsize(&attr, sysconf(_SC_PAGESIZE));
pthread_attr_setsigaltstacksize_np(&attr, sysconf(_SC_MINSIGSTKSZ) + 32768);
// make 9 helper threads
g_ready = nsync_counter_new(10);
pthread_t scorer, recenter, claimer, nower, replenisher, plotter;
pthread_t scorer_hour, scorer_day, scorer_week, scorer_month;
CHECK_EQ(0, pthread_create(&scorer, 0, ScoreWorker, 0));
CHECK_EQ(0, pthread_create(&scorer_hour, 0, ScoreHourWorker, 0));
CHECK_EQ(0, pthread_create(&scorer_day, 0, ScoreDayWorker, 0));
CHECK_EQ(0, pthread_create(&scorer_week, 0, ScoreWeekWorker, 0));
CHECK_EQ(0, pthread_create(&scorer_month, 0, ScoreMonthWorker, 0));
CHECK_EQ(0, pthread_create(&replenisher, 0, ReplenishWorker, 0));
CHECK_EQ(0, pthread_create(&recenter, 0, RecentWorker, 0));
CHECK_EQ(0, pthread_create(&claimer, 0, ClaimWorker, 0));
CHECK_EQ(0, pthread_create(&plotter, 0, PlotWorker, 0));
CHECK_EQ(0, pthread_create(&nower, 0, NowWorker, 0));
CHECK_EQ(0, pthread_create(&scorer, &attr, ScoreWorker, 0));
CHECK_EQ(0, pthread_create(&scorer_hour, &attr, ScoreHourWorker, 0));
CHECK_EQ(0, pthread_create(&scorer_day, &attr, ScoreDayWorker, 0));
CHECK_EQ(0, pthread_create(&scorer_week, &attr, ScoreWeekWorker, 0));
CHECK_EQ(0, pthread_create(&scorer_month, &attr, ScoreMonthWorker, 0));
CHECK_EQ(0, pthread_create(&replenisher, &attr, ReplenishWorker, 0));
CHECK_EQ(0, pthread_create(&recenter, &attr, RecentWorker, 0));
CHECK_EQ(0, pthread_create(&claimer, &attr, ClaimWorker, 0));
CHECK_EQ(0, pthread_create(&plotter, &attr, PlotWorker, 0));
CHECK_EQ(0, pthread_create(&nower, &attr, NowWorker, 0));
// wait for helper threads to warm up creating assets
if (nsync_counter_add(g_ready, -1)) { // #10
@ -2047,15 +2237,17 @@ int main(int argc, char *argv[]) {
}
// create one thread to listen
CHECK_EQ(0, pthread_create(&g_listener, 0, ListenWorker, 0));
CHECK_EQ(0, pthread_create(&g_listener, &attr, ListenWorker, 0));
// create lots of http workers to serve those assets
LOG("Online\n");
g_worker = xcalloc(g_workers, sizeof(*g_worker));
for (intptr_t i = 0; i < g_workers; ++i) {
CHECK_EQ(0, pthread_create(&g_worker[i].th, 0, HttpWorker, (void *)i));
CHECK_EQ(0, pthread_create(&g_worker[i].th, &attr, HttpWorker, (void *)i));
}
pthread_attr_destroy(&attr);
// time to serve
LOG("Ready\n");
Supervisor(0);

View file

@ -109,12 +109,15 @@ TEST(pledge, execpromises_notok) {
int ws, pid;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
putenv("COMDBG=REDACTED");
__pledge_mode = PLEDGE_PENALTY_RETURN_EPERM;
ASSERT_SYS(0, 0, pledge("stdio rpath exec", "stdio"));
execl("sock.elf", "sock.elf", 0);
_Exit(127);
}
EXPECT_NE(-1, wait(&ws));
EXPECT_FALSE(WIFSIGNALED(ws));
EXPECT_EQ(0, WTERMSIG(ws));
EXPECT_TRUE(WIFEXITED(ws));
EXPECT_EQ(129, WEXITSTATUS(ws));
}
@ -532,6 +535,7 @@ TEST(pledge, execpromises_ok) {
int ws, pid;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
putenv("COMDBG=REDACTED");
ASSERT_SYS(0, 0, pledge("stdio exec", "stdio"));
execl("life.elf", "life.elf", 0);
_Exit(127);
@ -547,6 +551,7 @@ TEST(pledge, execpromises_notok1) {
int ws, pid;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
putenv("COMDBG=REDACTED");
ASSERT_SYS(0, 0, pledge("stdio exec", "stdio"));
execl("sock.elf", "sock.elf", 0);
_Exit(127);
@ -562,6 +567,7 @@ TEST(pledge, execpromises_reducesAtExecOnLinux) {
int ws, pid;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
putenv("COMDBG=REDACTED");
ASSERT_SYS(0, 0, pledge("stdio inet tty exec", "stdio tty"));
execl("sock.elf", "sock.elf", 0);
_Exit(127);
@ -619,6 +625,7 @@ TEST(pledge_openbsd, execpromises_notok) {
int ws, pid;
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
putenv("COMDBG=REDACTED");
ASSERT_SYS(0, 0, pledge("stdio exec", "stdio"));
execl("sock.elf", "sock.elf", 0);
_Exit(127);