summaryrefslogtreecommitdiff
path: root/util/nvmutil/lib/file.c
diff options
context:
space:
mode:
Diffstat (limited to 'util/nvmutil/lib/file.c')
-rw-r--r--util/nvmutil/lib/file.c486
1 files changed, 255 insertions, 231 deletions
diff --git a/util/nvmutil/lib/file.c b/util/nvmutil/lib/file.c
index e722cb6a..ec45ac3f 100644
--- a/util/nvmutil/lib/file.c
+++ b/util/nvmutil/lib/file.c
@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
+ *
+ * Pathless i/o
*/
#include <sys/types.h>
@@ -14,6 +16,7 @@
#include "../include/common.h"
+
/* check that a file changed
*/
@@ -78,10 +81,10 @@ err_same_file:
void
xopen(int *fd_ptr, const char *path, int flags, struct stat *st)
{
- if ((*fd_ptr = open(path, flags)) == -1)
+ if ((*fd_ptr = open(path, flags)) < 0)
err(errno, "%s", path);
- if (fstat(*fd_ptr, st) == -1)
+ if (fstat(*fd_ptr, st) < 0)
err(errno, "%s: stat", path);
if (!S_ISREG(st->st_mode))
@@ -156,7 +159,7 @@ fsync_dir(const char *path)
dirbuf[1] = '\0';
}
- dirfd = open(dirbuf, O_RDONLY | O_CLOEXEC | O_NOCTTY
+ dirfd = fs_resolve(dirbuf, O_RDONLY | O_CLOEXEC | O_NOCTTY
#ifdef O_DIRECTORY
| O_DIRECTORY
#endif
@@ -167,17 +170,6 @@ fsync_dir(const char *path)
if (dirfd < 0)
goto err_fsync_dir;
- /* symlink/directory replacement
- attack mitigation
- */
- if (check_dirfd(dirfd, dirbuf) < 0) {
-
- (void) close_on_eintr(dirfd);
-
- dirfd = -1;
- goto err_fsync_dir;
- }
-
if (fstat(dirfd, &st) < 0)
goto err_fsync_dir;
@@ -284,7 +276,8 @@ new_tmpfile(int *fd, int local,
struct stat st_dir_initial;
#endif
- struct path_split ps;
+ char *dir = NULL;
+ char *base = NULL;
if (fd == NULL) {
@@ -402,20 +395,22 @@ new_tmpfile(int *fd, int local,
if (local) {
- if (split_path(path, &ps) < 0)
+ if (fs_dirname_basename(path, &dir, &base, 0) < 0)
goto err_new_tmpfile;
- if (slen(ps.base, maxlen, &baselen) < 0)
+ if (slen(base, maxlen, &baselen) < 0)
goto err_new_tmpfile;
/* ALWAYS set this right after
* split path, to avoid leaking fd:
*/
- dirfd = ps.dirfd;
+ dirfd = fs_resolve(dir, O_RDONLY | O_DIRECTORY);
+ if (dirfd < 0)
+ goto err_new_tmpfile;
*(dest) = '.';
- memcpy(dest + 1, ps.base, baselen);
+ memcpy(dest + 1, base, baselen);
memcpy(dest + 1 + baselen,
suffix, sizeof(suffix) - 1);
@@ -424,14 +419,9 @@ new_tmpfile(int *fd, int local,
((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation
* in mkhtemp(
*/
- fname = dest + 1;
+ fname = base;
#endif
- if (ps.buf != NULL) {
- free(ps.buf);
- ps.buf = NULL;
- }
-
} else {
memcpy(dest, tmpdir, dirlen);
@@ -445,7 +435,8 @@ new_tmpfile(int *fd, int local,
((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation
in mkhtemp()
*/
- dirfd = open_verified_dir(tmpdir);
+ dirfd = fs_resolve(tmpdir,
+ O_RDONLY | O_DIRECTORY);
if (dirfd < 0)
goto err_new_tmpfile;
@@ -639,11 +630,11 @@ same_dir(const char *a, const char *b)
if (rval_scmp == 0)
goto success_same_dir;
- fd_a = open_verified_dir(a);
+ fd_a = fs_resolve(a, O_RDONLY | O_DIRECTORY);
if (fd_a < 0)
goto err_same_dir;
- fd_b = open_verified_dir(b);
+ fd_b = fs_resolve(b, O_RDONLY | O_DIRECTORY);
if (fd_b < 0)
goto err_same_dir;
@@ -719,7 +710,7 @@ world_writeable_and_sticky(
/* mitigate symlink attacks*
*/
- dirfd = open_verified_dir(s);
+ dirfd = fs_resolve(s, O_RDONLY | O_DIRECTORY);
if (dirfd < 0)
goto sticky_hell;
@@ -773,7 +764,7 @@ world_writeable_and_sticky(
if (allow_noworld_unsticky)
goto sticky_heaven; /* sticky! */
- goto sticky_hell; /* definitely not sticky */
+ goto sticky_hell; /* heaven visa denied */
sticky_heaven:
/* i like the one in hamburg better */
@@ -1212,207 +1203,6 @@ err_mkhtemp:
return -1;
}
-/* split up a given
- * path into directory
- * and file name. used
- * for e.g. openat
- */
-int
-split_path(const char *path,
- struct path_split *ps)
-{
- size_t maxlen;
- size_t len;
-
- char *slash;
-
- int saved_errno = errno;
-
- if (path == NULL || ps == NULL)
- goto err_split_path;
-
-#if defined(PATH_LEN) && \
-(PATH_LEN) >= 256
- maxlen = PATH_LEN;
-#else
- maxlen = 4096;
-#endif
-
- if (slen(path, maxlen, &len) < 0)
- goto err_split_path;
-
- if (len == 0 || len >= maxlen) {
-
- errno = ERANGE;
- goto err_split_path;
- }
-
- ps->buf = malloc(len + 1);
- if (ps->buf == NULL) {
-
- errno = ENOMEM;
- goto err_split_path;
- }
-
- memcpy(ps->buf, path, len + 1);
-
- for ( ; len > 1 &&
- ps->buf[len - 1] == '/'; len--)
- ps->buf[len - 1] = '\0';
-
- slash = strrchr(ps->buf, '/');
-
- if (slash) {
-
- *slash = '\0';
- ps->base = slash + 1;
-
- if (*ps->buf == '\0') {
-
- ps->buf[0] = '/';
- ps->buf[1] = '\0';
- }
- } else {
-
- ps->base = ps->buf;
-
- ps->buf[0] = '.';
- ps->buf[1] = '\0';
- }
-
- ps->dirfd = open_verified_dir(ps->buf);
- if (ps->dirfd < 0)
- goto err_split_path;
-
- errno = saved_errno;
-
- return 0;
-
-err_split_path:
-
- saved_errno = errno;
-
- if (ps->buf != NULL) {
- free(ps->buf);
- ps->buf = NULL;
- }
-
- if (ps->dirfd >= 0) {
- (void) close_on_eintr(ps->dirfd);
- ps->dirfd = -1;
- }
-
- errno = saved_errno;
-
- if (errno == saved_errno)
- errno = EIO; /* likely open/check_dirfd */
-
- return -1;
-}
-
-/* when we open a directory,
- * we need to secure it each
- * time against replacement
- * attacks (e.g. symlinks)
- */
-int
-open_verified_dir(const char *path)
-{
- int fd;
- int saved_errno = errno;
-
- fd = open(path, O_RDONLY | O_DIRECTORY |
- O_CLOEXEC | O_NOCTTY
-#ifdef O_NOFOLLOW
- | O_NOFOLLOW
-#endif
- );
-
- if (fd < 0)
- return -1;
-
- errno = saved_errno;
-
- if (check_dirfd(fd, path) < 0) {
-
- saved_errno = errno;
- (void) close_on_eintr(fd);
-
- errno = saved_errno;
- return -1;
- }
-
- errno = saved_errno;
- return fd;
-}
-
-/* useful for mitigating directory
- * replacement attacks; call this
- * before e.g. stat(), right after
- * calling open/openat(). compares
- * the inode/device of a given path
- * relative to the file descriptor,
- * which if changed would indicate
- * a possible attack / race condition
- */
-int
-check_dirfd(int dirfd, const char *path)
-{
- struct stat st_fd;
- struct stat st_path;
-
- int saved_errno = errno;
-
- if (dirfd < 0) {
- errno = EBADF;
- goto err_check_dirfd;
- }
-
- if (path == NULL) {
- errno = EFAULT;
- goto err_check_dirfd;
- }
-
- if (fstat(dirfd, &st_fd) < 0)
- goto err_check_dirfd;
-
-#if !(defined(DISABLE_OPENAT) && \
- ((DISABLE_OPENAT) > 0))
- /*
- * mitigate symlink / directory replacement
- * attacks (fstatat added to linux in 2006,
- * and the BSDs added it later on)
- *
- * on older/weird unix, you'll see stat(2),
- * and would therefore be vulnerable.
- */
- if (fstatat(AT_FDCWD, path, &st_path,
- AT_SYMLINK_NOFOLLOW) != 0)
- goto err_check_dirfd;
-#else
- if (stat(path, &st_path) != 0)
- goto err_check_dirfd;
-#endif
-
- if (st_fd.st_dev != st_path.st_dev ||
- st_fd.st_ino != st_path.st_ino) {
-
- errno = ENOENT;
- goto err_check_dirfd;
- }
-
- errno = saved_errno;
-
- return 0;
-
-err_check_dirfd:
-
- if (errno == saved_errno)
- errno = EPERM; /* context: symlink attack */
-
- return -1;
-}
-
/* why doesn't literally
every libc have this?
@@ -2121,3 +1911,237 @@ 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)
+{
+#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];
+
+ int saved_errno = errno;
+ int r;
+
+ int is_last;
+
+ if (path == NULL || *path != '/') {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (slen(path, maxlen, &len) < 0)
+ return -1;
+
+ dirfd = fs_root_fd();
+ if (dirfd < 0)
+ 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;
+
+ close(dirfd);
+ dirfd = nextfd;
+ nextfd = -1;
+ }
+
+ errno = saved_errno;
+ return dirfd;
+
+err:
+ saved_errno = errno;
+
+ if (dirfd >= 0)
+ close(dirfd);
+ if (nextfd >= 0)
+ close(nextfd);
+
+ errno = saved_errno;
+ return -1;
+}
+
+int
+fs_root_fd(void)
+{
+ /* TODO: consider more flags here (and/or make configurable */
+ return open("/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+}
+
+int
+fs_next_component(const char **p,
+ char *name, size_t namesz)
+{
+ const char *s = *p;
+ size_t len = 0;
+#if defined(PATH_LEN) && \
+(PATH_LEN) >= 256
+ size_t maxlen = PATH_LEN;
+#else
+ size_t maxlen = 4096;
+#endif
+
+ while (*s == '/')
+ s++;
+
+ if (*s == '\0') {
+ *p = s;
+ return 0;
+ }
+
+ while (s[len] != '/' && s[len] != '\0')
+ len++;
+
+ if (len == 0 || len >= namesz ||
+ len >= maxlen) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ memcpy(name, s, len);
+ name[len] = '\0';
+
+ /* reject . and .. */
+ if ((name[0] == '.' && name[1] == '\0') ||
+ (name[0] == '.' && name[1] == '.' && name[2] == '\0')) {
+ errno = EPERM;
+ return -1;
+ }
+
+ *p = s + len;
+ return 1;
+}
+
+int
+fs_open_component(int dirfd, const char *name,
+ int flags, int is_last)
+{
+ int fd;
+
+/* TODO:
+ open() fallback if DISABLE_OPENAT > 0
+ change function signature
+ and ditto any callers using
+ the same ifdefs. this would
+ allow us to implement somewhat
+ openat-like functionality with
+ openat2-like features, even on
+ systems that lack openat(2),
+ let alone openat2
+ */
+
+ fd = openat(dirfd, name,
+ (is_last ? flags : (O_RDONLY | O_DIRECTORY)) |
+ O_NOFOLLOW | O_CLOEXEC);
+
+ /* on some systems, O_DIRECTORY is
+ * ignored or unreliable. We must
+ * assume that your operating system
+ * is lying. the OS always lies.
+ */
+ if (!is_last) {
+
+ struct stat st;
+
+ if (fstat(fd, &st) < 0)
+ return -1;
+
+ if (!S_ISDIR(st.st_mode)) {
+
+ close(fd);
+ errno = ENOTDIR;
+ return -1;
+ }
+ }
+
+ return fd;
+}
+
+int
+fs_dirname_basename(const char *path,
+ char **dir, char **base,
+ int allow_relative)
+{
+/* TODO: audit maxlen use vs PATH_LEN
+ -- should implement length checks
+ */
+ char *buf;
+ char *slash;
+ size_t len;
+ int rval;
+#if defined(PATH_LEN) && \
+(PATH_LEN) >= 256
+ size_t maxlen = PATH_LEN;
+#else
+ size_t maxlen = 4096;
+#endif
+
+ if (path == NULL || dir == NULL || base == NULL)
+ return -1;
+
+ if (slen(path, maxlen, &len) < 0)
+ return -1;
+
+ buf = malloc(len + 1);
+ if (buf == NULL)
+ return -1;
+
+ memcpy(buf, path, len + 1);
+
+ /* strip trailing slashes */
+ while (len > 1 && buf[len - 1] == '/')
+ buf[--len] = '\0';
+
+ slash = strrchr(buf, '/');
+
+ if (slash) {
+
+ *slash = '\0';
+ *dir = buf;
+ *base = slash + 1;
+
+ if (**dir == '\0') {
+ (*dir)[0] = '/';
+ (*dir)[1] = '\0';
+ }
+ } else if (allow_relative) {
+
+ *dir = strdup(".");
+ *base = buf;
+ } else {
+ errno = EINVAL;
+ free(buf);
+ return -1;
+ }
+
+ return 0;
+}