Add assimilate.com command for APE binaries

This commit is contained in:
Justine Tunney 2022-07-13 20:55:27 -07:00
parent 0cea6c560f
commit 60164a7266
23 changed files with 652 additions and 50 deletions

View file

@ -152,6 +152,8 @@ int sched_yield(void);
int seccomp(unsigned, unsigned, void *);
int setegid(uint32_t);
int seteuid(uint32_t);
int setfsgid(int);
int setfsuid(int);
int setgid(int);
int setpgid(int, int);
int setpgrp(void);

View file

@ -30,6 +30,9 @@
* Returns information about file, via open()'d descriptor.
*
* @return 0 on success or -1 w/ errno
* @raise EBADF if `fd` isn't a valid file descriptor
* @raise EIO if an i/o error happens while reading from file system
* @raise EOVERFLOW shouldn't be possible on 64-bit systems
* @asyncsignalsafe
*/
int fstat(int fd, struct stat *st) {

View file

@ -21,6 +21,10 @@
/**
* Sets effective group ID.
*
* @return 0 on success, or -1 w/ errno
* @raise EINVAL if euid not in legal range
* @raise EPERM if lack privileges
*/
int setegid(uint32_t egid) {
return setregid(-1, egid);

View file

@ -21,6 +21,10 @@
/**
* Sets effective user ID.
*
* @return 0 on success, or -1 w/ errno
* @raise EINVAL if euid not in legal range
* @raise EPERM if lack privileges
*/
int seteuid(uint32_t euid) {
return setregid(euid, -1);

37
libc/calls/setfsgid.c Normal file
View file

@ -0,0 +1,37 @@
/*-*- 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/calls/calls.h"
#include "libc/calls/strace.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
/**
* Sets user id of current process for file system ops.
* @return previous filesystem gid
*/
int setfsgid(int gid) {
int rc;
if (IsLinux()) {
rc = sys_setfsgid(gid);
} else {
rc = getegid();
setegid(gid);
}
STRACE("setfsgid(%d) → %d% m", gid, rc);
return rc;
}

37
libc/calls/setfsuid.c Normal file
View file

@ -0,0 +1,37 @@
/*-*- 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/calls/calls.h"
#include "libc/calls/strace.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
/**
* Sets user id of current process for file system ops.
* @return previous filesystem uid
*/
int setfsuid(int uid) {
int rc;
if (IsLinux()) {
rc = sys_setfsuid(uid);
} else {
rc = geteuid();
seteuid(uid);
};
STRACE("setfsuid(%d) → %d% m", uid, rc);
return rc;
}

View file

@ -22,7 +22,10 @@
/**
* Sets group id of current process.
* @return 0 on success or -1 w/ errno
*
* @return 0 on success, or -1 w/ errno
* @raise EINVAL if gid not in legal range
* @raise EPERM if lack privileges
*/
int setgid(int gid) {
int rc;

View file

@ -22,7 +22,12 @@
/**
* Sets user id of current process.
* @return 0 on success or -1 w/ errno
*
* @return 0 on success, or -1 w/ errno
* @raise EINVAL if uid not in legal range
* @raise EAGAIN on temporary failure
* @raise EAGAIN change would cause `RLIMIT_NPROC` to be exceeded
* @raise EPERM if lack privileges
*/
int setuid(int uid) {
int rc;

View file

@ -24,6 +24,15 @@
*
* @param st is where result is stored
* @see S_ISDIR(st.st_mode), S_ISREG(), etc.
* @raise EACCES if denied access to component in path prefix
* @raise EIO if i/o error occurred while reading from filesystem
* @raise ELOOP if a symbolic link loop exists in `path`
* @raise ENAMETOOLONG if a component in `path` exceeds `NAME_MAX`
* @raise ENOENT on empty string or if component in path doesn't exist
* @raise ENOTDIR if a parent component existed that wasn't a directory
* @raise EOVERFLOW shouldn't be possible on 64-bit systems
* @raise ELOOP may ahappen if `SYMLOOP_MAX` symlinks were dereferenced
* @raise ENAMETOOLONG may happen if `path` exceeded `PATH_MAX`
* @asyncsignalsafe
*/
int stat(const char *path, struct stat *st) {

View file

@ -74,6 +74,8 @@ i32 sys_pipe2(i32[hasatleast 2], u32) hidden;
i32 sys_pledge(const char *, const char *) hidden;
i32 sys_posix_openpt(i32) hidden;
i32 sys_renameat(i32, const char *, i32, const char *) hidden;
i32 sys_setfsgid(i32) hidden;
i32 sys_setfsuid(i32) hidden;
i32 sys_setgid(i32) hidden;
i32 sys_setpgid(i32, i32) hidden;
i32 sys_setpriority(i32, u32, i32) hidden;

View file

@ -1,2 +0,0 @@
.include "o/libc/sysv/macros.internal.inc"
.scall setfsgid,0xfffffffffffff07b,globl

View file

@ -1,2 +0,0 @@
.include "o/libc/sysv/macros.internal.inc"
.scall setfsuid,0xfffffffffffff07a,globl

View file

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

View file

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

View file

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

View file

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

View file

@ -187,8 +187,8 @@ scall sys_unmount 0x016016016209f0a6 globl hidden # umount2() on linux
scall umount2 0x016016016209f0a6 globl hidden # unmount() on bsd
scall sys_reboot 0x0d003703720370a9 globl hidden # two arguments b/c netbsd/sparc lool
scall quotactl 0xfff09409420a50b3 globl
scall setfsuid 0xfffffffffffff07a globl
scall setfsgid 0xfffffffffffff07b globl
scall sys_setfsuid 0xfffffffffffff07a globl hidden
scall sys_setfsgid 0xfffffffffffff07b globl hidden
scall capget 0xfffffffffffff07d globl
scall capset 0xfffffffffffff07e globl
scall sigtimedwait 0xffffff159ffff080 globl

View file

@ -20,6 +20,7 @@
#include "libc/calls/calls.h"
#include "libc/calls/struct/dirent.h"
#include "libc/calls/struct/stat.h"
#include "libc/errno.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
@ -59,8 +60,20 @@ static int rmrfdir(const char *dirpath) {
* Recursively removes file or directory.
*/
int rmrf(const char *path) {
int e;
struct stat st;
if (stat(path, &st) == -1) return -1;
if (!S_ISDIR(st.st_mode)) return unlink(path);
return rmrfdir(path);
e = errno;
if (stat(path, &st) == -1) {
if (errno == ENOENT) {
errno = e;
return 0;
} else {
return -1;
}
}
if (!S_ISDIR(st.st_mode)) {
return unlink(path);
} else {
return rmrfdir(path);
}
}

View file

@ -819,6 +819,20 @@ static int LuaUnixSetgid(lua_State *L) {
return LuaUnixSetid(L, "setgid", setgid);
}
// unix.setfsuid(fsuid:int)
// ├─→ true
// └─→ nil, unix.Errno
static int LuaUnixSetfsuid(lua_State *L) {
return LuaUnixSetid(L, "setfsuid", setfsuid);
}
// unix.setfsgid(fsgid:int)
// ├─→ true
// └─→ nil, unix.Errno
static int LuaUnixSetfsgid(lua_State *L) {
return LuaUnixSetid(L, "setfsgid", setfsgid);
}
static dontinline int LuaUnixSetresid(lua_State *L, const char *call,
int f(uint32_t, uint32_t, uint32_t)) {
int olderr = errno;
@ -2571,18 +2585,20 @@ static const luaL_Reg kLuaUnix[] = {
{"rmrf", LuaUnixRmrf}, // remove file recursively
{"send", LuaUnixSend}, // send tcp to some address
{"sendto", LuaUnixSendto}, // send udp to some address
{"setgid", LuaUnixSetgid}, // set real group id of process
{"setitimer", LuaUnixSetitimer}, // set alarm clock
{"setpgid", LuaUnixSetpgid}, // set process group id for pid
{"setpgrp", LuaUnixSetpgrp}, // sets process group id
{"setresgid", LuaUnixSetresgid}, // sets real/effective/saved gids
{"setresuid", LuaUnixSetresuid}, // sets real/effective/saved uids
{"setrlimit", LuaUnixSetrlimit}, // prevent cpu memory bombs
{"setsid", LuaUnixSetsid}, // create a new session id
{"setsockopt", LuaUnixSetsockopt}, // tune socket options
{"setuid", LuaUnixSetuid}, // set real user id of process
{"shutdown", LuaUnixShutdown}, // make socket half empty or full
{"sigaction", LuaUnixSigaction}, // install signal handler
{"setfsgid", LuaUnixSetfsgid}, // set/get group id for file system ops
{"setfsuid", LuaUnixSetfsuid}, // set/get user id for file system ops
{"setgid", LuaUnixSetgid}, // set real group id of process
{"setitimer", LuaUnixSetitimer}, // set alarm clock
{"setpgid", LuaUnixSetpgid}, // set process group id for pid
{"setpgrp", LuaUnixSetpgrp}, // sets process group id
{"setresgid", LuaUnixSetresgid}, // sets real/effective/saved gids
{"setresuid", LuaUnixSetresuid}, // sets real/effective/saved uids
{"setrlimit", LuaUnixSetrlimit}, // prevent cpu memory bombs
{"setsid", LuaUnixSetsid}, // create a new session id
{"setsockopt", LuaUnixSetsockopt}, // tune socket options
{"setuid", LuaUnixSetuid}, // set real user id of process
{"shutdown", LuaUnixShutdown}, // make socket half empty or full
{"sigaction", LuaUnixSigaction}, // install signal handler
{"sigprocmask", LuaUnixSigprocmask}, // change signal mask
{"sigsuspend", LuaUnixSigsuspend}, // wait for signal
{"siocgifconf", LuaUnixSiocgifconf}, // get list of network interfaces

283
tool/build/assimilate.c Normal file
View file

@ -0,0 +1,283 @@
/*-*- 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/bits/bits.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/dce.h"
#include "libc/fmt/conv.h"
#include "libc/intrin/kprintf.h"
#include "libc/log/check.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/msync.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
#include "third_party/getopt/getopt.h"
#include "third_party/regex/regex.h"
STATIC_YOINK("strerror_wr");
// options used: fhem
// letters not used: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdgijklnopqrstuvwxyz
// digits not used: 0123456789
// puncts not used: !"#$%&'()*+,-./;<=>@[\]^_`{|}~
// letters duplicated: none
#define GETOPTS "em"
#define USAGE \
"\
Usage: assimilate.com [-hfem] COMFILE...\n\
-h show help\n\
-e force elf\n\
-m force macho\n\
-f continue on soft errors\n\
\n\
αcτµαlly pδrταblε εxεcµταblε assimilate v1.o\n\
copyright 2022 justine alexandra roberts tunney\n\
https://twitter.com/justinetunney\n\
https://linkedin.com/in/jtunney\n\
https://justine.lol/ape.html\n\
https://github.com/jart\n\
\n\
This program converts Actually Portable Executable files so they're\n\
in the platform-local executable format, rather than the multiplatform\n\
APE shell script format. This is useful on UNIX operating systems when\n\
you want to use your APE programs as script interpreter or for setuid.\n\
"
#define MODE_ELF 1
#define MODE_MACHO 2
int g_mode;
bool g_force;
int exitcode;
const char *prog;
char errstr[128];
void GetOpts(int argc, char *argv[]) {
int opt;
while ((opt = getopt(argc, argv, GETOPTS)) != -1) {
switch (opt) {
case 'f':
g_force = true;
break;
case 'e':
g_mode = MODE_ELF;
break;
case 'm':
g_mode = MODE_MACHO;
break;
case 'h':
case '?':
write(1, USAGE, sizeof(USAGE) - 1);
exit(0);
default:
write(2, USAGE, sizeof(USAGE) - 1);
exit(64);
}
}
if (!g_mode) {
if (IsXnu()) {
g_mode = MODE_MACHO;
} else {
g_mode = MODE_ELF;
}
}
}
void GetElfHeader(char ehdr[hasatleast 64], const char *image) {
char *p;
int c, i;
for (p = image; p < image + 4096; ++p) {
if (READ64LE(p) != READ64LE("printf '")) continue;
for (i = 0, p += 8; p + 3 < image + 4096 && (c = *p++) != '\'';) {
if (c == '\\') {
if ('0' <= *p && *p <= '7') {
c = *p++ - '0';
if ('0' <= *p && *p <= '7') {
c *= 8;
c += *p++ - '0';
if ('0' <= *p && *p <= '7') {
c *= 8;
c += *p++ - '0';
}
}
}
}
if (i < 64) {
ehdr[i++] = c;
} else {
kprintf("%s: ape printf elf header too long\n", prog);
exit(__COUNTER__);
}
}
if (i != 64) {
kprintf("%s: ape printf elf header too short\n", prog);
exit(__COUNTER__);
}
if (READ32LE(ehdr) != READ32LE("\177ELF")) {
kprintf("%s: ape printf elf header didn't have elf magic\n", prog);
exit(__COUNTER__);
}
return;
}
kprintf("%s: printf statement not found in first 4096 bytes\n", prog);
exit(__COUNTER__);
}
void GetMachoPayload(const char *image, size_t imagesize, int *out_offset,
int *out_size) {
regex_t rx;
const char *script;
regmatch_t rm[1 + 3] = {0};
int rc, skip, count, bs, offset, size;
if (!(script = memmem(image, imagesize, "'\n#'\"\n", 6))) {
kprintf("%s: ape shell script not found\n", prog);
exit(__COUNTER__);
}
script += 6;
DCHECK_EQ(REG_OK, regcomp(&rx,
"bs=([ [:digit:]]+) "
"skip=\"([ [:digit:]]+)\" "
"count=\"([ [:digit:]]+)\"",
REG_EXTENDED));
rc = regexec(&rx, script, 4, rm, 0);
if (rc != REG_OK) {
if (rc == REG_NOMATCH) {
kprintf("%s: ape macho dd command not found\n", prog);
exit(__COUNTER__);
}
regerror(rc, &rx, errstr, sizeof(errstr));
kprintf("%s: ape macho dd regex failed: %s\n", prog, errstr);
exit(__COUNTER__);
}
bs = atoi(script + rm[1].rm_so);
skip = atoi(script + rm[2].rm_so);
count = atoi(script + rm[3].rm_so);
if (__builtin_mul_overflow(skip, bs, &offset) ||
__builtin_mul_overflow(count, bs, &size)) {
kprintf("%s: integer overflow parsing macho\n");
exit(__COUNTER__);
}
if (offset < 64) {
kprintf("%s: ape macho dd offset should be ≥64: %d\n", prog, offset);
exit(__COUNTER__);
}
if (offset >= imagesize) {
kprintf("%s: ape macho dd offset is outside file: %d\n", prog, offset);
exit(__COUNTER__);
}
if (size < 32) {
kprintf("%s: ape macho dd size should be ≥32: %d\n", prog, size);
exit(__COUNTER__);
}
if (size > imagesize - offset) {
kprintf("%s: ape macho dd size is outside file: %d\n", prog, size);
exit(__COUNTER__);
}
*out_offset = offset;
*out_size = size;
regfree(&rx);
}
void AssimilateElf(char *p, size_t n) {
char ehdr[64];
GetElfHeader(ehdr, p);
memcpy(p, ehdr, 64);
msync(p, 4096, MS_SYNC);
}
void AssimilateMacho(char *p, size_t n) {
int offset, size;
GetMachoPayload(p, n, &offset, &size);
memmove(p, p + offset, size);
msync(p, n, MS_SYNC);
}
void Assimilate(void) {
int fd;
char *p;
struct stat st;
if ((fd = open(prog, O_RDWR)) == -1) {
kprintf("%s: open(O_RDWR) failed: %m\n", prog);
exit(__COUNTER__);
}
if (fstat(fd, &st) == -1) {
kprintf("%s: fstat() failed: %m\n", prog);
exit(__COUNTER__);
}
if (st.st_size < 8192) {
kprintf("%s: ape binaries must be at least 4096 bytes\n", prog);
exit(__COUNTER__);
}
if ((p = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) ==
MAP_FAILED) {
kprintf("%s: mmap failed: %m\n", prog);
exit(__COUNTER__);
}
if (READ32LE(p) == READ32LE("\177ELF")) {
if (!g_force) {
kprintf("%s: program is already an elf binary\n", prog);
if (g_mode != MODE_ELF) {
exitcode = 1;
}
}
goto Finish;
}
if (READ32LE(p) == 0xFEEDFACE + 1) {
if (!g_force) {
kprintf("%s: program is already a mach-o binary\n", prog);
if (g_mode != MODE_MACHO) {
exitcode = 1;
}
}
goto Finish;
}
if (READ64LE(p) != READ64LE("MZqFpD='")) {
kprintf("%s: this file is not an actually portable executable\n", prog);
exit(__COUNTER__);
}
if (g_mode == MODE_ELF) {
AssimilateElf(p, st.st_size);
} else if (g_mode == MODE_MACHO) {
AssimilateMacho(p, st.st_size);
}
Finish:
if (munmap(p, st.st_size) == -1) {
kprintf("%s: munmap() failed: %m\n", prog);
exit(__COUNTER__);
}
}
int main(int argc, char *argv[]) {
int i;
GetOpts(argc, argv);
if (optind == argc) {
kprintf("error: need at least one program path to assimilate\n");
write(2, USAGE, sizeof(USAGE) - 1);
exit(64);
}
for (i = optind; i < argc; ++i) {
prog = argv[i];
Assimilate();
}
return exitcode;
}

View file

@ -46,8 +46,8 @@ TOOL_BUILD_DIRECTDEPS = \
LIBC_STUBS \
LIBC_SYSV \
LIBC_SYSV_CALLS \
LIBC_TIME \
LIBC_THREAD \
LIBC_TIME \
LIBC_TINYMATH \
LIBC_UNICODE \
LIBC_X \
@ -58,6 +58,7 @@ TOOL_BUILD_DIRECTDEPS = \
THIRD_PARTY_GETOPT \
THIRD_PARTY_MBEDTLS \
THIRD_PARTY_MUSL \
THIRD_PARTY_REGEX \
THIRD_PARTY_STB \
THIRD_PARTY_XED \
THIRD_PARTY_ZLIB \

View file

@ -17,20 +17,26 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/struct/rlimit.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/intrin/kprintf.h"
#include "libc/macros.internal.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/ok.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/rlimit.h"
#include "third_party/getopt/getopt.h"
// options used: hpugc
// letters not used: ABCDEFGHIJKLMNOPQRSTUVWXYZabdefijklmnoqrstvwxyz
// digits not used: 0123456789
// puncts not used: !"#$%&'()*+,-./;<=>@[\]^_`{|}~
// letters duplicated: none
#define GETOPTS "hp:u:g:c:"
STATIC_YOINK("strerror_wr");
#define GETOPTS "hFp:u:g:c:"
#define USAGE \
"\
@ -39,6 +45,7 @@ usage: pledge.com [-h] PROG ARGS...\n\
-g GID call setgid()\n\
-u UID call setuid()\n\
-c PATH call chroot()\n\
-F don't normalize file descriptors\n\
-p PLEDGE may contain any of following separated by spaces\n\
- stdio: allow stdio and benign system calls\n\
- rpath: read-only path ops\n\
@ -56,13 +63,26 @@ usage: pledge.com [-h] PROG ARGS...\n\
- thread: allow clone\n\
- id: allow setuid and friends\n\
- exec: allow executing ape binaries\n\
\n\
pledge.com v1.o\n\
copyright 2022 justine alexandra roberts tunney\n\
https://twitter.com/justinetunney\n\
https://linkedin.com/in/jtunney\n\
https://justine.lol/pledge/\n\
https://github.com/jart\n\
\n\
this program lets you launch linux commands in a sandbox that's\n\
inspired by the design of openbsd's pledge() system call. Visit\n\
the https://justine.lol/pledge/ page for online documentation.\n\
\n\
"
int g_gflag;
int g_uflag;
int g_hflag;
bool g_noclose;
const char *g_pflag;
const char *g_cflag;
const char *g_chroot;
static void GetOpts(int argc, char *argv[]) {
int opt;
@ -73,7 +93,7 @@ static void GetOpts(int argc, char *argv[]) {
g_pflag = optarg;
break;
case 'c':
g_cflag = optarg;
g_chroot = optarg;
break;
case 'g':
g_gflag = atoi(optarg);
@ -81,6 +101,9 @@ static void GetOpts(int argc, char *argv[]) {
case 'u':
g_uflag = atoi(optarg);
break;
case 'F':
g_noclose = true;
break;
case 'h':
case '?':
write(1, USAGE, sizeof(USAGE) - 1);
@ -95,42 +118,195 @@ static void GetOpts(int argc, char *argv[]) {
const char *prog;
char pledges[1024];
char pathbuf[PATH_MAX];
struct pollfd pfds[256];
int GetPollMaxFds(void) {
int n;
struct rlimit rl;
if (getrlimit(RLIMIT_NOFILE, &rl) != -1) {
n = rl.rlim_cur;
} else {
n = 64;
}
return MIN(ARRAYLEN(pfds), MAX(3, n));
}
void NormalizeFileDescriptors(void) {
int i, n, fd;
n = GetPollMaxFds();
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");
exit(__COUNTER__);
}
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");
exit(__COUNTER__);
}
if (fd != i) {
kprintf("error: open() is broken: %d vs. %d\n", fd, i);
exit(__COUNTER__);
}
}
}
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);
exit(__COUNTER__);
}
}
}
}
int main(int argc, char *argv[]) {
bool hasfunbits;
int useruid, usergid;
int owneruid, ownergid;
int oldfsuid, oldfsgid;
if (!IsLinux()) {
kprintf("error: this program is only intended for linux\n");
exit(__COUNTER__);
}
// parse flags
GetOpts(argc, argv);
if (optind == argc) {
kprintf("error: too few args\n", g_pflag);
write(2, USAGE, sizeof(USAGE) - 1);
exit(64);
}
if (g_cflag) {
if (chroot(g_cflag) == -1) {
kprintf("error: chroot(%`'s) failed: %s\n", g_cflag, strerror(errno));
return 1;
if (!g_noclose) {
NormalizeFileDescriptors();
}
// test for weird chmod bits
usergid = getgid();
ownergid = getegid();
useruid = getuid();
owneruid = geteuid();
hasfunbits = usergid != ownergid || useruid != owneruid;
if (hasfunbits) {
setuid(owneruid);
setgid(ownergid);
}
// 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(__COUNTER__);
}
}
// check if user has permission to chroot directory
if (hasfunbits) {
oldfsuid = setfsuid(useruid);
oldfsgid = setfsgid(usergid);
if (access(g_chroot, R_OK) == -1) {
kprintf("error: access(%#s) failed: %m\n", g_chroot);
_Exit(__COUNTER__);
}
setfsuid(oldfsuid);
setfsgid(oldfsgid);
}
// change root fs path
// all the documentation on the subject is unprofessional and crazy
// the linux devs willfully deprive linux users of security tools
// linux appears to not even forbid chroot on setuid binaries
// yes i've considered fchdir() and i don't really care
// ohh it's sooo insecure they say, and they solve it
// by imposing a requirement that we must only do
// the "insecure" thing as the root user lool
if (g_chroot) {
if (chdir(g_chroot) == -1) {
kprintf("error: chdir(%#s) failed: %m\n", g_chroot);
_Exit(__COUNTER__);
}
if (chroot(g_chroot) == -1) {
kprintf("error: chroot(%#s) failed: %m\n", g_chroot);
_Exit(__COUNTER__);
}
}
// find program
if (hasfunbits) {
oldfsuid = setfsuid(useruid);
oldfsgid = setfsgid(usergid);
}
if (!(prog = commandv(argv[optind], pathbuf, sizeof(pathbuf)))) {
kprintf("error: command not found: %s\n", argv[optind]);
return 2;
kprintf("error: command not found: %m\n", argv[optind]);
_Exit(__COUNTER__);
}
if (g_gflag) {
if (hasfunbits) {
setfsuid(oldfsuid);
setfsgid(oldfsgid);
}
// set group id
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(__COUNTER__);
}
if (getgid() != usergid || getegid() != usergid) {
kprintf("error: setgid() broken\n");
_Exit(__COUNTER__);
}
} else if (g_gflag) {
// otherwise we trust the gid flag
if (setgid(g_gflag) == -1) {
kprintf("error: setgid(%d) failed: %s\n", g_gflag, strerror(errno));
return 3;
kprintf("error: setgid(%d) failed: %m\n", g_gflag);
_Exit(__COUNTER__);
}
if (getgid() != g_gflag || getegid() != g_gflag) {
kprintf("error: setgid() broken\n");
_Exit(__COUNTER__);
}
}
if (g_uflag) {
// set user id
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(__COUNTER__);
}
if (getuid() != useruid || geteuid() != useruid) {
kprintf("error: setuid() broken\n");
_Exit(__COUNTER__);
}
} else if (g_uflag) {
// otherwise we trust the uid flag
if (setuid(g_uflag) == -1) {
kprintf("error: setuid(%d) failed: %s\n", g_uflag, strerror(errno));
return 4;
kprintf("error: setuid(%d) failed: %m\n", g_uflag);
_Exit(__COUNTER__);
}
if (getuid() != g_uflag || geteuid() != g_uflag) {
kprintf("error: setuid() broken\n");
_Exit(__COUNTER__);
}
}
// apply sandbox
ksnprintf(pledges, sizeof(pledges), "%s execnative", g_pflag);
if (pledge(pledges, 0) == -1) {
kprintf("error: pledge(%`'s) failed: %s\n", pledges, strerror(errno));
return 5;
kprintf("error: pledge(%#s) failed: %m\n", pledges);
_Exit(__COUNTER__);
}
execv(prog, argv + optind);
kprintf("error: execve(%`'s) failed: %s\n", prog, strerror(errno));
// launch program
__sys_execve(prog, argv + optind, environ);
kprintf("%s: execve failed: %m\n", prog);
return 127;
}

View file

@ -2810,6 +2810,13 @@ UNIX MODULE
Returns `ENOSYS` on Windows NT if `uid` isn't `getuid()`.
unix.setfsuid(uid:int)
├─→ true
└─→ nil, unix.Errno
Sets user id for file system ops.
unix.setgid(gid:int)
├─→ true
└─→ nil, unix.Errno