diff options
Diffstat (limited to 'util/nvmutil/lib')
| -rw-r--r-- | util/nvmutil/lib/file.c | 290 |
1 files changed, 143 insertions, 147 deletions
diff --git a/util/nvmutil/lib/file.c b/util/nvmutil/lib/file.c index 3fc1416c..20c49164 100644 --- a/util/nvmutil/lib/file.c +++ b/util/nvmutil/lib/file.c @@ -164,14 +164,15 @@ fsync_dir(const char *path) dirbuf[1] = '\0'; } - dirfd = fs_resolve(dirbuf, O_RDONLY | O_CLOEXEC | O_NOCTTY + dirfd = fs_open(dirbuf, + O_RDONLY | O_CLOEXEC | O_NOCTTY #ifdef O_DIRECTORY | O_DIRECTORY #endif #ifdef O_NOFOLLOW | O_NOFOLLOW #endif - ); +); if (dirfd < 0) goto err_fsync_dir; @@ -404,7 +405,8 @@ new_tmpfile(int *fd, int local, /* ALWAYS set this right after * split path, to avoid leaking fd: */ - dirfd = fs_resolve(dir, O_RDONLY | O_DIRECTORY); + + dirfd = fs_open(dir, O_RDONLY | O_DIRECTORY); if (dirfd < 0) goto err_new_tmpfile; @@ -426,7 +428,7 @@ new_tmpfile(int *fd, int local, memcpy(dest + dirlen + 1, suffix, sizeof(suffix) - 1); - dirfd = fs_resolve(tmpdir, + dirfd = fs_open(tmpdir, O_RDONLY | O_DIRECTORY); if (dirfd < 0) goto err_new_tmpfile; @@ -614,11 +616,11 @@ same_dir(const char *a, const char *b) if (rval_scmp == 0) goto success_same_dir; - fd_a = fs_resolve(a, O_RDONLY | O_DIRECTORY); + fd_a = fs_open(a, O_RDONLY | O_DIRECTORY); if (fd_a < 0) goto err_same_dir; - fd_b = fs_resolve(b, O_RDONLY | O_DIRECTORY); + fd_b = fs_open(b, O_RDONLY | O_DIRECTORY); if (fd_b < 0) goto err_same_dir; @@ -694,7 +696,7 @@ world_writeable_and_sticky( /* mitigate symlink attacks* */ - dirfd = fs_resolve(s, O_RDONLY | O_DIRECTORY); + dirfd = fs_open(s, O_RDONLY | O_DIRECTORY); if (dirfd < 0) goto sticky_hell; @@ -1854,128 +1856,98 @@ fsync_on_eintr(int fd) return r; } -/* pathless resolution. we - * walk directories ourselves; - * will also be used for a portable - * fallback against openat2 if unavailable - * - * only accepts global paths, from / because - * we don't want relative tmpdirs! - */ int -fs_resolve(const char *path, int flags) +fs_rename_at(int olddirfd, const char *old, + int newdirfd, const char *new) { -#if defined(PATH_LEN) && \ -(PATH_LEN) >= 256 - size_t maxlen = PATH_LEN; -#else - size_t maxlen = 4096; -#endif - size_t len; - int dirfd = -1; - int nextfd = -1; - - const char *p; - char name[256]; + if (new == NULL || old == NULL) { - int saved_errno = errno; - int close_errno; - int r; - - int is_last; - - if (path == NULL || *path != '/') { - errno = EINVAL; + errno = EFAULT; return -1; } - /* block "/" if flags pertain to creation/write. - don't mess with the user's root! - */ - if (path[0] == '/' && path[1] == '\0') { - if (flags & (O_CREAT | O_TRUNC | O_RDWR | O_WRONLY)) { - errno = EISDIR; - return -1; - } - } - - if (slen(path, maxlen, &len) < 0) - return -1; + if (olddirfd < 0 || newdirfd < 0) { - dirfd = fs_root_fd(); - if (dirfd < 0) + errno = EBADF; return -1; + } - p = path; - - for (;;) { - - r = fs_next_component(&p, name, sizeof(name)); - if (r < 0) - goto err; - if (r == 0) - break; - - is_last = (*p == '\0'); - - nextfd = fs_open_component(dirfd, - name, flags, is_last); - if (nextfd < 0) - goto err; + return renameat(olddirfd, old, newdirfd, new); +} - close_errno = errno; - (void) close_on_eintr(dirfd); - errno = close_errno; +int +fs_rm_rf_at(int dirfd, const char *path) +{ + int fd = -1; + struct stat st; - dirfd = nextfd; - nextfd = -1; + if (path == NULL) { + errno = EFAULT; + return -1; } - errno = saved_errno; - return dirfd; + fd = fs_resolve_at(dirfd, path, O_RDONLY | O_DIRECTORY); + if (fd >= 0) { + /* directory */ + /* iterate entries */ + /* recurse */ + /* unlinkat(dirfd, path, AT_REMOVEDIR) */ -err: - saved_errno = errno; - - if (dirfd >= 0) - (void) close_on_eintr(dirfd); - if (nextfd >= 0) - (void) close_on_eintr(nextfd); + return 0; + } - errno = saved_errno; - return -1; + /* fallback: file */ + return unlinkat(dirfd, path, 0); } int -fs_root_fd(void) +fs_mkdir_p(const char *path, mode_t mode) { - /* TODO: consider more flags here (and/or make configurable */ - return open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + struct filesystem *fs; + + if (path == NULL) { + errno = EFAULT; + return -1; + } + if (path[0] != '/') { + errno = EINVAL; + return -1; + } + + fs = rootfs(); + if (fs == NULL) + return -1; + + return fs_mkdir_p_at(fs->rootfd, path + 1, mode); } /* implementation of: mkdir -p */ int -fs_mkdir_p_at(int dirfd, const char *path, mode_t mode, - struct stat *st_dir_initial) +fs_mkdir_p_at(int dirfd, const char *path, mode_t mode) { - const char *p; + const char *p = path; char name[256]; int nextfd = -1; + struct stat st_parent_initial; + struct stat st_parent_now; int saved_errno = errno; - int close_errno; int r; - int is_last; - struct stat st_dir_now; - if (dirfd < 0 || path == NULL || - st_dir_initial == NULL || *path == '\0') { + if (path == NULL) { + + errno = EFAULT; + return -1; + } + + if (dirfd < 0 || *path == '\0') { errno = EINVAL; return -1; } - p = path; + if (fstat(dirfd, &st_parent_initial) < 0) + return -1; for (;;) { @@ -1985,88 +1957,112 @@ fs_mkdir_p_at(int dirfd, const char *path, mode_t mode, if (r == 0) break; - is_last = (*p == '\0'); + /* check parent integrity */ + if (fstat(dirfd, &st_parent_now) < 0) + goto err; + + if (st_parent_now.st_dev != st_parent_initial.st_dev || + st_parent_now.st_ino != st_parent_initial.st_ino) { + errno = ESTALE; + goto err; + } - /* TODO: consider more flags - * or make configurable - */ nextfd = openat2p(dirfd, name, - O_RDONLY | O_DIRECTORY | O_CLOEXEC, mode); + O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0); if (nextfd < 0) { - if (errno != ENOENT) goto err; if (mkdirat(dirfd, name, mode) < 0) goto err; - /* - * use the file descriptor instead. - * (danach prüfen wir die Sicherheit des Verzeichnisses) - */ - if (fstat(dirfd, &st_dir_now) < 0) - goto err; - /* - * mitigate symlink attacks before open - */ - if (st_dir_now.st_dev != st_dir_initial->st_dev || - st_dir_now.st_ino != st_dir_initial->st_ino) { - errno = ESTALE; - goto err; - } - - - /* TODO: consider more flags - * or make configurable? - */ nextfd = openat2p(dirfd, name, - O_RDONLY | O_DIRECTORY | O_CLOEXEC, - mode); + O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0); if (nextfd < 0) goto err; } - close_errno = errno; - (void) close_on_eintr(dirfd); - errno = close_errno; - + (void)close_on_eintr(dirfd); dirfd = nextfd; nextfd = -1; + + st_parent_initial = st_parent_now; } errno = saved_errno; - return 0; + return (0); err: - saved_errno = errno; - if (dirfd >= 0) - (void) close_on_eintr(dirfd); + (void)close_on_eintr(dirfd); if (nextfd >= 0) - (void) close_on_eintr(nextfd); + (void)close_on_eintr(nextfd); errno = saved_errno; - return -1; + return (-1); } -/* use in conjunction with fs_resolve. example: - * int rootfd = fs_resolve("/safe/root", O_RDONLY | O_DIRECTORY); - * now, you can resolve everything relatively, for instance: - * fd = fs_resolve_at(rootfd, "file.txt", O_RDONLY); - * if a user then tries e.g. ../etc/password ?????? - * BLOCKED - * basically userspace sandboxing, similar to e.g. - * openbsd unveil - * freebsd capsicum - * openat2 RESOLVE_BENEATH - * ...but in ****userspace**** - * no need for chroot (you should still use one in critical code) - * cannot escape the relative root that you set. - * no dependence on CWD - * probably safe in multi-threaded code (with some care) +/* secure open, based on + * relative path to root * - * ps: you should still use unveil. unveil is awesome. + * always a fixed fd for / + * see: rootfs() + */ +int +fs_open(const char *path, int flags) +{ + struct filesystem *fs; + const char *rel; + + if (path == NULL) { + errno = EFAULT; + return -1; + } + + if (path[0] != '/') { + errno = EINVAL; + return -1; + } + + fs = rootfs(); + if (!fs) + return -1; + + rel = path + 1; + + return fs_resolve_at(fs->rootfd, rel, flags); +} + +/* singleton function + * that returns a fixed + * descriptor of / + * + * used throughout, for + * repeated integrity checks + */ +struct filesystem * +rootfs(void) +{ + static struct filesystem global_fs; + static int fs_initialised = 0; + + if (!fs_initialised) { + + global_fs.rootfd = + open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC); + + if (global_fs.rootfd < 0) + return NULL; + + fs_initialised = 1; + } + + return &global_fs; +} + +/* filesystem sandboxing. + * (in userspace) */ int fs_resolve_at(int dirfd, const char *path, int flags) |
