diff options
Diffstat (limited to 'util')
| -rw-r--r-- | util/libreboot-utils/.gitignore | 1 | ||||
| -rw-r--r-- | util/libreboot-utils/Makefile | 26 | ||||
| -rw-r--r-- | util/libreboot-utils/README.md | 254 | ||||
| -rw-r--r-- | util/libreboot-utils/include/common.h | 10 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/file.c | 103 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/mkhtemp.c | 52 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/num.c | 41 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/rand.c | 86 | ||||
| -rw-r--r-- | util/libreboot-utils/lib/string.c | 51 | ||||
| -rw-r--r-- | util/libreboot-utils/lottery.c | 32 | ||||
| -rw-r--r-- | util/libreboot-utils/mkhtemp.c | 67 |
11 files changed, 458 insertions, 265 deletions
diff --git a/util/libreboot-utils/.gitignore b/util/libreboot-utils/.gitignore index fbf110f9..fbfbd130 100644 --- a/util/libreboot-utils/.gitignore +++ b/util/libreboot-utils/.gitignore @@ -1,6 +1,7 @@ /nvm /nvmutil /mkhtemp +/lottery *.bin *.o *.d diff --git a/util/libreboot-utils/Makefile b/util/libreboot-utils/Makefile index 692ebf0f..872a37c4 100644 --- a/util/libreboot-utils/Makefile +++ b/util/libreboot-utils/Makefile @@ -25,6 +25,7 @@ HELLFLAGS = $(STRICT) -Weverything PROG = nvmutil PROGMKH = mkhtemp +PROGLOT = lottery OBJS_NVMUTIL = \ obj/nvmutil.o \ @@ -48,11 +49,19 @@ OBJS_MKHTEMP = \ obj/lib/mkhtemp.o \ obj/lib/rand.o +OBJS_LOTTERY = \ + obj/lottery.o \ + obj/lib/file.o \ + obj/lib/string.o \ + obj/lib/num.o \ + obj/lib/mkhtemp.o \ + obj/lib/rand.o + # default mode CFLAGS_MODE = $(PORTABLE) CC_MODE = $(CC) -all: $(PROG) $(PROGMKH) +all: $(PROG) $(PROGMKH) $(PROGLOT) $(PROG): $(OBJS_NVMUTIL) $(CC_MODE) $(OBJS_NVMUTIL) -o $(PROG) $(LDFLAGS) @@ -60,9 +69,13 @@ $(PROG): $(OBJS_NVMUTIL) $(PROGMKH): $(OBJS_MKHTEMP) $(CC_MODE) $(OBJS_MKHTEMP) -o $(PROGMKH) $(LDFLAGS) +$(PROGLOT): $(OBJS_LOTTERY) + $(CC_MODE) $(OBJS_LOTTERY) -o $(PROGLOT) $(LDFLAGS) + # ensure obj directory exists $(OBJS_NVMUTIL): obj $(OBJS_MKHTEMP): obj +$(OBJS_LOTTERY): obj obj: mkdir obj || true @@ -76,6 +89,9 @@ obj/nvmutil.o: nvmutil.c obj/mkhtemp.o: mkhtemp.c $(CC_MODE) $(CFLAGS_MODE) -c mkhtemp.c -o obj/mkhtemp.o +obj/lottery.o: lottery.c + $(CC_MODE) $(CFLAGS_MODE) -c lottery.c -o obj/lottery.o + # library/helper objects obj/lib/state.o: lib/state.c @@ -113,19 +129,23 @@ obj/lib/rand.o: lib/rand.c # install -install: $(PROG) $(PROGMKH) +install: $(PROG) $(PROGMKH) $(PROGLOT) $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin $(INSTALL) $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG) chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROG) $(INSTALL) $(PROGMKH) $(DESTDIR)$(PREFIX)/bin/$(PROGMKH) chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROGMKH) + $(INSTALL) $(PROGLOT) $(DESTDIR)$(PREFIX)/bin/$(PROGLOT) + chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROGLOT) uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG) rm -f $(DESTDIR)$(PREFIX)/bin/$(PROGMKH) + rm -f $(DESTDIR)$(PREFIX)/bin/$(PROGLOT) clean: - rm -f $(PROG) $(PROGMKH) $(OBJS_NVMUTIL) $(OBJS_MKHTEMP) + rm -f $(PROG) $(PROGMKH) $(OBJS_NVMUTIL) $(OBJS_MKHTEMP) \ + $(OBJS_LOTTERY) $(PROGLOT) distclean: clean diff --git a/util/libreboot-utils/README.md b/util/libreboot-utils/README.md new file mode 100644 index 00000000..6e94035b --- /dev/null +++ b/util/libreboot-utils/README.md @@ -0,0 +1,254 @@ +Mkhtemp - Hardened mktemp +------------------------- + +Just like normal mktemp, but hardened. + +Create new files and directories randomly as determined by +the user's TMPDIR, or fallback. These temporary files and +directories can be generated from e.g. shell scripts, running +mkhtemp. There is also a library that you could use in your +program. Portable to Linux and BSD. **WORK IN PROGRESS. +This is a very new project. Expect bugs - a stable release +will be announced, when the code has matured.** + +A brief summary of *why* mkhtemp is more secure (more +details provided later in this readme - please also +read the source code): + +Detect and mitigate symlink attacks, directory access +race conditions, unsecure TMPDIR (e.g. bad enforce sticky +bit policy on world writeable dirs), implement in user +space a virtual sandbox (block directory escape and resolve +paths by walking from `/` manually instead of relying on +the kernel/system), voluntarily error out (halt all +operation) if accessing files you don't own - that's why +sticky bits are checked for example, even when you're root. + +It... blocks symlinks, relative paths, attempts to prevent +directory escape (outside of the directory that the file +you're creating is in), basically implementing an analog +of something like e.g. unveil, but in userspace! + +Mkhtemp is designed to be the most secure implementation +possible, of mktemp, offering a heavy amount of hardening +over traditional mktemp. Written in C89, and the plan is +very much to keep this code portable over time - patches +very much welcome. + +i.e. please read the source code + +``` +/* + * WARNING: WORK IN PROGRESS. + * Do not use this software in + * your distro yet. It's ready + * when it's ready. Read the src. + * + * What you see is an early beta. + * + * Please do not merge this in + * your Linux distro package repo + * yet (unless maybe you're AUR). + */ +``` + +Supported mktemp flags: + +``` +mkhtemp: usage: mkhtemp [-d] [-p dir] [template] + + -p DIR <-- set directory, overriding TMPDIR + -d <-- make a directory instead of a file + -q <-- silence errors (exit status unchanged) +``` + +The rest of them will be added later (the same ones +that GNU and BSD mktemp implement). With these options, +you can generate files/directories already. + +You can also write a template at the end. e.g. + +``` +mkhtemp -d -p path/to/directory vickysomething_XXXXXXXXXXX +``` + +On most sane/normal setups, the program should already +actually work, but please know that it's very different +internally than every other mktemp implementation. + +Read the source code if you're interested. As of this +time of writing, mkhtemp is very new, and under +development. A stable release will be announced when ready. + +### What does mkhtemp do differently? + +This software attempts to provide mitigation against +several TOCTOU-based +attacks e.g. directory rename / symlink / re-mount, and +generally provides much higher strictness than previous +implementations such as mktemp, mkstemp or even mkdtemp. +It uses several modern features by default, e.g. openat2 +and `O_TMPFILE` (plus `O_EXCL`) on Linux, with additional +hardening; BSD projects only have openat so the code uses +that there, but some (not all) of the kinds of checks +Openat2 enforces are done manually (in userspace). + +File system sandboxing in userspace (pathless discovery, +and operations are done only with FDs). At startup, the +root directory is opened, and then everything is relative +to that. + +Many programs rely on mktemp, and they use TMPDIR in a way +that is quite insecure. Mkhtemp intends to change that, +quite dramatically, with: userspace sandbox (and use OS +level options e.g. OBSD pledge where available), constant +identity/ownership checks on files, MUCH stricter ownership +restrictions (e.g. enforce sticky bit policy on world- +writeable tmpdirs), preventing operation on other people's +files (only your own files) - even root is restricted, +depending on how the code is compiled. Please read the code. + +Basically, the gist of it is that normal mktemp *trusts* +your system is set up properly. It will just run however +you tell it to, on whatever directory you tell it to, and +if you're able to write to it, it will write to it. +Some implementations (e.g. OpenBSD one) do some checks, +but not all of them do *all* checks. The purpose of +mkhtemp is to be as strict as possible, while still being +reliable enough that people can use it. Instead of catering +to legacy requirements, mkhtemp says that systems should +be secure. So if you're running in an insecure environment, +the goal of mkhtemp is to *exit* when you run it; better +this than files being corrupted. + +Security and reliability are the same thing. They both +mean that your computer is behaving as it should, in a +manner that you can predict. + +It doesn't matter how many containers you have, or how +memory-safe your programming language is, the same has +been true forever: code equals bugs, and code usually +has the same percentage of bugs, so more code equals +more bugs. Therefore, highly secure systems (such as +OpenBSD) typically try to keep their code as small and +clean as possible, so that they can audit it. Mkhtemp +assumes that your system is hostile, and is designed +accordingly. + +What? +----- + +This is the utility version, which makes use of the also- +included library. No docs yet - source code are the docs, +and the (ever evolving, and hardening) specification. + +This was written from scratch, for use in nvmutil, and +it is designed to be portable (BSD, Linux). Patches +very much welcome. + +Caution +------- + +This is a new utility. Expect bugs. + +``` +WARNING: This is MUCH stricter than every other mktemp + implementation, even more so than mkdtemp or + the OpenBSD version of mkstemp. It *will* break, + or more specifically, reveal the flaws in, almost + every major critical infrastructure, because most + people already use mktemp extremely insecurely. +``` + +This tool is written by me, for me, and also Libreboot, but +it will be summitted for review to various Linux distros +and BSD projects once it has reached maturity. + +### Why was this written? + +Atomic writes were implemented in nvmutil (Libreboot's +Intel GbE NVM editor), but one element remained: the +program mktemp, itself, which has virtually no securitty +checks whatsoever. GNU and BSD implementations use +mkstemp now, which is a bit more secure, and they offer +additional hardening, but I wanted to be reasonably +assured that my GbE files were not being corrupted in +any way, and that naturally led to writing a hardened +tool. It was originally just going to be for nvmutil, +but then it became its own standard utility. + +Existing implementations of mktemp just simply do not +have sufficient checks in place to prevent misuse. This +tool, mkhtemp, intentionally focuses on being secure +instead of easy. For individuals just running Linux on +their personal machine, it might not make much difference, +but corporations and projects running computers for lots +of big infrastructure need something reliable, since +mktemp is just one of those things everyone uses. +Every big program needs to make temporary files. + +But the real reason I wrote this tool is because, it's +fun, and because I wanted to challenge myself. + +Roadmap +------- + +Some things that are in the near future for mkhtemp +development: + +Thoroughly document every known case of CVEs in the wild, +and major attacks against individuals/projects/corporations +that were made possible by mktemp - that mkhtemp might +have prevented. There are several. + +More hardening; still a lot more that can be done, depending +on OS. E.g. integrate FreeBSD capsicum. + +Another example: although usually reliable, comparing the +inode and device of a file/directory isn't by itself sufficient. +There are other checks that mkhtemp does; for example I could +implement it so that directories are more aggressively re- +opened by mkhtemp itself, mid-operation. This re-opening +would be quite expensive computationally, but it would then +allow us to re-check everything, since we store state from +when the program starts. + +Tidy up the code: the current code was thrown together in +a week, and needs tidying. A proper specification should be +written, to define how it works, and then the code should +be auditted for compliance. A lot of the functions are +also quite complex and do a lot; they could be split up. + +Right now, mkhtemp mainly returns a file descriptor and +a path, after operation, ironic given the methods it uses +while opening your file/dir. After it's done, you then have +to handle everything again. Mkhtemp could keep everything +open instead, and continue to provide verification; in +other words, it could provide a completely unified way for +Linux/BSD programs to open files, write to them atomically, +and close. Programs like Vim will do this for example, or +other text editors, but every program has its own way. So +what mkhtemp could do is provide a well-defined API alongside +its mktemp hardening. Efforts would be made to avoid +feature creep, and ensure that the code remains small and +nimble. + +Compatibility mode: another thing is that mkhtemp is a bit +too strict for some users, so it may break some setups. What +it could do is provide a compatibility mode, and in this +mode, behave like regular mktemp. That way, it could become +a drop-in replacement on Linux distros (and BSDs if they +want it), while providing a more hardened version and +recommending that where possible. + +~~Rewrite it in rust~~ (nothing against it though, I just like C89 for some reason) + +Also, generally document the history of mktemp, and how +mkhtemp works in comparison. + +Also a manpage. + +Once all this is done, and the project is fully polished, +then it will be ready for your Linux distro. For now, I +just use it in nvmutil (and I also use it on my personal +computer). diff --git a/util/libreboot-utils/include/common.h b/util/libreboot-utils/include/common.h index 6d6d8d09..71f28fad 100644 --- a/util/libreboot-utils/include/common.h +++ b/util/libreboot-utils/include/common.h @@ -83,7 +83,7 @@ int fchmod(int fd, mode_t mode); #endif #ifndef REAL_POS_IO -#define REAL_POS_IO 0 +#define REAL_POS_IO 1 #endif #ifndef LOOP_EAGAIN @@ -375,6 +375,8 @@ int scmp(const char *a, const char *b, size_t maxlen, int *rval); int sdup(const char *s, size_t n, char **dest); +int scatn(ssize_t sc, const char **sv, + size_t max, char **rval); int scat(const char *s1, const char *s2, size_t n, char **dest); int dcat(const char *s, size_t n, @@ -385,9 +387,12 @@ int dcat(const char *s, size_t n, unsigned short hextonum(char ch_s); void *mkrbuf(size_t n); +void *rmalloc(size_t *size); /* don't ever use this */ void rset(void *buf, size_t n); void *mkrbuf(size_t n); char *mkrstr(size_t n); +int win_lottery(void); +size_t rsize(void); /* Helper functions for command: dump */ @@ -469,11 +474,8 @@ int io_args(int fd, void *mem, size_t nrw, off_t off, int rw_type); int check_file(int fd, struct stat *st); ssize_t rw_over_nrw(ssize_t r, size_t nrw); -#if !defined(REAL_POS_IO) || \ - REAL_POS_IO < 1 off_t lseek_on_eintr(int fd, off_t off, int whence, int loop_eagain, int loop_eintr); -#endif int try_err(int loop_err, int errval); /* Error handling and cleanup diff --git a/util/libreboot-utils/lib/file.c b/util/libreboot-utils/lib/file.c index 552618d6..5fdef7b3 100644 --- a/util/libreboot-utils/lib/file.c +++ b/util/libreboot-utils/lib/file.c @@ -69,29 +69,6 @@ err_same_file: return -1; } -/* open() but with abort traps - */ -/* TODO: also support other things here than files. - and then use, throughout the program. - in particular, use of openat might help - (split the path) - (see: link attack mitigations throughout nvmutil) - - make it return, and handle the return value/errno - - (this could return e.g. EINTR) - - TODO: this function is not used by mkhtemp, nor will - it probably be, it's currently used by nvmutil, - for opening intel gbe nvm config files. i can - probably remove it though and unify witth some - of the verification code now used for mkhtemp - -TODO: and don't abort. return -1. and handle in the caller. - -minor obstacle: the mkhtemp code always requires absolute -paths, whereas the gbe editor takes relative paths. - */ void xopen(int *fd_ptr, const char *path, int flags, struct stat *st) { @@ -108,10 +85,6 @@ xopen(int *fd_ptr, const char *path, int flags, struct stat *st) err_no_cleanup(0, errno, "%s: file not seekable", path); } -/* fsync() the directory of a file, - * useful for atomic writes - */ - int fsync_dir(const char *path) { @@ -196,19 +169,6 @@ err_fsync_dir: return -1; } -/* - * Safe I/O functions wrapping around - * read(), write() and providing a portable - * analog of both pread() and pwrite(). - * These functions are designed for maximum - * robustness, checking NULL inputs, overflowed - * outputs, and all kinds of errors that the - * standard libc functions don't. - * - * Looping on EINTR and EAGAIN is supported. - * EINTR/EAGAIN looping is done indefinitely. - */ - /* rw_file_exact() - Read perfectly or die * * Read/write, and absolutely insist on an @@ -243,6 +203,7 @@ rw_file_exact(int fd, unsigned char *mem, size_t nrw, size_t retries_on_zero; int saved_errno = errno; + errno = 0; rval = 0; @@ -317,12 +278,6 @@ err_rw_file_exact: /* prw() - portable read-write with more * safety checks than barebones libc * - * portable pwrite/pread on request, or real - * pwrite/pread libc functions can be used. - * the portable (non-libc) pread/pwrite is not - * thread-safe, because it does not prevent or - * mitigate race conditions on file descriptors - * * If you need real pwrite/pread, just compile * with flag: REAL_POS_IO=1 * @@ -340,6 +295,11 @@ err_rw_file_exact: * a change was detected, assuming * nothing else is touching it now * off_reset 0: never reset if changed + * + * REAL_POS_IO is enabled by default in common.h + * and the fallback version was written for fun. + * You should just use the real one (REAL_POS_IO 1), + * since it is generally more reliable. */ ssize_t @@ -359,6 +319,7 @@ prw(int fd, void *mem, size_t nrw, off_t off_last; #endif int saved_errno = errno; + errno = 0; if (io_args(fd, mem, nrw, off, rw_type) == -1) @@ -568,8 +529,6 @@ err_rw_over_nrw: return -1; } -#if !defined(REAL_POS_IO) || \ - REAL_POS_IO < 1 off_t lseek_on_eintr(int fd, off_t off, int whence, int loop_eagain, int loop_eintr) @@ -588,10 +547,9 @@ lseek_on_eintr(int fd, off_t off, int whence, return old; } -#endif /* two functions that reduce sloccount by - * two hundred lines... no, now three. */ + * two hundred lines */ int if_err(int condition, int errval) { @@ -603,13 +561,6 @@ if_err(int condition, int errval) return 1; } -/* technically pointless, but stylistically - * pleasing alongside if_err chains. - * use this one for syscalls that are - * expected to set errno - * also use it for non-system calls - * that act like them, e.g. prw() or - * rw_write_exact() */ int if_err_sys(int condition) { @@ -665,10 +616,8 @@ close_warn(int *fd, char *s) return 0; } -/* TODO: remove this. giant liability. - make close calls always err instead, - when they fail. otherwise we hide bugs! - */ +/* TODO: remove this, and just check + * err on every close. */ void close_no_err(int *fd) { @@ -685,7 +634,6 @@ close_no_err(int *fd) /* TODO: make fd a pointer insttead and automatically reset -1 here */ -/* BUT DO NOT reset -1 on error */ int close_on_eintr(int fd) { @@ -732,11 +680,10 @@ fs_rename_at(int olddirfd, const char *old, return renameat(olddirfd, old, newdirfd, new); } -/* secure open, based on - * relative path to root +/* secure open, based on relative path to root * - * always a fixed fd for / - * see: rootfs() + * always a fixed fd for / see: rootfs() + * and fs_resolve_at() */ int fs_open(const char *path, int flags) @@ -751,12 +698,8 @@ fs_open(const char *path, int flags) return fs_resolve_at(fs->rootfd, path + 1, flags); } -/* singleton function - * that returns a fixed - * descriptor of / - * - * used throughout, for - * repeated integrity checks +/* singleton function that returns a fixed descriptor of / + * used throughout, for repeated integrity checks */ struct filesystem * rootfs(void) @@ -778,8 +721,7 @@ rootfs(void) return &global_fs; } -/* filesystem sandboxing. - * (in userspace) +/* filesystem sandboxing in userspace */ int fs_resolve_at(int dirfd, const char *path, int flags) @@ -818,10 +760,9 @@ fs_resolve_at(int dirfd, const char *path, int flags) if (nextfd < 0) goto err; - /* close previous fd IF it is not the original input */ - if (curfd != dirfd) { + /* close previous fd if not the original input */ + if (curfd != dirfd) (void) close_on_eintr(curfd); - } curfd = nextfd; nextfd = -1; @@ -899,8 +840,6 @@ fs_open_component(int dirfd, const char *name, (is_last ? flags : (O_RDONLY | O_DIRECTORY)) | O_NOFOLLOW | O_CLOEXEC, (flags & O_CREAT) ? 0600 : 0); - /* the patient always lies - */ if (!is_last) { if (if_err(fd < 0, EBADF) || @@ -972,9 +911,6 @@ fs_dirname_basename(const char *path, /* portable wrapper for use of openat2 on linux, * with fallback for others e.g. openbsd - * - * BONUS: arg checks - * TODO: consider EINTR/EAGAIN retry loop */ int openat2p(int dirfd, const char *path, @@ -1025,8 +961,7 @@ retry: } int -mkdirat_on_eintr( /* <-- say that 10 times to please the demon */ - int dirfd, +mkdirat_on_eintr(int dirfd, const char *path, mode_t mode) { int saved_errno = errno; diff --git a/util/libreboot-utils/lib/mkhtemp.c b/util/libreboot-utils/lib/mkhtemp.c index a669e208..0e0169e4 100644 --- a/util/libreboot-utils/lib/mkhtemp.c +++ b/util/libreboot-utils/lib/mkhtemp.c @@ -51,22 +51,6 @@ new_tmpdir(int *fd, char **path, char *tmpdir, tmpdir, template); } -/* note: tmpdir is an override of TMPDIR or /tmp or /var/tmp */ -/* WARNING: - * on error, *path (at **path) may be - NULL, or if the error pertains to - an actual TMPDIR, set. if set, it - will be using *static* memory and - must not be freed. on success, - a pointer to heap memory is set - instead. - * see: - * env_tmpdir() - * this is for error reports if e.g. - * TMPDIR isn't found (but is set) - * if TMPDIR isn't set, it will - * default to /tmp or /var/tmp - */ int new_tmp_common(int *fd, char **path, int type, char *tmpdir, const char *template) @@ -443,20 +427,8 @@ world_writeable_and_sticky( goto sticky_hell; } - /* must be fully executable - * by everyone, or openat2 - * becomes unreliable** - * - * TODO: loosen these, as a toggle. - * execution rights isn't - * really a requirement for - * TMPDIR, except maybe search, - * but this function will be - * generalised at some point - * for use in other tools - * besides just mkhtemp. - */ - /* + /* all of these checks are probably + * redundant (execution rights) if (!(st.st_mode & S_IXUSR) || !(st.st_mode & S_IXGRP) || !(st.st_mode & S_IXOTH)) { @@ -473,7 +445,7 @@ world_writeable_and_sticky( if (bypass_all_sticky_checks) goto sticky_heaven; /* normal == no security */ - /* unhinged leah mode: + /* extremely not-libc mode: */ if (st.st_mode & S_IWOTH) { /* world writeable */ @@ -488,9 +460,7 @@ world_writeable_and_sticky( goto sticky_hell; /* not sticky */ } - /* if anyone even looks at you funny, drop - * everything on the floor and refuse to function - */ + /* for good measure */ if (faccessat(dirfd, ".", X_OK, AT_EACCESS) < 0) goto sticky_hell; @@ -503,7 +473,6 @@ world_writeable_and_sticky( goto sticky_hell; /* heaven visa denied */ sticky_heaven: -/* i like the one in hamburg better */ close_no_err(&dirfd); errno = saved_errno; @@ -515,10 +484,7 @@ sticky_hell: if (errno == saved_errno) errno = EPERM; - saved_errno = errno; - close_no_err(&dirfd); - errno = saved_errno; return 0; @@ -909,11 +875,9 @@ retry_rand: /* WARNING: **ONCE** per file. * - * !!! DO NOT RUN TWICE PER FILE. BEWARE OF THE DEMON !!! - * watch out for spikes! - */ -/* TODO: bad_flags can be negative, and is - * ignored if it is. should we err instead? + * some of these checks will trip up + * if you do them twice; all of them + * only need to be done once anyway. */ int secure_file(int *fd, struct stat *st, @@ -945,7 +909,7 @@ int secure_file(int *fd, if (check_seek) { /***********/ if (lseek(*fd, 0, SEEK_CUR) == (off_t)-1) goto err_demons; - } /* don't release the demon */ + } /* don't release the demon! */ if (if_err(st->st_nlink != 1, ELOOP) || if_err(st->st_uid != geteuid() && geteuid() != 0, EPERM) || diff --git a/util/libreboot-utils/lib/num.c b/util/libreboot-utils/lib/num.c index 92710c35..79d6b409 100644 --- a/util/libreboot-utils/lib/num.c +++ b/util/libreboot-utils/lib/num.c @@ -1,12 +1,8 @@ /* SPDX-License-Identifier: MIT * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> * - * Numerical functions. - * NOTE: randomness was moved to rand.c - */ - -/* -TODO: properly handle errno in this file + * Non-randomisation-related numerical functions. + * For rand functions, see: rand.c */ #ifdef __OpenBSD__ @@ -27,42 +23,11 @@ TODO: properly handle errno in this file #include "../include/common.h" -/* TODO: - * make this and errno handling more - * flexible - - in particular: - hextonum could be modified to - write into a buffer instead, - with the converted numbers, - of an arbitrary length - */ unsigned short hextonum(char ch_s) { int saved_errno = errno; - /* rlong() can return error, - but preserves errno if no - error. we need to detect - this because it handles - /dev/urandom sometimes - - therefore, if it's zero - at start, we know if there - was an err at the end, by - return value zero, if errno - was set; this is technically - valid, since zero is also - a valid random number! - - it's an edge case that i had - to fix. i'll rewrite the code - better later. for now, it - should be ok. - */ - errno = 0; - unsigned char ch; size_t rval; @@ -85,8 +50,6 @@ hextonum(char ch_s) if (ch == '?' || ch == 'x') { rset(&rval, sizeof(rval)); - if (errno > 0) - goto err_hextonum; goto hextonum_success; } diff --git a/util/libreboot-utils/lib/rand.c b/util/libreboot-utils/lib/rand.c index 392ec2ba..58cb211e 100644 --- a/util/libreboot-utils/lib/rand.c +++ b/util/libreboot-utils/lib/rand.c @@ -20,6 +20,9 @@ #if defined(USE_URANDOM) && \ ((USE_URANDOM) > 0) #include <fcntl.h> /* if not arc4random: /dev/urandom */ +#elif defined(__linux__) +#include <sys/random.h> +#include <sys/syscall.h> #endif #include <fcntl.h> @@ -69,48 +72,62 @@ * or your program dies. */ -#define ELOTTERY ECANCELED +#define MAX_ALLOC (2 << 16) int -win_lottery(void) +win_lottery(void) /* are u lucky? */ { - int saved_errno = errno; - size_t size1; - char *s1 = NULL; - size_t size2; - char *s2 = NULL; - size_t pool = BUFSIZ; - int rval; + size_t size = rsize(); + size_t size2 = rsize(); + char *s = NULL; + + if (size && + size == size2 && + size <= MAX_ALLOC << 1) { + + if (!memcmp(s = mkrbuf(size << 1), + s + size, size)) + size2 = 1; /* winner! */ + else + size2 = 0; + } else { + return 0; + } - rset(&size1, sizeof(size1)); - rset(&size2, sizeof(size2)); + free_if_null(&s); + return (int)size2; +} - size1 %= pool, size2 %= pool; - s1 = mkrstr(size1), s2 = mkrstr(size2); +size_t +rsize(void) +{ + size_t rval = 0; - if (scmp(s1, s2, BUFSIZ + 2, &rval) < 0) - goto err; - if (rval == 0) - goto win; + /* clamp rand to prevent modulo bias */ + size_t limit = SIZE_MAX - (SIZE_MAX % MAX_ALLOC); + + do { + rset(&rval, sizeof(rval)); + } while (rval >= limit); - free_if_null(&s1), free_if_null(&s2); + return rval % MAX_ALLOC; +} - fprintf(stderr, "Sorry, you lose! Try again.\n"); - return 0; -win: - free_if_null(&s1), free_if_null(&s2); - err_no_cleanup(0, ELOTTERY, - "Congratulations! you won the errno lottery"); +void * +rmalloc(size_t *rval) +{ + /* clamp rand to prevent modulo bias */ + size_t limit = SIZE_MAX - (SIZE_MAX % MAX_ALLOC); - exit(1); - return -1; -err: - /* the lottery won you */ - free_if_null(&s1), free_if_null(&s2); - err_no_cleanup(0, EFAULT, "lottery won you"); - exit(1); - return -1; + if (if_err(rval == NULL, EFAULT)) + return NULL; + + do { + rset(rval, sizeof(*rval)); + } while (*rval >= limit || *rval == 0); + + return mkrstr(*rval %= MAX_ALLOC); } char * @@ -122,7 +139,7 @@ mkrstr(size_t n) /* emulates spkmodem-decode */ if (n == 0) err_no_cleanup(0, EPERM, "mkrbuf: zero-byte request"); - if (n == SIZE_MAX) + if (n >= SIZE_MAX - 1) err_no_cleanup(0, EOVERFLOW, "mkrbuf: overflow"); if (if_err((s = mkrbuf(n + 1)) == NULL, EFAULT)) @@ -145,6 +162,9 @@ mkrbuf(size_t n) if (n == 0) err_no_cleanup(0, EPERM, "mkrbuf: zero-byte request"); + if (n >= SIZE_MAX - 1) + err_no_cleanup(0, EOVERFLOW, "integer overflow in mkrbuf"); + if ((buf = malloc(n)) == NULL) err_no_cleanup(0, ENOMEM, "mkrbuf: malloc"); diff --git a/util/libreboot-utils/lib/string.c b/util/libreboot-utils/lib/string.c index 0329c6c3..b639f0a4 100644 --- a/util/libreboot-utils/lib/string.c +++ b/util/libreboot-utils/lib/string.c @@ -117,6 +117,57 @@ sdup(const char *s, return 0; } +/* concatenate N number of strings */ +/* slen already checks null/termination */ +int +scatn(ssize_t sc, const char **sv, + size_t max, char **rval) +{ + ssize_t i = 0; + + size_t ts = 0; + size_t *size = NULL; + + char *ct = NULL; + int saved_errno = errno; + + if (if_err(sc <= 0, EINVAL) || + if_err(sc > SIZE_MAX / sizeof(size_t), EOVERFLOW) || + if_err(sv == NULL, EINVAL) || + if_err((size = malloc(sizeof(size_t) * sc)) == NULL, ENOMEM)) + goto err; + + for (i = 0; i < sc; i++, ts += size[i]) + if (if_err(sv[i] == NULL, EINVAL) || + slen(sv[i], max, &size[i]) < 0 || + if_err(size[i] > max - 1, EOVERFLOW) || + if_err((size[i] + ts) < ts, EOVERFLOW)) + goto err; + + if (if_err(ts > SIZE_MAX - 1, EOVERFLOW) || + if_err(ts > max - 1, EOVERFLOW) || + if_err((ct = malloc(ts + 1)) == NULL, ENOMEM)) + goto err; + + for (ts = i = 0; i < sc; i++, ts += size[i]) + memcpy(ct + ts, sv[i], size[i]); + + *(ct + ts) = '\0'; + *rval = ct; + + errno = saved_errno; + return 0; +err: + if (ct != NULL) + free(ct); + if (size != NULL) + free(size); + if (errno == saved_errno) + errno = EFAULT; + + return -1; +} + /* strict strcat */ int scat(const char *s1, const char *s2, diff --git a/util/libreboot-utils/lottery.c b/util/libreboot-utils/lottery.c new file mode 100644 index 00000000..4c3b0f70 --- /dev/null +++ b/util/libreboot-utils/lottery.c @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: MIT + * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> + */ + +#ifdef __OpenBSD__ +#include <sys/param.h> /* pledge(2) */ +#endif + +#include <stdio.h> +#include "include/common.h" + +int +main(int argc, char *argv[]) +{ + int lucky; +#if defined(__OpenBSD__) && defined(OpenBSD) +#if (OpenBSD) >= 509 + if (pledge("stdio", NULL) == -1) + err_no_cleanup(0, errno, "openbsd won it"); +#endif +#endif + setvbuf(stdout, NULL, _IONBF, 0); + + lucky = win_lottery(); + + printf("%s\n", lucky ? "You won!" : "You lose! Sorry!"); + return lucky ? EXIT_SUCCESS : EXIT_FAILURE; +}/* + + ( >:3 ) + /| |\ + / \ */ diff --git a/util/libreboot-utils/mkhtemp.c b/util/libreboot-utils/mkhtemp.c index 261227cb..7564800a 100644 --- a/util/libreboot-utils/mkhtemp.c +++ b/util/libreboot-utils/mkhtemp.c @@ -1,47 +1,17 @@ /* SPDX-License-Identifier: MIT * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> * - * WORK IN PROGRESS (proof of concept), or, v0.0000001 - * - * Mkhtemp - Hardened mktemp. Create files and directories - * randomly as determined by user's TMPDIR, or fallback. It - * attemps to provide mitigation against several TOCTOU-based - * attacks e.g. directory rename / symlink attacks, and it - * generally provides much higher strictness than previous - * implementations such as mktemp, mkstemp or even mkdtemp. - * - * It uses several modern features by default, e.g. openat2 - * and O_TMPFILE on Linux, with additional hardening; BSD - * projects only have openat so the code uses that there. + * Hardened mktemp (mkhtemp!) * - * Many programs rely on mktemp, and they use TMPDIR in a way - * that is quite insecure. Mkhtemp intends to change that, - * quite dramatically, with: userspace sandbox (and use OS - * level options e.g. OBSD pledge where available), constant - * identity/ownership checks on files, MUCH stricter ownership - * restrictions (e.g. enforce sticky bit policy on world- - * writeable tmpdirs), preventing operation on other people's - * files (only your own files) - even root is restricted, - * depending on how the code is compiled. Please read the code. - * - * This is the utility version, which makes use of the also- - * included library. No docs yet - source code are the docs, - * and the (ever evolving, and hardening) specification. - * - * This was written from scratch, for use in nvmutil, and - * it is designed to be portable (BSD, Linux). Patches - * very much welcome. + * WORK IN PROGRESS (proof of concept), or, v0.0000001 + * DO NOT PUT THIS IN YOUR LINUX DISTRO YET. * - * WARNING: This is MUCH stricter than every other mktemp - * implementation, even more so than mkdtemp or - * the OpenBSD version of mkstemp. It *will* break, - * or more specifically, reveal the flaws in, almost - * every major critical infrastructure, because most - * people already use mktemp extremely insecurely. + * I will remove this notice when the code is mature, and + * probably contact several of your projects myself. * - * This tool is written by me, for me, and also Libreboot, but - * it will be summitted for review to various Linux distros - * and BSD projects once it has reached maturity. + * See README. This is an ongoing project; no proper docs + * yet, and no manpage (yet!) - the code is documentation, + * while the specification that it implements evolves. */ #if defined(__linux__) && !defined(_GNU_SOURCE) @@ -187,25 +157,6 @@ err_usage: "usage: %s [-d] [-p dir] [template]\n", getnvmprogname()); }/* - ( >:3 ) /| |\ - / \ - - - - - - */ - - - - - - - - - - - - + / \ */ |
