From c473c10a447a285f8c7b762f34c0650f587e1ff4 Mon Sep 17 00:00:00 2001 From: Mike Frysinger Date: Mon, 15 Nov 2010 05:42:11 -0500 Subject: libsandbox: handle dirfd in mkdir/open/unlink *at prechecks Ignoring the dirfd hasn't been a problem in the past as people weren't really using it, but now that core packages are (like tar), we need to handle things properly. URL: http://bugs.gentoo.org/342983 Reported-by: Xake Signed-off-by: Mike Frysinger --- libsandbox/libsandbox.c | 86 +++++++++++++++++---------- libsandbox/libsandbox.h | 1 + libsandbox/wrapper-funcs/mkdirat_pre_check.c | 15 ++++- libsandbox/wrapper-funcs/openat_pre_check.c | 45 +++++++++----- libsandbox/wrapper-funcs/unlinkat_pre_check.c | 34 +++++++---- 5 files changed, 123 insertions(+), 58 deletions(-) diff --git a/libsandbox/libsandbox.c b/libsandbox/libsandbox.c index e5619dc..2fe9ae9 100644 --- a/libsandbox/libsandbox.c +++ b/libsandbox/libsandbox.c @@ -136,6 +136,54 @@ static const char *sb_get_cmdline(pid_t pid) return path; } +/* resolve_dirfd_path - get the path relative to a dirfd + * + * return value: + * -1 - error! + * 0 - path is in @resolved_path + * 1 - path is in @path (no resolution necessary) + * 2 - errno issues -- ignore this path + */ +int resolve_dirfd_path(int dirfd, const char *path, char *resolved_path) +{ + /* The *at style functions have the following semantics: + * - dirfd = AT_FDCWD: same as non-at func: file is based on CWD + * - file is absolute: dirfd is ignored + * - otherwise: file is relative to dirfd + * Since maintaining fd state based on open's is real messy, we'll + * just rely on the kernel doing it for us with /proc//fd/ ... + */ + if (dirfd == AT_FDCWD || !path || path[0] == '/') + return 1; + + save_errno(); + + size_t at_len = sizeof(resolved_path) - 1 - 1 - (path ? strlen(path) : 0); + sprintf(resolved_path, "/proc/%i/fd/%i", trace_pid ? : getpid(), dirfd); + ssize_t ret = readlink(resolved_path, resolved_path, at_len); + if (ret == -1) { + /* see comments at end of check_syscall() */ + if (errno_is_too_long()) { + restore_errno(); + return 2; + } + if (is_env_on(ENV_SANDBOX_DEBUG)) + SB_EINFO("AT_FD LOOKUP", " fail: %s: %s\n", + resolved_path, strerror(errno)); + /* If the fd isn't found, some guys (glibc) expect errno */ + if (errno == ENOENT) + errno = EBADF; + return -1; + } + resolved_path[ret] = '/'; + resolved_path[ret + 1] = '\0'; + if (path) + strcat(resolved_path, path); + + restore_errno(); + return 0; +} + int canonicalize(const char *path, char *resolved_path) { int old_errno = errno; @@ -1011,40 +1059,14 @@ bool before_syscall(int dirfd, int sb_nr, const char *func, const char *file, in } } - save_errno(); - - /* The *at style functions have the following semantics: - * - dirfd = AT_FDCWD: same as non-at func: file is based on CWD - * - file is absolute: dirfd is ignored - * - otherwise: file is relative to dirfd - * Since maintaining fd state based on open's is real messy, we'll - * just rely on the kernel doing it for us with /proc//fd/ ... - */ - if (dirfd != AT_FDCWD && (!file || file[0] != '/')) { - size_t at_len = sizeof(at_file_buf) - 1 - 1 - (file ? strlen(file) : 0); - sprintf(at_file_buf, "/proc/%i/fd/%i", trace_pid ? : getpid(), dirfd); - ssize_t ret = readlink(at_file_buf, at_file_buf, at_len); - if (ret == -1) { - /* see comments at end of check_syscall() */ - if (errno_is_too_long()) { - restore_errno(); - return true; - } - if (is_env_on(ENV_SANDBOX_DEBUG)) - SB_EINFO("AT_FD LOOKUP", " fail: %s: %s\n", - at_file_buf, strerror(errno)); - /* If the fd isn't found, some guys (glibc) expect errno */ - if (errno == ENOENT) - errno = EBADF; - return false; - } - at_file_buf[ret] = '/'; - at_file_buf[ret + 1] = '\0'; - if (file) - strcat(at_file_buf, file); - file = at_file_buf; + switch (resolve_dirfd_path(dirfd, file, at_file_buf)) { + case -1: return false; + case 0: file = at_file_buf; break; + case 2: return true; } + save_errno(); + /* Need to protect the global sbcontext structure */ sb_lock(); diff --git a/libsandbox/libsandbox.h b/libsandbox/libsandbox.h index dc847cb..3ef7c71 100644 --- a/libsandbox/libsandbox.h +++ b/libsandbox/libsandbox.h @@ -71,6 +71,7 @@ __attribute__((noreturn)) void sb_abort(void); char *erealpath(const char *, char *); char *egetcwd(char *, size_t); int canonicalize(const char *, char *); +int resolve_dirfd_path(int, const char *, char *); /* most linux systems use ENAMETOOLONG, but some (ia64) use ERANGE, as do some BSDs */ #define errno_is_too_long() (errno == ENAMETOOLONG || errno == ERANGE) diff --git a/libsandbox/wrapper-funcs/mkdirat_pre_check.c b/libsandbox/wrapper-funcs/mkdirat_pre_check.c index c999e46..d037546 100644 --- a/libsandbox/wrapper-funcs/mkdirat_pre_check.c +++ b/libsandbox/wrapper-funcs/mkdirat_pre_check.c @@ -8,10 +8,23 @@ bool sb_mkdirat_pre_check(const char *func, const char *pathname, int dirfd) { char canonic[SB_PATH_MAX]; + char dirfd_path[SB_PATH_MAX]; save_errno(); - /* XXX: need to check pathname with dirfd */ + /* Expand the dirfd path first */ + switch (resolve_dirfd_path(dirfd, pathname, dirfd_path)) { + case -1: + if (is_env_on(ENV_SANDBOX_DEBUG)) + SB_EINFO("EARLY FAIL", " %s(%s) @ resolve_dirfd_path: %s\n", + func, pathname, strerror(errno)); + return false; + case 0: + pathname = dirfd_path; + break; + } + + /* Then break down any relative/symlink paths */ if (-1 == canonicalize(pathname, canonic)) /* see comments in check_syscall() */ if (ENAMETOOLONG != errno) { diff --git a/libsandbox/wrapper-funcs/openat_pre_check.c b/libsandbox/wrapper-funcs/openat_pre_check.c index 7f5e823..4a63413 100644 --- a/libsandbox/wrapper-funcs/openat_pre_check.c +++ b/libsandbox/wrapper-funcs/openat_pre_check.c @@ -7,22 +7,37 @@ bool sb_openat_pre_check(const char *func, const char *pathname, int dirfd, int flags) { - if (!(flags & O_CREAT)) { - /* If we're not trying to create, fail normally if - * file does not stat - */ - if (dirfd == AT_FDCWD || pathname[0] == '/') { - struct stat st; - save_errno(); - if (-1 == stat(pathname, &st)) { - if (is_env_on(ENV_SANDBOX_DEBUG)) - SB_EINFO("EARLY FAIL", " %s(%s): %s\n", - func, pathname, strerror(errno)); - return false; - } - restore_errno(); - } + /* If we're not trying to create, fail normally if + * file does not stat + */ + if (flags & O_CREAT) + return true; + + save_errno(); + + /* Expand the dirfd path first */ + char dirfd_path[SB_PATH_MAX]; + switch (resolve_dirfd_path(dirfd, pathname, dirfd_path)) { + case -1: + if (is_env_on(ENV_SANDBOX_DEBUG)) + SB_EINFO("EARLY FAIL", " %s(%s) @ resolve_dirfd_path: %s\n", + func, pathname, strerror(errno)); + return false; + case 0: + pathname = dirfd_path; + break; } + /* Doesn't exist -> skip permission checks */ + struct stat st; + if (-1 == stat(pathname, &st)) { + if (is_env_on(ENV_SANDBOX_DEBUG)) + SB_EINFO("EARLY FAIL", " %s(%s): %s\n", + func, pathname, strerror(errno)); + return false; + } + + restore_errno(); + return true; } diff --git a/libsandbox/wrapper-funcs/unlinkat_pre_check.c b/libsandbox/wrapper-funcs/unlinkat_pre_check.c index 961c31f..4e4a38d 100644 --- a/libsandbox/wrapper-funcs/unlinkat_pre_check.c +++ b/libsandbox/wrapper-funcs/unlinkat_pre_check.c @@ -8,14 +8,31 @@ bool sb_unlinkat_pre_check(const char *func, const char *pathname, int dirfd) { char canonic[SB_PATH_MAX]; + char dirfd_path[SB_PATH_MAX]; save_errno(); - /* XXX: need to check pathname with dirfd */ + /* Expand the dirfd path first */ + switch (resolve_dirfd_path(dirfd, pathname, dirfd_path)) { + case -1: + if (is_env_on(ENV_SANDBOX_DEBUG)) + SB_EINFO("EARLY FAIL", " %s(%s) @ resolve_dirfd_path: %s\n", + func, pathname, strerror(errno)); + return false; + case 0: + pathname = dirfd_path; + break; + } + + /* Then break down any relative/symlink paths */ if (-1 == canonicalize(pathname, canonic)) /* see comments in check_syscall() */ - if (ENAMETOOLONG != errno) - goto error; + if (ENAMETOOLONG != errno) { + if (is_env_on(ENV_SANDBOX_DEBUG)) + SB_EINFO("EARLY FAIL", " %s(%s) @ canonicalize: %s\n", + func, pathname, strerror(errno)); + return false; + } /* XXX: Hack to make sure sandboxed process cannot remove * a device node, bug #79836. */ @@ -23,16 +40,13 @@ bool sb_unlinkat_pre_check(const char *func, const char *pathname, int dirfd) 0 == strcmp(canonic, "/dev/zero")) { errno = EACCES; - goto error; + if (is_env_on(ENV_SANDBOX_DEBUG)) + SB_EINFO("EARLY FAIL", " %s(%s): %s\n", + func, pathname, strerror(errno)); + return false; } restore_errno(); return true; - - error: - if (is_env_on(ENV_SANDBOX_DEBUG)) - SB_EINFO("EARLY FAIL", " %s(%s): %s\n", - func, pathname, strerror(errno)); - return false; } -- cgit v1.2.3