mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
Fix bugs and regressions in the pledge command
This change gets the pledge (formerly pledge.com) command back in tip top shape for a 3.0.1 cosmos release. It now runs on all platforms, even though it's mostly a no-op on ones that lack the kernel security stuff. The binary footprint is now smaller, since it no longer needs to link malloc. It's also now able to be built as a fat binary.
This commit is contained in:
parent
b0e3d89942
commit
7b284f6bda
18 changed files with 493 additions and 272 deletions
|
@ -16,6 +16,7 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/blockcancel.internal.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/syscall-sysv.internal.h"
|
||||
#include "libc/dce.h"
|
||||
|
@ -37,13 +38,12 @@
|
|||
* @raise EBADF on OpenBSD if `first` is greater than highest fd
|
||||
* @raise EINVAL if flags are bad or first is greater than last
|
||||
* @raise EMFILE if a weird race condition happens on Linux
|
||||
* @raise ECANCELED if thread was cancelled in masked mode
|
||||
* @raise EINTR possibly on OpenBSD
|
||||
* @raise ENOMEM on Linux maybe
|
||||
*/
|
||||
int closefrom(int first) {
|
||||
int rc, err;
|
||||
(void)err;
|
||||
int rc;
|
||||
BLOCK_CANCELATION;
|
||||
if (first < 0) {
|
||||
// consistent with openbsd
|
||||
// freebsd allows this but it's dangerous
|
||||
|
@ -58,6 +58,7 @@ int closefrom(int first) {
|
|||
} else {
|
||||
rc = enosys();
|
||||
}
|
||||
ALLOW_CANCELATION;
|
||||
STRACE("closefrom(%d) → %d% m", first, rc);
|
||||
return rc;
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ ssize_t copyfd(int in, int out, size_t n) {
|
|||
if (dw != dr) {
|
||||
// POSIX requires atomic IO up to PIPE_BUF
|
||||
// The minimum permissible PIPE_BUF is 512
|
||||
abort();
|
||||
notpossible;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
|
|
|
@ -25,5 +25,6 @@
|
|||
bool IsApeLoadable(char buf[8]) {
|
||||
return READ32LE(buf) == READ32LE("\177ELF") ||
|
||||
READ64LE(buf) == READ64LE("MZqFpD='") ||
|
||||
READ64LE(buf) == READ64LE("JTqFpD='");
|
||||
READ64LE(buf) == READ64LE("jartsr='") ||
|
||||
READ64LE(buf) == READ64LE("APEDBG='");
|
||||
}
|
||||
|
|
|
@ -33,6 +33,9 @@ static int FindPromise(const char *name) {
|
|||
/**
|
||||
* Parses the arguments to pledge() into a bitmask.
|
||||
*
|
||||
* @param out receives the integral promises mask, which zero is defined
|
||||
* as the set of all promises, and -1 is defined as the empty set of
|
||||
* promises, which is equivalent to `promises` being an empty string
|
||||
* @return 0 on success, or -1 if invalid
|
||||
*/
|
||||
int ParsePromises(const char *promises, unsigned long *out,
|
||||
|
|
|
@ -536,6 +536,9 @@ static const struct thatispacked SyscallName {
|
|||
|
||||
static const uint16_t kPledgeDefault[] = {
|
||||
__NR_linux_exit, // thread return / exit()
|
||||
#ifdef __NR_linux_arch_prctl //
|
||||
__NR_linux_arch_prctl, // or else launching musl process crashes (tls)
|
||||
#endif //
|
||||
};
|
||||
|
||||
// stdio contains all the benign system calls. openbsd makes the
|
||||
|
|
|
@ -29,6 +29,7 @@ void __stat2cosmo(struct stat *restrict st, const union metastat *ms) {
|
|||
st->st_uid = ms->linux.st_uid;
|
||||
st->st_gid = ms->linux.st_gid;
|
||||
st->st_flags = 0;
|
||||
st->st_gen = 0;
|
||||
st->st_rdev = ms->linux.st_rdev;
|
||||
st->st_size = ms->linux.st_size;
|
||||
st->st_blksize = ms->linux.st_blksize;
|
||||
|
|
|
@ -12,7 +12,7 @@ struct sysinfo {
|
|||
uint64_t bufferram; /* lingering disk pages; see fadvise */
|
||||
uint64_t totalswap; /* size of emergency memory */
|
||||
uint64_t freeswap; /* hopefully equal to totalswap */
|
||||
int16_t procs; /* number of processes */
|
||||
uint16_t procs; /* number of processes */
|
||||
int16_t __ignore1; /* padding */
|
||||
int32_t __ignore2; /* padding */
|
||||
uint64_t totalhigh; /* wut */
|
||||
|
|
|
@ -16,27 +16,55 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/struct/sigset.internal.h"
|
||||
#include "libc/calls/struct/sysinfo.h"
|
||||
#include "libc/calls/syscall_support-nt.internal.h"
|
||||
#include "libc/calls/struct/sysinfo.internal.h"
|
||||
#include "libc/calls/syscall-nt.internal.h"
|
||||
#include "libc/intrin/weaken.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/nt/accounting.h"
|
||||
#include "libc/nt/process.h"
|
||||
#include "libc/nt/struct/memorystatusex.h"
|
||||
#include "libc/nt/struct/systeminfo.h"
|
||||
#include "libc/nt/synchronization.h"
|
||||
#include "libc/nt/systeminfo.h"
|
||||
|
||||
static textwindows uint16_t GetProcessCount(void) {
|
||||
uint16_t res;
|
||||
uint32_t have, got, *pids;
|
||||
uint32_t stack_memory[1000];
|
||||
have = 0xFFFF * 4;
|
||||
if (!_weaken(malloc) || !(pids = _weaken(malloc)(have))) {
|
||||
pids = stack_memory;
|
||||
have = sizeof(stack_memory);
|
||||
}
|
||||
if (EnumProcesses(pids, have, &got)) {
|
||||
res = got / 4;
|
||||
} else {
|
||||
res = 0;
|
||||
}
|
||||
if (pids != stack_memory && _weaken(free)) {
|
||||
_weaken(free)(pids);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
textwindows int sys_sysinfo_nt(struct sysinfo *info) {
|
||||
int i;
|
||||
double load[3];
|
||||
struct NtMemoryStatusEx memstat;
|
||||
struct NtSystemInfo sysinfo;
|
||||
GetSystemInfo(&sysinfo);
|
||||
BLOCK_SIGNALS;
|
||||
if (sys_getloadavg_nt(load, 3) != -1) {
|
||||
for (i = 0; i < 3; ++i) {
|
||||
info->loads[i] = load[i] * 65536;
|
||||
}
|
||||
}
|
||||
memstat.dwLength = sizeof(struct NtMemoryStatusEx);
|
||||
if (GlobalMemoryStatusEx(&memstat)) {
|
||||
info->mem_unit = 1;
|
||||
info->totalram = memstat.ullTotalPhys;
|
||||
info->freeram = memstat.ullAvailPhys;
|
||||
info->procs = sysinfo.dwNumberOfProcessors;
|
||||
info->uptime = GetTickCount64() / 1000;
|
||||
return 0;
|
||||
} else {
|
||||
return __winerr();
|
||||
}
|
||||
info->uptime = GetTickCount64() / 1000;
|
||||
info->procs = GetProcessCount();
|
||||
info->mem_unit = 1;
|
||||
ALLOW_SIGNALS;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -188,56 +188,6 @@ static int unveil_init(void) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins paths, e.g.
|
||||
*
|
||||
* 0 + 0 → 0
|
||||
* "" + "" → ""
|
||||
* "a" + 0 → "a"
|
||||
* "a" + "" → "a/"
|
||||
* 0 + "b" → "b"
|
||||
* "" + "b" → "b"
|
||||
* "." + "b" → "./b"
|
||||
* "b" + "." → "b/."
|
||||
* "a" + "b" → "a/b"
|
||||
* "a/" + "b" → "a/b"
|
||||
* "a" + "b/" → "a/b/"
|
||||
* "a" + "/b" → "/b"
|
||||
*
|
||||
* @return joined path, which may be `buf`, `path`, or `other`, or null
|
||||
* if (1) `buf` didn't have enough space, or (2) both `path` and
|
||||
* `other` were null
|
||||
*/
|
||||
static char *JoinPaths(char *buf, size_t size, const char *path,
|
||||
const char *other) {
|
||||
size_t pathlen, otherlen;
|
||||
if (!other) return (char *)path;
|
||||
if (!path) return (char *)other;
|
||||
pathlen = strlen(path);
|
||||
if (!pathlen || *other == '/') {
|
||||
return (/*unconst*/ char *)other;
|
||||
}
|
||||
otherlen = strlen(other);
|
||||
if (path[pathlen - 1] == '/') {
|
||||
if (pathlen + otherlen + 1 <= size) {
|
||||
memmove(buf, path, pathlen);
|
||||
memmove(buf + pathlen, other, otherlen + 1);
|
||||
return buf;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (pathlen + 1 + otherlen + 1 <= size) {
|
||||
memmove(buf, path, pathlen);
|
||||
buf[pathlen] = '/';
|
||||
memmove(buf + pathlen + 1, other, otherlen + 1);
|
||||
return buf;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int sys_unveil_linux(const char *path, const char *permissions) {
|
||||
#pragma GCC push_options
|
||||
#pragma GCC diagnostic ignored "-Wframe-larger-than="
|
||||
|
@ -302,7 +252,7 @@ int sys_unveil_linux(const char *path, const char *permissions) {
|
|||
// next = join(dirname(next), link)
|
||||
strcpy(b.buf2, next);
|
||||
dir = dirname(b.buf2);
|
||||
if ((next = JoinPaths(b.buf3, PATH_MAX, dir, b.lbuf))) {
|
||||
if ((next = __join_paths(b.buf3, PATH_MAX, dir, b.lbuf))) {
|
||||
// next now points to either: buf3, buf2, lbuf, rodata
|
||||
strcpy(b.buf4, next);
|
||||
next = b.buf4;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
#ifdef __COSMOPOLITAN__
|
||||
#undef __COSMOPOLITAN__
|
||||
#endif
|
||||
|
||||
#define __COSMOPOLITAN_MAJOR__ 3
|
||||
#define __COSMOPOLITAN_MINOR__ 0
|
||||
#define __COSMOPOLITAN_PATCH__ 0
|
||||
|
|
|
@ -430,6 +430,9 @@ privileged void klog(const char *b, size_t n) {
|
|||
: "=a"(rax), "=D"(rdi), "=S"(rsi), "=d"(rdx)
|
||||
: "0"(__NR_write), "1"(h), "2"(b), "3"(n)
|
||||
: "rcx", "r8", "r9", "r10", "r11", "memory", "cc");
|
||||
if (rax < 0) {
|
||||
__klog_handle = 0;
|
||||
}
|
||||
}
|
||||
#elif defined(__aarch64__)
|
||||
// this isn't a cancelation point because we don't acknowledge eintr
|
||||
|
@ -444,6 +447,9 @@ privileged void klog(const char *b, size_t n) {
|
|||
: "=r"(res_x0)
|
||||
: "r"(r0), "r"(r1), "r"(r2), "r"(r8), "r"(r16)
|
||||
: "memory");
|
||||
if (res_x0 < 0) {
|
||||
__klog_handle = 0;
|
||||
}
|
||||
#else
|
||||
#error "unsupported architecture"
|
||||
#endif
|
||||
|
|
69
libc/str/joinpaths.c
Normal file
69
libc/str/joinpaths.c
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*-*- 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 2023 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/str/str.h"
|
||||
|
||||
/**
|
||||
* Joins paths, e.g.
|
||||
*
|
||||
* 0 + 0 → 0
|
||||
* "" + "" → ""
|
||||
* "a" + 0 → "a"
|
||||
* "a" + "" → "a/"
|
||||
* 0 + "b" → "b"
|
||||
* "" + "b" → "b"
|
||||
* "." + "b" → "./b"
|
||||
* "b" + "." → "b/."
|
||||
* "a" + "b" → "a/b"
|
||||
* "a/" + "b" → "a/b"
|
||||
* "a" + "b/" → "a/b/"
|
||||
* "a" + "/b" → "/b"
|
||||
*
|
||||
* @return joined path, which may be `buf`, `path`, or `other`, or null
|
||||
* if (1) `buf` didn't have enough space, or (2) both `path` and
|
||||
* `other` were null
|
||||
*/
|
||||
char *__join_paths(char *buf, size_t size, const char *path,
|
||||
const char *other) {
|
||||
size_t pathlen, otherlen;
|
||||
if (!other) return (char *)path;
|
||||
if (!path) return (char *)other;
|
||||
pathlen = strlen(path);
|
||||
if (!pathlen || *other == '/') {
|
||||
return (/*unconst*/ char *)other;
|
||||
}
|
||||
otherlen = strlen(other);
|
||||
if (path[pathlen - 1] == '/') {
|
||||
if (pathlen + otherlen + 1 <= size) {
|
||||
memmove(buf, path, pathlen);
|
||||
memmove(buf + pathlen, other, otherlen + 1);
|
||||
return buf;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (pathlen + 1 + otherlen + 1 <= size) {
|
||||
memmove(buf, path, pathlen);
|
||||
buf[pathlen] = '/';
|
||||
memmove(buf + pathlen + 1, other, otherlen + 1);
|
||||
return buf;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -217,6 +217,7 @@ axdx_t tprecode8to16(char16_t *, size_t, const char *);
|
|||
axdx_t tprecode16to8(char *, size_t, const char16_t *);
|
||||
bool wcsstartswith(const wchar_t *, const wchar_t *) strlenesque;
|
||||
bool wcsendswith(const wchar_t *, const wchar_t *) strlenesque;
|
||||
char *__join_paths(char *, size_t, const char *, const char *) __wur;
|
||||
#endif /* _COSMO_SOURCE */
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
|
|
|
@ -6,7 +6,7 @@ if [ $# = 0 ]; then
|
|||
if ! [ $(id -u) = 0 ]; then
|
||||
make -j16 MODE=fastbuild \
|
||||
o/fastbuild/examples/ls.com \
|
||||
o/fastbuild/examples/curl.com \
|
||||
o/fastbuild/tool/curl/curl.com \
|
||||
o/fastbuild/examples/life.com \
|
||||
o/fastbuild/examples/hello.com \
|
||||
o/fastbuild/examples/printargs.com \
|
||||
|
@ -14,7 +14,7 @@ if [ $# = 0 ]; then
|
|||
o/fastbuild/tool/build/pledge.com || exit
|
||||
make -j16 MODE=$m \
|
||||
o/$m/examples/ls.com \
|
||||
o/$m/examples/curl.com \
|
||||
o/$m/tool/curl/curl.com \
|
||||
o/$m/examples/life.com \
|
||||
o/$m/examples/hello.com \
|
||||
o/$m/examples/printargs.com \
|
||||
|
@ -76,7 +76,7 @@ elif [ "$1" = ape_binfmt_test_suite ]; then
|
|||
checkem
|
||||
|
||||
startit ape binfmt curl.com
|
||||
[ "$(o/fastbuild/tool/build/pledge.com -p 'stdio inet dns rpath prot_exec' o/fastbuild/examples/curl.com https://justine.lol/hello.txt)" = "hello world" ]
|
||||
[ "$(o/fastbuild/tool/build/pledge.com -p 'stdio inet dns rpath prot_exec' o/fastbuild/tool/curl/curl.com https://justine.lol/hello.txt)" = "hello world" ]
|
||||
checkem
|
||||
|
||||
elif [ "$1" = ape_loader_test_suite ]; then
|
||||
|
@ -93,7 +93,7 @@ elif [ "$1" = ape_loader_test_suite ]; then
|
|||
checkem
|
||||
|
||||
startit ape loader curl.com
|
||||
[ "$(o/fastbuild/tool/build/pledge.com -p 'stdio inet dns rpath prot_exec' o/fastbuild/examples/curl.com https://justine.lol/hello.txt)" = "hello world" ]
|
||||
[ "$(o/fastbuild/tool/build/pledge.com -p 'stdio inet dns rpath prot_exec' o/fastbuild/tool/curl/curl.com https://justine.lol/hello.txt)" = "hello world" ]
|
||||
checkem
|
||||
|
||||
ape/apeinstall.sh >/dev/null 2>&1
|
||||
|
@ -116,7 +116,7 @@ elif [ "$1" = ape_assimilated_test_suite ]; then
|
|||
checkem
|
||||
|
||||
startit ape assimilated curl.com
|
||||
cp o/fastbuild/examples/curl.com $t/assimilated
|
||||
cp o/fastbuild/tool/curl/curl.com $t/assimilated
|
||||
o/fastbuild/tool/build/assimilate.com $t/assimilated/curl.com
|
||||
[ "$(o/$m/tool/build/pledge.com -p 'stdio rpath inet dns' $t/assimilated/curl.com https://justine.lol/hello.txt)" = "hello world" ]
|
||||
checkem
|
||||
|
@ -133,7 +133,7 @@ elif [ "$1" = ape_native_test_suite ]; then
|
|||
checkem
|
||||
|
||||
startit ape native curl.com
|
||||
[ "$(o/$m/tool/build/pledge.com -p 'stdio rpath inet dns' o/$m/examples/curl.com https://justine.lol/hello.txt)" = "hello world" ]
|
||||
[ "$(o/$m/tool/build/pledge.com -p 'stdio rpath inet dns' o/$m/tool/curl/curl.com https://justine.lol/hello.txt)" = "hello world" ]
|
||||
checkem
|
||||
|
||||
elif [ "$1" = setuid_test_suite ]; then
|
||||
|
@ -148,7 +148,7 @@ elif [ "$1" = setuid_test_suite ]; then
|
|||
checkem
|
||||
|
||||
startit setuid curl.com
|
||||
[ "$($t/pledge.com -p 'stdio rpath inet dns' o/$m/examples/curl.com https://justine.lol/hello.txt)" = "hello world" ]
|
||||
[ "$($t/pledge.com -p 'stdio rpath inet dns' o/$m/tool/curl/curl.com https://justine.lol/hello.txt)" = "hello world" ]
|
||||
checkem
|
||||
|
||||
startit setuid getuid
|
||||
|
|
|
@ -77,7 +77,7 @@ o/$(MODE)/tool/build/%.com.dbg: \
|
|||
$(APE_NO_MODIFY_SELF)
|
||||
@$(APELINK)
|
||||
|
||||
o/$(MODE)/tool/build/dso/sandbox.so.zip.o \
|
||||
o/$(MODE)/tool/build/dso/sandbox-$(ARCH).so.zip.o \
|
||||
o/$(MODE)/tool/build/false.com.zip.o \
|
||||
o/$(MODE)/tool/build/echo.com.zip.o \
|
||||
o/$(MODE)/tool/build/cocmd.com.zip.o: private \
|
||||
|
@ -99,7 +99,7 @@ o/$(MODE)/tool/build/dso/sandbox.o: \
|
|||
libc/intrin/promises.internal.h \
|
||||
tool/build/build.mk
|
||||
|
||||
o/$(MODE)/tool/build/dso/sandbox.so: \
|
||||
o/$(MODE)/tool/build/dso/sandbox-$(ARCH).so: \
|
||||
o/$(MODE)/tool/build/dso/sandbox.o \
|
||||
o/$(MODE)/libc/calls/pledge-linux.o \
|
||||
o/$(MODE)/libc/sysv/restorert.o
|
||||
|
@ -118,7 +118,7 @@ o/$(MODE)/tool/build/dso/sandbox.so: \
|
|||
o/$(MODE)/tool/build/pledge.com.dbg: \
|
||||
$(TOOL_BUILD_DEPS) \
|
||||
o/$(MODE)/tool/build/build.pkg \
|
||||
o/$(MODE)/tool/build/dso/sandbox.so.zip.o \
|
||||
o/$(MODE)/tool/build/dso/sandbox-$(ARCH).so.zip.o \
|
||||
o/$(MODE)/tool/build/pledge.o \
|
||||
$(CRT) \
|
||||
$(APE_NO_MODIFY_SELF)
|
||||
|
|
|
@ -22,10 +22,8 @@
|
|||
#include "libc/intrin/promises.internal.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
|
||||
/*
|
||||
* runs pledge at glibc executable load time, e.g.
|
||||
* strace -vff bash -c '_PLEDGE=4194303,0 LD_PRELOAD=$HOME/sandbox.so ls'
|
||||
*/
|
||||
// runs pledge at glibc executable load time, e.g.
|
||||
// strace -vff bash -c '_PLEDGE=4194303,0 LD_PRELOAD=$HOME/sandbox.so ls'
|
||||
|
||||
__attribute__((__constructor__)) void init(void) {
|
||||
int c, i, j;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/pledge.h"
|
||||
#include "libc/assert.h"
|
||||
#include "ape/ape.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/landlock.h"
|
||||
#include "libc/calls/pledge.internal.h"
|
||||
|
@ -26,6 +26,7 @@
|
|||
#include "libc/calls/struct/seccomp.internal.h"
|
||||
#include "libc/calls/struct/stat.h"
|
||||
#include "libc/calls/struct/sysinfo.h"
|
||||
#include "libc/calls/syscall-nt.internal.h"
|
||||
#include "libc/calls/syscall-sysv.internal.h"
|
||||
#include "libc/calls/syscall_support-sysv.internal.h"
|
||||
#include "libc/dce.h"
|
||||
|
@ -35,6 +36,7 @@
|
|||
#include "libc/elf/struct/phdr.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/fmt/conv.h"
|
||||
#include "libc/fmt/itoa.h"
|
||||
#include "libc/intrin/bits.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/intrin/promises.internal.h"
|
||||
|
@ -42,13 +44,14 @@
|
|||
#include "libc/limits.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/math.h"
|
||||
#include "libc/mem/gc.internal.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/mem/alloca.h"
|
||||
#include "libc/nexgen32e/kcpuids.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/runtime/stack.h"
|
||||
#include "libc/sock/sock.h"
|
||||
#include "libc/sock/struct/pollfd.h"
|
||||
#include "libc/stdio/stdio.h"
|
||||
#include "libc/stdio/sysparam.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/ioprio.h"
|
||||
#include "libc/sysv/consts/map.h"
|
||||
|
@ -75,7 +78,7 @@ __static_yoink("zipos");
|
|||
|
||||
#define USAGE \
|
||||
"\
|
||||
usage: pledge.com [-hnN] PROG ARGS...\n\
|
||||
usage: pledge [-hnN] PROG ARGS...\n\
|
||||
-h show help\n\
|
||||
-g GID call setgid()\n\
|
||||
-u UID call setuid()\n\
|
||||
|
@ -116,7 +119,7 @@ usage: pledge.com [-hnN] PROG ARGS...\n\
|
|||
- vminfo: allows /proc/stat, /proc/self/maps, etc.\n\
|
||||
- tmppath: allows /tmp, $TMPPATH, lstat, unlink\n\
|
||||
\n\
|
||||
pledge.com v1.8\n\
|
||||
Cosompolitan Pledge v1.9\n\
|
||||
copyright 2022 justine alexandra roberts tunney\n\
|
||||
notice licenses are embedded in the binary\n\
|
||||
https://twitter.com/justinetunney\n\
|
||||
|
@ -130,13 +133,27 @@ the https://justine.lol/pledge/ page for online documentation.\n\
|
|||
\n\
|
||||
"
|
||||
|
||||
#ifdef __x86_64__
|
||||
#define ARCH_NAME "x86_64"
|
||||
#elif defined(__aarch64__)
|
||||
#define ARCH_NAME "aarch64"
|
||||
#else
|
||||
#error "unsupported architecture"
|
||||
#endif
|
||||
|
||||
enum Strategy {
|
||||
kStrategyNull,
|
||||
kStrategyStatic,
|
||||
kStrategyDynamic,
|
||||
kStrategyApe,
|
||||
};
|
||||
|
||||
int g_gflag;
|
||||
int g_uflag;
|
||||
int g_kflag;
|
||||
int g_hflag;
|
||||
bool g_nice;
|
||||
bool g_qflag;
|
||||
bool isdynamic;
|
||||
bool g_noclose;
|
||||
long g_cpuquota;
|
||||
long g_fszquota;
|
||||
|
@ -147,19 +164,60 @@ long g_dontdrop;
|
|||
long g_dontunveil;
|
||||
const char *g_test;
|
||||
const char *g_chroot;
|
||||
const char *g_promises;
|
||||
char pledgevar[64];
|
||||
char g_promises[256];
|
||||
char dsopath[PATH_MAX];
|
||||
char tmppath[PATH_MAX];
|
||||
char preloadvar[PATH_MAX];
|
||||
|
||||
struct {
|
||||
int n;
|
||||
char **p;
|
||||
char *p[10000];
|
||||
} unveils;
|
||||
|
||||
static void GetOpts(int argc, char *argv[]) {
|
||||
unsigned long HasPromise(unsigned long ipromises, int promise) {
|
||||
return ~ipromises & (1ul << promise);
|
||||
}
|
||||
|
||||
long Atoi(const char *s) {
|
||||
long i;
|
||||
char *ep;
|
||||
errno = 0;
|
||||
i = strtol(s, &ep, 0);
|
||||
if (*ep || errno) {
|
||||
tinyprint(2, program_invocation_name, ": invalid integer: ", s, "\n", NULL);
|
||||
exit(1);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
long ParseSiSize(const char *s, long b) {
|
||||
long i;
|
||||
errno = 0;
|
||||
i = sizetol(s, b);
|
||||
if (errno) {
|
||||
tinyprint(2, program_invocation_name, ": invalid size: ", s, "\n", NULL);
|
||||
exit(1);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
void AddPromise(const char *s) {
|
||||
while (isspace(*s)) ++s;
|
||||
if (!*s) return;
|
||||
if (*g_promises) {
|
||||
strlcat(g_promises, " ", sizeof(g_promises));
|
||||
}
|
||||
if (strlcat(g_promises, s, sizeof(g_promises)) >= sizeof(g_promises)) {
|
||||
tinyprint(2, program_invocation_name, ": too many promises\n", NULL);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void GetOpts(int argc, char *argv[]) {
|
||||
int opt;
|
||||
struct sysinfo si;
|
||||
g_promises = 0;
|
||||
bool got_promise_flag = false;
|
||||
g_nfdquota = 64;
|
||||
g_fszquota = 256 * 1000 * 1000;
|
||||
if (!sysinfo(&si)) {
|
||||
|
@ -196,46 +254,36 @@ static void GetOpts(int argc, char *argv[]) {
|
|||
g_chroot = optarg;
|
||||
break;
|
||||
case 'g':
|
||||
g_gflag = atoi(optarg);
|
||||
g_gflag = Atoi(optarg);
|
||||
break;
|
||||
case 'u':
|
||||
g_uflag = atoi(optarg);
|
||||
g_uflag = Atoi(optarg);
|
||||
break;
|
||||
case 'C':
|
||||
g_cpuquota = atoi(optarg);
|
||||
g_cpuquota = Atoi(optarg);
|
||||
break;
|
||||
case 'P':
|
||||
g_proquota = atoi(optarg);
|
||||
g_proquota = Atoi(optarg);
|
||||
break;
|
||||
case 'O':
|
||||
g_nfdquota = atoi(optarg);
|
||||
g_nfdquota = Atoi(optarg);
|
||||
break;
|
||||
case 'F':
|
||||
errno = 0;
|
||||
g_fszquota = sizetol(optarg, 1000);
|
||||
if (errno) {
|
||||
kprintf("error: invalid size: -F %s\n", optarg);
|
||||
exit(1);
|
||||
}
|
||||
g_fszquota = ParseSiSize(optarg, 1024);
|
||||
break;
|
||||
case 'M':
|
||||
errno = 0;
|
||||
g_memquota = sizetol(optarg, 1024);
|
||||
if (errno) {
|
||||
kprintf("error: invalid size: -F %s\n", optarg);
|
||||
exit(1);
|
||||
}
|
||||
g_memquota = ParseSiSize(optarg, 1024);
|
||||
break;
|
||||
case 'p':
|
||||
if (g_promises) {
|
||||
g_promises = xstrcat(g_promises, ' ', optarg);
|
||||
} else {
|
||||
g_promises = optarg;
|
||||
}
|
||||
AddPromise(optarg);
|
||||
got_promise_flag = true;
|
||||
break;
|
||||
case 'v':
|
||||
unveils.p = realloc(unveils.p, ++unveils.n * sizeof(*unveils.p));
|
||||
unveils.p[unveils.n - 1] = optarg;
|
||||
if (unveils.n == ARRAYLEN(unveils.p)) {
|
||||
tinyprint(2, program_invocation_name, ": too many unveils\n", NULL);
|
||||
exit(1);
|
||||
}
|
||||
unveils.p[unveils.n++] = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
case '?':
|
||||
|
@ -246,16 +294,16 @@ static void GetOpts(int argc, char *argv[]) {
|
|||
exit(64);
|
||||
}
|
||||
}
|
||||
if (!g_promises) {
|
||||
g_promises = "stdio rpath";
|
||||
if (!got_promise_flag) {
|
||||
stpcpy(g_promises, "stdio rpath");
|
||||
}
|
||||
}
|
||||
|
||||
const char *prog;
|
||||
const char *g_prog;
|
||||
char pathbuf[PATH_MAX];
|
||||
struct pollfd pfds[256];
|
||||
|
||||
static bool SupportsLandlock(void) {
|
||||
bool SupportsLandlock(void) {
|
||||
int e = errno;
|
||||
bool r = landlock_create_ruleset(0, 0, LANDLOCK_CREATE_RULESET_VERSION) >= 0;
|
||||
errno = e;
|
||||
|
@ -265,7 +313,7 @@ static bool SupportsLandlock(void) {
|
|||
int GetPollMaxFds(void) {
|
||||
int n;
|
||||
struct rlimit rl;
|
||||
if (getrlimit(RLIMIT_NOFILE, &rl) != -1) {
|
||||
if (!getrlimit(RLIMIT_NOFILE, &rl)) {
|
||||
n = rl.rlim_cur;
|
||||
} else {
|
||||
n = 64;
|
||||
|
@ -274,27 +322,26 @@ int GetPollMaxFds(void) {
|
|||
}
|
||||
|
||||
void NormalizeFileDescriptors(void) {
|
||||
int e, i, n, fd;
|
||||
int i, n, fd;
|
||||
closefrom(3); // faster and more secure if linux 5.9+ or bsd
|
||||
n = GetPollMaxFds();
|
||||
e = errno;
|
||||
closefrom(3); // more secure if linux 5.9+
|
||||
errno = e;
|
||||
for (i = 0; i < n; ++i) {
|
||||
pfds[i].fd = i;
|
||||
pfds[i].events = POLLIN;
|
||||
}
|
||||
if (poll(pfds, n, 0) == -1) {
|
||||
kprintf("error: poll() failed: %m\n");
|
||||
perror("poll");
|
||||
exit(1);
|
||||
}
|
||||
for (i = 0; i < 3; ++i) {
|
||||
if (pfds[i].revents & POLLNVAL) {
|
||||
if ((fd = open("/dev/null", O_RDWR)) == -1) {
|
||||
kprintf("error: open(\"/dev/null\") failed: %m\n");
|
||||
perror("/dev/null");
|
||||
exit(2);
|
||||
}
|
||||
if (fd != i) {
|
||||
kprintf("error: open() is broken: %d vs. %d\n", fd, i);
|
||||
tinyprint(2, program_invocation_name, ": poll() or open() is broken\n",
|
||||
NULL);
|
||||
exit(3);
|
||||
}
|
||||
}
|
||||
|
@ -302,7 +349,7 @@ void NormalizeFileDescriptors(void) {
|
|||
for (i = 3; i < n; ++i) {
|
||||
if (~pfds[i].revents & POLLNVAL) {
|
||||
if (close(pfds[i].fd) == -1) {
|
||||
kprintf("error: close(%d) failed: %m\n", pfds[i].fd);
|
||||
perror("close");
|
||||
exit(4);
|
||||
}
|
||||
}
|
||||
|
@ -354,8 +401,8 @@ bool PathExists(const char *path) {
|
|||
|
||||
void Unveil(const char *path, const char *perm) {
|
||||
if (unveil(path, perm) == -1) {
|
||||
kprintf("error: unveil(%#s, %#s) failed: %m\n", path, perm);
|
||||
_Exit(20);
|
||||
perror(path);
|
||||
exit(20);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,22 +415,62 @@ int UnveilIfExists(const char *path, const char *perm) {
|
|||
} else if (errno == ENOENT) {
|
||||
errno = err;
|
||||
} else {
|
||||
kprintf("error: unveil(%#s, %#s) failed: %m\n", path, perm);
|
||||
_Exit(20);
|
||||
perror(path);
|
||||
exit(20);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
enum Strategy GetStrategy(void) {
|
||||
static enum Strategy strategy;
|
||||
if (strategy == kStrategyNull) {
|
||||
strategy = kStrategyStatic;
|
||||
int fd;
|
||||
if ((fd = open(g_prog, O_RDONLY)) != -1) {
|
||||
union {
|
||||
char magic[8];
|
||||
Elf64_Ehdr ehdr;
|
||||
} hdr = {0};
|
||||
if (pread(fd, &hdr, sizeof(hdr), 0) == sizeof(hdr)) {
|
||||
if (READ64LE(hdr.magic) == READ64LE("MZqFpD='") ||
|
||||
READ64LE(hdr.magic) == READ64LE("jartsr='") ||
|
||||
READ64LE(hdr.magic) == READ64LE("APEDBG='")) {
|
||||
strategy = kStrategyApe;
|
||||
} else if ((IsLinux() || IsFreebsd() || IsNetbsd() || IsOpenbsd()) &&
|
||||
IsElf64Binary(&hdr.ehdr, sizeof(hdr))) {
|
||||
if (hdr.ehdr.e_type == ET_DYN) {
|
||||
strategy = kStrategyDynamic;
|
||||
} else {
|
||||
Elf64_Phdr phdrs[16];
|
||||
int count = MIN(hdr.ehdr.e_phnum, ARRAYLEN(phdrs));
|
||||
int bytes = count * sizeof(Elf64_Phdr);
|
||||
if (pread(fd, phdrs, bytes, hdr.ehdr.e_phoff) == bytes) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (phdrs[i].p_type == PT_INTERP ||
|
||||
phdrs[i].p_type == PT_DYNAMIC) {
|
||||
strategy = kStrategyDynamic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
return strategy;
|
||||
}
|
||||
|
||||
void ApplyFilesystemPolicy(unsigned long ipromises) {
|
||||
const char *p;
|
||||
|
||||
if (g_dontunveil) return;
|
||||
if (!SupportsLandlock()) return;
|
||||
|
||||
Unveil(prog, "rx");
|
||||
Unveil(g_prog, "rx");
|
||||
|
||||
if (isdynamic) {
|
||||
if (GetStrategy() == kStrategyDynamic) {
|
||||
Unveil(dsopath, "rx");
|
||||
UnveilIfExists("/lib", "rx");
|
||||
UnveilIfExists("/lib64", "rx");
|
||||
|
@ -391,14 +478,16 @@ void ApplyFilesystemPolicy(unsigned long ipromises) {
|
|||
UnveilIfExists("/usr/lib64", "rx");
|
||||
UnveilIfExists("/usr/local/lib", "rx");
|
||||
UnveilIfExists("/usr/local/lib64", "rx");
|
||||
UnveilIfExists("/etc/ld-musl-x86_64.path", "r");
|
||||
UnveilIfExists("/etc/ld-musl-" ARCH_NAME ".path", "r");
|
||||
UnveilIfExists("/etc/ld.so.conf", "r");
|
||||
UnveilIfExists("/etc/ld.so.cache", "r");
|
||||
UnveilIfExists("/etc/ld.so.conf.d", "r");
|
||||
UnveilIfExists("/etc/ld.so.preload", "r");
|
||||
// in case musl is symlinked somewhere else
|
||||
UnveilIfExists("/lib/ld-musl-" ARCH_NAME ".so.1", "rx");
|
||||
}
|
||||
|
||||
if (~ipromises & (1ul << PROMISE_STDIO)) {
|
||||
if (HasPromise(ipromises, PROMISE_STDIO)) {
|
||||
UnveilIfExists("/dev/fd", "r");
|
||||
UnveilIfExists("/dev/log", "w");
|
||||
UnveilIfExists("/dev/zero", "r");
|
||||
|
@ -422,15 +511,15 @@ void ApplyFilesystemPolicy(unsigned long ipromises) {
|
|||
UnveilIfExists("/proc/sys/vm/overcommit_memory", "r");
|
||||
}
|
||||
|
||||
if (~ipromises & (1ul << PROMISE_INET)) {
|
||||
if (HasPromise(ipromises, PROMISE_INET)) {
|
||||
UnveilIfExists("/etc/ssl/certs/ca-certificates.crt", "r");
|
||||
}
|
||||
|
||||
if (~ipromises & (1ul << PROMISE_RPATH)) {
|
||||
if (HasPromise(ipromises, PROMISE_RPATH)) {
|
||||
UnveilIfExists("/proc/filesystems", "r");
|
||||
}
|
||||
|
||||
if (~ipromises & (1ul << PROMISE_DNS)) {
|
||||
if (HasPromise(ipromises, PROMISE_DNS)) {
|
||||
UnveilIfExists("/etc/hosts", "r");
|
||||
UnveilIfExists("/etc/hostname", "r");
|
||||
UnveilIfExists("/etc/services", "r");
|
||||
|
@ -438,7 +527,7 @@ void ApplyFilesystemPolicy(unsigned long ipromises) {
|
|||
UnveilIfExists("/etc/resolv.conf", "r");
|
||||
}
|
||||
|
||||
if (~ipromises & (1ul << PROMISE_TTY)) {
|
||||
if (HasPromise(ipromises, PROMISE_TTY)) {
|
||||
UnveilIfExists(ttyname(0), "rw");
|
||||
UnveilIfExists("/dev/tty", "rw");
|
||||
UnveilIfExists("/dev/console", "rw");
|
||||
|
@ -447,18 +536,21 @@ void ApplyFilesystemPolicy(unsigned long ipromises) {
|
|||
UnveilIfExists("/usr/share/terminfo", "r");
|
||||
}
|
||||
|
||||
if (~ipromises & (1ul << PROMISE_PROT_EXEC)) {
|
||||
if (GetStrategy() == kStrategyApe) {
|
||||
if (UnveilIfExists("/usr/bin/ape", "rx") == -1) {
|
||||
char buf[PATH_MAX];
|
||||
if ((p = getenv("TMPDIR"))) {
|
||||
UnveilIfExists(xjoinpaths(p, ".ape"), "rx");
|
||||
UnveilIfExists(
|
||||
__join_paths(buf, sizeof(buf), p, ".ape-" APE_VERSION_STR), "rx");
|
||||
}
|
||||
if ((p = getenv("HOME"))) {
|
||||
UnveilIfExists(xjoinpaths(p, ".ape"), "rx");
|
||||
UnveilIfExists(
|
||||
__join_paths(buf, sizeof(buf), p, ".ape-" APE_VERSION_STR), "rx");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (~ipromises & (1ul << PROMISE_VMINFO)) {
|
||||
if (HasPromise(ipromises, PROMISE_VMINFO)) {
|
||||
UnveilIfExists("/proc/stat", "r");
|
||||
UnveilIfExists("/proc/meminfo", "r");
|
||||
UnveilIfExists("/proc/cpuinfo", "r");
|
||||
|
@ -467,7 +559,7 @@ void ApplyFilesystemPolicy(unsigned long ipromises) {
|
|||
UnveilIfExists("/sys/devices/system/cpu", "r");
|
||||
}
|
||||
|
||||
if (~ipromises & (1ul << PROMISE_TMPPATH)) {
|
||||
if (HasPromise(ipromises, PROMISE_TMPPATH)) {
|
||||
UnveilIfExists("/tmp", "rwc");
|
||||
UnveilIfExists(getenv("TMPPATH"), "rwc");
|
||||
}
|
||||
|
@ -489,21 +581,22 @@ void ApplyFilesystemPolicy(unsigned long ipromises) {
|
|||
}
|
||||
|
||||
if (unveil(0, 0) == -1) {
|
||||
kprintf("error: unveil(0, 0) failed: %m\n");
|
||||
_Exit(20);
|
||||
perror("unveil");
|
||||
exit(20);
|
||||
}
|
||||
}
|
||||
|
||||
void DropCapabilities(void) {
|
||||
int e, i;
|
||||
if (!IsLinux()) return;
|
||||
for (e = errno, i = 0;; ++i) {
|
||||
if (prctl(PR_CAPBSET_DROP, i) == -1) {
|
||||
if (errno == EINVAL || errno == EPERM) {
|
||||
errno = e;
|
||||
break;
|
||||
} else {
|
||||
kprintf("error: prctl(PR_CAPBSET_DROP, %d) failed: %m\n", i);
|
||||
_Exit(25);
|
||||
perror("prctl(PR_CAPBSET_DROP)");
|
||||
exit(25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -533,68 +626,20 @@ int Extract(const char *from, const char *to, int mode) {
|
|||
return close(fdout) | close(fdin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if ELF executable uses dynamic loading magic.
|
||||
*/
|
||||
static bool IsDynamicExecutable(const char *prog) {
|
||||
bool res;
|
||||
Elf64_Ehdr *e;
|
||||
Elf64_Phdr *p;
|
||||
struct stat st;
|
||||
int i, fd, err;
|
||||
fd = -1;
|
||||
err = errno;
|
||||
e = MAP_FAILED;
|
||||
if ((fd = open(prog, O_RDONLY)) == -1) {
|
||||
res = false;
|
||||
goto Finish;
|
||||
}
|
||||
if (fstat(fd, &st) == -1 || st.st_size < 64) {
|
||||
res = false;
|
||||
goto Finish;
|
||||
}
|
||||
if ((e = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
|
||||
res = false;
|
||||
goto Finish;
|
||||
}
|
||||
if (READ32LE(e->e_ident) != READ32LE(ELFMAG)) {
|
||||
res = false;
|
||||
goto Finish;
|
||||
}
|
||||
if (e->e_type == ET_DYN) {
|
||||
res = true;
|
||||
goto Finish;
|
||||
}
|
||||
for (i = 0; i < e->e_phnum; ++i) {
|
||||
p = GetElfProgramHeaderAddress(e, st.st_size, i);
|
||||
if (p->p_type == PT_INTERP || p->p_type == PT_DYNAMIC) {
|
||||
res = true;
|
||||
goto Finish;
|
||||
}
|
||||
}
|
||||
res = false;
|
||||
goto Finish;
|
||||
Finish:
|
||||
if (e != MAP_FAILED) munmap(e, st.st_size);
|
||||
if (fd != -1) close(fd);
|
||||
errno = err;
|
||||
int CountEnviron(char **ep) {
|
||||
int res = 0;
|
||||
while (*ep++) ++res;
|
||||
return res;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const char *s;
|
||||
bool hasfunbits;
|
||||
char buf[PATH_MAX];
|
||||
int useruid, usergid;
|
||||
int owneruid, ownergid;
|
||||
int oldfsuid, oldfsgid;
|
||||
unsigned long ipromises;
|
||||
|
||||
if (!IsLinux()) {
|
||||
kprintf("error: this program is only intended for linux\n");
|
||||
exit(5);
|
||||
}
|
||||
|
||||
// parse flags
|
||||
GetOpts(argc, argv);
|
||||
if (g_test) {
|
||||
|
@ -612,41 +657,33 @@ int main(int argc, char *argv[]) {
|
|||
exit(1);
|
||||
}
|
||||
}
|
||||
kprintf("error: unknown test: %s\n", g_test);
|
||||
tinyprint(2, g_test, ": unknown test\n", NULL);
|
||||
exit(2);
|
||||
}
|
||||
if (optind == argc) {
|
||||
kprintf("error: too few args\n");
|
||||
tinyprint(2, "error: missing command\n", NULL);
|
||||
write(2, USAGE, sizeof(USAGE) - 1);
|
||||
exit(64);
|
||||
}
|
||||
|
||||
// perform process setup
|
||||
if (!g_noclose) {
|
||||
NormalizeFileDescriptors();
|
||||
}
|
||||
|
||||
if (g_nice) {
|
||||
verynice();
|
||||
}
|
||||
|
||||
if (SetCpuLimit(g_cpuquota) == -1) {
|
||||
kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_CPU");
|
||||
exit(1);
|
||||
perror("setrlimit(RLIMIT_CPU)");
|
||||
}
|
||||
|
||||
if (SetLimit(RLIMIT_FSIZE, g_fszquota, g_fszquota * 1.5) == -1) {
|
||||
kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_FSIZE");
|
||||
exit(1);
|
||||
perror("setrlimit(RLIMIT_FSIZE)");
|
||||
}
|
||||
|
||||
if (SetLimit(RLIMIT_AS, g_memquota, g_memquota) == -1) {
|
||||
kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_AS");
|
||||
exit(1);
|
||||
perror("setrlimit(RLIMIT_AS)");
|
||||
}
|
||||
|
||||
if (SetLimit(RLIMIT_NPROC, g_proquota, g_proquota) == -1) {
|
||||
kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_NPROC");
|
||||
exit(1);
|
||||
perror("setrlimit(RLIMIT_NPROC)");
|
||||
}
|
||||
|
||||
// test for weird chmod bits
|
||||
|
@ -664,8 +701,9 @@ int main(int argc, char *argv[]) {
|
|||
// some flags can't be allowed if binary has setuid bits
|
||||
if (hasfunbits) {
|
||||
if (g_uflag || g_gflag) {
|
||||
kprintf("error: setuid flags forbidden on setuid binaries\n");
|
||||
_Exit(6);
|
||||
tinyprint(2, program_invocation_name,
|
||||
": setuid flags forbidden on setuid binaries\n", NULL);
|
||||
exit(6);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -674,8 +712,8 @@ int main(int argc, char *argv[]) {
|
|||
oldfsuid = setfsuid(useruid);
|
||||
oldfsgid = setfsgid(usergid);
|
||||
if (access(g_chroot, R_OK) == -1) {
|
||||
kprintf("error: access(%#s) failed: %m\n", g_chroot);
|
||||
_Exit(7);
|
||||
perror(g_chroot);
|
||||
exit(7);
|
||||
}
|
||||
setfsuid(oldfsuid);
|
||||
setfsgid(oldfsgid);
|
||||
|
@ -684,12 +722,12 @@ int main(int argc, char *argv[]) {
|
|||
// change root fs path
|
||||
if (g_chroot) {
|
||||
if (chdir(g_chroot) == -1) {
|
||||
kprintf("error: chdir(%#s) failed: %m\n", g_chroot);
|
||||
_Exit(8);
|
||||
perror(g_chroot);
|
||||
exit(8);
|
||||
}
|
||||
if (chroot(g_chroot) == -1) {
|
||||
kprintf("error: chroot(%#s) failed: %m\n", g_chroot);
|
||||
_Exit(9);
|
||||
perror(g_chroot);
|
||||
exit(9);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -698,42 +736,63 @@ int main(int argc, char *argv[]) {
|
|||
oldfsuid = setfsuid(useruid);
|
||||
oldfsgid = setfsgid(usergid);
|
||||
}
|
||||
if (!(prog = commandv(argv[optind], pathbuf, sizeof(pathbuf)))) {
|
||||
kprintf("error: command not found: %m\n", argv[optind]);
|
||||
_Exit(10);
|
||||
if (!(g_prog = commandv(argv[optind], pathbuf, sizeof(pathbuf)))) {
|
||||
perror(argv[optind]);
|
||||
exit(10);
|
||||
}
|
||||
if (hasfunbits) {
|
||||
setfsuid(oldfsuid);
|
||||
setfsgid(oldfsgid);
|
||||
}
|
||||
|
||||
// copy environment
|
||||
// to setup child environment
|
||||
// and remove variables we might create
|
||||
unsetenv("_PLEDGE");
|
||||
unsetenv("LD_PRELOAD");
|
||||
int child_environ_count = CountEnviron(environ);
|
||||
int child_environ_capacity = child_environ_count + 2 + 1;
|
||||
int child_environ_bytes = child_environ_capacity * sizeof(char *);
|
||||
char **child_environ = alloca(child_environ_bytes);
|
||||
CheckLargeStackAllocation(child_environ, child_environ_bytes);
|
||||
memcpy(child_environ, environ, child_environ_count * sizeof(char *));
|
||||
bzero(child_environ + child_environ_count,
|
||||
(child_environ_capacity - child_environ_count) * sizeof(char *));
|
||||
|
||||
// figure out where we want the dso
|
||||
if (IsDynamicExecutable(prog)) {
|
||||
isdynamic = true;
|
||||
if (GetStrategy() == kStrategyDynamic) {
|
||||
if ((s = getenv("TMPDIR")) || //
|
||||
(s = getenv("HOME")) || //
|
||||
(s = ".")) {
|
||||
ksnprintf(dsopath, sizeof(dsopath), "%s/sandbox.so", s);
|
||||
strlcpy(dsopath, s, sizeof(dsopath));
|
||||
strlcat(dsopath, "/.pledge-sandbox.so", sizeof(dsopath));
|
||||
if (!FileExistsAndIsNewerThan(dsopath, GetProgramExecutableName())) {
|
||||
ksnprintf(tmppath, sizeof(tmppath), "%s/sandbox.so.%d", s, getpid());
|
||||
if (Extract("/zip/sandbox.so", tmppath, 0755) == -1) {
|
||||
kprintf("error: extract dso failed: %m\n");
|
||||
errno = 0;
|
||||
char pidstr[21];
|
||||
FormatInt64(pidstr, getpid());
|
||||
strlcpy(tmppath, s, sizeof(tmppath));
|
||||
strlcat(tmppath, "/.pledge-sandbox.so.", sizeof(tmppath));
|
||||
strlcat(tmppath, pidstr, sizeof(tmppath));
|
||||
if (Extract("/zip/sandbox-" ARCH_NAME ".so", tmppath, 0755) == -1) {
|
||||
perror(tmppath);
|
||||
exit(1);
|
||||
}
|
||||
if (rename(tmppath, dsopath) == -1) {
|
||||
kprintf("error: rename dso failed: %m\n");
|
||||
perror(dsopath);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
ksnprintf(buf, sizeof(buf), "LD_PRELOAD=%s", dsopath);
|
||||
putenv(buf);
|
||||
stpcpy(preloadvar, "LD_PRELOAD=");
|
||||
strlcat(preloadvar, dsopath, sizeof(preloadvar));
|
||||
child_environ[child_environ_count++] = preloadvar;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_dontdrop) {
|
||||
if (hasfunbits) {
|
||||
kprintf("error: -D flag forbidden on setuid binaries\n");
|
||||
_Exit(6);
|
||||
tinyprint(2, program_invocation_name,
|
||||
": -D flag forbidden on setuid binaries\n", NULL);
|
||||
exit(6);
|
||||
}
|
||||
} else {
|
||||
DropCapabilities();
|
||||
|
@ -743,22 +802,22 @@ int main(int argc, char *argv[]) {
|
|||
if (usergid != ownergid) {
|
||||
// setgid binaries must use the gid of the user that ran it
|
||||
if (setgid(usergid) == -1) {
|
||||
kprintf("error: setgid(%d) failed: %m\n", usergid);
|
||||
_Exit(11);
|
||||
perror("setgid");
|
||||
exit(11);
|
||||
}
|
||||
if (getgid() != usergid || getegid() != usergid) {
|
||||
kprintf("error: setgid() broken\n");
|
||||
_Exit(12);
|
||||
tinyprint(2, "error: setgid() broken\n", NULL);
|
||||
exit(12);
|
||||
}
|
||||
} else if (g_gflag) {
|
||||
// otherwise we trust the gid flag
|
||||
if (setgid(g_gflag) == -1) {
|
||||
kprintf("error: setgid(%d) failed: %m\n", g_gflag);
|
||||
_Exit(13);
|
||||
perror("setgid");
|
||||
exit(13);
|
||||
}
|
||||
if (getgid() != g_gflag || getegid() != g_gflag) {
|
||||
kprintf("error: setgid() broken\n");
|
||||
_Exit(14);
|
||||
tinyprint(2, "error: setgid() broken\n", NULL);
|
||||
exit(14);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -766,30 +825,34 @@ int main(int argc, char *argv[]) {
|
|||
if (useruid != owneruid) {
|
||||
// setuid binaries must use the uid of the user that ran it
|
||||
if (setuid(useruid) == -1) {
|
||||
kprintf("error: setuid(%d) failed: %m\n", useruid);
|
||||
_Exit(15);
|
||||
perror("setuid");
|
||||
exit(15);
|
||||
}
|
||||
if (getuid() != useruid || geteuid() != useruid) {
|
||||
kprintf("error: setuid() broken\n");
|
||||
_Exit(16);
|
||||
tinyprint(2, "error: setuid() broken\n", NULL);
|
||||
exit(16);
|
||||
}
|
||||
} else if (g_uflag) {
|
||||
// otherwise we trust the uid flag
|
||||
if (setuid(g_uflag) == -1) {
|
||||
kprintf("error: setuid(%d) failed: %m\n", g_uflag);
|
||||
_Exit(17);
|
||||
perror("setuid");
|
||||
exit(17);
|
||||
}
|
||||
if (getuid() != g_uflag || geteuid() != g_uflag) {
|
||||
kprintf("error: setuid() broken\n");
|
||||
_Exit(18);
|
||||
tinyprint(2, "error: setuid() broken\n", NULL);
|
||||
exit(18);
|
||||
}
|
||||
}
|
||||
|
||||
// parse requested promises
|
||||
// further changes to g_promises will be *effective* transient promises
|
||||
if (ParsePromises(g_promises, &ipromises, 0) == -1) {
|
||||
kprintf("error: bad promises list: %s\n", g_promises);
|
||||
_Exit(21);
|
||||
tinyprint(2, program_invocation_name, ": bad promises list: ", g_promises,
|
||||
"\n", NULL);
|
||||
exit(21);
|
||||
}
|
||||
|
||||
// perform unveiling
|
||||
ApplyFilesystemPolicy(ipromises);
|
||||
|
||||
// pledge.com uses the return eperm instead of killing the process
|
||||
|
@ -802,38 +865,70 @@ int main(int argc, char *argv[]) {
|
|||
__pledge_mode = PLEDGE_PENALTY_RETURN_EPERM;
|
||||
}
|
||||
|
||||
// we need to be able to call execv and mmap the dso
|
||||
// it'll be pledged away once/if the dso gets loaded
|
||||
if (!(~ipromises & (1ul << PROMISE_EXEC))) {
|
||||
g_promises = xstrcat(g_promises, ' ', "exec");
|
||||
// weaken system call policy to allow execution
|
||||
//
|
||||
// 1. we always need to pledge("exec") in order to use execve(). this
|
||||
// is the primary disadvantage to using the `pledge` command to
|
||||
// bolt security onto unsecured programs (as opposed to using
|
||||
// pledge as it was intended, by having the program authors update
|
||||
// their code to invoke the pledge() system call from within their
|
||||
// programs at the appropriate moments)
|
||||
//
|
||||
// 2. we usually need to force pledge("rpath prot_exec") too; dynamic
|
||||
// executables need it mmap() shared objects during initialization
|
||||
// and actually portable executables need it so /usr/bin/ape can
|
||||
// mmap() the ELF program headers. the only time we don't require
|
||||
// `prot_exec` is when launching a native ELF PT_EXEC binaries,
|
||||
// e.g. assimilated actually portable executables.
|
||||
//
|
||||
// 3. in some cases we can remove the `exec` and `prot_exec` promises
|
||||
// later on in the loading process. on musl and glibc systems, we
|
||||
// do that by injecting an LD_PRELOAD DSO which calls pledge()
|
||||
// again with the requested promises.
|
||||
if (!HasPromise(ipromises, PROMISE_EXEC)) {
|
||||
AddPromise("exec");
|
||||
if (!g_qflag) {
|
||||
// TODO(jart): Fix me.
|
||||
// __pledge_mode |= PLEDGE_STDERR_LOGGING;
|
||||
}
|
||||
}
|
||||
if (isdynamic) {
|
||||
g_promises = xstrcat(g_promises, ' ', "prot_exec");
|
||||
if (GetStrategy() != kStrategyStatic) {
|
||||
if (!HasPromise(ipromises, PROMISE_RPATH)) {
|
||||
AddPromise("rpath");
|
||||
}
|
||||
if (!HasPromise(ipromises, PROMISE_PROT_EXEC)) {
|
||||
AddPromise("prot_exec");
|
||||
}
|
||||
}
|
||||
|
||||
// pass arguments to pledge() inside the dso
|
||||
if (isdynamic) {
|
||||
ksnprintf(buf, sizeof(buf), "_PLEDGE=%ld,%ld", ~ipromises, __pledge_mode);
|
||||
putenv(buf);
|
||||
// pass parameters to injected dso
|
||||
if (GetStrategy() == kStrategyDynamic) {
|
||||
char *p = pledgevar;
|
||||
p = stpcpy(p, "_PLEDGE=");
|
||||
p = FormatInt64(p, ~ipromises);
|
||||
p = stpcpy(p, ",");
|
||||
p = FormatInt64(p, __pledge_mode);
|
||||
child_environ[child_environ_count++] = pledgevar;
|
||||
}
|
||||
|
||||
// this limit needs to come last since unveil() opens fds
|
||||
if (SetLimit(RLIMIT_NOFILE, g_nfdquota, g_nfdquota) == -1) {
|
||||
kprintf("error: setrlimit(%s) failed: %m\n", "RLIMIT_NOFILE");
|
||||
perror("setrlimit(RLIMIT_NOFILE)");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// apply sandbox
|
||||
if (pledge(g_promises, g_promises) == -1) {
|
||||
kprintf("error: pledge(%#s) failed: %m\n", g_promises);
|
||||
_Exit(19);
|
||||
perror("pledge");
|
||||
exit(19);
|
||||
}
|
||||
|
||||
// launch program
|
||||
sys_execve(prog, argv + optind, environ);
|
||||
kprintf("%s: execve failed: %m\n", prog);
|
||||
if (!IsWindows()) {
|
||||
sys_execve(g_prog, argv + optind, child_environ);
|
||||
} else {
|
||||
sys_execve_nt(g_prog, argv + optind, child_environ);
|
||||
}
|
||||
perror(g_prog);
|
||||
return 127;
|
||||
}
|
||||
|
|
61
tool/build/verynice.c
Normal file
61
tool/build/verynice.c
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*-*- 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 2023 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/calls/calls.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/stdio/stdio.h"
|
||||
#include "third_party/getopt/getopt.internal.h"
|
||||
|
||||
/**
|
||||
* @fileoverview `verynice foo` launches `foo` as low priority as possible
|
||||
*
|
||||
* This is particularly useful on Linux systems with a spinning disk
|
||||
* where the classic `nice` command doesn't do much.
|
||||
*/
|
||||
|
||||
const char *prog;
|
||||
|
||||
static wontreturn void PrintUsage(int rc, int fd) {
|
||||
tinyprint(fd, "Usage: ", prog, " COMMAND...\n", NULL);
|
||||
exit(rc);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
prog = argv[0];
|
||||
if (!prog) prog = "verynice";
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "h")) != -1) {
|
||||
switch (opt) {
|
||||
case 'h':
|
||||
PrintUsage(0, 1);
|
||||
default:
|
||||
PrintUsage(1, 2);
|
||||
}
|
||||
}
|
||||
if (optind == argc) {
|
||||
tinyprint(2, prog, ": missing command\n", NULL);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
verynice();
|
||||
execvp(argv[optind], argv + optind);
|
||||
perror(argv[optind]);
|
||||
exit(127);
|
||||
}
|
Loading…
Reference in a new issue