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>
This commit is contained in:
Leah Rowe
2026-09-07 08:12:24 +01:00
parent 01cb422c97
commit 123639e3db
20 changed files with 0 additions and 5455 deletions
-7
View File
@@ -1,7 +0,0 @@
/nvm
/nvmutil
/mkhtemp
/lottery
*.bin
*.o
*.d
-2
View File
@@ -1,2 +0,0 @@
Leah Rowe
Riku Viitanen
-21
View File
@@ -1,21 +0,0 @@
Copyright (C) 2022-2026 Leah Rowe <leah@libreboot.org>
Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-60
View File
@@ -1,60 +0,0 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2022,2026 Leah Rowe <leah@libreboot.org>
# Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com>
CC = cc
CFLAGS = -Os -Wall -Wextra -std=c99 -pedantic
LDFLAGS =
PREFIX = /usr/local
DESTDIR =
INSTALL = install
PROGS = nvmutil mkhtemp lottery
LIB_OBJS = \
lib/state.o \
lib/file.o \
lib/string.o \
lib/usage.o \
lib/command.o \
lib/num.o \
lib/io.o \
lib/checksum.o \
lib/word.o \
lib/mkhtemp.o \
lib/rand.o
OBJS_NVMUTIL = nvmutil.o $(LIB_OBJS)
OBJS_MKHTEMP = mkhtemp.o lib/file.o lib/string.o lib/num.o lib/mkhtemp.o lib/rand.o
OBJS_LOTTERY = lottery.o lib/file.o lib/string.o lib/num.o lib/mkhtemp.o lib/rand.o
all: $(PROGS)
nvmutil: $(OBJS_NVMUTIL)
$(CC) $(CFLAGS) $(OBJS_NVMUTIL) -o $@ $(LDFLAGS)
mkhtemp: $(OBJS_MKHTEMP)
$(CC) $(CFLAGS) $(OBJS_MKHTEMP) -o $@ $(LDFLAGS)
lottery: $(OBJS_LOTTERY)
$(CC) $(CFLAGS) $(OBJS_LOTTERY) -o $@ $(LDFLAGS)
.c.o:
$(CC) $(CFLAGS) -c $< -o $@
install: $(PROGS)
mkdir -p $(DESTDIR)$(PREFIX)/bin
for p in $(PROGS); do \
$(INSTALL) $$p $(DESTDIR)$(PREFIX)/bin/$$p; \
chmod 755 $(DESTDIR)$(PREFIX)/bin/$$p; \
done
uninstall:
for p in $(PROGS); do \
rm -f $(DESTDIR)$(PREFIX)/bin/$$p; \
done
clean:
rm -f $(PROGS) *.o lib/*.o
distclean: clean
-254
View File
@@ -1,254 +0,0 @@
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 C99, 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 C99 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).
-607
View File
@@ -1,607 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
TODO: this file should be split, into headers for each
C source file specifically. it was originally just
for nvmutil, until i added mkhtemp to the mix
*/
#ifndef COMMON_H
#define COMMON_H
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
/* dangerously cool macros:
*/
#define SUCCESS(x) ((x) >= 0)
/* syscalls can set errno even on success; this
* is rare, but permitted. in various functions, we
* reset errno on success, to what the caller had,
* but we must still honour what was returned.
*
* lib/file.c is littered with examples
*/
#define reset_caller_errno(return_value) \
do { \
if (SUCCESS(return_value) && (!errno)) \
errno = saved_errno; \
} while (0)
#define items(x) (sizeof((x)) / sizeof((x)[0]))
#define MKHTEMP_RETRY_MAX 512
#define MKHTEMP_SPIN_THRESHOLD 32
#define MKHTEMP_FILE 0
#define MKHTEMP_DIR 1
/* if 1: on operations that
* check ownership, always
* permit root to access even
* if not the file/dir owner
*/
#ifndef ALLOW_ROOT_OVERRIDE
#define ALLOW_ROOT_OVERRIDE 0
#endif
/*
*/
#ifndef SSIZE_MAX
#define SSIZE_MAX ((ssize_t)(~((ssize_t)1 << (sizeof(ssize_t)*CHAR_BIT-1))))
#endif
/* build config
*/
#ifndef NVMUTIL_H
#define NVMUTIL_H
#define MAX_CMD_LEN 50
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
#ifndef PATH_MAX
#error PATH_MAX_undefined
#elif ((PATH_MAX) < 1024)
#error PATH_MAX_too_low
#endif
#ifndef S_ISVTX
#define S_ISVTX 01000
#endif
#if defined(S_IFMT) && ((S_ISVTX & S_IFMT) != 0)
#error "Unexpected bit layout"
#endif
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef O_NOCTTY
#define O_NOCTTY 0
#endif
#ifndef O_ACCMODE
#define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR)
#endif
#ifndef O_BINARY
#define O_BINARY 0
#endif
#ifndef O_EXCL
#define O_EXCL 0
#endif
#ifndef O_CREAT
#define O_CREAT 0
#endif
#ifndef O_NONBLOCK
#define O_NONBLOCK 0
#endif
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
#ifndef O_NOFOLLOW
#define O_NOFOLLOW 0
#endif
#ifndef FD_CLOEXEC
#define FD_CLOEXEC 0
#endif
/* Sizes in bytes:
*/
#define SIZE_1KB 1024
#define SIZE_4KB (4 * SIZE_1KB)
#define SIZE_8KB (8 * SIZE_1KB)
#define SIZE_16KB (16 * SIZE_1KB)
#define SIZE_128KB (128 * SIZE_1KB)
#define GBE_BUF_SIZE (SIZE_128KB)
/* First 128 bytes of gbe.bin is NVM.
* Then extended area. All of NVM must
* add up to BABA, truncated (LE)
*
* First 4KB of each half of the file
* contains NVM+extended.
*/
#define GBE_WORK_SIZE (SIZE_8KB)
#define GBE_PART_SIZE (GBE_WORK_SIZE >> 1)
#define NVM_CHECKSUM 0xBABA
#define NVM_SIZE 128
#define NVM_WORDS (NVM_SIZE >> 1)
#define NVM_CHECKSUM_WORD (NVM_WORDS - 1)
/* argc minimum (dispatch)
*/
#define ARGC_3 3
#define ARGC_4 4
/* For checking if an fd is a normal file.
* Portable for old Unix e.g. v7 (S_IFREG),
* 4.2BSD (S_IFMT), POSIX (S_ISREG).
*
* IFREG: assumed 0100000 (classic bitmask)
*/
#ifndef S_ISREG
#if defined(S_IFMT) && defined(S_IFREG)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#elif defined(S_IFREG)
#define S_ISREG(m) (((m) & S_IFREG) != 0)
#else
#error "can't determine types with stat()"
#endif
#endif
#define IO_READ 0
#define IO_WRITE 1
#define IO_PREAD 2
#define IO_PWRITE 3
/* for nvmutil commands
*/
#define CMD_DUMP 0
#define CMD_SETMAC 1
#define CMD_SWAP 2
#define CMD_COPY 3
#define CMD_CAT 4
#define CMD_CAT16 5
#define CMD_CAT128 6
#define ARG_NOPART 0
#define ARG_PART 1
#define SKIP_CHECKSUM_READ 0
#define CHECKSUM_READ 1
#define SKIP_CHECKSUM_WRITE 0
#define CHECKSUM_WRITE 1
/* command table
*/
typedef void (*func_t)(void);
struct commands {
size_t chk;
char *str;
func_t run;
int argc;
unsigned char arg_part;
unsigned char chksum_read;
unsigned char chksum_write;
size_t rw_size; /* within the 4KB GbE part */
int flags; /* e.g. O_RDWR or O_RDONLY */
};
/* mac address
*/
struct macaddr {
char *str; /* set to rmac, or argv string */
char rmac[18]; /* xx:xx:xx:xx:xx:xx */
unsigned short mac_buf[3];
};
/* gbe.bin and tmpfile
*/
struct xfile {
int gbe_fd;
struct stat gbe_st;
int tmp_fd;
struct stat tmp_st;
char *tname; /* path of tmp file */
char *fname; /* path of gbe file */
unsigned char *buf; /* work memory for files */
int io_err_gbe; /* intermediary write (verification) */
int io_err_gbe_bin; /* final write (real file) */
int rw_check_err_read[2];
int rw_check_partial_read[2];
int rw_check_bad_part[2];
int post_rw_checksum[2];
off_t gbe_file_size;
off_t gbe_tmp_size;
size_t part;
unsigned char part_modified[2];
unsigned char part_valid[2];
unsigned char real_buf[GBE_BUF_SIZE];
unsigned char bufcmp[GBE_BUF_SIZE]; /* compare gbe/tmp/reads */
unsigned char pad[GBE_WORK_SIZE]; /* the file that wouldn't die */
/* we later rename in-place, using old fd. renameat() */
int dirfd;
char *base;
char *tmpbase;
};
/* Command table, MAC address, files
*
* BE CAREFUL when editing this
* to ensure that you also update
* the tables in xstatus()
*/
struct xstate {
struct commands cmd[7];
struct macaddr mac;
struct xfile f;
size_t i; /* index to cmd[] for current command */
int no_cmd;
/* Cat commands set this.
the cat cmd helpers check it */
int cat;
};
struct filesystem {
int rootfd;
};
struct xstate *xstart(int argc, char *argv[]);
struct xstate *xstatus(void);
/* Sanitize command tables.
*/
void sanitize_command_list(void);
void sanitize_command_index(size_t c);
/* Argument handling (user input)
*/
void set_cmd(int argc, char *argv[]);
void set_cmd_args(int argc, char *argv[]);
size_t conv_argv_part_num(const char *part_str);
/* Prep files for reading
*/
void open_gbe_file(void);
int fd_verify_regular(int fd,
const struct stat *expected,
struct stat *out);
int fd_verify_identity(int fd,
const struct stat *expected,
struct stat *out);
int fd_verify_dir_identity(int fd,
const struct stat *expected);
int is_owner(struct stat *st);
int lock_file(int fd, int flags);
int same_file(int fd, struct stat *st_old, int check_size);
/* Read GbE file and verify checksums
*/
void copy_gbe(void);
void read_file(void);
void read_checksums(void);
int good_checksum(size_t partnum);
/* validate commands
*/
void check_command_num(size_t c);
unsigned char valid_command(size_t c);
/* Helper functions for command: setmac
*/
void cmd_helper_setmac(void);
void parse_mac_string(void);
void set_mac_byte(size_t mac_byte_pos);
void set_mac_nib(size_t mac_str_pos,
size_t mac_byte_pos, size_t mac_nib_pos);
void write_mac_part(size_t partnum);
/* string functions
*/
size_t page_remain(const void *p);
long pagesize(void);
char *smalloc(char **buf, size_t size);
void *vmalloc(void **buf, size_t size);
size_t slen(const char *scmp, size_t maxlen,
size_t *rval);
int vcmp(const void *s1, const void *s2, size_t n);
int scmp(const char *a, const char *b,
size_t maxlen, int *rval);
int ccmp(const char *a, const char *b, size_t i,
int *rval);
int dup_pair(char **dir, const char *d,
char **base, const char *b);
char *sdup(const char *s,
size_t n, char **dest);
char *scatn(ssize_t sc, const char **sv,
size_t max, char **rval);
char *scat(const char *s1, const char *s2,
size_t n, char **dest);
void dcat(const char *s, size_t n,
size_t off, char **dest1,
char **dest2);
/* numerical functions
*/
unsigned short hextonum(char ch_s);
void spew_hex(const void *data, size_t len);
void *rmalloc(size_t n);
void rset(void *buf, size_t n);
void *rmalloc(size_t n);
char *rchars(size_t n);
size_t rsize(size_t n);
/* Helper functions for command: dump
*/
void cmd_helper_dump(void);
void print_mac_from_nvm(size_t partnum);
/* Helper functions for command: swap
*/
void cmd_helper_swap(void);
/* Helper functions for command: copy
*/
void cmd_helper_copy(void);
/* Helper functions for commands:
* cat, cat16 and cat128
*/
void cmd_helper_cat(void);
void cmd_helper_cat16(void);
void cmd_helper_cat128(void);
void cat(size_t nff);
void cat_buf(unsigned char *b);
/* Command verification/control
*/
void check_cmd(void (*fn)(void), const char *name);
void cmd_helper_err(void);
/* Write GbE files to disk
*/
void write_gbe_file(void);
void set_checksum(size_t part);
unsigned short calculated_checksum(size_t p);
/* NVM read/write
*/
unsigned short nvm_word(size_t pos16, size_t part);
void set_nvm_word(size_t pos16,
size_t part, unsigned short val16);
void set_part_modified(size_t p);
void check_nvm_bound(size_t pos16, size_t part);
void check_bin(size_t a, const char *a_name);
/* GbE file read/write
*/
void rw_gbe_file_part(size_t p, int rw_type,
const char *rw_type_str);
void write_to_gbe_bin(void);
int gbe_mv(void);
void check_written_part(size_t p);
void report_io_err_rw(void);
unsigned char *gbe_mem_offset(size_t part, const char *f_op);
off_t gbe_file_offset(size_t part, const char *f_op);
off_t gbe_x_offset(size_t part, const char *f_op,
const char *d_type, off_t nsize, off_t ncmp);
ssize_t rw_gbe_file_exact(int fd, unsigned char *mem, size_t nrw,
off_t off, int rw_type);
/* Generic read/write
*/
int fsync_dir(const char *path);
ssize_t rw_exact(int fd, unsigned char *mem, size_t len,
off_t off, int rw_type);
ssize_t rw(int fd, void *mem, size_t nrw,
off_t off, int rw_type);
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);
int sys_retry(int saved_errno, long rval);
int fs_retry(int saved_errno, int rval);
int rw_retry(int saved_errno, ssize_t rval);
/* Error handling and cleanup
*/
void usage(void);
int with_fallback_errno(int fallback);
void exitf(const char *msg, ...);
func_t errhook(func_t ptr); /* hook function for cleanup on err */
const char *lbgetprogname(void);
void no_op(void);
void err_mkhtemp(int errval, const char *msg, ...);
/* libc hardening
*/
int new_tmpfile(int *fd, char **path, char *tmpdir,
const char *template);
int new_tmpdir(int *fd, char **path, char *tmpdir,
const char *template);
int new_tmp_common(int *fd, char **path, int type,
char *tmpdir, const char *template);
int mkhtemp_try_create(int dirfd,
struct stat *st_dir_first,
char *fname_copy,
char *p,
size_t xc,
int *fd,
struct stat *st,
int type);
int
mkhtemp_tmpfile_linux(int dirfd,
struct stat *st_dir_first,
char *fname_copy,
char *p,
size_t xc,
int *fd,
struct stat *st);
int mkhtemp(int *fd, struct stat *st,
char *template, int dirfd, const char *fname,
struct stat *st_dir_first, int type);
int world_writeable_and_sticky(const char *s,
int sticky_allowed, int always_sticky);
int same_dir(const char *a, const char *b);
int tmpdir_policy(const char *path,
int *allow_noworld_unsticky);
char *env_tmpdir(int always_sticky, char **tmpdir,
char *override_tmpdir);
int secure_file(int *fd,
struct stat *st,
struct stat *expected,
int bad_flags,
int check_seek,
int do_lock,
mode_t mode);
void xclose(int *fd);
int fsync_on_eintr(int fd);
int fs_rename_at(int olddirfd, const char *old,
int newdirfd, const char *new);
int fs_open(const char *path, int flags);
void free_and_set_null(char **buf);
void open_file_on_eintr(const char *path, int *fd, int flags, mode_t mode,
struct stat *st);
struct filesystem *rootfs(void);
int fs_resolve_at(int dirfd, const char *path, int flags);
int fs_next_component(const char **p,
char *name, size_t namesz);
int fs_open_component(int dirfd, const char *name,
int flags, int is_last);
int fs_dirname_basename(const char *path,
char **dir, char **base, int allow_relative);
int openat_on_eintr(int dirfd, const char *path,
int flags, mode_t mode);
int mkdirat_on_eintr(int dirfd,
const char *pathname, mode_t mode);
int if_err(int condition, int errval);
int if_err_sys(int condition);
char *lbsetprogname(char *argv0);
/* asserts */
/* type asserts */
typedef char static_assert_char_is_8_bits[(CHAR_BIT == 8) ? 1 : -1];
typedef char static_assert_char_is_1[(sizeof(char) == 1) ? 1 : -1];
typedef char static_assert_unsigned_char_is_1[
(sizeof(unsigned char) == 1) ? 1 : -1];
typedef char static_assert_unsigned_short_is_2[
(sizeof(unsigned short) >= 2) ? 1 : -1];
typedef char static_assert_short_is_2[(sizeof(short) >= 2) ? 1 : -1];
typedef char static_assert_unsigned_int_is_4[
(sizeof(unsigned int) >= 4) ? 1 : -1];
typedef char static_assert_unsigned_ssize_t_is_4[
(sizeof(size_t) >= 4) ? 1 : -1];
typedef char static_assert_ssize_t_ussize_t[
(sizeof(size_t) == sizeof(ssize_t)) ? 1 : -1];
typedef char static_assert_int_ge_32[(sizeof(int) >= 4) ? 1 : -1];
typedef char static_assert_twos_complement[
((-1 & 3) == 3) ? 1 : -1
];
typedef char assert_unsigned_ssize_t_ptr[
(sizeof(size_t) >= sizeof(void *)) ? 1 : -1
];
/*
* We set _FILE_OFFSET_BITS 64, but we only handle
* but we only need smaller files, so require 4-bytes.
* Some operating systems ignore the define, hence assert:
*/
typedef char static_assert_off_t_is_32[(sizeof(off_t) >= 4) ? 1 : -1];
/*
* asserts (variables/defines sanity check)
*/
typedef char assert_argc3[(ARGC_3==3)?1:-1];
typedef char assert_argc4[(ARGC_4==4)?1:-1];
typedef char assert_read[(IO_READ==0)?1:-1];
typedef char assert_write[(IO_WRITE==1)?1:-1];
typedef char assert_pread[(IO_PREAD==2)?1:-1];
typedef char assert_pwrite[(IO_PWRITE==3)?1:-1];
typedef char assert_pathlen[(PATH_MAX>=1024)?1:-1];
/* commands */
typedef char assert_cmd_dump[(CMD_DUMP==0)?1:-1];
typedef char assert_cmd_setmac[(CMD_SETMAC==1)?1:-1];
typedef char assert_cmd_swap[(CMD_SWAP==2)?1:-1];
typedef char assert_cmd_copy[(CMD_COPY==3)?1:-1];
typedef char assert_cmd_cat[(CMD_CAT==4)?1:-1];
typedef char assert_cmd_cat16[(CMD_CAT16==5)?1:-1];
typedef char assert_cmd_cat128[(CMD_CAT128==6)?1:-1];
/* bool */
typedef char bool_arg_nopart[(ARG_NOPART==0)?1:-1];
typedef char bool_arg_part[(ARG_PART==1)?1:-1];
typedef char bool_skip_checksum_read[(SKIP_CHECKSUM_READ==0)?1:-1];
typedef char bool_checksum_read[(CHECKSUM_READ==1)?1:-1];
typedef char bool_skip_checksum_write[(SKIP_CHECKSUM_WRITE==0)?1:-1];
typedef char bool_checksum_write[(CHECKSUM_WRITE==1)?1:-1];
#endif
#endif
-108
View File
@@ -1,108 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
*
* Functions related to GbE NVM checksums.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include "../include/common.h"
void
read_checksums(void)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
size_t _p;
size_t _skip_part;
unsigned char _num_invalid;
unsigned char _max_invalid;
f->part_valid[0] = 0;
f->part_valid[1] = 0;
if (!cmd->chksum_read)
return;
_num_invalid = 0;
_max_invalid = 2;
if (cmd->arg_part)
_max_invalid = 1;
/* Skip verification on this part,
* but only when arg_part is set.
*/
_skip_part = f->part ^ 1;
for (_p = 0; _p < 2; _p++) {
/* Only verify a part if it was *read*
*/
if (cmd->arg_part && (_p == _skip_part))
continue;
f->part_valid[_p] = good_checksum(_p);
if (!f->part_valid[_p])
++_num_invalid;
}
if (_num_invalid >= _max_invalid) {
if (_max_invalid == 1)
exitf("%s: part %lu has a bad checksum",
f->fname, (size_t)f->part);
exitf("%s: No valid checksum found in file",
f->fname);
}
}
int
good_checksum(size_t partnum)
{
unsigned short expected_checksum;
unsigned short actual_checksum;
expected_checksum =
calculated_checksum(partnum);
actual_checksum =
nvm_word(NVM_CHECKSUM_WORD, partnum);
if (expected_checksum == actual_checksum) {
return 1;
} else {
return 0;
}
}
void
set_checksum(size_t p)
{
check_bin(p, "part number");
set_nvm_word(NVM_CHECKSUM_WORD, p, calculated_checksum(p));
}
unsigned short
calculated_checksum(size_t p)
{
size_t c;
unsigned int val16;
val16 = 0;
for (c = 0; c < NVM_CHECKSUM_WORD; c++)
val16 += (unsigned int)nvm_word(c, p);
return (unsigned short)((NVM_CHECKSUM - val16) & 0xffff);
}
-521
View File
@@ -1,521 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include "../include/common.h"
void
sanitize_command_list(void)
{
struct xstate *x = xstatus();
size_t c;
size_t num_commands;
num_commands = items(x->cmd);
for (c = 0; c < num_commands; c++)
sanitize_command_index(c);
}
void
sanitize_command_index(size_t c)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[c];
int _flag;
size_t gbe_rw_size;
size_t rval;
check_command_num(c);
if (cmd->argc < 3)
exitf("cmd index %lu: argc below 3, %d",
(size_t)c, cmd->argc);
if (cmd->str == NULL)
exitf("cmd index %lu: NULL str",
(size_t)c);
if (*cmd->str == '\0')
exitf("cmd index %lu: empty str",
(size_t)c);
if (slen(cmd->str, MAX_CMD_LEN +1, &rval) > MAX_CMD_LEN) {
exitf("cmd index %lu: str too long: %s",
(size_t)c, cmd->str);
}
if (cmd->run == NULL)
exitf("cmd index %lu: cmd ptr null",
(size_t)c);
check_bin(cmd->arg_part, "cmd.arg_part");
check_bin(cmd->chksum_read, "cmd.chksum_read");
check_bin(cmd->chksum_write, "cmd.chksum_write");
gbe_rw_size = cmd->rw_size;
switch (gbe_rw_size) {
case GBE_PART_SIZE:
case NVM_SIZE:
break;
default:
exitf("Unsupported rw_size: %lu",
(size_t)gbe_rw_size);
}
if (gbe_rw_size > GBE_PART_SIZE)
exitf("rw_size larger than GbE part: %lu",
(size_t)gbe_rw_size);
_flag = (cmd->flags & O_ACCMODE);
if (_flag != O_RDONLY &&
_flag != O_RDWR)
exitf("invalid cmd.flags setting");
}
void
set_cmd(int argc, char *argv[])
{
struct xstate *x = xstatus();
const char *cmd;
int rval;
size_t c;
for (c = 0; c < items(x->cmd); c++) {
cmd = x->cmd[c].str;
if (scmp(argv[2], cmd, MAX_CMD_LEN, &rval))
continue; /* not the right command */
/* valid command found */
if (argc >= x->cmd[c].argc) {
x->no_cmd = 0;
x->i = c; /* set command */
return;
}
exitf(
"Too few args on command '%s'", cmd);
}
x->no_cmd = 1;
}
void
set_cmd_args(int argc, char *argv[])
{
struct xstate *x = xstatus();
size_t i = x->i;
struct commands *cmd = &x->cmd[i];
struct xfile *f = &x->f;
if (!valid_command(i) || argc < 3)
usage();
if (x->no_cmd)
usage();
/* Maintainer bug
*/
if (cmd->arg_part && argc < 4)
exitf(
"arg_part set for command that needs argc4");
if (cmd->arg_part && i == CMD_SETMAC)
exitf(
"arg_part set on CMD_SETMAC");
if (i == CMD_SETMAC) {
if (argc >= 4)
x->mac.str = argv[3];
else
x->mac.str = x->mac.rmac;
} else if (cmd->arg_part) {
f->part = conv_argv_part_num(argv[3]);
}
}
size_t
conv_argv_part_num(const char *part_str)
{
unsigned char ch;
if (part_str[0] == '\0' || part_str[1] != '\0')
exitf("Partnum string '%s' wrong length", part_str);
/* char signedness is implementation-defined
*/
ch = (unsigned char)part_str[0];
if (ch < '0' || ch > '1')
exitf("Bad part number (%c)", ch);
return (size_t)(ch - '0');
}
void
check_command_num(size_t c)
{
if (!valid_command(c))
exitf("Invalid run_cmd arg: %lu",
(size_t)c);
}
unsigned char
valid_command(size_t c)
{
struct xstate *x = xstatus();
struct commands *cmd;
if (c >= items(x->cmd))
return 0;
cmd = &x->cmd[c];
if (c != cmd->chk)
exitf(
"Invalid cmd chk value (%lu) vs arg: %lu",
cmd->chk, c);
return 1;
}
void
cmd_helper_setmac(void)
{
struct xstate *x = xstatus();
struct macaddr *mac = &x->mac;
size_t partnum;
check_cmd(cmd_helper_setmac, "setmac");
printf("MAC address to be written: %s\n", mac->str);
parse_mac_string();
for (partnum = 0; partnum < 2; partnum++)
write_mac_part(partnum);
}
void
parse_mac_string(void)
{
struct xstate *x = xstatus();
struct macaddr *mac = &x->mac;
size_t mac_byte;
size_t rval;
if (slen(x->mac.str, 18, &rval) != 17)
exitf("MAC address is the wrong length");
memset(mac->mac_buf, 0, sizeof(mac->mac_buf));
for (mac_byte = 0; mac_byte < 6; mac_byte++)
set_mac_byte(mac_byte);
if ((mac->mac_buf[0] | mac->mac_buf[1] | mac->mac_buf[2]) == 0)
exitf("Must not specify all-zeroes MAC address");
if (mac->mac_buf[0] & 1)
exitf("Must not specify multicast MAC address");
}
void
set_mac_byte(size_t mac_byte_pos)
{
struct xstate *x = xstatus();
struct macaddr *mac = &x->mac;
char separator;
size_t mac_str_pos;
size_t mac_nib_pos;
mac_str_pos = mac_byte_pos * 3;
if (mac_str_pos < 15) {
if ((separator = mac->str[mac_str_pos + 2]) != ':')
exitf("Invalid MAC address separator '%c'",
separator);
}
for (mac_nib_pos = 0; mac_nib_pos < 2; mac_nib_pos++)
set_mac_nib(mac_str_pos, mac_byte_pos, mac_nib_pos);
}
void
set_mac_nib(size_t mac_str_pos,
size_t mac_byte_pos, size_t mac_nib_pos)
{
struct xstate *x = xstatus();
struct macaddr *mac = &x->mac;
char mac_ch;
unsigned short hex_num;
mac_ch = mac->str[mac_str_pos + mac_nib_pos];
if ((hex_num = hextonum(mac_ch)) > 15) {
if (hex_num >= 17)
exitf("Randomisation failure");
else
exitf("Invalid character '%c'",
mac->str[mac_str_pos + mac_nib_pos]);
}
/* If random, ensure that local/unicast bits are set.
*/
if ((mac_byte_pos == 0) && (mac_nib_pos == 1) &&
((mac_ch | 0x20) == 'x' ||
(mac_ch == '?')))
hex_num = (hex_num & 0xE) | 2; /* local, unicast */
/* MAC words stored big endian in-file, little-endian
* logically, so we reverse the order.
*/
mac->mac_buf[mac_byte_pos >> 1] |= hex_num <<
(((mac_byte_pos & 1) << 3) /* left or right byte? */
| ((mac_nib_pos ^ 1) << 2)); /* left or right nib? */
}
void
write_mac_part(size_t partnum)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
struct macaddr *mac = &x->mac;
size_t w;
check_bin(partnum, "part number");
if (!f->part_valid[partnum])
return;
for (w = 0; w < 3; w++)
set_nvm_word(w, partnum, mac->mac_buf[w]);
printf("Wrote MAC address to part %lu: ",
(size_t)partnum);
print_mac_from_nvm(partnum);
}
void
cmd_helper_dump(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
size_t p;
check_cmd(cmd_helper_dump, "dump");
f->part_valid[0] = good_checksum(0);
f->part_valid[1] = good_checksum(1);
for (p = 0; p < 2; p++) {
if (!f->part_valid[p]) {
fprintf(stderr,
"BAD checksum %04x in part %lu (expected %04x)\n",
nvm_word(NVM_CHECKSUM_WORD, p),
(size_t)p,
calculated_checksum(p));
}
printf("MAC (part %lu): ",
(size_t)p);
print_mac_from_nvm(p);
spew_hex(f->buf + (p * GBE_PART_SIZE), NVM_SIZE);
}
}
void
print_mac_from_nvm(size_t partnum)
{
size_t c;
unsigned short val16;
for (c = 0; c < 3; c++) {
val16 = nvm_word(c, partnum);
printf("%02x:%02x",
(unsigned int)(val16 & 0xff),
(unsigned int)(val16 >> 8));
if (c == 2)
printf("\n");
else
printf(":");
}
}
void
cmd_helper_swap(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
check_cmd(cmd_helper_swap, "swap");
memcpy(
f->buf + (size_t)GBE_WORK_SIZE,
f->buf,
GBE_PART_SIZE);
memcpy(
f->buf,
f->buf + (size_t)GBE_PART_SIZE,
GBE_PART_SIZE);
memcpy(
f->buf + (size_t)GBE_PART_SIZE,
f->buf + (size_t)GBE_WORK_SIZE,
GBE_PART_SIZE);
set_part_modified(0);
set_part_modified(1);
}
void
cmd_helper_copy(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
check_cmd(cmd_helper_copy, "copy");
memcpy(
f->buf + (size_t)((f->part ^ 1) * GBE_PART_SIZE),
f->buf + (size_t)(f->part * GBE_PART_SIZE),
GBE_PART_SIZE);
set_part_modified(f->part ^ 1);
}
void
cmd_helper_cat(void)
{
struct xstate *x = xstatus();
check_cmd(cmd_helper_cat, "cat");
x->cat = 0;
cat(0);
}
void
cmd_helper_cat16(void)
{
struct xstate *x = xstatus();
check_cmd(cmd_helper_cat16, "cat16");
x->cat = 1;
cat(1);
}
void
cmd_helper_cat128(void)
{
struct xstate *x = xstatus();
check_cmd(cmd_helper_cat128, "cat128");
x->cat = 15;
cat(15);
}
void
cat(size_t nff)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
size_t p;
size_t ff;
p = 0;
ff = 0;
if ((size_t)x->cat != nff) {
exitf("erroneous call to cat");
}
fflush(NULL);
memset(f->pad, 0xff, GBE_PART_SIZE);
for (p = 0; p < 2; p++) {
cat_buf(f->bufcmp +
(size_t)(p * (f->gbe_file_size >> 1)));
for (ff = 0; ff < nff; ff++) {
cat_buf(f->pad);
}
}
}
void
cat_buf(unsigned char *b)
{
if (b == NULL)
exitf("null pointer in cat command");
if (rw_exact(STDOUT_FILENO, b,
GBE_PART_SIZE, 0, IO_WRITE) < 0)
exitf("stdout: cat");
}
void
check_cmd(void (*fn)(void),
const char *name)
{
struct xstate *x = xstatus();
size_t i = x->i;
if (x->cmd[i].run != fn)
exitf("Running %s, but cmd %s is set",
name, x->cmd[i].str);
/* prevent second command
*/
for (i = 0; i < items(x->cmd); i++)
x->cmd[i].run = cmd_helper_err;
}
void
cmd_helper_err(void)
{
exitf(
"Erroneously running command twice");
}
-817
View File
@@ -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();
}
-563
View File
@@ -1,563 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* I/O functions specific to nvmutil.
*/
/* TODO: local tmpfiles not being deleted
when flags==O_RDONLY e.g. dump command
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../include/common.h"
void
open_gbe_file(void)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
int saved_errno = errno;
errno = 0;
int _flags;
f->gbe_fd = -1;
open_file_on_eintr(f->fname, &f->gbe_fd,
O_NOFOLLOW | O_CLOEXEC | O_NOCTTY,
((cmd->flags & O_ACCMODE) == O_RDONLY) ? 0400 : 0600,
&f->gbe_st);
if (f->gbe_st.st_nlink > 1)
exitf(
"%s: warning: file has multiple (%lu) hard links\n",
f->fname, (size_t)f->gbe_st.st_nlink);
if (f->gbe_st.st_nlink == 0)
exitf("%s: file unlinked while open", f->fname);
if ((_flags = fcntl(f->gbe_fd, F_GETFL)) == -1)
exitf("%s: fcntl(F_GETFL)", f->fname);
/* O_APPEND allows POSIX write() to ignore
* the current write offset and write at EOF,
* which would break positional read/write
*/
if (_flags & O_APPEND)
exitf("%s: O_APPEND flag", f->fname);
f->gbe_file_size = f->gbe_st.st_size;
switch (f->gbe_file_size) {
case SIZE_8KB:
case SIZE_16KB:
case SIZE_128KB:
break;
default:
exitf("File size must be 8KB, 16KB or 128KB");
}
/* currently fails (EBADF), locks are advisory anyway: */
/*
if (lock_file(f->gbe_fd, cmd->flags) == -1)
exitf("%s: can't lock", f->fname);
*/
reset_caller_errno(0);
}
void
copy_gbe(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
read_file();
if (f->gbe_file_size == SIZE_8KB)
return;
memcpy(f->buf + (size_t)GBE_PART_SIZE,
f->buf + (size_t)(f->gbe_file_size >> 1),
(size_t)GBE_PART_SIZE);
}
void
read_file(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
struct stat _st;
ssize_t _r;
/* read main file
*/
_r = rw_exact(f->gbe_fd, f->buf, f->gbe_file_size,
0, IO_PREAD);
if (_r < 0)
exitf("%s: read failed", f->fname);
/* copy to tmpfile
*/
_r = rw_exact(f->tmp_fd, f->buf, f->gbe_file_size,
0, IO_PWRITE);
if (_r < 0)
exitf("%s: %s: copy failed",
f->fname, f->tname);
/* file size comparison
*/
if (fstat(f->tmp_fd, &_st) == -1)
exitf("%s: stat", f->tname);
f->gbe_tmp_size = _st.st_size;
if (f->gbe_tmp_size != f->gbe_file_size)
exitf("%s: %s: not the same size",
f->fname, f->tname);
/* needs sync, for verification
*/
if (fsync_on_eintr(f->tmp_fd) == -1)
exitf("%s: fsync (tmpfile copy)", f->tname);
_r = rw_exact(f->tmp_fd, f->bufcmp, f->gbe_file_size,
0, IO_PREAD);
if (_r < 0)
exitf("%s: read failed (cmp)", f->tname);
if (vcmp(f->buf, f->bufcmp, f->gbe_file_size) != 0)
exitf("%s: %s: read contents differ (pre-test)",
f->fname, f->tname);
}
void
write_gbe_file(void)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
size_t p;
unsigned char update_checksum;
if ((cmd->flags & O_ACCMODE) == O_RDONLY)
return;
if (same_file(f->tmp_fd, &f->tmp_st, 0) < 0)
exitf("%s: file inode/device changed", f->tname);
if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0)
exitf("%s: file has changed", f->fname);
update_checksum = cmd->chksum_write;
for (p = 0; p < 2; p++) {
if (!f->part_modified[p])
continue;
if (update_checksum)
set_checksum(p);
rw_gbe_file_part(p, IO_PWRITE, "pwrite");
}
}
void
rw_gbe_file_part(size_t p, int rw_type,
const char *rw_type_str)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
ssize_t rval;
off_t file_offset;
size_t gbe_rw_size;
unsigned char *mem_offset;
gbe_rw_size = cmd->rw_size;
if (rw_type < IO_PREAD || rw_type > IO_PWRITE)
exitf("%s: %s: part %lu: invalid rw_type, %d",
f->fname, rw_type_str, (size_t)p, rw_type);
mem_offset = gbe_mem_offset(p, rw_type_str);
file_offset = (off_t)gbe_file_offset(p, rw_type_str);
rval = rw_gbe_file_exact(f->tmp_fd, mem_offset,
gbe_rw_size, file_offset, rw_type);
if (rval == -1)
exitf("%s: %s: part %lu",
f->fname, rw_type_str, (size_t)p);
if ((size_t)rval != gbe_rw_size)
exitf("%s: partial %s: part %lu",
f->fname, rw_type_str, (size_t)p);
}
void
write_to_gbe_bin(void)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
int saved_errno;
int mv;
if ((cmd->flags & O_ACCMODE) != O_RDWR)
return;
write_gbe_file();
/* We may otherwise read from
* cache, so we must sync.
*/
if (fsync_on_eintr(f->tmp_fd) == -1)
exitf("%s: fsync (pre-verification)",
f->tname);
check_written_part(0);
check_written_part(1);
report_io_err_rw();
if (f->io_err_gbe)
exitf("%s: bad write", f->fname);
saved_errno = errno;
xclose(&f->tmp_fd);
xclose(&f->gbe_fd);
errno = saved_errno;
/* tmpfile written, now we
* rename it back to the main file
* (we do atomic writes)
*/
f->tmp_fd = -1;
f->gbe_fd = -1;
if (!f->io_err_gbe_bin) {
mv = gbe_mv();
if (mv < 0) {
f->io_err_gbe_bin = 1;
fprintf(stderr, "%s: %s\n",
f->fname, strerror(errno));
} else {
/* removed by rename
*/
free_and_set_null(&f->tname);
}
}
if (!f->io_err_gbe_bin)
return;
fprintf(stderr, "FAIL (rename): %s: skipping fsync\n",
f->fname);
if (errno)
fprintf(stderr,
"errno %d: %s\n", errno, strerror(errno));
}
void
check_written_part(size_t p)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
ssize_t rval;
size_t gbe_rw_size;
off_t file_offset;
unsigned char *mem_offset;
unsigned char *buf_restore;
if (!f->part_modified[p])
return;
gbe_rw_size = cmd->rw_size;
mem_offset = gbe_mem_offset(p, "pwrite");
file_offset = (off_t)gbe_file_offset(p, "pwrite");
memset(f->pad, 0xff, sizeof(f->pad));
if (same_file(f->tmp_fd, &f->tmp_st, 0) < 0)
exitf("%s: file inode/device changed", f->tname);
if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0)
exitf("%s: file changed during write", f->fname);
rval = rw_gbe_file_exact(f->tmp_fd, f->pad,
gbe_rw_size, file_offset, IO_PREAD);
if (rval == -1)
f->rw_check_err_read[p] = f->io_err_gbe = 1;
else if ((size_t)rval != gbe_rw_size)
f->rw_check_partial_read[p] = f->io_err_gbe = 1;
else if (vcmp(mem_offset, f->pad, gbe_rw_size) != 0)
f->rw_check_bad_part[p] = f->io_err_gbe = 1;
if (f->rw_check_err_read[p] ||
f->rw_check_partial_read[p])
return;
/* We only load one part on-file, into memory but
* always at offset zero, for post-write checks.
* That's why we hardcode good_checksum(0)
*/
buf_restore = f->buf;
/* good_checksum works on f->buf
* so let's change f->buf for now
*/
f->buf = f->pad;
if (good_checksum(0))
f->post_rw_checksum[p] = 1;
f->buf = buf_restore;
}
void
report_io_err_rw(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
size_t p;
if (!f->io_err_gbe)
return;
for (p = 0; p < 2; p++) {
if (!f->part_modified[p])
continue;
if (f->rw_check_err_read[p])
fprintf(stderr,
"%s: pread: p%lu (post-verification)\n",
f->fname, (size_t)p);
if (f->rw_check_partial_read[p])
fprintf(stderr,
"%s: partial pread: p%lu (post-verification)\n",
f->fname, (size_t)p);
if (f->rw_check_bad_part[p])
fprintf(stderr,
"%s: pwrite: corrupt write on p%lu\n",
f->fname, (size_t)p);
if (f->rw_check_err_read[p] ||
f->rw_check_partial_read[p]) {
fprintf(stderr,
"%s: p%lu: skipped checksum verification "
"(because read failed)\n",
f->fname, (size_t)p);
continue;
}
fprintf(stderr, "%s: ", f->fname);
if (f->post_rw_checksum[p])
fprintf(stderr, "GOOD");
else
fprintf(stderr, "BAD");
fprintf(stderr, " checksum in p%lu on-disk.\n",
(size_t)p);
if (f->post_rw_checksum[p]) {
fprintf(stderr,
" This does NOT mean it's safe. it may be\n"
" salvageable if you use the cat feature.\n");
}
}
}
int
gbe_mv(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
int rval;
int saved_errno;
int tmp_gbe_bin_exists;
/* will be set 0 if it doesn't
*/
tmp_gbe_bin_exists = 1;
saved_errno = errno;
rval = fs_rename_at(f->dirfd, f->tmpbase,
f->dirfd, f->base);
if (rval > -1)
tmp_gbe_bin_exists = 0;
if (f->gbe_fd > -1) {
xclose(&f->gbe_fd);
if (fsync_dir(f->fname) < 0) {
f->io_err_gbe_bin = 1;
rval = -1;
}
}
xclose(&f->tmp_fd);
/* before this function is called,
* tmp_fd may have been moved
*/
if (tmp_gbe_bin_exists) {
if (unlink(f->tname) < 0)
rval = -1;
else
tmp_gbe_bin_exists = 0;
}
if (rval >= 0)
goto out;
return with_fallback_errno(EIO);
out:
reset_caller_errno(rval);
return rval;
}
/* This one is similar to gbe_file_offset,
* but used to check Gbe bounds in memory,
* and it is *also* used during file I/O.
*/
unsigned char *
gbe_mem_offset(size_t p, const char *f_op)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
off_t gbe_off;
gbe_off = gbe_x_offset(p, f_op, "mem",
GBE_PART_SIZE, GBE_WORK_SIZE);
return (unsigned char *)
(f->buf + (size_t)gbe_off);
}
/* I/O operations filtered here. These operations must
* only write from the 0th position or the half position
* within the GbE file, and write 4KB of data.
*/
off_t
gbe_file_offset(size_t p, const char *f_op)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
off_t gbe_file_half_size;
gbe_file_half_size = f->gbe_file_size >> 1;
return gbe_x_offset(p, f_op, "file",
gbe_file_half_size, f->gbe_file_size);
}
off_t
gbe_x_offset(size_t p, const char *f_op, const char *d_type,
off_t nsize, off_t ncmp)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
off_t off;
check_bin(p, "part number");
off = ((off_t)p) * (off_t)nsize;
if (off > ncmp - GBE_PART_SIZE)
exitf("%s: GbE %s %s out of bounds",
f->fname, d_type, f_op);
if (off != 0 && off != ncmp >> 1)
exitf("%s: GbE %s %s at bad offset",
f->fname, d_type, f_op);
return off;
}
ssize_t
rw_gbe_file_exact(int fd, unsigned char *mem, size_t nrw,
off_t off, int rw_type)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
ssize_t r;
if (io_args(fd, mem, nrw, off, rw_type) == -1)
return -1;
if (mem != (void *)f->pad) {
if (mem < f->buf)
goto err_rw_gbe_file_exact;
if ((size_t)(mem - f->buf) >= GBE_WORK_SIZE)
goto err_rw_gbe_file_exact;
}
if (off < 0 || off >= f->gbe_file_size)
goto err_rw_gbe_file_exact;
if (nrw > (size_t)(f->gbe_file_size - off))
goto err_rw_gbe_file_exact;
if (nrw > (size_t)GBE_PART_SIZE)
goto err_rw_gbe_file_exact;
r = rw_exact(fd, mem, nrw, off, rw_type);
return rw_over_nrw(r, nrw);
err_rw_gbe_file_exact:
return with_fallback_errno(EIO);
}
-914
View File
@@ -1,914 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* Hardened mktemp (be nice to the demon).
*/
/* for openat2 / fast path: */
#ifdef __linux__
#if !defined(USE_OPENAT) || \
((USE_OPENAT) < 1) /* if 1: use openat, not openat2 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <sys/syscall.h>
#include <linux/openat2.h>
#ifndef O_TMPFILE
#define O_TMPFILE 020000000
#endif
#ifndef AT_EMPTY_PATH
#define AT_EMPTY_PATH 0x1000
#endif
#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"
/* note: tmpdir is an override of TMPDIR or /tmp or /var/tmp */
int
new_tmpfile(int *fd, char **path, char *tmpdir,
const char *template)
{
return new_tmp_common(fd, path, MKHTEMP_FILE,
tmpdir, template);
}
/* note: tmpdir is an override of TMPDIR or /tmp or /var/tmp */
int
new_tmpdir(int *fd, char **path, char *tmpdir,
const char *template)
{
return new_tmp_common(fd, path, MKHTEMP_DIR,
tmpdir, template);
}
int
new_tmp_common(int *fd, char **path, int type,
char *tmpdir, const char *template)
{
struct stat st;
const char *templatestr;
size_t dirlen;
char *dest = NULL; /* final path (will be written into "path") */
int saved_errno = errno;
int dirfd = -1;
const char *fname = NULL;
struct stat st_dir_first;
char *fail_dir = NULL;
errno = 0;
if (if_err(path == NULL || fd == NULL, EFAULT) ||
if_err(*fd >= 0, EEXIST)) /* don't touch someone else's file */
goto err;
/* regarding **path:
* the pointer (to the pointer)
* must nott be null, but we don't
* care about the pointer it points
* to. you should expect it to be
* replaced upon successful return
*
* (on error, it will not be touched)
*/
*fd = -1;
if (tmpdir == NULL) { /* no user override */
#if defined(PERMIT_NON_STICKY_ALWAYS) && \
((PERMIT_NON_STICKY_ALWAYS) > 0)
tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS, &fail_dir, NULL);
#else
tmpdir = env_tmpdir(0, &fail_dir, NULL);
#endif
} else {
#if defined(PERMIT_NON_STICKY_ALWAYS) && \
((PERMIT_NON_STICKY_ALWAYS) > 0)
tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS, &fail_dir,
tmpdir);
#else
tmpdir = env_tmpdir(0, &fail_dir, tmpdir);
#endif
}
if (if_err(tmpdir ==NULL || *tmpdir == '\0' || *tmpdir != '/', EINVAL))
goto err;
if (template != NULL)
templatestr = template;
else
templatestr = "tmp.XXXXXXXXXX";
/* may as well calculate in advance */
dirlen = slen(tmpdir, PATH_MAX, &dirlen);
/* full path: */
dest = scatn(3, (const char *[]) { tmpdir, "/", templatestr },
PATH_MAX, &dest);
fname = dest + dirlen + 1;
dirfd = fs_open(tmpdir,
O_RDONLY | O_DIRECTORY);
if (dirfd < 0)
goto err;
if (fstat(dirfd, &st_dir_first) < 0)
goto err;
*fd = mkhtemp(fd, &st, dest, dirfd,
fname, &st_dir_first, type);
if (*fd < 0)
goto err;
xclose(&dirfd);
errno = saved_errno;
*path = dest;
reset_caller_errno(0);
return 0;
err:
free_and_set_null(&dest);
xclose(&dirfd);
xclose(fd);
/* where a TMPDIR isn't found, and we err,
* we pass this back through for the
* error message
*/
if (fail_dir != NULL)
*path = fail_dir;
errno = saved_errno;
return with_fallback_errno(EIO);
}
/* hardened TMPDIR parsing
*/
char *
env_tmpdir(int bypass_all_sticky_checks, char **tmpdir,
char *override_tmpdir)
{
char *t = NULL;
int allow_noworld_unsticky;
int saved_errno = errno;
static const char tmp[] = "/tmp";
static const char vartmp[] = "/var/tmp";
char *rval = NULL;
errno = 0;
/* tmpdir is a user override, if set */
if (override_tmpdir == NULL)
t = getenv("TMPDIR");
else
t = override_tmpdir;
if (t != NULL && *t != '\0') {
if (tmpdir_policy(t,
&allow_noworld_unsticky) < 0)
goto err;
if (!world_writeable_and_sticky(t,
allow_noworld_unsticky,
bypass_all_sticky_checks))
goto err;
rval = NULL;
if (t != NULL) {
if (sdup(t, PATH_MAX, &rval) == NULL)
goto err;
}
goto out;
}
allow_noworld_unsticky = 0;
if (world_writeable_and_sticky(tmp, allow_noworld_unsticky,
bypass_all_sticky_checks))
rval = (char *)tmp;
else if (world_writeable_and_sticky(vartmp,
allow_noworld_unsticky, bypass_all_sticky_checks))
rval = (char *)vartmp;
else
goto err;
out:
reset_caller_errno(0);
if (tmpdir != NULL)
*tmpdir = rval;
return rval;
err:
if (tmpdir != NULL && t != NULL)
*tmpdir = t;
(void) with_fallback_errno(EPERM);
return NULL;
}
int
tmpdir_policy(const char *path,
int *allow_noworld_unsticky)
{
int saved_errno = errno;
int r;
errno = 0;
if (if_err(path == NULL ||
allow_noworld_unsticky == NULL, EFAULT))
goto err_tmpdir_policy;
*allow_noworld_unsticky = 1;
r = same_dir(path, "/tmp");
if (r < 0)
goto err_tmpdir_policy;
if (r > 0)
*allow_noworld_unsticky = 0;
r = same_dir(path, "/var/tmp");
if (r < 0)
goto err_tmpdir_policy;
if (r > 0)
*allow_noworld_unsticky = 0;
reset_caller_errno(0);
return 0;
err_tmpdir_policy:
return with_fallback_errno(EPERM);
}
int
same_dir(const char *a, const char *b)
{
int fd_a = -1;
int fd_b = -1;
struct stat st_a;
struct stat st_b;
int saved_errno = errno;
int rval = 0; /* LOGICAL error, 0, if 0 is returned */
errno = 0;
/* optimisation: if both dirs
are the same, we don't need
to check anything. sehr schnell!
*/
/* bonus: scmp checks null for us */
if (!scmp(a, b, PATH_MAX, &rval))
goto success_same_dir;
else
rval = 0; /* reset */
if ((fd_a = fs_open(a, O_RDONLY | O_DIRECTORY | O_NOFOLLOW)) < 0 ||
(fd_b = fs_open(b, O_RDONLY | O_DIRECTORY | O_NOFOLLOW)) < 0 ||
fstat(fd_a, &st_a) < 0 ||
fstat(fd_b, &st_b) < 0)
goto err_same_dir;
if (st_a.st_dev == st_b.st_dev &&
st_a.st_ino == st_b.st_ino) {
success_same_dir:
rval = 1; /* SUCCESS */
}
xclose(&fd_a);
xclose(&fd_b);
/* we reset caller errno regardless
* of success, so long as it's not
* a syscall error
*/
reset_caller_errno(0);
return rval;
err_same_dir:
/* FAILURE (probably syscall) - returns -1
*/
xclose(&fd_a);
xclose(&fd_b);
return with_fallback_errno(EIO); /* -1 */
}
/* bypass_all_sticky_checks: if set,
disable stickiness checks (libc behaviour)
(if not set: leah behaviour)
allow_noworld_unsticky:
allow non-sticky files if not world-writeable
(still block non-sticky in standard TMPDIR)
*/
int
world_writeable_and_sticky(
const char *s,
int allow_noworld_unsticky,
int bypass_all_sticky_checks)
{
struct stat st;
int dirfd = -1;
int saved_errno = errno;
errno = 0;
if (if_err(s == NULL || *s == '\0', EINVAL) ||
(dirfd = fs_open(s, O_RDONLY | O_DIRECTORY)) < 0 ||
fstat(dirfd, &st) < 0 ||
if_err(!S_ISDIR(st.st_mode), ENOTDIR))
goto sticky_hell;
/* *normal-**ish mode (libc):
*/
if (bypass_all_sticky_checks)
goto sticky_heaven; /* normal == no security */
/* extremely not-libc mode:
* only require stickiness on world-writeable dirs:
*/
if (st.st_mode & S_IWOTH) { /* world writeable */
if (if_err(!(st.st_mode & S_ISVTX), EPERM))
goto sticky_hell; /* not sticky */
goto sticky_heaven; /* sticky! */
} else if (allow_noworld_unsticky) {
goto sticky_heaven; /* sticky visa */
} else {
goto sticky_hell; /* visa denied */
}
sticky_heaven:
if (faccessat(dirfd, ".", X_OK, AT_EACCESS) < 0)
goto sticky_hell; /* down you go! */
xclose(&dirfd);
reset_caller_errno(0);
return 1;
sticky_hell:
xclose(&dirfd);
(void) with_fallback_errno(EPERM);
return 0;
}
/* mk(h)temp - hardened mktemp.
* like mkstemp, but (MUCH) harder.
*
* designed to resist TOCTOU attacks
* e.g. directory race / symlink attack
*
* extremely strict and even implements
* some limited userspace-level sandboxing,
* similar in spirit to openbsd unveil,
* though unveil is from kernel space.
*
* supports both files and directories.
* file: type = MKHTEMP_FILE (0)
* dir: type = MKHTEMP_DIR (1)
*
* DESIGN NOTES:
*
* caller is expected to handle
* cleanup e.g. free(), on *st,
* *template, *fname (all of the
* pointers). ditto fd cleanup.
*
* some limited cleanup is
* performed here, e.g. directory/file
* cleanup on error in mkhtemp_try_create
*
* we only check if these are not NULL,
* and the caller is expected to take
* care; without too many conditions,
* these functions are more flexible,
* but some precauttions are taken:
*
* when used via the function new_tmpfile
* or new_tmpdir, thtis is extremely strict,
* much stricter than previous mktemp
* variants. for example, it is much
* stricter about stickiness on world
* writeable directories, and it enforces
* file ownership under hardened mode
* (only lets you touch your own files/dirs)
*/
/*
TODO:
some variables e.g. template vs suffix,
assumes they match.
we should test this explicitly,
but the way this is called is
currently safe - this would however
be nice for future library use
by outside projects.
this whole code needs to be reorganised
*/
int
mkhtemp(int *fd,
struct stat *st,
char *template,
int dirfd,
const char *fname,
struct stat *st_dir_first,
int type)
{
size_t template_len = 0;
size_t xc = 0;
size_t fname_len = 0;
char *fname_copy = NULL;
char *p;
size_t retries;
int saved_errno = errno;
int r;
char *end;
errno = 0;
if (if_err(fd == NULL || template == NULL || fname == NULL ||
st_dir_first == NULL, EFAULT) ||
if_err(*fd >= 0, EEXIST) ||
if_err(dirfd < 0, EBADF))
goto err;
/* count X */
for (end = template + slen(template, PATH_MAX, &template_len);
end > template && *--end == 'X'; xc++);
fname_len = slen(fname, PATH_MAX, &fname_len);
if (if_err(strrchr(fname, '/') != NULL, EINVAL))
goto err;
if (if_err(xc < 3 || xc > template_len, EINVAL) ||
if_err(fname_len > template_len, EOVERFLOW))
goto err;
if (if_err(vcmp(fname, template + template_len - fname_len,
fname_len) != 0, EINVAL))
goto err;
/* fname_copy = templatestr region only; p points to trailing XXXXXX */
memcpy(smalloc(&fname_copy, fname_len + 1),
template + template_len - fname_len,
fname_len + 1);
p = fname_copy + fname_len - xc;
for (retries = 0; retries < MKHTEMP_RETRY_MAX; retries++) {
r = mkhtemp_try_create(dirfd,
st_dir_first, fname_copy,
p, xc, fd, st, type);
if (r == 0)
continue;
if (r < 0)
goto err;
/* success: copy final name back */
memcpy(template + template_len - fname_len,
fname_copy, fname_len);
errno = saved_errno;
goto success;
}
errno = EEXIST;
err:
xclose(fd);
free_and_set_null(&fname_copy);
return with_fallback_errno(EIO);
success:
free_and_set_null(&fname_copy);
reset_caller_errno(0);
return *fd;
}
int
mkhtemp_try_create(int dirfd,
struct stat *st_dir_first,
char *fname_copy,
char *p,
size_t xc,
int *fd,
struct stat *st,
int type)
{
struct stat st_open;
int saved_errno = errno;
int rval = -1;
char *rstr = NULL;
int file_created = 0;
int dir_created = 0;
errno = 0;
if (if_err(fd == NULL || st == NULL || p ==NULL || fname_copy ==NULL ||
st_dir_first == NULL, EFAULT) ||
if_err(*fd >= 0, EEXIST))
goto err;
/* TODO: potential infinite loop under entropy failure.
* if attacker has control of rand - TODO: maybe add timeout
*/
memcpy(p, rstr = rchars(xc), xc);
free_and_set_null(&rstr);
if (if_err_sys(fd_verify_dir_identity(dirfd, st_dir_first) < 0))
goto err;
if (type == MKHTEMP_FILE) {
#if defined(__linux__) && \
(!defined(USE_OPENAT) || ((USE_OPENAT) < 1))
/* try O_TMPFILE fast path */
if (mkhtemp_tmpfile_linux(dirfd,
st_dir_first, fname_copy,
p, xc, fd, st) >= 0) {
errno = saved_errno;
rval = 1;
goto out;
}
#endif
*fd = openat_on_eintr(dirfd, fname_copy,
O_RDWR | O_CREAT | O_EXCL |
O_NOFOLLOW | O_CLOEXEC | O_NOCTTY, 0600);
/* O_CREAT and O_EXCL guarantees creation upon success
*/
if (*fd >= 0)
file_created = 1;
} else { /* dir: MKHTEMP_DIR */
if (mkdirat_on_eintr(dirfd, fname_copy, 0700) < 0)
goto err;
/* ^ NOTE: opening the directory here
will never set errno=EEXIST,
since we're not creating it */
dir_created = 1;
/* do it again (mitigate directory race) */
if (fd_verify_dir_identity(dirfd, st_dir_first) < 0)
goto err;
if ((*fd = openat_on_eintr(dirfd, fname_copy,
O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0)) < 0)
goto err;
if (if_err_sys(fstat(*fd, &st_open) < 0) ||
if_err(!S_ISDIR(st_open.st_mode), ENOTDIR))
goto err;
/* NOTE: pointless to check nlink here (only just opened) */
if (fd_verify_dir_identity(dirfd, st_dir_first) < 0)
goto err;
}
/* NOTE: openat_on_eintr and mkdirat_on_eintr
* already handled EINTR/EAGAIN looping
*/
if (*fd < 0) {
if (errno == EEXIST) {
rval = 0;
goto out;
}
goto err;
}
if (fstat(*fd, &st_open) < 0)
goto err;
if (type == MKHTEMP_FILE) {
if (fd_verify_dir_identity(dirfd, st_dir_first) < 0)
goto err;
if (secure_file(fd, st, &st_open,
O_APPEND, 1, 1, 0600) < 0) /* WARNING: only once */
goto err;
} else { /* dir: MKHTEMP_DIR */
if (fd_verify_identity(*fd, &st_open, st_dir_first) < 0)
goto err;
if (if_err(!S_ISDIR(st_open.st_mode), ENOTDIR) ||
if_err_sys(is_owner(&st_open) < 0) ||
if_err(st_open.st_mode & (S_IWGRP | S_IWOTH), EPERM))
goto err;
}
rval = 1;
out:
reset_caller_errno(0);
return rval;
err:
xclose(fd);
if (file_created)
(void) unlinkat(dirfd, fname_copy, 0);
if (dir_created)
(void) unlinkat(dirfd, fname_copy, AT_REMOVEDIR);
return with_fallback_errno(EPERM);
}
/* linux has its own special hardening
available specifically for tmpfiles,
which eliminates many race conditions.
we still use openat() on bsd, which is
still ok with our other mitigations
*/
#if defined(__linux__) && \
(!defined(USE_OPENAT) || ((USE_OPENAT) < 1))
int
mkhtemp_tmpfile_linux(int dirfd,
struct stat *st_dir_first,
char *fname_copy,
char *p,
size_t xc,
int *fd,
struct stat *st)
{
int saved_errno = errno;
int tmpfd = -1;
size_t retries;
int linked = 0;
char *rstr = NULL;
errno = 0;
if (if_err(fd == NULL || st == NULL ||
fname_copy == NULL || p == NULL ||
st_dir_first == NULL, EFAULT))
goto err;
/* create unnamed tmpfile */
tmpfd = openat_on_eintr(dirfd, ".",
O_TMPFILE | O_RDWR | O_CLOEXEC, 0600);
if (tmpfd < 0)
goto err;
if (fd_verify_dir_identity(dirfd, st_dir_first) < 0)
goto err;
for (retries = 0; retries < MKHTEMP_RETRY_MAX; retries++) {
memcpy(p, rstr = rchars(xc), xc);
free_and_set_null(&rstr);
if (fd_verify_dir_identity(dirfd,
st_dir_first) < 0)
goto err;
if (linkat(tmpfd, "", dirfd,
fname_copy, AT_EMPTY_PATH) == -1) {
if (errno == EEXIST)
continue; /* retry on collision */
else
goto err;
}
linked = 1; /* file created */
/* TODO: potential fd leak here.
* probably should only set *fd on successful
* return from this function (see below)
*/
if (fd_verify_dir_identity(dirfd, st_dir_first) < 0 ||
fstat(*fd = tmpfd, st) < 0 ||
secure_file(fd, st, st, O_APPEND, 1, 1, 0600) < 0)
goto err;
goto out;
}
if (!errno)
errno = EEXIST;
err:
if (linked)
(void) unlinkat(dirfd, fname_copy, 0);
xclose(&tmpfd);
return with_fallback_errno(EIO);
out:
reset_caller_errno(0);
return 0;
}
#endif
/* WARNING: **ONCE** per file.
*
* 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,
struct stat *expected,
int bad_flags,
int check_seek,
int do_lock,
mode_t mode)
{
int flags = -1;
struct stat st_now;
int saved_errno = errno;
errno = 0;
if (if_err(fd == NULL || st == NULL, EFAULT) ||
if_err(*fd < 0, EBADF))
goto err_demons;
if ((flags = fcntl(*fd, F_GETFL)) == -1)
goto err_demons;
if (if_err(bad_flags > 0 && (flags & bad_flags), EPERM))
goto err_demons;
if (expected != NULL) {
if (fd_verify_regular(*fd, expected, st) < 0)
goto err_demons;
} else if (if_err_sys(fstat(*fd, &st_now) == -1) ||
if_err(!S_ISREG(st_now.st_mode), EBADF)) {
goto err_demons; /***********/
} else /* ( >:3 ) */
*st = st_now; /* /| |\ */ /* don't let him out */
/* / \ */
if (check_seek) { /***********/
if (lseek(*fd, 0, SEEK_CUR) == (off_t)-1)
goto err_demons;
} /* don't release the demon! */
if (if_err(st->st_nlink != 1, ELOOP) ||
if_err(st->st_uid != geteuid() && geteuid() != 0, EPERM) ||
if_err_sys(is_owner(st) < 0) ||
if_err(st->st_mode & (S_IWGRP | S_IWOTH), EPERM))
goto err_demons;
if (do_lock) {
if (lock_file(*fd, flags) == -1)
goto err_demons;
/* TODO: why would this be NULL? audit
* to find out. we should always verify! */
if (expected != NULL)
if (fd_verify_identity(*fd, expected, &st_now) < 0)
goto err_demons;
}
if (fchmod(*fd, mode) == -1)
goto err_demons;
reset_caller_errno(0);
return 0;
err_demons:
return with_fallback_errno(EIO);
}
int
fd_verify_regular(int fd,
const struct stat *expected,
struct stat *out)
{
int saved_errno = errno;
errno = 0;
if (if_err_sys(fd_verify_identity(fd, expected, out) < 0) ||
if_err(!S_ISREG(out->st_mode), EBADF)) {
return with_fallback_errno(EIO);
} else {
reset_caller_errno(0);
return 0; /* regular file */
}
}
int
fd_verify_identity(int fd,
const struct stat *expected,
struct stat *out)
{
struct stat st_now;
int saved_errno = errno;
errno = 0;
if( if_err(fd < 0 || expected == NULL, EFAULT) ||
if_err_sys(fstat(fd, &st_now)) ||
if_err(st_now.st_dev != expected->st_dev ||
st_now.st_ino != expected->st_ino, ESTALE))
return with_fallback_errno(EIO);
if (out != NULL)
*out = st_now;
reset_caller_errno(0);
return 0;
}
int
fd_verify_dir_identity(int fd,
const struct stat *expected)
{
struct stat st_now;
int saved_errno = errno;
errno = 0;
if (if_err(fd < 0 || expected == NULL, EFAULT) ||
if_err_sys(fstat(fd, &st_now) < 0) ||
if_err(st_now.st_dev != expected->st_dev, ESTALE) ||
if_err(st_now.st_ino != expected->st_ino, ESTALE) ||
if_err(!S_ISDIR(st_now.st_mode), ENOTDIR))
goto err;
reset_caller_errno(0);
return 0;
err:
return with_fallback_errno(EIO);
}
int
is_owner(struct stat *st)
{
int saved_errno = errno;
errno = 0;
if (if_err(st == NULL, EFAULT) ||
if_err(st->st_uid != geteuid() /* someone else's file */
#if defined(ALLOW_ROOT_OVERRIDE) && ((ALLOW_ROOT_OVERRIDE) > 0)
&& geteuid() != 0 /* override for root */
#endif
, EPERM)) return with_fallback_errno(EIO);
reset_caller_errno(0);
return 0;
}
int
lock_file(int fd, int flags)
{
struct flock fl;
int saved_errno = errno;
int fcntl_rval = -1;
errno = 0;
if (if_err(fd < 0, EBADF) ||
if_err(flags < 0, EINVAL))
goto err_lock_file;
memset(&fl, 0, sizeof(fl));
if ((flags & O_ACCMODE) == O_RDONLY)
fl.l_type = F_RDLCK;
else
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
if ((fcntl_rval = fcntl(fd, F_SETLK, &fl)) == -1)
goto err_lock_file;
reset_caller_errno(0);
return 0;
err_lock_file:
return with_fallback_errno(EIO);
}
-116
View File
@@ -1,116 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* Non-randomisation-related numerical functions.
* For rand functions, see: rand.c
*/
#ifdef __OpenBSD__
#include <sys/param.h>
#endif
#include <sys/types.h>
#include <errno.h>
#if !((defined(__OpenBSD__) && (OpenBSD) >= 201) || \
defined(__FreeBSD__) || \
defined(__NetBSD__) || defined(__APPLE__))
#include <fcntl.h> /* if not arc4random: /dev/urandom */
#endif
#include <ctype.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "../include/common.h"
unsigned short
hextonum(char ch_s)
{
unsigned char ch;
ch = (unsigned char)ch_s;
if ((unsigned int)(ch - '0') <= 9)
return ch - '0';
ch |= 0x20;
if ((unsigned int)(ch - 'a') <= 5)
return ch - 'a' + 10;
if (ch == '?' || ch == 'x') /* random */
return (short)rsize(16); /* <-- with rejection sampling! */
return 16;
}
/* basically hexdump -C */
/*
TODO: optimise this
write a full util for hexdump
how to optimise:
don't call print tens of thousands of times!
convert the numbers manually, and cache everything
in a BUFSIZ sized buffer, with everything properly
aligned. i worked out that i could fit 79 rows
in a 8KB buffer (1264 bytes of numbers represented
as strings in hex)
this depends on the OS, and would be calculated at
runtime.
then:
don't use printf. just write it to stdout (basically
a simple cat implementation)
*/
void
spew_hex(const void *data, size_t len)
{
const unsigned char *buf = (const unsigned char *)data;
unsigned char c;
size_t i;
size_t j;
if (buf == NULL ||
len == 0)
return;
for (i = 0; i < len; i += 16) {
if (len <= 4294967296) /* below 4GB */
printf("%08zx ", i);
else
printf("%16zu ", i);
for (j = 0; j < 16; j++) {
if (i + j < len)
printf("%02x ", buf[i + j]);
else
printf(" ");
if (j == 7)
printf(" ");
}
printf(" |");
for (j = 0; j < 16 && i + j < len; j++) {
c = buf[i + j];
printf("%c", isprint(c) ? c : '.');
}
printf("|\n");
}
printf("%08zx\n", len);
}
void
check_bin(size_t a, const char *a_name)
{
if (a > 1)
exitf("%s must be 0 or 1, but is %lu",
a_name, (size_t)a);
}
-200
View File
@@ -1,200 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* Random number generation
*/
#if defined(USE_ARC4) && \
((USE_ARC4) > 0)
#define _DEFAULT_SOURCE 1 /* for arc4random on *linux* */
/* (not needed on bsd - on bsd,
it is used automatically unless
overridden with USE_URANDOM */
#elif defined(USE_URANDOM) && \
((USE_URANDOM) > 0)
#include <fcntl.h> /* if not arc4random: /dev/urandom */
#elif defined(__linux__) && \
!(defined(USE_ARC4) && ((USE_ARC4) > 0))
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <sys/syscall.h>
#include <sys/random.h>
#endif
#ifdef __OpenBSD__
#include <sys/param.h>
#endif
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include "../include/common.h"
/* Regarding Linux getrandom/urandom:
*
* For maximum security guarantee, we *only*
* use getrandom via syscall, or /dev/urandom;
* use of urandom is ill advised. This is why
* we use the syscall, in case the libc version
* of getrandom() might defer to /dev/urandom
*
* We *abort* on error, for both /dev/urandom
* and getrandom(), because the BSD arc4random
* never returns with error; therefore, for the
* most parity in terms of behaviour, we abort,
* because otherwise the function would have two
* return modes: always successful (BSD), or only
* sometimes (Linux). The BSD arc4random could
* theoretically abort; it is extremely unlikely
* there, and just so on Linux, hence this design.
*
* This is important, because cryptographic code
* for example must not rely on weak randomness.
* We must therefore treat broken randomness as
* though the world is broken, and burn accordingly.
*
* Similarly, any invalid input (NULL, zero bytes
* requested) are treated as fatal errors; again,
* cryptographic code must be reliable. If your
* code erroneously requested zero bytes, you might
* then end up with a non-randomised buffer, where
* you likely intended otherwise.
*
* In other words: call rset() correctly, or your
* program dies, and rset will behave correctly,
* or your program dies.
*/
/* random string generator, with
* rejection sampling. NOTE: only
* uses ASCII-safe characters, for
* printing on a unix terminal
*
* you still shouldn't use this for
* password generation; open diceware
* passphrases are better for that
*
* NOTE: the generated strings must
* ALSO be safe for file/directory names
* on unix-like os e.g. linux/bsd
*/
char *
rchars(size_t n) /* emulates spkmodem-decode */
{
static char ch[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
char *s = NULL;
size_t i;
smalloc(&s, n + 1);
for (i = 0; i < n; i++)
s[i] = ch[rsize(sizeof(ch) - 1)];
*(s + n) = '\0';
return s;
}
size_t
rsize(size_t n)
{
size_t rval = SIZE_MAX;
if (!n)
exitf("rsize: division by zero");
/* rejection sampling (clamp rand to eliminate modulo bias) */
for (; rval >= SIZE_MAX - (SIZE_MAX % n); rset(&rval, sizeof(rval)));
return rval % n;
}
void *
rmalloc(size_t n)
{
void *buf = NULL;
rset(vmalloc(&buf, n), n);
return buf; /* basically malloc() but with rand */
}
void
rset(void *buf, size_t n)
{
int saved_errno = errno;
errno = 0;
if (if_err(buf == NULL, EFAULT))
goto err;
if (n == 0)
exitf("rset: zero-byte request");
/* on linux, getrandom is recommended,
but you can pass -DUSE_ARC4=1 to use arc4random.
useful for portability testing from linux.
*/
#if (defined(USE_ARC4) && ((USE_ARC4) > 0)) || \
((defined(__OpenBSD__) || defined(__FreeBSD__) || \
defined(__NetBSD__) || defined(__APPLE__) || \
defined(__DragonFly__)) && !(defined(USE_URANDOM) && \
((USE_URANDOM) > 0)))
arc4random_buf(buf, n);
#else
size_t off = 0;
retry_rand: {
#if defined(USE_URANDOM) && \
((USE_URANDOM) > 0)
ssize_t rval;
int fd = -1;
open_file_on_eintr("/dev/urandom", &fd, O_RDONLY, 0400, NULL);
while (rw_retry(saved_errno,
rval = rw(fd, (unsigned char *)buf + off, n - off, 0, IO_READ)));
#elif defined(__linux__)
long rval;
while (sys_retry(saved_errno,
rval = syscall(SYS_getrandom,
(unsigned char *)buf + off, n - off, 0)));
#else
#error Unsupported operating system (possibly unsecure randomisation)
#endif
if (rval < 0 || /* syscall fehler */
rval == 0) { /* prevent infinite loop on fatal err */
#if defined(USE_URANDOM) && \
((USE_URANDOM) > 0)
xclose(&fd);
#endif
goto err;
}
if ((off += (size_t)rval) < n)
goto retry_rand;
#if defined(USE_URANDOM) && \
((USE_URANDOM) > 0)
xclose(&fd);
#endif
}
#endif
reset_caller_errno(0);
return;
err:
(void) with_fallback_errno(ECANCELED);
exitf("Randomisierungsfehler");
exit(EXIT_FAILURE);
}
-164
View File
@@ -1,164 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
*
* State machine (singleton) for nvmutil data.
*/
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../include/common.h"
struct xstate *
xstart(int argc, char *argv[])
{
static int first_run = 1;
static char *dir = NULL;
static char *base = NULL;
char *realdir = NULL;
char *tmpdir = NULL;
char *tmpbase_local = NULL;
static struct xstate us = {
{
/* be careful when modifying xstate. you
* must set everything precisely */
{
CMD_DUMP, "dump", cmd_helper_dump, ARGC_3,
ARG_NOPART,
SKIP_CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
NVM_SIZE, O_RDONLY
}, {
CMD_SETMAC, "setmac", cmd_helper_setmac, ARGC_3,
ARG_NOPART,
CHECKSUM_READ, CHECKSUM_WRITE,
NVM_SIZE, O_RDWR
}, {
CMD_SWAP, "swap", cmd_helper_swap, ARGC_3,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDWR
}, {
CMD_COPY, "copy", cmd_helper_copy, ARGC_4,
ARG_PART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDWR
}, {
CMD_CAT, "cat", cmd_helper_cat, ARGC_3,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDONLY
}, {
CMD_CAT16, "cat16", cmd_helper_cat16, ARGC_3,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDONLY
}, {
CMD_CAT128, "cat128", cmd_helper_cat128, ARGC_3,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDONLY
}
},
/* ->mac */
{NULL, "xx:xx:xx:xx:xx:xx", {0, 0, 0}}, /* .str, .rmac, .mac_buf */
/* .f */
{0},
/* ->i (index to cmd[]) */
0,
/* .no_cmd (set 0 when a command is found) */
1,
/* .cat (cat helpers set this) */
-1
};
if (!first_run)
return &us;
if (argc < 3)
exitf("xstart: Too few arguments");
if (argv == NULL)
exitf("xstart: NULL argv");
first_run = 0;
us.f.buf = us.f.real_buf;
us.f.fname = argv[1];
us.f.tmp_fd = -1;
us.f.tname = NULL;
if ((realdir = realpath(us.f.fname, NULL)) == NULL)
exitf("xstart: can't get realpath of %s",
us.f.fname);
if (fs_dirname_basename(realdir, &dir, &base, 0) < 0)
exitf("xstart: don't know CWD of %s",
us.f.fname);
sdup(base, PATH_MAX, &us.f.base);
us.f.dirfd = fs_open(dir,
O_RDONLY | O_DIRECTORY);
if (us.f.dirfd < 0)
exitf("%s: open dir", dir);
if (new_tmpfile(&us.f.tmp_fd, &us.f.tname, dir, ".gbe.XXXXXXXXXX") < 0)
exitf("%s", us.f.tname);
if (fs_dirname_basename(us.f.tname,
&tmpdir, &tmpbase_local, 0) < 0)
exitf("tmp basename");
sdup(tmpbase_local, PATH_MAX, &us.f.tmpbase);
free_and_set_null(&tmpdir);
if (us.f.tname == NULL)
exitf("x->f.tname null");
if (*us.f.tname == '\0')
exitf("x->f.tname empty");
if (fstat(us.f.tmp_fd, &us.f.tmp_st) < 0)
exitf("%s: stat", us.f.tname);
memset(us.f.real_buf, 0, sizeof(us.f.real_buf));
memset(us.f.bufcmp, 0, sizeof(us.f.bufcmp));
/* for good measure */
memset(us.f.pad, 0, sizeof(us.f.pad));
return &us;
}
struct xstate *
xstatus(void)
{
struct xstate *x = xstart(0, NULL);
if (x == NULL)
exitf("NULL pointer to xstate");
return x;
}
-643
View File
@@ -1,643 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* String functions
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <stdint.h>
#include "../include/common.h"
/* for null detection inside
* word-optimised string functions
*/
#define ff ((size_t)-1 / 0xFF)
#define high ((ff) * 0x80)
/* NOTE:
* do not assume that a match means
* both words have null at the same
* location. see how this is handled
* e.g. in scmp.
*/
#define zeroes(x) (((x) - (ff)) & ~(x) & (high))
size_t
page_remain(const void *p)
{
/* calling sysconf repeatedly
* is folly. cache it (static)
*/
static size_t pagesz = 0;
if (!pagesz)
pagesz = (size_t)pagesize();
return pagesz - ((uintptr_t)p & (pagesz - 1));
}
long
pagesize(void)
{
static long rval = 0;
static int set = 0;
int saved_errno = 0;
if (!set) {
if ((rval = sysconf(_SC_PAGESIZE)) < 0)
exitf("could not determine page size");
set = 1;
}
reset_caller_errno(0);
return rval;
}
void
free_and_set_null(char **buf)
{
if (buf == NULL)
exitf(
"null ptr (to ptr for freeing) in free_and_set_null");
if (*buf == NULL)
return;
free(*buf);
*buf = NULL;
}
/* safe(ish) malloc.
use this and free_and_set_null()
in your program, to reduce the
chance of use after frees!
if you use these functions in the
intended way, you will greatly reduce
the number of bugs in your code
*/
char *
smalloc(char **buf, size_t size)
{
return (char *)vmalloc((void **)buf, size);
}
void *
vmalloc(void **buf, size_t size)
{
int saved_errno = errno;
void *rval = NULL;
errno = 0;
if (size >= SIZE_MAX - 1)
exitf("integer overflow in vmalloc");
if (buf == NULL)
exitf("Bad pointer passed to vmalloc");
/* lots of programs will
* re-initialise a buffer
* that was allocated, without
* freeing or NULLing it. this
* is here intentionally, to
* force the programmer to behave
*/
if (*buf != NULL)
exitf("Non-null pointer given to vmalloc");
if (!size)
exitf(
"Tried to vmalloc(0) and that is very bad. Fix it now");
if ((rval = malloc(size)) == NULL)
exitf("malloc fail in vmalloc");
reset_caller_errno(0);
return *buf = rval;
}
/* strict word-based strcmp */
int
scmp(const char *a,
const char *b,
size_t maxlen,
int *rval)
{
size_t i = 0;
size_t j;
size_t wa;
size_t wb;
int saved_errno = errno;
errno = 0;
if (if_err(a == NULL || b == NULL || rval == NULL, EFAULT))
goto err;
for ( ; ((uintptr_t)(a + i) % sizeof(size_t)) != 0; i++) {
if (if_err(i >= maxlen, EOVERFLOW))
goto err;
else if (!ccmp(a, b, i, rval))
goto out;
}
for ( ; i + sizeof(size_t) <= maxlen;
i += sizeof(size_t)) {
/* prevent crossing page boundary on word check */
if (page_remain(a + i) < sizeof(size_t) ||
page_remain(b + i) < sizeof(size_t))
break;
memcpy(&wa, a + i, sizeof(size_t));
memcpy(&wb, b + i, sizeof(size_t));
if (wa != wb)
for (j = 0; j < sizeof(size_t); j++)
if (!ccmp(a, b, i + j, rval))
goto out;
if (!zeroes(wa))
continue;
*rval = 0;
goto out;
}
for ( ; i < maxlen; i++)
if (!ccmp(a, b, i, rval))
goto out;
err:
(void) with_fallback_errno(EFAULT);
if (rval != NULL)
*rval = -1;
exitf("scmp");
return -1;
out:
reset_caller_errno(0);
return *rval;
}
int ccmp(const char *a, const char *b,
size_t i, int *rval)
{
unsigned char ac;
unsigned char bc;
if (if_err(a == NULL || b == NULL || rval == NULL, EFAULT))
exitf("ccmp");
ac = (unsigned char)a[i];
bc = (unsigned char)b[i];
if (ac != bc) {
*rval = ac - bc;
return 0;
} else if (ac == '\0') {
*rval = 0;
return 0;
}
return 1;
}
/* strict word-based strlen */
size_t
slen(const char *s,
size_t maxlen,
size_t *rval)
{
int saved_errno = errno;
size_t i = 0;
size_t w;
size_t j;
errno = 0;
if (if_err(s == NULL || rval == NULL, EFAULT))
goto err;
for ( ; ((uintptr_t)(s + i) % sizeof(size_t)) != 0; i++) {
if (if_err(i >= maxlen, EOVERFLOW))
goto err;
if (s[i] == '\0') {
*rval = i;
goto out;
}
}
for ( ; i + sizeof(size_t) <= maxlen;
i += sizeof(size_t)) {
memcpy(&w, s + i, sizeof(size_t));
if (!zeroes(w))
continue;
for (j = 0; j < sizeof(size_t); j++) {
if (s[i + j] == '\0') {
*rval = i + j;
goto out;
}
}
}
for ( ; i < maxlen; i++) {
if (s[i] == '\0') {
*rval = i;
goto out;
}
}
err:
(void) with_fallback_errno(EFAULT);
if (rval != NULL)
*rval = 0;
exitf("slen"); /* abort */
return 0; /* gcc15 is happy */
out:
reset_caller_errno(0);
return *rval;
}
int
dup_pair(char **dir, const char *d,
char **base, const char *b)
{
char *dtmp = NULL;
char *btmp = NULL;
if (d && sdup(d, PATH_MAX, &dtmp) == NULL)
return -1;
if (b && sdup(b, PATH_MAX, &btmp) == NULL) {
free(dtmp);
return -1;
}
*dir = dtmp;
*base = btmp;
return 0;
}
/* strict word-based strdup */
char *
sdup(const char *s,
size_t max, char **dest)
{
size_t j;
size_t w;
size_t i = 0;
char *out = NULL;
int saved_errno = errno;
errno = 0;
if (if_err(dest == NULL || *dest != NULL || s == NULL, EFAULT))
goto err;
out = smalloc(dest, max);
for ( ; ((uintptr_t)(s + i) % sizeof(size_t)) != 0; i++) {
if (if_err(i >= max, EOVERFLOW))
goto err;
out[i] = s[i];
if (s[i] == '\0') {
*dest = out;
goto out;
}
}
for ( ; i + sizeof(size_t) <= max; i += sizeof(size_t)) {
if (page_remain(s + i) < sizeof(size_t))
break;
memcpy(&w, s + i, sizeof(size_t));
if (!zeroes(w)) {
memcpy(out + i, &w, sizeof(size_t));
continue;
}
for (j = 0; j < sizeof(size_t); j++) {
out[i + j] = s[i + j];
if (s[i + j] == '\0') {
*dest = out;
goto out;
}
}
}
for ( ; i < max; i++) {
out[i] = s[i];
if (s[i] == '\0') {
*dest = out;
goto out;
}
}
err:
free_and_set_null(&out);
if (dest != NULL)
*dest = NULL;
(void) with_fallback_errno(EFAULT);
exitf("sdup");
return NULL;
out:
reset_caller_errno(0);
return *dest;
}
/* concatenate N number of strings */
char *
scatn(ssize_t sc, const char **sv,
size_t max, char **rval)
{
int saved_errno = errno;
char *final = NULL;
char *rcur = NULL;
char *rtmp = NULL;
ssize_t i;
errno = 0;
if (if_err(sc < 2, EINVAL) ||
if_err(sv == NULL, EFAULT) ||
if_err(rval == NULL || *rval != NULL, EFAULT))
goto err;
for (i = 0; i < sc; i++) {
if (if_err(sv[i] == NULL, EFAULT))
goto err;
else if (i == 0) {
(void) sdup(sv[0], max, &final);
continue;
}
rtmp = NULL;
scat(final, sv[i], max, &rtmp);
free_and_set_null(&final);
final = rtmp;
rtmp = NULL;
}
reset_caller_errno(0);
*rval = final;
return *rval;
err:
free_and_set_null(&rcur);
free_and_set_null(&rtmp);
free_and_set_null(&final);
(void) with_fallback_errno(EFAULT);
exitf("scatn");
return NULL;
}
/* strict strcat */
char *
scat(const char *s1, const char *s2,
size_t n, char **dest)
{
size_t size1;
size_t size2;
char *rval = NULL;
int saved_errno = errno;
errno = 0;
if (if_err(dest == NULL || *dest != NULL, EFAULT))
goto err;
slen(s1, n, &size1);
slen(s2, n, &size2);
if (if_err(size1
> SIZE_MAX - size2 - 1, EOVERFLOW))
goto err;
smalloc(&rval, size1 + size2 + 1);
memcpy(rval, s1, size1);
memcpy(rval + size1, s2, size2);
*(rval + size1 + size2) = '\0';
reset_caller_errno(0);
*dest = rval;
return *dest;
err:
(void) with_fallback_errno(EINVAL);
if (dest != NULL)
*dest = NULL;
exitf("scat");
return NULL;
}
/* strict split/de-cat - off is where
2nd buffer will start from */
void
dcat(const char *s, size_t n,
size_t off, char **dest1,
char **dest2)
{
size_t size;
char *rval1 = NULL;
char *rval2 = NULL;
int saved_errno = errno;
errno = 0;
if (if_err(dest1 == NULL || dest2 == NULL, EFAULT))
goto err;
if (if_err(slen(s, n, &size) >= SIZE_MAX - 1, EOVERFLOW) ||
if_err(off >= size, EOVERFLOW))
goto err;
memcpy(smalloc(&rval1, off + 1),
s, off);
*(rval1 + off) = '\0';
memcpy(smalloc(&rval2, size - off +1),
s + off, size - off);
*(rval2 + size - off) = '\0';
*dest1 = rval1;
*dest2 = rval2;
reset_caller_errno(0);
return;
err:
*dest1 = *dest2 = NULL;
free_and_set_null(&rval1);
free_and_set_null(&rval2);
(void) with_fallback_errno(EINVAL);
exitf("dcat");
}
/* because no libc reimagination is complete
* without a reimplementation of memcmp. and
* no safe one is complete without null checks.
*/
int
vcmp(const void *s1, const void *s2, size_t n)
{
int saved_errno = errno;
size_t i = 0;
size_t a;
size_t b;
const unsigned char *x;
const unsigned char *y;
errno = 0;
if (if_err(s1 == NULL || s2 == NULL, EFAULT))
exitf("vcmp: null input");
x = s1;
y = s2;
for ( ; i + sizeof(size_t) <= n; i += sizeof(size_t)) {
memcpy(&a, x + i, sizeof(size_t));
memcpy(&b, y + i, sizeof(size_t));
if (a != b)
break;
}
for ( ; i < n; i++)
if (x[i] != y[i])
return (int)x[i] - (int)y[i];
reset_caller_errno(0);
return 0;
}
/* on functions that return with errno,
* i sometimes have a default fallback,
* which is set if errno wasn't changed,
* under error condition.
*/
int
with_fallback_errno(int fallback)
{
if (!errno)
errno = fallback;
return -1;
}
/* the one for nvmutil state is in state.c */
/* this one just exits */
void
exitf(const char *msg, ...)
{
va_list args;
int saved_errno = errno;
func_t err_cleanup = errhook(NULL);
err_cleanup();
reset_caller_errno(0);
saved_errno = errno;
if (!errno)
saved_errno = errno = ECANCELED;
fprintf(stderr, "%s: ", lbgetprogname());
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
errno = saved_errno;
fprintf(stderr, ": %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* the err function will
* call this upon exit, and
* cleanup will be performed
* e.g. you might want to
* close some files, depending
* on your program.
* see: exitf()
*/
func_t errhook(func_t ptr)
{
static int set = 0;
static func_t hook = NULL;
if (!set) {
set = 1;
if (ptr == NULL)
hook = no_op;
else
hook = ptr;
}
return hook;
}
void
no_op(void)
{
return;
}
const char *
lbgetprogname(void)
{
char *name = lbsetprogname(NULL);
char *p = NULL;
if (name)
p = strrchr(name, '/');
if (p)
return p + 1;
else if (name)
return name;
else
return "libreboot-utils";
}
/* singleton. if string not null,
sets the string. after set,
will not set anymore. either
way, returns the string
*/
char *
lbsetprogname(char *argv0)
{
static char *progname = NULL;
static int set = 0;
if (!set) {
if (argv0 == NULL)
return "libreboot-utils";
(void) sdup(argv0, PATH_MAX, &progname);
set = 1;
}
return progname;
}
-30
View File
@@ -1,30 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com>
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*/
#include <errno.h>
#include <stdio.h>
#include "../include/common.h"
void
usage(void)
{
const char *util = lbgetprogname();
fprintf(stderr,
"Modify Intel GbE NVM images e.g. set MAC\n"
"USAGE:\n"
"\t%s FILE dump\n"
"\t%s FILE setmac [MAC]\n"
"\t%s FILE swap\n"
"\t%s FILE copy 0|1\n"
"\t%s FILE cat\n"
"\t%s FILE cat16\n"
"\t%s FILE cat128\n",
util, util, util, util,
util, util, util);
exitf("Too few arguments");
}
-68
View File
@@ -1,68 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
*
* Manipulate Intel GbE NVM words, which are 16-bit little
* endian in the files (MAC address words are big endian).
*/
#include <sys/types.h>
#include <errno.h>
#include <stddef.h>
#include "../include/common.h"
unsigned short
nvm_word(size_t pos16, size_t p)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
size_t pos;
check_nvm_bound(pos16, p);
pos = (pos16 << 1) + (p * GBE_PART_SIZE);
return (unsigned short)f->buf[pos] |
((unsigned short)f->buf[pos + 1] << 8);
}
void
set_nvm_word(size_t pos16, size_t p, unsigned short val16)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
size_t pos;
check_nvm_bound(pos16, p);
pos = (pos16 << 1) + (p * GBE_PART_SIZE);
f->buf[pos] = (unsigned char)(val16 & 0xff);
f->buf[pos + 1] = (unsigned char)(val16 >> 8);
set_part_modified(p);
}
void
set_part_modified(size_t p)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
check_bin(p, "part number");
f->part_modified[p] = 1;
}
void
check_nvm_bound(size_t c, size_t p)
{
/* Block out of bound NVM access
*/
check_bin(p, "part number");
if (c >= NVM_WORDS)
exitf("check_nvm_bound: out of bounds %lu",
(size_t)c);
}
-74
View File
@@ -1,74 +0,0 @@
/* SPDX-License-Identifier: MIT ( >:3 )
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org> /| |\
Something something non-determinism / \ */
#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "include/common.h"
static void
exit_cleanup(void);
int
main(int argc, char **argv)
{
#ifndef __linux__
#error This code is currently buggy on BSD systems. Only use on Linux.
#endif
int same = 0;
char *buf;
size_t size = BUFSIZ;
(void) argc, (void) argv;
(void) errhook(exit_cleanup);
(void) lbsetprogname(argv[0]);
#ifdef __OpenBSD__
/* https://man.openbsd.org/pledge.2 */
if (pledge("stdio", NULL) == -1)
exitf("pledge");
#endif
buf = rmalloc(size);
if (!vcmp(buf, buf + (size >> 1), size >> 1))
same = 1;
if (argc < 2) /* no spew */
spew_hex(buf, size);
free_and_set_null(&buf);
fprintf(stderr, "\n%s\n", same ? "You win!" : "You lose!");
return same ? EXIT_SUCCESS : EXIT_FAILURE;
}
static void
exit_cleanup(void)
{
#if defined(__OpenBSD__)
fprintf(stderr, "OpenBSD wins\n");
#elif defined(__FreeBSD__)
fprintf(stderr, "FreeBSD wins\n");
#elif defined(__NetBSD__)
fprintf(stderr, "NetBSD wins\n");
#elif defined(__APPLE__)
fprintf(stderr, "MacOS wins\n");
#elif defined(__DragonFly__)
fprintf(stderr, "DragonFly BSD wins\n");
#elif defined(__linux__)
#if defined(__GLIBC__)
fprintf(stderr, "GNU/Linux wins\n");
#elif defined(__MUSL__)
fprintf(stderr, "Rich Felker wins\n");
#else
fprintf(stderr, "Linux wins\n");
#endif
#else
fprintf(stderr, "Your operating system wins\n");
#endif
return;
}
-152
View File
@@ -1,152 +0,0 @@
/* SPDX-License-Identifier: MIT ( >:3 )
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org> /| |\
* / \
* Hardened mktemp (mkhtemp!)
*
* WORK IN PROGRESS (proof of concept), or, v0.0000001
* DO NOT PUT THIS IN YOUR LINUX DISTRO YET.
*
* In other words: for reference only -- PATCHES WELCOME!
*
* I will remove this notice when the code is mature, and
* probably contact several of your projects myself.
*
* 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.
*/
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "include/common.h"
static void
exit_cleanup(void);
int
main(int argc, char *argv[])
{
#ifndef __linux__
#error This code is currently buggy on BSD systems. Only use on Linux.
#endif
size_t len;
size_t tlen;
size_t xc = 0;
char *tmpdir = NULL;
char *template = NULL;
char *p;
char *s = NULL;
char *rp;
char resolved[PATH_MAX];
char c;
int fd = -1;
int type = MKHTEMP_FILE;
(void) errhook(exit_cleanup);
(void) lbsetprogname(argv[0]);
#ifdef __OpenBSD__
/* https://man.openbsd.org/pledge.2 */
if (pledge("stdio flock rpath wpath cpath fattr", NULL) == -1)
exitf("pledge");
#endif
while ((c =
getopt(argc, argv, "qdp:")) != -1) {
switch (c) {
case 'd':
type = MKHTEMP_DIR;
break;
case 'p':
tmpdir = optarg;
break;
case 'q': /* don't print errors */
/* (exit status unchanged) */
break;
default:
goto err_usage;
}
}
if (optind < argc)
template = argv[optind];
if (optind + 1 < argc)
goto err_usage;
/* custom template e.g. foo.XXXXXXXXXXXXXXXXXXXXX */
if (template != NULL) {
for (p = template + slen(template, PATH_MAX, &tlen);
p > template && *--p == 'X'; xc++);
if (xc < 3) /* the gnu mktemp errs on less than 3 */
exitf(
"template must have 3 X or more on end (12+ advised");
}
/* user supplied -p PATH - WARNING:
* this permits symlinks, but only here,
* not in the library, so they are resolved
* here first, and *only here*. the mkhtemp
* library blocks them. be careful
* when using -p
*/
if (tmpdir != NULL) {
rp = realpath(tmpdir, resolved);
if (rp == NULL)
exitf("%s", tmpdir);
tmpdir = resolved;
}
if (new_tmp_common(&fd, &s, type,
tmpdir, template) < 0)
exitf("%s", s);
#ifdef __OpenBSD__
if (pledge("stdio", NULL) == -1)
exitf("pledge");
#endif
if (s == NULL)
exitf("bad string initialisation");
if (*s == '\0')
exitf("empty string initialisation");
slen(s, PATH_MAX, &len); /* Nullterminierung prüfen */
/* for good measure. (bonus: also re-checks length overflow) */
printf("%s\n", s);
return EXIT_SUCCESS;
err_usage:
exitf(
"usage: %s [-d] [-p dir] [template]\n", lbgetprogname());
}
static void
exit_cleanup(void)
{
return;
}
-134
View File
@@ -1,134 +0,0 @@
/* SPDX-License-Identifier: MIT ( >:3 )
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> /| |\
* / \
* This tool lets you modify Intel GbE NVM (Gigabit Ethernet
* Non-Volatile Memory) images, e.g. change the MAC address.
* These images configure your Intel Gigabit Ethernet adapter.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "include/common.h"
static void
exit_cleanup(void);
int
main(int argc, char *argv[])
{
#ifndef __linux__
#error This code is currently buggy on BSD systems. Only use on Linux.
#endif
struct xstate *x;
struct commands *cmd;
struct xfile *f;
size_t c;
(void) lbsetprogname(argv[0]);
if (argc < 3)
usage();
(void) errhook(exit_cleanup);
#ifdef __OpenBSD
/* https://man.openbsd.org/pledge.2 */
/* https://man.openbsd.org/unveil.2 */
if (pledge("stdio flock rpath wpath cpath unveil", NULL) == -1)
exitf("pledge");
if (unveil("/dev/urandom", "r") == -1)
exitf("unveil");
#endif
#ifndef S_ISREG
exitf(
"Can't determine file types (S_ISREG undefined)");
#endif
#if ((CHAR_BIT) != 8)
exitf("Unsupported char size");
#endif
if ((x = xstart(argc, argv)) == NULL)
exitf("NULL state on init");
/* parse user command */
/* TODO: CHECK ACCESSES VIA xstatus() */
set_cmd(argc, argv);
set_cmd_args(argc, argv);
cmd = &x->cmd[x->i];
f = &x->f;
#ifdef __OpenBSD__
if ((cmd->flags & O_ACCMODE) == O_RDONLY) {
if (unveil(f->fname, "r") == -1)
exitf("unveil");
} else {
if (unveil(f->fname, "rwc") == -1)
exitf("unveil");
}
if (unveil(f->tname, "rwc") == -1)
exitf("unveil");
if (unveil(NULL, NULL) == -1)
exitf("unveil");
if (pledge("stdio flock rpath wpath cpath", NULL) == -1)
exitf("pledge");
#endif
if (cmd->run == NULL)
exitf("Command not set");
sanitize_command_list();
open_gbe_file();
copy_gbe();
read_checksums();
cmd->run();
for (c = 0; c < items(x->cmd); c++)
x->cmd[c].run = cmd_helper_err;
if ((cmd->flags & O_ACCMODE) == O_RDWR)
write_to_gbe_bin();
exit_cleanup();
if (f->io_err_gbe_bin)
exitf("%s: error writing final file");
free_and_set_null(&f->tname);
return EXIT_SUCCESS;
}
static void
exit_cleanup(void)
{
struct xstate *x;
struct xfile *f;
x = xstatus();
if (x == NULL)
return;
f = &x->f;
/* close fds if still open */
xclose(&f->tmp_fd);
xclose(&f->gbe_fd);
/* unlink tmpfile if it exists */
if (f->tname != NULL) {
(void) unlink(f->tname);
free_and_set_null(&f->tname);
}
}