playground

Sandbox, container or whatever utilities for linux.
git clone https://pi.duncano.de/git/playground.git

libpledge.c (10120B)


      1 #include <sys/prctl.h>
      2 #include <sys/ioctl.h>
      3 #include <sys/syscall.h>
      4 #include <sys/socket.h>
      5 #include <sys/types.h>
      6 #include <sys/stat.h>
      7 
      8 #include <asm/bitsperlong.h>	/* for __BITS_PER_LONG */
      9 
     10 #include <linux/filter.h>
     11 #include <linux/seccomp.h>
     12 #include <linux/audit.h>
     13 
     14 #include <endian.h>
     15 #include <errno.h>
     16 #include <fcntl.h>
     17 #include <stdio.h>
     18 #include <stdlib.h>
     19 #include <stddef.h>
     20 #include <stdint.h>
     21 #include <string.h>
     22 #include <unistd.h>
     23 
     24 #include "pledge.h"
     25 #include "pledge_syscalls.h"
     26 #include "seccomp_bpf_utils.h"
     27 
     28 #ifndef nitems
     29 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
     30 #endif
     31 
     32 struct promise {
     33 	const char *name;
     34 	uint64_t flags;
     35 };
     36 
     37 static const struct promise strpromises[] = {
     38 	{ "chown",	PLEDGE_CHOWN | PLEDGE_CHOWNUID },
     39 	{ "cpath",	PLEDGE_CPATH },
     40 	{ "dpath",	PLEDGE_DPATH },
     41 	{ "drm",	PLEDGE_DRM },
     42 	{ "exec",	PLEDGE_EXEC },
     43 	{ "fattr",	PLEDGE_FATTR | PLEDGE_CHOWN },
     44 	{ "flock",	PLEDGE_FLOCK },
     45 	{ "getpw",	PLEDGE_GETPW },
     46 	{ "id",		PLEDGE_ID },
     47 	{ "inet",	PLEDGE_INET },
     48 	{ "ioctl",	PLEDGE_IOCTL },
     49 	{ "proc",	PLEDGE_PROC },
     50 	{ "rpath",	PLEDGE_RPATH },
     51 	{ "settime",	PLEDGE_SETTIME },
     52 	{ "stdio",	PLEDGE_STDIO },
     53 	{ "unix",	PLEDGE_UNIX },
     54 	{ "wpath",	PLEDGE_WPATH },
     55 	{ "debug",	PLEDGE_DEBUG },
     56 	{ "verbose",	PLEDGE_VERBOSE },
     57 	{ "ipc",	 PLEDGE_IPC },
     58 	{ "emul",	 PLEDGE_EMUL },
     59 	{ "mount",	 PLEDGE_MOUNT },
     60 	{ "key",	 PLEDGE_KEY },
     61 	{ "kern",	 PLEDGE_KERN },
     62 	{ 0, 0 },
     63 };
     64 
     65 
     66 struct sock_fprog *
     67 pledge_whitelist(uint64_t flags)
     68 {
     69 	uint64_t len, num, i;
     70 	uint64_t calls[nitems(pledge_syscalls)];
     71 	struct sock_fprog *fprog;
     72 	struct sock_filter *fp;
     73 
     74 	num = 0;
     75 
     76 	for (i = 0; i < nitems(pledge_syscalls); i++) {
     77 		if (!(flags & pledge_syscalls[i]))
     78 			continue;
     79 		calls[num++] = i;
     80 #ifndef NOVERBOSE
     81 		if (flags & PLEDGE_VERBOSE)
     82 			fprintf(stderr, "whitelist syscall %ld\n", i);
     83 #endif
     84 	}
     85 
     86 	/* space for all syscall comparisons */
     87 	len = num;
     88 	/* space arch validation, syscall load and and two return statements */
     89 	len += 5;
     90 
     91 	if (!(fprog = calloc(1, sizeof(struct sock_fprog))))
     92 		return 0;
     93 	if (!(fprog->filter = calloc(len, sizeof(struct sock_filter)))) {
     94 		free(fprog);
     95 		return 0;
     96 	}
     97 	fprog->len = len;
     98 	fp = fprog->filter;
     99 
    100 	/* validate architecture, jump to the RET_KILL if not equal */
    101 	_LOAD_ARCH;
    102 	_JUMP_EQ(AUDIT_ARCH_X86_64, 0, _END-1);
    103 	/* compare syscall numbers */
    104 	_LOAD_SYSCALL_NR;
    105 	for (i = 0; i < num; i++)
    106 		_JUMP_EQ(calls[i], _END, 0);
    107 	/* no match */
    108 #ifndef NODEBUG
    109 	_RET((flags & PLEDGE_DEBUG) ? SECCOMP_RET_TRAP : SECCOMP_RET_KILL);
    110 #else
    111 	_RET(SECCOMP_RET_KILL);
    112 #endif
    113 	/* matching syscall jump here */
    114 	_RET(SECCOMP_RET_ALLOW);
    115 
    116 	return fprog;
    117 }
    118 
    119 struct sock_fprog *
    120 pledge_blacklist(uint64_t flags, uint64_t oldflags)
    121 {
    122 	uint64_t len, num, i;
    123 	uint64_t calls[nitems(pledge_syscalls)];
    124 	struct sock_fprog *fprog;
    125 	struct sock_filter *fp;
    126 
    127 	num = 0;
    128 
    129 	for (i = 0; i < nitems(pledge_syscalls); i++) {
    130 		if (!pledge_syscalls[i])
    131 			continue;
    132 		if ((flags & pledge_syscalls[i]) || !(oldflags & pledge_syscalls[i]))
    133 			continue;
    134 		calls[num++] = i;
    135 #ifndef NOVERBOSE
    136 		if (flags & PLEDGE_VERBOSE)
    137 			fprintf(stderr, "blacklist syscall %ld\n", i);
    138 #endif
    139 	}
    140 
    141 	/* no new rules to apply */
    142 	if (!num)
    143 		return 0;
    144 
    145 	/* space for all syscall comparisons */
    146 	len = num;
    147 	/* syscall load and and two return statements */
    148 	len += 3;
    149 
    150 	if (!(fprog = calloc(1, sizeof(struct sock_fprog))))
    151 		return 0;
    152 	if (!(fprog->filter = calloc(len, sizeof(struct sock_filter)))) {
    153 		free(fprog);
    154 		return 0;
    155 	}
    156 	fprog->len = len;
    157 	fp = fprog->filter;
    158 
    159 	/* compare all syscall numbers */
    160 	_LOAD_SYSCALL_NR;
    161 	for (i = 0; i < num; i++)
    162 		_JUMP_EQ(calls[i], _END, 0);
    163 	/* no match */
    164 	_RET(SECCOMP_RET_ALLOW);
    165 	/* matching syscall jump here */
    166 #ifndef NODEBUG
    167 	_RET((flags & PLEDGE_DEBUG) ? SECCOMP_RET_TRAP : SECCOMP_RET_KILL);
    168 #else
    169 	_RET(SECCOMP_RET_KILL);
    170 #endif
    171 
    172 	return fprog;
    173 }
    174 
    175 struct sock_fprog *
    176 pledge_filter(uint64_t flags, uint64_t oldflags)
    177 {
    178 	struct sock_fprog *fprog;
    179 	struct sock_filter *fp;
    180 	uint64_t len;
    181 	int allow_prctl, allow_socket, allow_selfkill, allow_fcntl, allow_selfchown, allow_ioctl_always, allow_ioctl_ioctl;
    182 	int filter_open;
    183 
    184 	len = 0;
    185 
    186 	allow_selfchown = _FILTER_CHOWN;
    187 	allow_prctl = _FILTER_PRCTL;
    188 	allow_socket = _FILTER_SOCKET;
    189 	allow_selfkill = _FILTER_KILL;
    190 	allow_fcntl = _FILTER_FCNTL;
    191 	allow_ioctl_always = _FILTER_IOCTL_ALWAYS;
    192 	allow_ioctl_ioctl= _FILTER_IOCTL_IOCTL;
    193 	filter_open = _FILTER_OPEN;
    194 
    195 	if (filter_open)
    196 		len += 9;
    197 
    198 	/* chown(2), fchown(2), lchown(2), fchownat(2) */
    199 	if (allow_selfchown)
    200 		len += 32;
    201 
    202 	if (allow_prctl)
    203 		len += 4;
    204 
    205 	if (allow_socket) {
    206 		len += 3;
    207 		/* AF_INET[6]? */
    208 		if ((flags&PLEDGE_INET))
    209 			len += 2;
    210 		/* AF_UNIX */
    211 		if ((flags&PLEDGE_UNIX))
    212 			len += 1;
    213 	}
    214 
    215 	if (allow_selfkill)
    216 		len += 11;
    217 
    218 	if (allow_fcntl)
    219 		len += 3;
    220 
    221 	if (allow_ioctl_always || allow_ioctl_ioctl) {
    222 		len += 6;
    223 		if (allow_ioctl_always)
    224 			len += 12;
    225 		if (allow_ioctl_ioctl)
    226 			len += 21;
    227 	}
    228 
    229 	/* no new filters */
    230 	if (!len)
    231 		return 0;
    232 
    233 	/* space for 3 different return statements (KILL,ALLOW,EPERM) */
    234 	len += 4;
    235 
    236 	printf("allowsocket %d unix=%d inet=%d\n", allow_socket,
    237 	    ((flags&PLEDGE_UNIX) == PLEDGE_UNIX),
    238 	    ((flags&PLEDGE_INET) == PLEDGE_INET));
    239 	printf("allowselfchown %d\n", allow_selfchown);
    240 	printf("allowprctl %d\n", allow_prctl);
    241 	printf("allowselfkill %d\n", allow_selfkill);
    242 	printf("allowfcntl %d\n", allow_fcntl);
    243 	printf("allow ioctl always %d\n", allow_ioctl_always);
    244 	printf("allow ioctl ioctl %d\n", allow_ioctl_ioctl);
    245 	printf("filter open %d\n", filter_open);
    246 
    247 	if (!(fprog = calloc(1, sizeof(struct sock_fprog))))
    248 		return 0;
    249 	if (!(fprog->filter = calloc(len, sizeof(struct sock_filter)))) {
    250 		free(fprog);
    251 		return 0;
    252 	}
    253 	fprog->len = len;
    254 	fp = fprog->filter;
    255 
    256 #define _KILL		_END
    257 #define _EPERM	_END-1
    258 #define _ALLOW	_END-2
    259 
    260 	_LOAD_SYSCALL_NR;
    261 
    262 	if (filter_open) {
    263 		/* allow kill(0 | getpid(), ...) */
    264 		_JUMP_EQ(SYS_open, 0, 8);
    265 		_ARG32(1);
    266 		_JUMP_SET(O_RDWR, _EPERM, 0);
    267 		_JUMP_SET(O_WRONLY, _EPERM, 0);
    268 		_JUMP_SET(O_APPEND, _EPERM, 0);
    269 		_JUMP_SET(O_CREAT, _EPERM, 0);
    270 		/* O_TMPFILE and O_DIRECTORY conflict... */
    271 		/* _JUMP_SET(O_TMPFILE, _EPERM, 0); */
    272 		_JUMP_SET(O_TRUNC, _EPERM, 0);
    273 		_JUMP_SET(O_TRUNC, _EPERM, 0);
    274 		_LOAD_SYSCALL_NR;
    275 	}
    276 
    277 	if (allow_selfkill) {
    278 		pid_t pid = getpid();
    279 		/* allow kill(0 | getpid(), ...) */
    280 		_JUMP_EQ(SYS_kill, 0, 10); // XXX: fix offset
    281 		_ARG64(0); // +4
    282 		_JUMP_EQ64(0, _ALLOW, 0); // +3
    283 		_JUMP_EQ64(pid, _ALLOW, _EPERM); // +3
    284 	}
    285 
    286 	if (allow_selfchown) {
    287 		uid_t uid = getuid();
    288 		gid_t gid = getgid();
    289 
    290 		/* chown(2), fchown(2), lchown(2) */
    291 		_JUMP_EQ(SYS_chown, 4, 0);
    292 		_JUMP_EQ(SYS_fchown, 3, 0);
    293 		_JUMP_EQ(SYS_lchown, 2, 0); // XXX: fix offset
    294 		_JUMP_EQ(SYS_fchownat, 14, 28); // XXX: fix offset
    295 
    296 		/* [fl]chown(.., uid, gid) */
    297 		_ARG64(1); // +4
    298 		_JUMP_EQ64(uid, 0, _EPERM); // +3
    299 		_ARG64(2); // + 4
    300 		_JUMP_EQ64(gid, _ALLOW, _EPERM); // +3
    301 
    302 	/* fchownat(.., .., uid, gid, ..) */
    303 		_ARG64(2); // +4
    304 		_JUMP_EQ64(uid, 0, _EPERM); // +3
    305 		_ARG64(4); // + 4
    306 		_JUMP_EQ64(gid, _ALLOW, _EPERM); // +3
    307 	}
    308 
    309 	if (allow_prctl) {
    310 		/* allow prctl(PR_[SG]ET_SECCOMP, ...) */
    311 		_JUMP_EQ(SYS_prctl, 0, 3);
    312 		_ARG32(0);
    313 		_JUMP_EQ(PR_SET_SECCOMP, _ALLOW, 0);
    314 		_JUMP_EQ(PR_GET_SECCOMP, _ALLOW, _KILL);
    315 	}
    316 
    317 	if (allow_socket) {
    318 		/* allow specific domains: socket(domain, .., ..)  */
    319 		_JUMP_EQ(SYS_socket, 0, 2 + ((flags & PLEDGE_INET) ? 2 : 0) + ((flags & PLEDGE_UNIX) ? 1 : 0));
    320 		_ARG32(0);
    321 		if (flags & PLEDGE_INET) {
    322 			_JUMP_EQ(AF_INET, _ALLOW, 0);
    323 			_JUMP_EQ(AF_INET6, _ALLOW, 0);
    324 		}
    325 		if (flags & PLEDGE_UNIX) {
    326 			_JUMP_EQ(AF_UNIX, _ALLOW, 0);
    327 		}
    328 		_JUMP(_EPERM);
    329 	}
    330 
    331 	if (allow_fcntl) {
    332 		/* allow fcntl(..., != F_SETOWN, ...) */
    333 		_JUMP_EQ(SYS_fcntl, 0, 2);
    334 		_ARG32(1);
    335 		_JUMP_EQ(F_SETOWN, _EPERM, _ALLOW);
    336 	}
    337 
    338 	if (allow_ioctl_always || allow_ioctl_ioctl) {
    339 		/* allow ioctl(..., FIONREAD|FIONBIO|FIOCLEX|FIONCLEX, ...) */
    340 		_JUMP_EQ(SYS_ioctl, 0, 5 +
    341 		    (allow_ioctl_always ? 12 : 0) +
    342 		    (allow_ioctl_ioctl ? 21 : 0));
    343 		_ARG64(1); // 4
    344 		if (allow_ioctl_always) {
    345 			_JUMP_EQ64(FIONREAD, _ALLOW, 0);
    346 			_JUMP_EQ64(FIONBIO, _ALLOW, 0);
    347 			_JUMP_EQ64(FIOCLEX, _ALLOW, 0);
    348 			_JUMP_EQ64(FIONCLEX, _ALLOW, 0);
    349 		}
    350 		if (allow_ioctl_ioctl) {
    351 #define _JTRUE	(allow_ioctl_ioctl == FILTER_WHITELIST ? _ALLOW : _EPERM)
    352 			_JUMP_EQ64(TCFLSH, _JTRUE, 0);
    353 			_JUMP_EQ64(TCGETS, _JTRUE, 0);
    354 			_JUMP_EQ64(TIOCGWINSZ, _JTRUE, 0);
    355 			_JUMP_EQ64(TIOCGPGRP, _JTRUE, 0);
    356 			_JUMP_EQ64(TIOCSPGRP, _JTRUE, 0);
    357 			_JUMP_EQ64(TCSETSF, _JTRUE, 0);
    358 			_JUMP_EQ64(TCSETSW, _JTRUE, 0);
    359 		}
    360 		_JUMP(_EPERM);
    361 	}
    362 
    363 	/* no match */
    364 	_RET(SECCOMP_RET_ALLOW);
    365 	/* no permissions */
    366 	_RET(SECCOMP_RET_ERRNO|(EPERM & SECCOMP_RET_DATA));
    367 	/* matching syscall jump here */
    368 #ifndef NODEBUG
    369 	_RET((flags & PLEDGE_DEBUG) ? SECCOMP_RET_TRAP : SECCOMP_RET_KILL);
    370 #else
    371 	_RET(SECCOMP_RET_KILL);
    372 #endif
    373 
    374 	printf("length=%ld expected=%ld\n", (fp-fprog->filter), len);
    375 
    376 	return fprog;
    377 }
    378 
    379 uint64_t
    380 pledge_flags(const char *promises)
    381 {
    382 	uint64_t flags, f;
    383 	const struct promise *pp;
    384 	char *buf, *p;
    385 
    386 	flags = 0;
    387 	buf = strdup(promises);
    388 	for ((p = strtok(buf, " ")); p; (p = strtok(0, " "))) {
    389 		f = 0;
    390 		for (pp = strpromises; pp->name; pp++) {
    391 			if (strcmp(p, pp->name) == 0)
    392 				f = pp->flags;
    393 		}
    394 		if (!f) {
    395 			free(buf);
    396 			return 0;
    397 		}
    398 		flags |= f;
    399 	}
    400 	free(buf);
    401 	return flags;
    402 }
    403 
    404 static uint64_t currflags = 0;
    405 
    406 int
    407 pledge(const char *promises, const char *paths[])
    408 {
    409 	struct sock_fprog *filterprog;
    410 	uint64_t flags;
    411 	int rv = 0;
    412 
    413 	if (paths) {
    414 		errno = EINVAL;
    415 		return -1;
    416 	}
    417 
    418 	if (!promises)
    419 		return 0;
    420 
    421 	if (!(flags = pledge_flags(promises))) {
    422 		errno = EINVAL;
    423 		return -1;
    424 	}
    425 
    426 	if ((currflags & PLEDGED) != PLEDGED) {
    427 		if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
    428 			return -1;
    429 		filterprog = pledge_whitelist(flags);
    430 	} else {
    431 		filterprog = pledge_blacklist(flags, currflags);
    432 	}
    433 
    434 	if (filterprog) {
    435 		if ((rv = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, filterprog)) == -1)
    436 			goto ret;
    437 		free(filterprog);
    438 	}
    439 
    440 	if ((filterprog = pledge_filter(flags, currflags)))
    441 		if ((rv = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, filterprog)) == -1)
    442 			goto ret;
    443 
    444 	currflags = flags | PLEDGED;
    445 
    446 ret:
    447 	free(filterprog);
    448 	return rv;
    449 }