Use LD_PRELOAD to inject pledge() in glibc progs

We're now able to drop both `exec` and `prot_exec` privileges
automatically when launching glibc dynamic executables. We also have
really outstanding standard error logging now, that explains which
promises are needed, even in cases where `exec` is used.
This commit is contained in:
Justine Tunney 2022-08-08 21:23:37 -07:00
parent 0277d7d6e9
commit 6b3d257588
5 changed files with 286 additions and 178 deletions

View file

@ -119,23 +119,45 @@ o/$(MODE)/tool/build/dd.zip.o: o/$(MODE)/tool/build/dd
# we need pic because:
# so it can be an LD_PRELOAD payload
o/$(MODE)/tool/build/sandbox.o: \
o/$(MODE)/tool/build/dso/sandbox.o: \
OVERRIDE_CFLAGS += \
-fPIC
o/$(MODE)/tool/build/sandbox.so: \
o/$(MODE)/tool/build/sandbox.o \
o/$(MODE)/tool/build/dso/sandbox.o: \
libc/calls/calls.h \
tool/build/dso/sandbox.c \
libc/calls/pledge.h \
libc/runtime/runtime.h \
libc/calls/pledge.internal.h \
libc/intrin/promises.internal.h \
tool/build/build.mk
o/$(MODE)/tool/build/dso/sandbox.so: \
o/$(MODE)/tool/build/dso/sandbox.o \
o/$(MODE)/libc/calls/pledge-linux.o \
o/$(MODE)/libc/sysv/restorert.o
@$(COMPILE) -ALINK.so \
$(CC) \
-s \
@$(CC) -s \
-shared \
-nostdlib \
-Wl,--gc-sections \
$(LINKARGS) \
o/$(MODE)/tool/build/dso/sandbox.o \
o/$(MODE)/libc/calls/pledge-linux.o \
o/$(MODE)/libc/sysv/restorert.o \
$(OUTPUT_OPTION)
o/$(MODE)/tool/build/dso/sandbox.so.zip.o: \
ZIPOBJ_FLAGS += \
-B
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/pledge.o \
$(CRT) \
$(APE_NO_MODIFY_SELF)
@$(APELINK)
.PHONY: o/$(MODE)/tool/build
o/$(MODE)/tool/build: \
o/$(MODE)/tool/build/emucrt \

View file

@ -16,13 +16,35 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/pledge.h"
#include "libc/calls/pledge.internal.h"
#include "libc/intrin/promises.internal.h"
#include "libc/runtime/runtime.h"
hidden char __privileged_start;
hidden char __privileged_end;
/*
* runs pledge at glibc executable load time, e.g.
* strace -vff bash -c '_PLEDGE=4194303,0,1 LD_PRELOAD=$HOME/sandbox.so ls'
*/
__attribute__((__constructor__)) void InitializeSandbox(void) {
sys_pledge_linux(~(1ul << PROMISE_STDIO), kPledgeModeErrno, false);
hidden uint8_t __privileged_start[1];
hidden uint8_t __privileged_end[1];
__attribute__((__constructor__)) void init(void) {
int c, i, j;
const char *s;
uint64_t arg[3] = {0};
s = getenv("_PLEDGE");
for (i = j = 0; i < 3; ++i) {
while ((c = s[j] & 255)) {
++j;
if ('0' <= c & c <= '9') {
arg[i] *= 10;
arg[i] += c - '0';
} else {
break;
}
}
}
sys_pledge_linux(~arg[0], arg[1], arg[2]);
}

View file

@ -16,6 +16,7 @@
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/bits/safemacros.internal.h"
#include "libc/calls/calls.h"
@ -36,6 +37,7 @@
#include "libc/intrin/promises.internal.h"
#include "libc/macros.internal.h"
#include "libc/math.h"
#include "libc/mem/io.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/kcpuids.h"
#include "libc/runtime/gc.internal.h"
@ -65,6 +67,7 @@
//
STATIC_YOINK("strerror_wr");
STATIC_YOINK("zip_uri_support");
#define USAGE \
"\
@ -123,6 +126,7 @@ int g_uflag;
int g_kflag;
int g_hflag;
bool g_nice;
bool isdynamic;
bool g_noclose;
long g_cpuquota;
long g_fszquota;
@ -131,6 +135,8 @@ long g_proquota;
long g_dontdrop;
const char *g_chroot;
const char *g_promises;
char dsopath[PATH_MAX];
char tmppath[PATH_MAX];
struct {
int n;
@ -393,7 +399,8 @@ void ApplyFilesystemPolicy(unsigned long ipromises) {
Unveil(prog, "rx");
if (IsDynamicExecutable(prog)) {
if (isdynamic) {
Unveil(dsopath, "rx");
UnveilIfExists("/lib", "rx");
UnveilIfExists("/lib64", "rx");
UnveilIfExists("/usr/lib", "rx");
@ -518,8 +525,36 @@ void DropCapabilities(void) {
}
}
bool FileExistsAndIsNewerThan(const char *filepath, const char *thanpath) {
struct stat st1, st2;
if (stat(filepath, &st1) == -1) return false;
if (stat(thanpath, &st2) == -1) return false;
if (st1.st_mtim.tv_sec < st2.st_mtim.tv_sec) return false;
if (st1.st_mtim.tv_sec > st2.st_mtim.tv_sec) return true;
return st1.st_mtim.tv_nsec >= st2.st_mtim.tv_nsec;
}
int Extract(const char *from, const char *to, int mode) {
int fdin, fdout;
if ((fdin = open(from, O_RDONLY)) == -1) return -1;
if ((fdout = creat(to, mode)) == -1) {
close(fdin);
return -1;
}
if (_copyfd(fdin, fdout, -1) == -1) {
close(fdout);
close(fdin);
return -1;
}
return close(fdout) | close(fdin);
}
int main(int argc, char *argv[]) {
const char *s;
bool hasfunbits;
int fdin, fdout;
char buf[PATH_MAX];
int e, zipfd, memfd;
int useruid, usergid;
int owneruid, ownergid;
int oldfsuid, oldfsgid;
@ -607,6 +642,29 @@ int main(int argc, char *argv[]) {
setfsgid(oldfsgid);
}
// figure out where we want the dso
if (IsDynamicExecutable(prog)) {
isdynamic = true;
if ((s = getenv("TMPDIR")) || //
(s = getenv("HOME")) || //
(s = ".")) {
ksnprintf(dsopath, sizeof(dsopath), "%s/sandbox.so", s);
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");
exit(1);
}
if (rename(tmppath, dsopath) == -1) {
kprintf("error: rename dso failed: %m\n");
exit(1);
}
}
ksnprintf(buf, sizeof(buf), "LD_PRELOAD=%s", dsopath);
putenv(buf);
}
}
if (g_dontdrop) {
if (hasfunbits) {
kprintf("error: -D flag forbidden on setuid binaries\n");
@ -669,11 +727,6 @@ int main(int argc, char *argv[]) {
ApplyFilesystemPolicy(ipromises);
// we always need exec which is a weakness of this model
if (!(~ipromises & (1ul << PROMISE_EXEC))) {
g_promises = xstrcat(g_promises, ' ', "exec");
}
// pledge.com uses the return eperm instead of killing the process
// model. we do this becasue it's only possible to have sigsys print
// crash messages if we're not pledging exec, which is what this tool
@ -684,6 +737,22 @@ int main(int argc, char *argv[]) {
__pledge_mode = kPledgeModeErrno;
}
// 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");
}
if (isdynamic) {
g_promises = xstrcat(g_promises, ' ', "prot_exec");
}
// pass arguments to pledge() inside the dso
if (isdynamic) {
ksnprintf(buf, sizeof(buf), "_PLEDGE=%ld,%ld,%ld", ~ipromises,
__pledge_mode, false);
putenv(buf);
}
// apply sandbox
if (pledge(g_promises, g_promises) == -1) {
kprintf("error: pledge(%#s) failed: %m\n", g_promises);