diff options
| author | Leah Rowe <leah@libreboot.org> | 2026-09-07 08:12:24 +0100 |
|---|---|---|
| committer | Leah Rowe <leah@libreboot.org> | 2026-09-07 08:12:24 +0100 |
| commit | 123639e3db0757fb3370516f7eaf50cbfae4d026 (patch) | |
| tree | 2fefd5b712f2935949bd447038cfb867f49f6342 /util/libreboot-utils/lib/file.c | |
| parent | 01cb422c9771c7daa00c6f832c3294d59802a635 (diff) | |
delete util/libreboot-utils (unused code)
this was an intense audit of nvmutil that somehow evolved
into writing a new, hardened implementation of mktemp.
it all works, a few memory bugs to solve on bsd, but i don't
see the point in keeping it. mktemp is fine, and nvmutil
already works.
lbutils implemented atomic writes and integrity checking,
in a manner completely overengineered for what it was
actually doing (modifying a few bytes in 8KB GbE files)
just delete it. i'll bring it back if i ever finish the
code. i don't want to leave dead/unfinished code in the tree.
Signed-off-by: Leah Rowe <leah@libreboot.org>
Diffstat (limited to 'util/libreboot-utils/lib/file.c')
| -rw-r--r-- | util/libreboot-utils/lib/file.c | 817 |
1 files changed, 0 insertions, 817 deletions
diff --git a/util/libreboot-utils/lib/file.c b/util/libreboot-utils/lib/file.c deleted file mode 100644 index 0385ebbb..00000000 --- a/util/libreboot-utils/lib/file.c +++ /dev/null @@ -1,817 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> - * - * Pathless i/o, and some stuff you - * probably never saw in userspace. - * - * Be nice to the demon. - */ - -/* -TODO: putting it here just so it's somewhere: -PATH_MAX is not reliable as a limit for paths, -because the real length depends on mount point, -and specific file systems. -more correct usage example: -long max = pathconf("/", _PC_PATH_MAX); - */ - -/* for openat2: */ -#ifdef __linux__ -#if !defined(USE_OPENAT) || \ - ((USE_OPENAT) < 1) /* if 1: use openat, not openat2 */ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE 1 -#endif -#include <linux/openat2.h> -#include <sys/syscall.h> -#endif -#endif - -#include <sys/types.h> -#include <sys/stat.h> - -#include <errno.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "../include/common.h" - -/* check that a file changed - */ - -int -same_file(int fd, struct stat *st_old, - int check_size) -{ - struct stat st; - int saved_errno = errno; - int rval = 0; - errno = 0; - - if (if_err(st_old == NULL, EFAULT) || - if_err(fd < 0, EBADF) || - (rval = fstat(fd, &st)) < 0 || - (rval = fd_verify_regular(fd, st_old, &st)) < 0 || - if_err(check_size && st.st_size != st_old->st_size, ESTALE)) - return with_fallback_errno(ESTALE); - - reset_caller_errno(rval); - return 0; -} - -int -fsync_dir(const char *path) -{ - int saved_errno = errno; - size_t pathlen = 0; - char *dirbuf = NULL; - int dirfd = -1; - char *slash = NULL; - struct stat st = {0}; - int rval = 0; - errno = 0; - - if (if_err(slen(path, PATH_MAX, &pathlen) == 0, EINVAL)) - goto err_fsync_dir; - - memcpy(smalloc(&dirbuf, pathlen + 1), - path, pathlen + 1); - slash = strrchr(dirbuf, '/'); - - if (slash != NULL) { - *slash = '\0'; - if (*dirbuf == '\0') { - dirbuf[0] = '/'; - dirbuf[1] = '\0'; - } - } else { - dirbuf[0] = '.'; - dirbuf[1] = '\0'; - } - - dirfd = fs_open(dirbuf, - O_RDONLY | O_CLOEXEC | O_NOCTTY -#ifdef O_DIRECTORY - | O_DIRECTORY -#endif -#ifdef O_NOFOLLOW - | O_NOFOLLOW -#endif -); - - if (if_err_sys(dirfd < 0) || - if_err_sys((rval = fstat(dirfd, &st)) < 0) || - if_err(!S_ISDIR(st.st_mode), ENOTDIR) - || - if_err_sys((rval = fsync_on_eintr(dirfd)) == -1)) - goto err_fsync_dir; - - xclose(&dirfd); - free_and_set_null(&dirbuf); - - reset_caller_errno(rval); - return 0; - -err_fsync_dir: - free_and_set_null(&dirbuf); - xclose(&dirfd); - - return with_fallback_errno(EIO); -} - -/* rw_exact() - Read perfectly or die - * - * Read/write, and absolutely insist on an - * absolute read; e.g. if 100 bytes are - * requested, this MUST return 100. - * - * This function will never return zero. - * It will only return below (error), - * or above (success). On error, -1 is - * returned and errno is set accordingly. - * - * Zero-byte returns are not allowed. - * It will re-spin a finite number of - * times upon zero-return, to recover, - * otherwise it will return an error. - */ - -ssize_t -rw_exact(int fd, unsigned char *mem, size_t nrw, - off_t off, int rw_type) -{ - int saved_errno = errno; - ssize_t rval = 0; - ssize_t rc = 0; - size_t nrw_cur; - off_t off_cur; - void *mem_cur; - errno = 0; - - if (io_args(fd, mem, nrw, off, rw_type) == -1) - goto err_rw_exact; - - while (1) { - - /* Prevent theoretical overflow */ - if (if_err(rval >= 0 && (size_t)rval > (nrw - (size_t)rc), - EOVERFLOW)) - goto err_rw_exact; - - rc += rval; - if ((size_t)rc >= nrw) - break; - - mem_cur = (void *)(mem + (size_t)rc); - nrw_cur = (size_t)(nrw - (size_t)rc); - - if (if_err(off < 0, EOVERFLOW)) - goto err_rw_exact; - - off_cur = off + (off_t)rc; - - if ((rval = rw(fd, mem_cur, nrw_cur, off_cur, rw_type)) <= 0) - goto err_rw_exact; - } - - if (if_err((size_t)rc != nrw, EIO) || - (rval = rw_over_nrw(rc, nrw)) < 0) - goto err_rw_exact; - - reset_caller_errno(rval); - return rval; - -err_rw_exact: - return with_fallback_errno(EIO); -} - -/** - * rw() - read-write but with more - * safety checks than barebones libc - * - * A fallback is provided for regular read/write. - * rw_type can be IO_READ (read), IO_WRITE (write), - * IO_PREAD (pread) or IO_PWRITE - * - * WARNING: this function allows zero-byte returns. - * this is intentional, to mimic libc behaviour. - * use rw_exact if you need to avoid this. - * (ditto partial writes/reads) - * - */ -ssize_t -rw(int fd, void *mem, size_t nrw, - off_t off, int rw_type) -{ - ssize_t rval = 0; - ssize_t r = -1; - int saved_errno = errno; - errno = 0; - - if (io_args(fd, mem, nrw, off, rw_type) == -1 || - if_err(mem == NULL, EFAULT) || - if_err(fd < 0, EBADF) || - if_err(off < 0, EFAULT) || - if_err(nrw == 0, EINVAL)) - return with_fallback_errno(EIO); - - do { - switch (rw_type) { - case IO_READ: - r = read(fd, mem, nrw); - break; - case IO_WRITE: - r = write(fd, mem, nrw); - break; - case IO_PREAD: - r = pread(fd, mem, nrw, off); - break; - case IO_PWRITE: - r = pwrite(fd, mem, nrw, off); - break; - default: - errno = EINVAL; - break; - } - - } while (rw_retry(saved_errno, r)); - - if ((rval = rw_over_nrw(r, nrw)) < 0) - return with_fallback_errno(EIO); - - reset_caller_errno(rval); - return rval; -} - -int -io_args(int fd, void *mem, size_t nrw, - off_t off, int rw_type) -{ - int saved_errno = errno; - errno = 0; - - if (if_err(mem == NULL, EFAULT) || - if_err(fd < 0, EBADF) || - if_err(off < 0, ERANGE) || - if_err(!nrw, EPERM) || /* TODO: toggle zero-byte check */ - if_err(nrw > (size_t)SSIZE_MAX, ERANGE) || - if_err(((size_t)off + nrw) < (size_t)off, ERANGE) || - if_err(rw_type > IO_PWRITE, EINVAL)) - goto err_io_args; - - reset_caller_errno(0); - return 0; - -err_io_args: - return with_fallback_errno(EINVAL); -} - -int -check_file(int fd, struct stat *st) -{ - int saved_errno = errno; - int rval = 0; - errno = 0; - - if (if_err(fd < 0, EBADF) || - if_err(st == NULL, EFAULT) || - ((rval = fstat(fd, st)) == -1) || - if_err(!S_ISREG(st->st_mode), EBADF)) - goto err_is_file; - - reset_caller_errno(rval); - return 0; - -err_is_file: - return with_fallback_errno(EINVAL); -} - -/* POSIX can say whatever it wants. - * specification != implementation - */ -ssize_t -rw_over_nrw(ssize_t r, size_t nrw) -{ - if (if_err(!nrw, EIO) || - (r == -1) || - if_err((size_t)r > SSIZE_MAX, ERANGE) || - if_err((size_t)r > nrw, ERANGE)) - return with_fallback_errno(EIO); - - return r; -} - -/* two functions that reduce sloccount by - * two hundred lines */ -int -if_err(int condition, int errval) -{ - if (!condition) - return 0; - if (errval) - errno = errval; - return 1; -} -int -if_err_sys(int condition) -{ - if (!condition) - return 0; - return 1; -} - -int -fs_rename_at(int olddirfd, const char *old, - int newdirfd, const char *new) -{ - if (if_err(new == NULL || old == NULL, EFAULT) || - if_err(olddirfd < 0 || newdirfd < 0, EBADF)) - return -1; - - return renameat(olddirfd, old, newdirfd, new); -} - -/* secure open, based on relative path to root - * - * always a fixed fd for / see: rootfs() - * and fs_resolve_at() - */ -int -fs_open(const char *path, int flags) -{ - struct filesystem *fs; - - if (if_err(path == NULL, EFAULT) || - if_err(path[0] != '/', EINVAL) || - if_err_sys((fs = rootfs()) == NULL)) - return -1; - - return fs_resolve_at(fs->rootfd, path + 1, 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 = -1; - - open_file_on_eintr("/", &global_fs.rootfd, - O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0400, NULL); - - if (global_fs.rootfd < 0) - return NULL; - - fs_initialised = 1; - } - - return &global_fs; -} - -/* filesystem sandboxing in userspace - * TODO: - missing length bound check. - potential CPU DoS on very long paths, spammed repeatedly. - perhaps cap at MAX_PATH? - */ -int -fs_resolve_at(int dirfd, const char *path, int flags) -{ - int nextfd = -1; - int curfd; - const char *p; - char name[PATH_MAX]; - int saved_errno = errno; - int r; - int is_last; - errno = 0; - - if (dirfd < 0 || path == NULL || *path == '\0') { - errno = EINVAL; - return -1; - } - - p = path; - curfd = dirfd; /* start here */ - - 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(curfd, name, flags, is_last); - if (nextfd < 0) - goto err; - - /* close previous fd if not the original input */ - if (curfd != dirfd) - xclose(&curfd); - - curfd = nextfd; - nextfd = -1; - } - - reset_caller_errno(0); - return curfd; - -err: - saved_errno = errno; - - if (nextfd >= 0) - xclose(&nextfd); - - /* close curfd only if it's not the original */ - if (curfd != dirfd && curfd >= 0) - xclose(&curfd); - - errno = saved_errno; - return with_fallback_errno(EIO); -} - -/* NOTE: - rejects . and .. but not empty strings - after normalisation. edge case: - ////// - - normalised implicitly, but might be good - to add a defensive check regardless. code - probably not exploitable in current state. - */ -int -fs_next_component(const char **p, - char *name, size_t namesz) -{ - const char *s = *p; - size_t len = 0; - - while (*s == '/') - s++; - - if (*s == '\0') { - *p = s; - return 0; - } - - while (s[len] != '/' && s[len] != '\0') - len++; - - if (len == 0 || len >= namesz || - len >= PATH_MAX) { - errno = ENAMETOOLONG; - return -1; - } - - memcpy(name, s, len); - name[len] = '\0'; - - /* reject . and .. */ - if (if_err((name[0] == '.' && name[1] == '\0') || - (name[0] == '.' && name[1] == '.' && name[2] == '\0'), EPERM)) - goto err; - - *p = s + len; - return 1; -err: - return with_fallback_errno(EPERM); -} - -int -fs_open_component(int dirfd, const char *name, - int flags, int is_last) -{ - int saved_errno = errno; - int fd; - struct stat st; - errno = 0; - - fd = openat_on_eintr(dirfd, name, - (is_last ? flags : (O_RDONLY | O_DIRECTORY)) | - O_NOFOLLOW | O_CLOEXEC, (flags & O_CREAT) ? 0600 : 0); - - if (!is_last && - (if_err(fd < 0, EBADF) || - if_err_sys(fstat(fd, &st) < 0) || - if_err(!S_ISDIR(st.st_mode), ENOTDIR))) - return with_fallback_errno(EIO); - - reset_caller_errno(fd); - return fd; -} - -int -fs_dirname_basename(const char *path, - char **dir, char **base, - int allow_relative) -{ - int saved_errno = errno; - char *buf = NULL; - char *slash; - size_t len; - const char *d = NULL; - const char *b = NULL; - errno = 0; - - if (if_err(path == NULL || dir == NULL || base == NULL, EFAULT)) - goto err; - - slen(path, PATH_MAX, &len); - memcpy(smalloc(&buf, len + 1), - path, len + 1); - - /* strip trailing slashes */ - while (len > 1 && buf[len - 1] == '/') - buf[--len] = '\0'; - - slash = strrchr(buf, '/'); - - if (slash) { - - *slash = '\0'; - d = buf; - b = slash + 1; - - if (*d == '\0') - d = "/"; - } else if (allow_relative) { - - d = "."; - b = buf; - } else { - free_and_set_null(&buf); - goto err; - } - - if (dup_pair(dir, d, base, b) < 0) { - free_and_set_null(&buf); - goto err; - } - - free_and_set_null(&buf); - - reset_caller_errno(0); - return 0; -err: - return with_fallback_errno(EINVAL); -} - -/* TODO: why does this abort, but others - e.g. open_file_on_eintr, don't??? - */ -void -open_file_on_eintr(const char *path, - int *fd, int flags, mode_t mode, - struct stat *st) -{ - int saved_errno = errno; - int rval = 0; - errno = 0; - - if (path == NULL) - exitf("open_file_on_eintr: null path"); - if (fd == NULL) - exitf("%s: open_file_on_eintr: null fd ptr", path); - if (*fd >= 0) - exitf( - "%s: open_file_on_eintr: file already open", path); - - errno = 0; - while (fs_retry(saved_errno, - rval = open(path, flags, mode))); - - if (rval < 0) - exitf( - "%s: open_file_on_eintr: could not close", path); - - reset_caller_errno(rval); - *fd = rval; - - /* we don't care about edge case behaviour here, - even if the next operation sets errno on success, - because the open() call is our main concern. - however, we also must preserve the new errno, - assuming it changed above under the same edge case */ - - saved_errno = errno; - - if (st != NULL) { - if (fstat(*fd, st) < 0) - exitf("%s: stat", path); - - if (!S_ISREG(st->st_mode)) - exitf("%s: not a regular file", path); - } - - if (lseek(*fd, 0, SEEK_CUR) == (off_t)-1) - exitf("%s: file not seekable", path); - - errno = saved_errno; /* see previous comment */ -} - - -#if defined(__linux__) && \ - (!defined(USE_OPENAT) || ((USE_OPENAT) < 1)) /* we use openat2 on linux */ -int -openat_on_eintr(int dirfd, const char *path, - int flags, mode_t mode) -{ - struct open_how how = { - .flags = (unsigned long long)flags, - .mode = mode, - .resolve = - RESOLVE_BENEATH | - RESOLVE_NO_SYMLINKS | - RESOLVE_NO_MAGICLINKS - }; - int saved_errno = errno; - long rval = 0; - errno = 0; - - if (if_err(dirfd < 0, EBADF) || - if_err(path == NULL, EFAULT)) - goto err; - - errno = 0; - while (sys_retry(saved_errno, - rval = syscall(SYS_openat2, dirfd, path, &how, sizeof(how)))); - - if (rval == -1) /* avoid long->int UB for -1 */ - goto err; - - reset_caller_errno(rval); - return (int)rval; -err: - return with_fallback_errno(EIO); /* -1 */ -} -#else /* regular openat on non-linux e.g. openbsd */ -int -openat_on_eintr(int dirfd, const char *path, - int flags, mode_t mode) -{ - int saved_errno = errno; - int rval = 0; - errno = 0; - - if (if_err(dirfd < 0, EBADF) || - if_err(path == NULL, EFAULT)) - return with_fallback_errno(EIO); - - while (fs_retry(saved_errno, - rval = openat(dirfd, path, flags, mode))); - - reset_caller_errno(rval); - return rval; -} -#endif - -int -mkdirat_on_eintr(int dirfd, - const char *path, mode_t mode) -{ - int saved_errno = errno; - int rval = 0; - errno = 0; - - if (if_err(dirfd < 0, EBADF) || - if_err(path == NULL, EFAULT)) - return with_fallback_errno(EIO); - - while (fs_retry(saved_errno, - rval = mkdirat(dirfd, path, mode))); - - reset_caller_errno(rval); - return rval; -} - -int -fsync_on_eintr(int fd) -{ - int saved_errno = errno; - int rval = 0; - errno = 0; - - if (if_err(fd < 0, EBADF)) - return with_fallback_errno(EIO); - - while (fs_retry(saved_errno, - rval = fsync(fd))); - - reset_caller_errno(rval); - return rval; -} - -void -xclose(int *fd) -{ - int saved_errno = errno; - int rval = 0; - - if (fd == NULL) - exitf("xclose: null pointer"); - if (*fd < 0) - return; - - /* nuance regarding EINTR on close(): - * EINTR can be set on error, but there's - * no guarantee whether the fd is then still - * open or closed. on some other commands, we - * loop EINTR, but for close, we instead skip - * aborting *if the errno is EINTR* - so don't - * loop it, but do regard EINTR with rval -1 - * as essenitally a successful close() - */ - - /* because we don't want to mess with someone - * elses file if that fd is then reassigned. - * if the operation truly did fail, we ignore - * it. just leave it flying in the wind */ - - errno = 0; - if ((rval = close(*fd)) < 0) { - if (errno != EINTR) - exitf("xclose: could not close"); - - /* regard EINTR as a successful close */ - rval = 0; - } - - *fd = -1; - - reset_caller_errno(rval); -} - -/* unified eintr looping. - * differently typed functions - * to avoid potential UB - * - * ONE MACRO TO RULE THEM ALL: - */ -#define fs_err_retry() \ - do { \ - if ((rval == -1) && \ - (errno == EINTR)) \ - return 1; \ - if (rval >= 0 && !errno) \ - errno = saved_errno; \ - return 0; \ - } while(0) -/* - * Regarding the errno logic above: - * on success, it is permitted that - * a syscall could still set errno. - * We reset errno after storingit - * for later preservation, in functions - * that call *_retry() functions. - * - * They rely ultimately on this - * macro for errno restoration. We - * assume therefore that errno was - * reset to zero before the retry - * loop. If errno is then *set* on - * success, we leave it alone. Otherwise, - * we restore the caller's saved errno. - * - * This offers some consistency, while - * complying with POSIX specification. - */ - - -/* retry switch for functions that - return long status e.g. linux syscall - */ -int -sys_retry(int saved_errno, long rval) -{ - fs_err_retry(); -} - -/* retry switch for functions that - return int status e.g. mkdirat - */ -int -fs_retry(int saved_errno, int rval) -{ - fs_err_retry(); -} - -/* retry switch for functions that - return rw count in ssize_t e.g. read() - */ -int -rw_retry(int saved_errno, ssize_t rval) -{ - fs_err_retry(); -} |
