Prevent Make from talking to public Internet

This change introduces the nointernet() function which may be called to
prevent a process and its descendants from communicating with publicly
routable Internet addresses. GNU Make has been modified to always call
this function. In the future Landlock Make will have a way to whitelist
subnets to override this behavior, or disable it entirely. Support is
available for Linux only. Our firewall does not require root access.

Calling nointernet() will return control to the caller inside a new
process that has a SECCOMP BPF filter installed, which traps network
related system calls. Your original process then becomes a permanent
ptrace() supervisor that monitors all processes and threads descending
from the returned child. Whenever a networking system call happens the
kernel will stop the process and wakes up the monitor, which then peeks
into the child memory to read the sockaddr_in to determine if it's ok.

The downside to doing this is that there can be only one supervisor at a
time using ptrace() on a process. So this firewall won't be enabled if
you run make under strace or inside gdb. It also makes testing tricky.
This commit is contained in:
Justine Tunney 2022-08-12 05:17:06 -07:00
parent 8a0a2c0c36
commit 7cf66bc161
48 changed files with 4924 additions and 2046 deletions

View file

@ -35,10 +35,11 @@
#define WIFCONTINUED(s) ((s) == 0xffff)
#define WIFEXITED(s) (!WTERMSIG(s))
#define WIFSIGNALED(s) ((0xffff & (s)) - 1u < 0xffu)
#define WIFSTOPPED(s) ((short)(((0xffff & (s)) * 0x10001) >> 8) > 0x7f00)
#define WSTOPSIG(s) WEXITSTATUS(s)
#define WTERMSIG(s) (127 & (s))
#define W_STOPCODE(s) ((s) << 8 | 0177)
#define WIFSTOPPED(s) \
((short)(((0xffff & (unsigned)(s)) * 0x10001) >> 8) > 0x7f00)
#define WSTOPSIG(s) WEXITSTATUS(s)
#define WTERMSIG(s) (127 & (s))
#define W_STOPCODE(s) ((s) << 8 | 0177)
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@ -115,6 +116,7 @@ int getresuid(uint32_t *, uint32_t *, uint32_t *);
int getsid(int) nosideeffect libcesque;
int gettid(void) libcesque;
int getuid(void) libcesque;
int iopl(int);
int ioprio_get(int, int);
int ioprio_set(int, int, int);
int issetugid(void);
@ -178,6 +180,7 @@ int siginterrupt(int, int);
int symlink(const char *, const char *);
int symlinkat(const char *, int, const char *);
int sync_file_range(int, int64_t, int64_t, unsigned);
int sys_ptrace(int, ...);
int sysctl(const int *, unsigned, void *, size_t *, void *, size_t);
int tgkill(int, int, int);
int tkill(int, int);
@ -194,7 +197,6 @@ int wait(int *);
int waitpid(int, int *, int);
intptr_t syscall(int, ...);
long ptrace(int, ...);
size_t GetFileSize(const char *);
ssize_t copy_file_range(int, long *, int, long *, size_t, uint32_t);
ssize_t copyfd(int, int64_t *, int, int64_t *, size_t, uint32_t);
ssize_t getfiledescriptorsize(int);

View file

@ -16,7 +16,6 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/intrin/likely.h"
#include "libc/calls/calls.h"
#include "libc/calls/pledge.internal.h"
#include "libc/calls/struct/bpf.h"
@ -25,6 +24,7 @@
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/likely.h"
#include "libc/intrin/promises.internal.h"
#include "libc/macros.internal.h"
#include "libc/nexgen32e/bsr.h"
@ -517,6 +517,7 @@ static const uint16_t kPledgeStdio[] = {
__NR_linux_migrate_pages, //
__NR_linux_sync_file_range, //
__NR_linux_set_tid_address, //
__NR_linux_membarrier, //
__NR_linux_nanosleep, //
__NR_linux_pipe, //
__NR_linux_pipe2, //

View file

@ -19,47 +19,15 @@
#include "libc/calls/strace.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/sysv/consts/ptrace.h"
#include "libc/sysv/errfuns.h"
static const char *__ptrace_describe_request(int x) {
if (x == -1) return "-1";
if (x == PTRACE_TRACEME) return "PTRACE_TRACEME";
if (x == PTRACE_PEEKDATA) return "PTRACE_PEEKDATA";
if (x == PTRACE_GETFPREGS) return "PTRACE_GETFPREGS";
if (x == PTRACE_PEEKTEXT) return "PTRACE_PEEKTEXT";
if (x == PTRACE_POKEDATA) return "PTRACE_POKEDATA";
if (x == PTRACE_PEEKUSER) return "PTRACE_PEEKUSER";
if (x == PTRACE_POKETEXT) return "PTRACE_POKETEXT";
if (x == PTRACE_POKEUSER) return "PTRACE_POKEUSER";
if (x == PTRACE_GETREGS) return "PTRACE_GETREGS";
if (x == PTRACE_GETREGSET) return "PTRACE_GETREGSET";
if (x == PTRACE_SETFPREGS) return "PTRACE_SETFPREGS";
if (x == PTRACE_SETREGS) return "PTRACE_SETREGS";
if (x == PTRACE_SETREGSET) return "PTRACE_SETREGSET";
if (x == PTRACE_GETSIGINFO) return "PTRACE_GETSIGINFO";
if (x == PTRACE_SETSIGINFO) return "PTRACE_SETSIGINFO";
if (x == PTRACE_PEEKSIGINFO) return "PTRACE_PEEKSIGINFO";
if (x == PTRACE_GETSIGMASK) return "PTRACE_GETSIGMASK";
if (x == PTRACE_SETSIGMASK) return "PTRACE_SETSIGMASK";
if (x == PTRACE_SETOPTIONS) return "PTRACE_SETOPTIONS";
if (x == PTRACE_GETEVENTMSG) return "PTRACE_GETEVENTMSG";
if (x == PTRACE_CONT) return "PTRACE_CONT";
if (x == PTRACE_SINGLESTEP) return "PTRACE_SINGLESTEP";
if (x == PTRACE_SYSCALL) return "PTRACE_SYSCALL";
if (x == PTRACE_LISTEN) return "PTRACE_LISTEN";
if (x == PTRACE_KILL) return "PTRACE_KILL";
if (x == PTRACE_INTERRUPT) return "PTRACE_INTERRUPT";
if (x == PTRACE_ATTACH) return "PTRACE_ATTACH";
if (x == PTRACE_SEIZE) return "PTRACE_SEIZE";
if (x == PTRACE_SECCOMP_GET_FILTER) return "PTRACE_SECCOMP_GET_FILTER";
if (x == PTRACE_DETACH) return "PTRACE_DETACH";
return "PTRACE_WUT";
}
/**
* Traces process.
*
* This API is terrible. Consider using sys_ptrace().
*
* @param request can be PTRACE_xxx
* @note de facto linux only atm
* @vforksafe
@ -69,22 +37,21 @@ long ptrace(int request, ...) {
int pid;
va_list va;
bool ispeek;
long rc, peek;
void *addr, *data;
long rc, peek, addr, *data;
va_start(va, request);
pid = va_arg(va, int);
addr = va_arg(va, void *);
data = va_arg(va, void *);
addr = va_arg(va, long);
data = va_arg(va, long *);
va_end(va);
if (request == -1) {
rc = einval(); /* see consts.sh */
} else {
ispeek = IsLinux() && request - 1u < 3;
if (ispeek) data = &peek;
rc = sys_ptrace(request, pid, addr, data);
rc = __sys_ptrace(request, pid, addr, data);
if (rc != -1 && ispeek) rc = peek;
}
STRACE("ptrace(%s, %d, %p, %p) → %ld% m", __ptrace_describe_request(request),
pid, addr, data);
STRACE("ptrace(%s, %d, %p, %p) → %p% m", DescribePtrace(request), pid, addr,
data, rc);
return rc;
}

View file

@ -1,7 +1,7 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2021 Justine Alexandra Roberts Tunney
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
@ -16,35 +16,45 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/intrin/weaken.h"
#include "libc/calls/calls.h"
#include "libc/calls/strace.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/log/log.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/internal.h"
#include "libc/sock/sock.h"
#include "libc/sysv/consts/af.h"
#include "net/http/ip.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/likely.h"
#define IsPeek(request) (IsLinux() && (request)-1u < 3)
/**
* Checks outbound address against firewall.
* Traces process.
*
* The goal is to keep local software local, by raising an error if our
* Makefile tries to talk to the Internet.
* @param op can be PTRACE_xxx
* @param pid is child process id
* @param addr points inside child address space
* @param data is address of output word when peeking
* @note de facto linux only
* @vforksafe
*/
void _firewall(const void *addr, uint32_t addrsize) {
char b[64], *p;
if (!IsTiny() && addr && addrsize >= sizeof(struct sockaddr_in) &&
((struct sockaddr_in *)addr)->sin_family == AF_INET &&
IsPublicIp(ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr)) &&
IsRunningUnderMake()) {
p = stpcpy(b, "make shan't internet: ");
p = stpcpy(p, inet_ntoa(((struct sockaddr_in *)addr)->sin_addr));
*p++ = '\n';
write(2, b, p - b);
if (weaken(__die)) weaken(__die)();
__restorewintty();
_Exit(66);
int sys_ptrace(int op, ...) {
va_list va;
int rc, pid;
long addr, *data;
va_start(va, op);
pid = va_arg(va, int);
addr = va_arg(va, long);
data = va_arg(va, long *);
va_end(va);
rc = __sys_ptrace(op, pid, addr, data);
#ifdef SYSDEBUG
if (UNLIKELY(__strace > 0)) {
if (rc != -1 && IsPeek(op) && data) {
STRACE("sys_ptrace(%s, %d, %p, [%p]) → %p% m", DescribePtrace(op), pid,
addr, *data, rc);
} else {
STRACE("sys_ptrace(%s, %d, %p, %p) → %p% m", DescribePtrace(op), pid,
addr, data, rc);
}
}
#endif
return rc;
}

View file

@ -104,10 +104,10 @@ i32 sys_uname(void *) hidden;
i32 sys_unlinkat(i32, const char *, i32) hidden;
i32 sys_unmount(const char *, i32) hidden;
i32 sys_unveil(const char *, const char *) hidden;
i64 __sys_ptrace(i32, i32, i64, long *) hidden;
i64 sys_copy_file_range(i32, long *, i32, long *, u64, u32) hidden;
i64 sys_getrandom(void *, u64, u32) hidden;
i64 sys_pread(i32, void *, u64, i64, i64) hidden;
i64 sys_ptrace(int, i32, void *, void *) hidden;
i64 sys_pwrite(i32, const void *, u64, i64, i64) hidden;
i64 sys_read(i32, void *, u64) hidden;
i64 sys_readlink(const char *, char *, u64) hidden;

View file

@ -13,11 +13,6 @@
#define DEV_BSIZE 512
#define NOGROUP (-1)
#undef MIN
#undef MAX
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/strace.internal.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
@ -32,9 +33,12 @@
* @error ENOENT
*/
int truncate(const char *path, uint64_t length) {
int rc;
if (!IsWindows()) {
return sys_truncate(path, length, length);
rc = sys_truncate(path, length, length);
} else {
return sys_truncate_nt(path, length);
rc = sys_truncate_nt(path, length);
}
STRACE("truncate(%#s, %'ld) → %d% m", path, length, rc);
return rc;
}

View file

@ -16,18 +16,18 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/intrin/bits.h"
#include "libc/calls/calls.h"
#include "libc/dns/consts.h"
#include "libc/dns/dns.h"
#include "libc/dns/dnsheader.h"
#include "libc/dns/dnsquestion.h"
#include "libc/dns/resolvconf.h"
#include "libc/intrin/bits.h"
#include "libc/mem/mem.h"
#include "libc/stdio/rand.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/internal.h"
#include "libc/sock/sock.h"
#include "libc/stdio/rand.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/af.h"
@ -101,7 +101,6 @@ int ResolveDns(const struct ResolvConf *resolvconf, int af, const char *name,
a4 = (struct sockaddr_in *)addr;
a4->sin_family = AF_INET;
memcpy(&a4->sin_addr.s_addr, p + 10, 4);
_firewall(a4, sizeof(struct sockaddr_in));
break;
}
p += 10 + rdlength;

View file

@ -53,6 +53,8 @@ const char *DescribePersonalityFlags(char[128], int);
const char *DescribePollFlags(char[64], int);
const char *DescribePrctlOperation(int);
const char *DescribeProtFlags(char[48], int);
const char *DescribePtrace(char[12], int);
const char *DescribePtraceEvent(char[32], int);
const char *DescribeRemapFlags(char[48], int);
const char *DescribeRlimit(char[64], int, const struct rlimit *);
const char *DescribeRlimitName(char[20], int);
@ -101,6 +103,8 @@ void DescribeIovNt(const struct NtIovec *, uint32_t, ssize_t);
#define DescribePersonalityFlags(p) DescribePersonalityFlags(alloca(128), p)
#define DescribePollFlags(p) DescribePollFlags(alloca(64), p)
#define DescribeProtFlags(dirfd) DescribeProtFlags(alloca(48), dirfd)
#define DescribePtrace(i) DescribePtrace(alloca(12), i)
#define DescribePtraceEvent(x) DescribePtraceEvent(alloca(32), x)
#define DescribeRemapFlags(dirfd) DescribeRemapFlags(alloca(48), dirfd)
#define DescribeRlimit(rc, rl) DescribeRlimit(alloca(64), rc, rl)
#define DescribeRlimitName(rl) DescribeRlimitName(alloca(20), rl)

View file

@ -0,0 +1,57 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/fmt/itoa.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/sysv/consts/ptrace.h"
const char *(DescribePtrace)(char buf[12], int x) {
if (x == -1) return "-1";
if (x == PTRACE_TRACEME) return "PTRACE_TRACEME";
if (x == PTRACE_PEEKDATA) return "PTRACE_PEEKDATA";
if (x == PTRACE_GETFPREGS) return "PTRACE_GETFPREGS";
if (x == PTRACE_PEEKTEXT) return "PTRACE_PEEKTEXT";
if (x == PTRACE_POKEDATA) return "PTRACE_POKEDATA";
if (x == PTRACE_PEEKUSER) return "PTRACE_PEEKUSER";
if (x == PTRACE_POKETEXT) return "PTRACE_POKETEXT";
if (x == PTRACE_POKEUSER) return "PTRACE_POKEUSER";
if (x == PTRACE_GETREGS) return "PTRACE_GETREGS";
if (x == PTRACE_GETREGSET) return "PTRACE_GETREGSET";
if (x == PTRACE_SETFPREGS) return "PTRACE_SETFPREGS";
if (x == PTRACE_SETREGS) return "PTRACE_SETREGS";
if (x == PTRACE_SETREGSET) return "PTRACE_SETREGSET";
if (x == PTRACE_GETSIGINFO) return "PTRACE_GETSIGINFO";
if (x == PTRACE_SETSIGINFO) return "PTRACE_SETSIGINFO";
if (x == PTRACE_PEEKSIGINFO) return "PTRACE_PEEKSIGINFO";
if (x == PTRACE_GETSIGMASK) return "PTRACE_GETSIGMASK";
if (x == PTRACE_SETSIGMASK) return "PTRACE_SETSIGMASK";
if (x == PTRACE_SETOPTIONS) return "PTRACE_SETOPTIONS";
if (x == PTRACE_GETEVENTMSG) return "PTRACE_GETEVENTMSG";
if (x == PTRACE_CONT) return "PTRACE_CONT";
if (x == PTRACE_SINGLESTEP) return "PTRACE_SINGLESTEP";
if (x == PTRACE_SYSCALL) return "PTRACE_SYSCALL";
if (x == PTRACE_LISTEN) return "PTRACE_LISTEN";
if (x == PTRACE_KILL) return "PTRACE_KILL";
if (x == PTRACE_INTERRUPT) return "PTRACE_INTERRUPT";
if (x == PTRACE_ATTACH) return "PTRACE_ATTACH";
if (x == PTRACE_SEIZE) return "PTRACE_SEIZE";
if (x == PTRACE_SECCOMP_GET_FILTER) return "PTRACE_SECCOMP_GET_FILTER";
if (x == PTRACE_DETACH) return "PTRACE_DETACH";
FormatInt32(buf, x);
return buf;
}

View file

@ -1,7 +1,7 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
@ -16,21 +16,19 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/dce.h"
#include "libc/limits.h"
#include "libc/nt/files.h"
#include "libc/str/str.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/sysv/consts/ptrace.h"
/**
* Returns the byte length of file by path.
*
* @return number of bytes, or -1ul w/ errno
* @see getfiledescriptorsize()
*/
size_t GetFileSize(const char *pathname) {
struct stat st;
if (stat(pathname, &st) == -1) return SIZE_MAX;
return st.st_size;
const char *(DescribePtraceEvent)(char buf[32], int x) {
if (x == PTRACE_EVENT_FORK) return "PTRACE_EVENT_FORK";
if (x == PTRACE_EVENT_VFORK) return "PTRACE_EVENT_VFORK";
if (x == PTRACE_EVENT_CLONE) return "PTRACE_EVENT_CLONE";
if (x == PTRACE_EVENT_EXEC) return "PTRACE_EVENT_EXEC";
if (x == PTRACE_EVENT_VFORK_DONE) return "PTRACE_EVENT_VFORK_DONE";
if (x == PTRACE_EVENT_EXIT) return "PTRACE_EVENT_EXIT";
if (x == PTRACE_EVENT_SECCOMP) return "PTRACE_EVENT_SECCOMP";
if (x == PTRACE_EVENT_STOP) return "PTRACE_EVENT_STOP";
FormatInt32(buf, x);
return buf;
}

View file

@ -40,7 +40,6 @@
int connect(int fd, const void *addr, uint32_t addrsize) {
int rc;
if (addr && !(IsAsan() && !__asan_is_valid(addr, addrsize))) {
_firewall(addr, addrsize);
if (!IsWindows()) {
rc = sys_connect(fd, addr, addrsize);
} else if (__isfdkind(fd, kFdSocket)) {

View file

@ -91,8 +91,6 @@ struct SockFd {
errno_t __dos2errno(uint32_t) hidden;
void _firewall(const void *, uint32_t) hidden;
int32_t __sys_accept(int32_t, void *, uint32_t *, int) dontdiscard hidden;
int32_t __sys_accept4(int32_t, void *, uint32_t *, int) dontdiscard hidden;
int32_t __sys_bind(int32_t, const void *, uint32_t) hidden;

348
libc/sock/nointernet.c Normal file
View file

@ -0,0 +1,348 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/bpf.h"
#include "libc/calls/struct/filter.h"
#include "libc/calls/struct/seccomp.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/syscall_support-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/likely.h"
#include "libc/macros.internal.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
#include "libc/sock/struct/msghdr.h"
#include "libc/sock/struct/sockaddr.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/audit.h"
#include "libc/sysv/consts/nr.h"
#include "libc/sysv/consts/nrlinux.h"
#include "libc/sysv/consts/pr.h"
#include "libc/sysv/consts/ptrace.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "net/http/ip.h"
#define ORIG_RAX 120
#define RAX 80
#define RDI 112
#define RSI 104
#define RDX 96
#define R8 72
#define R9 64
#define __WALL 0x40000000
#define OFF(f) offsetof(struct seccomp_data, f)
#if 0
#define DEBUG(...) kprintf(__VA_ARGS__)
#else
#define DEBUG(...) donothing
#endif
#define ORDIE(x) \
do { \
if (UNLIKELY((x) == -1)) { \
DEBUG("%s:%d: %s failed %m\n", __FILE__, __LINE__, #x); \
asm("hlt"); \
unreachable; \
} \
} while (0)
static const struct sock_filter kInetBpf[] = {
// cargo culted architecture assertion
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(arch)),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
// block system calls from the future
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, OFF(nr)),
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, __NR_linux_memfd_secret, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | 38), // ENOSYS
// only allow local and internet sockets
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_linux_socket, 0, 5),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(args[0])),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x001, 2, 0), // AF_UNIX
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0x002, 1, 0), // AF_INET
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | 1), // EPERM
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, OFF(nr)),
// support for these not implemented yet
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x133, 0, 1), // sendmmsg
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | 1), // EPERM
// trace syscalls with struct sockaddr
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x02e, 3, 0), // sendmsg
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x02c, 2, 0), // sendto
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x031, 1, 0), // bind
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x02a, 0, 1), // connect
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_TRACE),
// default course of action
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
};
static int PeekData(int pid, long addr, void *buf, size_t size) {
long i, j, w;
for (i = 0; i < size; i += sizeof(long)) {
if (sys_ptrace(PTRACE_PEEKTEXT, pid, addr + i, &w) != -1) {
for (j = 0; i + j < size && j < sizeof(long); ++j) {
((char *)buf)[i + j] = w;
w >>= 8;
}
} else {
return -1;
}
}
return 0;
}
static void LogProcessEvent(int main, int pid, int ws) {
DEBUG("trace: %s%06d%s 0x%06x", //
pid == main ? "\e[31;1m" : "", //
pid, //
pid == main ? "\e[0m" : "", //
ws);
if (WIFEXITED(ws)) {
DEBUG(" exit %d", WEXITSTATUS(ws));
}
if (WIFSIGNALED(ws)) {
DEBUG(" sig %d", WTERMSIG(ws));
}
if (WIFSTOPPED(ws)) {
DEBUG(" stop %s %s", strsignal(WSTOPSIG(ws)),
DescribePtraceEvent((ws & 0xff0000) >> 16));
}
if (WIFCONTINUED(ws)) {
DEBUG(" cont");
}
if (WCOREDUMP(ws)) {
DEBUG(" core");
}
DEBUG("\n");
}
static int Raise(int sig) {
sigset_t mask;
sigaction(sig, &(struct sigaction){0}, 0);
sigfillset(&mask);
sigprocmask(SIG_SETMASK, &mask, 0);
kill(getpid(), sig);
sigdelset(&mask, sig);
sigprocmask(SIG_SETMASK, &mask, 0);
_Exit(128 + sig);
}
static bool IsSockaddrAllowed(struct sockaddr_storage *addr) {
uint32_t ip;
if (addr->ss_family == AF_UNIX) {
return true;
}
if (addr->ss_family == AF_INET) {
ip = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr);
if (!IsPublicIp(ip)) {
return true;
} else {
kprintf("warning: attempted to communicate with public ip "
"%hhd.%hhd.%hhd.%hhd\n",
ip >> 24, ip >> 16, ip >> 8, ip);
return false;
}
}
DEBUG("bad family %d\n", addr->ss_family);
return false;
}
static void OnSockaddrSyscall(int pid, int r1, int r2) {
long si, dx;
uint32_t addrlen;
struct sockaddr_storage addr = {0};
ORDIE(sys_ptrace(PTRACE_PEEKUSER, pid, r1, &si));
ORDIE(sys_ptrace(PTRACE_PEEKUSER, pid, r2, &dx));
addrlen = dx;
if (!si) {
// if address isn't supplied, it's probably safe. for example,
// send() is implemented in cosmo using sendto() with 0/0 addr
return;
}
if (PeekData(pid, si, &addr, MIN(addrlen, sizeof(addr))) == -1) {
DEBUG("failed to peek addr\n"); // probably an efault
goto Deny;
}
if (IsSockaddrAllowed(&addr)) {
return;
} else {
goto Deny;
}
Deny:
ORDIE(sys_ptrace(PTRACE_POKEUSER, pid, ORIG_RAX, -1));
}
static void OnSendmsg(int pid) {
long si;
struct msghdr msg = {0};
struct sockaddr_storage addr = {0};
ORDIE(sys_ptrace(PTRACE_PEEKUSER, pid, RSI, &si));
if (PeekData(pid, si, &msg, sizeof(msg)) == -1) {
DEBUG("failed to peek msg\n"); // probably an efault
goto Deny;
}
if (!msg.msg_name) {
// if address isn't supplied, it's probably fine.
return;
}
if (PeekData(pid, (long)msg.msg_name, &addr,
MIN(msg.msg_namelen, sizeof(addr))) == -1) {
DEBUG("failed to peek msg name\n"); // probably an efault
goto Deny;
}
if (IsSockaddrAllowed(&addr)) {
return;
} else {
goto Deny;
}
Deny:
ORDIE(sys_ptrace(PTRACE_POKEUSER, pid, ORIG_RAX, -1));
}
static void HandleSeccompTrace(int pid) {
long ax;
ORDIE(sys_ptrace(PTRACE_PEEKUSER, pid, ORIG_RAX, &ax));
switch (ax) {
case 0x031: // bind
case 0x02a: // connect
OnSockaddrSyscall(pid, RSI, RDX);
break;
case 0x02c: // sendto
OnSockaddrSyscall(pid, R8, R9);
break;
case 0x02e: // sendmsg
OnSendmsg(pid);
break;
default:
break;
}
}
static int WaitForTrace(int main) {
int ws, pid;
for (;;) {
// waits for state change on any child process or thread
// eintr isn't possible since we're blocking all signals
ORDIE(pid = waitpid(-1, &ws, __WALL));
LogProcessEvent(main, pid, ws);
// once main child exits or dies, we exit / die the same way. we're
// not currently tracking pids, so it's important that a child does
// not exit before its children. otherwise the grandchildren get in
// a permanently stopped state. to address that, we'll send sigterm
// to the process group which we defined earlier.
if (WIFEXITED(ws)) {
if (pid == main) {
kill(-getpid(), SIGTERM);
_Exit(WEXITSTATUS(ws));
}
} else if (WIFSIGNALED(ws)) {
if (pid == main) {
kill(-getpid(), SIGTERM);
Raise(WTERMSIG(ws));
}
} else if (WIFSTOPPED(ws)) {
if ((ws >> 8) == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8))) {
return pid;
} else if ((ws >> 8) == (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) {
ORDIE(ptrace(PTRACE_CONT, pid, 0, 0));
} else if ((ws >> 8) == (SIGTRAP | (PTRACE_EVENT_FORK << 8)) ||
(ws >> 8) == (SIGTRAP | (PTRACE_EVENT_VFORK << 8)) ||
(ws >> 8) == (SIGTRAP | (PTRACE_EVENT_CLONE << 8))) {
ORDIE(ptrace(PTRACE_CONT, pid, 0, 0));
} else {
ORDIE(ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(ws)));
}
}
}
}
/**
* Disables internet access.
*/
int nointernet(void) {
int ws, act, main;
sigset_t set, old;
char path[PATH_MAX];
struct sock_fprog prog = {.filter = kInetBpf, .len = ARRAYLEN(kInetBpf)};
// seccomp bpf and ptrace are pretty much just linux for now.
if (!IsLinux() || !__is_linux_2_6_23()) {
return enosys();
}
// ensure we're at the root of a process group, so we're able to
// broadcast a termination signal later on that catches dangling
// subprocesss our child forgot to destroy. without calling this
// subprocesses could end up permanently stopped if monitor dies
setpgrp();
// prevent crash handlers from intercepting sigsegv
ORDIE(sigfillset(&set));
ORDIE(sigprocmask(SIG_SETMASK, &set, &old));
// create traced child that'll replace this program
if ((main = fork()) == -1) {
ORDIE(sigprocmask(SIG_SETMASK, &old, 0));
return -1;
}
if (!main) {
if (sys_ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
// there can be only one
// throw sigsegv on eperm
// we're already being traced
asm("hlt");
}
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
ORDIE(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog));
ORDIE(kill(getpid(), SIGSTOP));
ORDIE(sigprocmask(SIG_SETMASK, &old, 0));
// return to caller from child
return 0;
}
// wait for child to stop itself
ORDIE(waitpid(main, &ws, 0));
if (WIFSIGNALED(ws)) {
// child couldn't enable ptrace or seccomp
sigprocmask(SIG_SETMASK, &old, 0);
return eperm();
}
assert(WIFSTOPPED(ws));
// parent process becomes monitor of subprocess tree. all signals
// continue to be blocked since we assume they'll also be sent to
// children, which will die, and then the monitor dies afterwards
ORDIE(sys_ptrace(PTRACE_SETOPTIONS, main, 0,
PTRACE_O_TRACESECCOMP | PTRACE_O_TRACEFORK |
PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE |
PTRACE_O_TRACEEXEC));
for (act = main;;) {
ORDIE(sys_ptrace(PTRACE_CONT, act, 0, 0));
act = WaitForTrace(main);
HandleSeccompTrace(act);
}
}

View file

@ -58,7 +58,6 @@ ssize_t sendto(int fd, const void *buf, size_t size, uint32_t flags,
(opt_addr && !__asan_is_valid(opt_addr, addrsize)))) {
rc = efault();
} else {
_firewall(opt_addr, addrsize);
if (!IsWindows()) {
if (!IsBsd() || !opt_addr) {
rc = sys_sendto(fd, buf, size, flags, opt_addr, addrsize);

View file

@ -24,6 +24,7 @@ uint32_t inet_addr(const char *);
int parseport(const char *);
uint32_t *GetHostIps(void);
int nointernet(void);
int socket(int, int, int);
int accept(int, void *, uint32_t *);
int accept4(int, void *, uint32_t *, int);

View file

@ -0,0 +1,2 @@
.include "o/libc/sysv/macros.internal.inc"
.scall __sys_ptrace,0x01a01a01a201a065,globl,hidden

View file

@ -1,2 +0,0 @@
.include "o/libc/sysv/macros.internal.inc"
.scall sys_ptrace,0x01a01a01a201a065,globl,hidden

View file

@ -139,7 +139,7 @@ scall sys_getrlimit 0x0c20c20c220c2061 globl hidden
scall __sys_getrusage 0x1bd0130752075062 globl hidden
scall sys_sysinfo 0xfffffffffffff063 globl hidden
scall sys_times 0xfffffffffffff064 globl hidden
scall sys_ptrace 0x01a01a01a201a065 globl hidden
scall __sys_ptrace 0x01a01a01a201a065 globl hidden # ptrace() wrapper api is terrible
scall sys_syslog 0xfffffffffffff067 globl hidden
scall sys_getuid 0x0180180182018066 globl hidden
scall sys_getgid 0x02f02f02f202f068 globl hidden