diff --git a/libc/calls/finddebugbinary.c b/libc/calls/finddebugbinary.c index e52e464eb..04dbaea05 100644 --- a/libc/calls/finddebugbinary.c +++ b/libc/calls/finddebugbinary.c @@ -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(); +} diff --git a/libc/calls/pledge.c b/libc/calls/pledge.c index 88f1b236f..812844502 100644 --- a/libc/calls/pledge.c +++ b/libc/calls/pledge.c @@ -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` diff --git a/libc/calls/unveil.c b/libc/calls/unveil.c index c4bcbb559..971c7b2b0 100644 --- a/libc/calls/unveil.c +++ b/libc/calls/unveil.c @@ -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 diff --git a/net/turfwar/turfwar.c b/net/turfwar/turfwar.c index 77efea1d3..dc857a264 100644 --- a/net/turfwar/turfwar.c +++ b/net/turfwar/turfwar.c @@ -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); diff --git a/test/libc/calls/pledge_test.c b/test/libc/calls/pledge_test.c index d53886841..71d600834 100644 --- a/test/libc/calls/pledge_test.c +++ b/test/libc/calls/pledge_test.c @@ -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);