diff options
author | Ted Unangst <tedu@cvs.openbsd.org> | 2015-07-20 21:36:28 +0000 |
---|---|---|
committer | Ted Unangst <tedu@cvs.openbsd.org> | 2015-07-20 21:36:28 +0000 |
commit | 5261693a1ce34fc37f47e917adb10fc9a62fbd4d (patch) | |
tree | 7d4cc1c013cbfb6a5a137d2707817180f54f6307 /sys | |
parent | ddce99ae206b9a40347515419cbcadd1136f1c54 (diff) |
crudely canonicalize paths before taming them. ok deraadt doug
Diffstat (limited to 'sys')
-rw-r--r-- | sys/kern/kern_tame.c | 74 |
1 files changed, 72 insertions, 2 deletions
diff --git a/sys/kern/kern_tame.c b/sys/kern/kern_tame.c index 9fc57ba1ad2..069993d25a9 100644 --- a/sys/kern/kern_tame.c +++ b/sys/kern/kern_tame.c @@ -1,4 +1,4 @@ -/* $OpenBSD: kern_tame.c,v 1.10 2015/07/20 18:58:53 jeremy Exp $ */ +/* $OpenBSD: kern_tame.c,v 1.11 2015/07/20 21:36:27 tedu Exp $ */ /* * Copyright (c) 2015 Nicholas Marriott <nicm@openbsd.org> @@ -46,6 +46,8 @@ #include <sys/syscallargs.h> #include <sys/systm.h> +int canonpath(const char *input, char *buf, size_t bufsize); + const u_int tame_syscalls[SYS_MAXSYSCALL] = { [SYS_exit] = 0xffffffff, @@ -273,8 +275,13 @@ tame_fail(struct proc *p, int error, int code) * without the right flags set */ int -tame_namei(struct proc *p, char *path) +tame_namei(struct proc *p, char *origpath) { + char path[PATH_MAX]; + + if (canonpath(origpath, path, sizeof(path)) != 0) + return (tame_fail(p, EPERM, TAME_RPATH)); + /* Detect what looks like a mkstemp(3) family operation */ if ((p->p_p->ps_tame & _TM_TMPPATH) && (p->p_tame_syscall == SYS_open) && @@ -830,3 +837,66 @@ tame_dns_check(struct proc *p, in_port_t port) return (0); /* Allow a DNS connect outbound */ return (EPERM); } + +int +canonpath(const char *input, char *buf, size_t bufsize) +{ + char *p, *q, *s, *end; + + /* can't canon relative paths, don't bother */ + if (input[0] != '/') { + if (strlcpy(buf, input, bufsize) >= bufsize) + return (EINVAL); + return (0); + } + + /* easiest to work with strings always ending in '/' */ + if (snprintf(buf, bufsize, "%s/", input) >= bufsize) + return (EINVAL); + + /* after this we will only be shortening the string. */ + p = buf; + q = p; + while (*p) { + if (p[0] == '/' && p[1] == '/') { + p += 1; + } else if (p[0] == '/' && p[1] == '.' && + p[2] == '/') { + p += 2; + } else { + *q++ = *p++; + } + } + *q = 0; + + end = buf + strlen(buf); + s = buf; + p = s; + while (1) { + /* find "/../" (where's strstr when you need it?) */ + while (p < end) { + if (p[0] == '/' && strncmp(p + 1, "../", 3) == 0) + break; + p++; + } + if (p == end) + break; + if (p == s) { + memmove(s, p + 3, end - p - 3 + 1); + end -= 3; + } else { + /* s starts with '/', so we know there's one + * somewhere before p. */ + q = p - 1; + while (*q != '/') + q--; + memmove(q, p + 3, end - p - 3 + 1); + end -= p + 3 - q; + p = q; + } + } + if (end > s + 1) + *(end - 1) = 0; /* remove trailing '/' */ + + return 0; +} |