util/mkhtemp: extremely hardened mkhtemp

This will also be used in lbmk itself at some point,
which currently just uses regular mktemp, for tmpdir
handling during the build process.

Renamed util/nvmutil to util/libreboot-utils, which
now contains two tools. The new tool, mkhtemp, is a
hardened implementation of mktemp, which nvmutil
also uses now. Still experimental, but good enough
for nvmutil.

Mkhtemp attempts to provide TOCTOU resistance on
Linux, by using modern features in Linux such as
Openat2 (syscall) with O_EXCL and O_TMPFILE,
and many various security checks e.g.
inode/dev during creation. Checks are done constantly,
to try to detect race conditions. The code is very
strict about things like sticky bits in world writeable
directories, also ownership (it can be made to bar even
root access on files and directories it doesn't own).

It's a security-first implementation of mktemp, likely
even more secure than the OpenBSD mkstemp, but more
auditing and testing is needed - more features are
also planned, including a compatibility mode to make
it also work like traditional mktemp/mkstemp. The
intention, once this becomes stable, is that it will
become a modern drop-in replacement for mkstemp on
Linux and BSD systems.

Some legacy code has been removed, and in general
cleaned up. I wrote mkhtemp for nvmutil, as part of
its atomic write behaviour, but mktemp was the last
remaining liability, so I rewrote that too!

Docs/manpage/website will be made for mkhtemp once
the code is mature.

Other changes have also been made. This is from another
experimental branch of Libreboot, that I'm pushing
early. For example, nvmutil's state machine has been
tidied up, moving more logic back into main.

Mktemp is historically prone to race conditions,
e.g. symlink attacks, directory replacement, remounting
during operation, all sorts of things. Mkhtemp has
been written to solve, or otherwise mitigate, that
problem. Mkhtemp is currently experimental and will
require a major cleanup at some point, but it
already works well enough, and you can in fact use
it; at this time, the -d, -p and -q flags are
supported, and you can add a custom template at
the end, e.g.

mkhtemp -p test -d

Eventually, I will make this have complete parity
with the GNU and BSD implementations, so that it is
fully useable on existing setups, while optionally
providing the hardening as well.

A lot of code has also been tidied up. I didn't
track the changes I made with this one, because
it was a major re-write of nvmutil; it is now
libreboot-utils, and I will continue to write
more programs in here over time. It's basically
now a bunch of hardened wrappers around various
libc functions, e.g. there is also a secure I/O
wrapper for read/write.

There is a custom randomisation function, rlong,
which simply uses arc4random or getrandom, on
BSD and Linux respectively. Efforts are made to
make it as reliable as possible, to the extent
that it never returns with failure; in the unlikely
event that it fails, it aborts. It also sleeps
between failure, to mitigate certain DoS attacks.

You can just go in util/libreboot-utils and
type make, then you will have the nvmutil and
mkhtemp binaries, which you can just use. It
all works. Everything was massively rewritten.

Signed-off-by: Leah Rowe <leah@libreboot.org>
This commit is contained in:
Leah Rowe
2026-03-20 04:02:51 +00:00
parent c2ad2f9b40
commit 718095b0fe
23 changed files with 3553 additions and 1852 deletions
+108
View File
@@ -0,0 +1,108 @@
/* 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)
b0rk(ECANCELED, "%s: part %lu has a bad checksum",
f->fname, (size_t)f->part);
b0rk(ECANCELED, "%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);
}
+564
View File
@@ -0,0 +1,564 @@
/* 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"
/* Guard against regressions by maintainers (command table)
*/
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)
b0rk(EINVAL, "cmd index %lu: argc below 3, %d",
(size_t)c, cmd->argc);
if (cmd->str == NULL)
b0rk(EINVAL, "cmd index %lu: NULL str",
(size_t)c);
if (*cmd->str == '\0')
b0rk(EINVAL, "cmd index %lu: empty str",
(size_t)c);
if (slen(cmd->str, MAX_CMD_LEN +1, &rval) < 0)
b0rk(errno, "Could not get command length");
if (rval > MAX_CMD_LEN) {
b0rk(EINVAL, "cmd index %lu: str too long: %s",
(size_t)c, cmd->str);
}
if (cmd->run == NULL)
b0rk(EINVAL, "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:
b0rk(EINVAL, "Unsupported rw_size: %lu",
(size_t)gbe_rw_size);
}
if (gbe_rw_size > GBE_PART_SIZE)
b0rk(EINVAL, "rw_size larger than GbE part: %lu",
(size_t)gbe_rw_size);
_flag = (cmd->flags & O_ACCMODE);
if (_flag != O_RDONLY &&
_flag != O_RDWR)
b0rk(EINVAL, "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) < 0)
err_no_cleanup(0, EINVAL,
"could not compare command strings");
if (rval != 0)
continue; /* not the right command */
/* valid command found */
if (argc >= x->cmd[c].argc) {
x->no_cmd = 0;
x->i = c; /* set command */
return;
}
err_no_cleanup(0, EINVAL,
"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)
b0rk(EINVAL,
"arg_part set for command that needs argc4");
if (cmd->arg_part && i == CMD_SETMAC)
b0rk(EINVAL,
"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')
b0rk(EINVAL, "Partnum string '%s' wrong length", part_str);
/* char signedness is implementation-defined
*/
ch = (unsigned char)part_str[0];
if (ch < '0' || ch > '1')
b0rk(EINVAL, "Bad part number (%c)", ch);
return (size_t)(ch - '0');
}
void
check_command_num(size_t c)
{
if (!valid_command(c))
b0rk(EINVAL, "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)
b0rk(EINVAL,
"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) < 0)
b0rk(EINVAL, "Could not determine MAC length");
if (rval != 17)
b0rk(EINVAL, "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)
b0rk(EINVAL, "Must not specify all-zeroes MAC address");
if (mac->mac_buf[0] & 1)
b0rk(EINVAL, "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]) != ':')
b0rk(EINVAL, "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)
b0rk(EIO, "Randomisation failure");
else
b0rk(EINVAL, "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);
hexdump(p);
}
}
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
hexdump(size_t partnum)
{
size_t c;
size_t row;
unsigned short val16;
for (row = 0; row < 8; row++) {
printf("%08lx ",
(size_t)((size_t)row << 4));
for (c = 0; c < 8; c++) {
val16 = nvm_word((row << 3) + c, partnum);
if (c == 4)
printf(" ");
printf(" %02x %02x",
(unsigned int)(val16 & 0xff),
(unsigned int)(val16 >> 8));
}
printf("\n");
}
}
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) {
b0rk(ECANCELED, "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)
b0rk(errno, "null pointer in cat command");
if (rw_file_exact(STDOUT_FILENO, b,
GBE_PART_SIZE, 0, IO_WRITE, LOOP_EAGAIN, LOOP_EINTR,
MAX_ZERO_RW_RETRY, OFF_ERR) < 0)
b0rk(errno, "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)
b0rk(ECANCELED, "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)
{
b0rk(ECANCELED,
"Erroneously running command twice");
}
File diff suppressed because it is too large Load Diff
+591
View File
@@ -0,0 +1,591 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* I/O functions specific to nvmutil.
*/
#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 _flags;
xopen(&f->gbe_fd, f->fname,
cmd->flags | O_BINARY |
O_NOFOLLOW | O_CLOEXEC | O_NOCTTY, &f->gbe_st);
if (f->gbe_st.st_nlink > 1)
b0rk(EINVAL,
"%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)
b0rk(EIO, "%s: file unlinked while open", f->fname);
_flags = fcntl(f->gbe_fd, F_GETFL);
if (_flags == -1)
b0rk(errno, "%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)
b0rk(EIO, "%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:
b0rk(EINVAL, "File size must be 8KB, 16KB or 128KB");
}
if (lock_file(f->gbe_fd, cmd->flags) == -1)
b0rk(errno, "%s: can't lock", f->fname);
}
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_file_exact(f->gbe_fd, f->buf, f->gbe_file_size,
0, IO_PREAD, NO_LOOP_EAGAIN, LOOP_EINTR,
MAX_ZERO_RW_RETRY, OFF_ERR);
if (_r < 0)
b0rk(errno, "%s: read failed", f->fname);
/* copy to tmpfile
*/
_r = rw_file_exact(f->tmp_fd, f->buf, f->gbe_file_size,
0, IO_PWRITE, NO_LOOP_EAGAIN, LOOP_EINTR,
MAX_ZERO_RW_RETRY, OFF_ERR);
if (_r < 0)
b0rk(errno, "%s: %s: copy failed",
f->fname, f->tname);
/* file size comparison
*/
if (fstat(f->tmp_fd, &_st) == -1)
b0rk(errno, "%s: stat", f->tname);
f->gbe_tmp_size = _st.st_size;
if (f->gbe_tmp_size != f->gbe_file_size)
b0rk(EIO, "%s: %s: not the same size",
f->fname, f->tname);
/* needs sync, for verification
*/
if (fsync_on_eintr(f->tmp_fd) == -1)
b0rk(errno, "%s: fsync (tmpfile copy)", f->tname);
_r = rw_file_exact(f->tmp_fd, f->bufcmp, f->gbe_file_size,
0, IO_PREAD, NO_LOOP_EAGAIN, LOOP_EINTR,
MAX_ZERO_RW_RETRY, OFF_ERR);
if (_r < 0)
b0rk(errno, "%s: read failed (cmp)", f->tname);
if (memcmp(f->buf, f->bufcmp, f->gbe_file_size) != 0)
b0rk(errno, "%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)
b0rk(errno, "%s: file inode/device changed", f->tname);
if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0)
b0rk(errno, "%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)
b0rk(errno, "%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)
b0rk(errno, "%s: %s: part %lu",
f->fname, rw_type_str, (size_t)p);
if ((size_t)rval != gbe_rw_size)
b0rk(EIO, "%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)
b0rk(errno, "%s: fsync (pre-verification)",
f->tname);
check_written_part(0);
check_written_part(1);
report_io_err_rw();
if (f->io_err_gbe)
b0rk(EIO, "%s: bad write", f->fname);
saved_errno = errno;
f->io_err_gbe_bin |= -close_warn(&f->tmp_fd, f->tname);
f->io_err_gbe_bin |= -close_warn(&f->gbe_fd, f->fname);
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_if_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)
b0rk(errno, "%s: file inode/device changed", f->tname);
if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0)
b0rk(errno, "%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 (memcmp(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;
char *dest_tmp;
int dest_fd = -1;
char *dir = NULL;
char *base = NULL;
char *dest_name = NULL;
int dirfd = -1;
struct stat st_dir;
/* will be set 0 if it doesn't
*/
tmp_gbe_bin_exists = 1;
dest_tmp = NULL;
dest_fd = -1;
saved_errno = errno;
rval = fs_rename_at(f->dirfd, f->tmpbase,
f->dirfd, f->base);
if (rval > -1)
tmp_gbe_bin_exists = 0;
ret_gbe_mv:
/* TODO: this whole section is bloat.
it can be generalised
*/
if (f->gbe_fd > -1) {
if (close_on_eintr(f->gbe_fd) < 0) {
f->gbe_fd = -1;
rval = -1;
}
f->gbe_fd = -1;
if (fsync_dir(f->fname) < 0) {
f->io_err_gbe_bin = 1;
rval = -1;
}
}
if (f->tmp_fd > -1) {
if (close_on_eintr(f->tmp_fd) < 0) {
f->tmp_fd = -1;
rval = -1;
}
f->tmp_fd = -1;
}
/* 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) {
/* if nothing set errno,
* we assume EIO, or we
* use what was set
*/
if (errno == saved_errno)
errno = EIO;
} else {
errno = saved_errno;
}
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)
b0rk(ECANCELED, "%s: GbE %s %s out of bounds",
f->fname, d_type, f_op);
if (off != 0 && off != ncmp >> 1)
b0rk(ECANCELED, "%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_file_exact(fd, mem, nrw, off, rw_type,
NO_LOOP_EAGAIN, LOOP_EINTR, MAX_ZERO_RW_RETRY,
OFF_ERR);
return rw_over_nrw(r, nrw);
err_rw_gbe_file_exact:
errno = EIO;
return -1;
}
File diff suppressed because it is too large Load Diff
+119
View File
@@ -0,0 +1,119 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* Numerical functions.
* NOTE: randomness was moved to rand.c
*/
/*
TODO: properly handle errno in this file
*/
#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 <limits.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include "../include/common.h"
/* TODO:
* make this and errno handling more
* flexible
in particular:
hextonum could be modified to
write into a buffer instead,
with the converted numbers,
of an arbitrary length
*/
unsigned short
hextonum(char ch_s)
{
int saved_errno = errno;
/* rlong() can return error,
but preserves errno if no
error. we need to detect
this because it handles
/dev/urandom sometimes
therefore, if it's zero
at start, we know if there
was an err at the end, by
return value zero, if errno
was set; this is technically
valid, since zero is also
a valid random number!
it's an edge case that i had
to fix. i'll rewrite the code
better later. for now, it
should be ok.
*/
errno = 0;
unsigned char ch;
size_t rval;
ch = (unsigned char)ch_s;
if ((unsigned int)(ch - '0') <= 9) {
rval = ch - '0';
goto hextonum_success;
}
ch |= 0x20;
if ((unsigned int)(ch - 'a') <= 5) {
rval = ch - 'a' + 10;
goto hextonum_success;
}
if (ch == '?' || ch == 'x') {
rval = rlong();
if (errno > 0)
goto err_hextonum;
goto hextonum_success;
}
goto err_hextonum;
hextonum_success:
errno = saved_errno;
return (unsigned short)rval & 0xf;
err_hextonum:
if (errno == saved_errno)
errno = EINVAL;
else
return 17; /* 17 indicates getrandom/urandom fail */
return 16; /* invalid character */
/* caller just checks >15. */
}
void
check_bin(size_t a, const char *a_name)
{
if (a > 1)
err_no_cleanup(0, EINVAL, "%s must be 0 or 1, but is %lu",
a_name, (size_t)a);
}
+114
View File
@@ -0,0 +1,114 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* Random number generation
*/
#ifndef RAND_H
#define RAND_H
#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 <limits.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include "../include/common.h"
/* Random numbers
*/
/* when calling this: save errno
* first, then set errno to zero.
* on error, this function will
* set errno and possibly return
*
* rlong also preserves errno
* and leaves it unchanged on
* success, so if you do it
* right, you can detect error.
* this is because it uses
* /dev/urandom which can err.
* ditto getrandom (EINTR),
* theoretically.
*/
/* for the linux version: we use only the
* syscall, because we cannot trust /dev/urandom
* to be as robust, and some libc implementations
* may default to /dev/urandom under fault conditions.
*
* for general high reliability, we must abort on
* failure. in practise, it will likely never fail.
* the arc4random call on bsd never returns error.
*/
size_t
rlong(void)
{
size_t rval;
int saved_errno = errno;
errno = 0;
#if (defined(__OpenBSD__) || defined(__FreeBSD__) || \
defined(__NetBSD__) || defined(__APPLE__) || \
defined(__DragonFly__))
arc4random_buf(&rval, sizeof(size_t));
goto out;
#elif defined(__linux__)
size_t off = 0;
size_t len = sizeof(rval);
ssize_t rc;
retry_rand:
rc = (ssize_t)syscall(SYS_getrandom,
(char *)&rval + off, len - off, 0);
if (rc < 0) {
if (errno == EINTR || errno == EAGAIN) {
usleep(100);
goto retry_rand;
}
goto err; /* possibly unsupported by kernel */
}
if ((off += (size_t)rc) < len)
goto retry_rand;
goto out;
err:
/*
* getrandom can return with error, but arc4random
* doesn't. generally, getrandom will be reliable,
* but we of course have to maintain parity with
* BSD. So a rand failure is to be interpreted as
* a major systems failure, and we act accordingly.
*/
err_no_cleanup(1, ECANCELED,
"Randomisation failure, possibly unsupported in your kernel.");
exit(EXIT_FAILURE);
#else
#error Unsupported operating system (possibly unsecure randomisation)
#endif
out:
errno = saved_errno;
return rval;
}
#endif
+233
View File
@@ -0,0 +1,233 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
*
* State machine (singleton) for nvmutil data.
*/
#ifdef __OpenBSD__
#include <sys/param.h>
#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},
/* .argv0 (for our getprogname implementation) */
NULL,
/* ->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)
err_no_cleanup(0, EINVAL, "xstart: Too few arguments");
if (argv == NULL)
err_no_cleanup(0, EINVAL, "xstart: NULL argv");
first_run = 0;
us.f.buf = us.f.real_buf;
us.argv0 = argv[0];
us.f.fname = argv[1];
us.f.tmp_fd = -1;
us.f.tname = NULL;
if ((realdir = realpath(us.f.fname, NULL)) == NULL)
err_no_cleanup(0, errno, "xstart: can't get realpath of %s",
us.f.fname);
if (fs_dirname_basename(realdir, &dir, &base, 0) < 0)
err_no_cleanup(0, errno, "xstart: don't know CWD of %s",
us.f.fname);
if ((us.f.base = strdup(base)) == NULL)
err_no_cleanup(0, errno, "strdup base");
us.f.dirfd = fs_open(dir,
O_RDONLY | O_DIRECTORY);
if (us.f.dirfd < 0)
err_no_cleanup(0, errno, "%s: open dir", dir);
if (new_tmpfile(&us.f.tmp_fd, &us.f.tname, dir, ".gbe.XXXXXXXXXX") < 0)
err_no_cleanup(0, errno, "%s", us.f.tname);
if (fs_dirname_basename(us.f.tname,
&tmpdir, &tmpbase_local, 0) < 0)
err_no_cleanup(0, errno, "tmp basename");
us.f.tmpbase = strdup(tmpbase_local);
if (us.f.tmpbase == NULL)
err_no_cleanup(0, errno, "strdup tmpbase");
free_if_null(&tmpdir);
if (us.f.tname == NULL)
err_no_cleanup(0, errno, "x->f.tname null");
if (*us.f.tname == '\0')
err_no_cleanup(0, errno, "x->f.tname empty");
if (fstat(us.f.tmp_fd, &us.f.tmp_st) < 0)
err_no_cleanup(0, errno, "%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)
err_no_cleanup(0, EACCES, "NULL pointer to xstate");
return x;
}
void
b0rk(int nvm_errval, const char *msg, ...)
{
struct xstate *x = xstatus();
va_list args;
if (errno == 0)
errno = nvm_errval;
if (!errno)
errno = ECANCELED;
(void)exit_cleanup();
if (x != NULL)
fprintf(stderr, "%s: ", getnvmprogname());
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
fprintf(stderr, ": %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
int
exit_cleanup(void)
{
struct xstate *x = xstatus();
struct xfile *f;
int close_err;
int saved_errno;
close_err = 0;
saved_errno = errno;
if (x != NULL) {
f = &x->f;
close_no_err(&f->gbe_fd);
close_no_err(&f->tmp_fd);
close_no_err(&f->tmp_fd);
if (f->tname != NULL)
if (unlink(f->tname) == -1)
close_err = 1;
close_no_err(&f->dirfd);
free_if_null(&f->base);
free_if_null(&f->tmpbase);
}
if (saved_errno)
errno = saved_errno;
if (close_err)
return -1;
return 0;
}
+284
View File
@@ -0,0 +1,284 @@
/* 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"
/* strict strcmp */
int
scmp(const char *a,
const char *b,
size_t maxlen,
int *rval)
{
size_t ch;
unsigned char ac;
unsigned char bc;
if (a == NULL ||
b == NULL ||
rval == NULL) {
errno = EFAULT;
goto err;
}
for (ch = 0; ch < maxlen; ch++) {
ac = (unsigned char)a[ch];
bc = (unsigned char)b[ch];
if (ac != bc) {
*rval = ac - bc;
return 0;
}
if (ac == '\0') {
*rval = 0;
return 0;
}
}
err:
errno = EFAULT;
if (rval != NULL)
*rval = -1;
return -1;
}
/* strict strlen */
int
slen(const char *s,
size_t maxlen,
size_t *rval)
{
size_t ch;
if (s == NULL ||
rval == NULL) {
errno = EFAULT;
goto err;
}
for (ch = 0;
ch < maxlen && s[ch] != '\0';
ch++);
if (ch == maxlen) {
/* unterminated */
errno = EFAULT;
goto err;
}
*rval = ch;
return 0;
err:
if (rval != NULL)
*rval = 0;
return -1;
}
/* strict strdup */
int
sdup(const char *s,
size_t n, char **dest)
{
size_t size;
char *rval;
if (dest == NULL ||
slen(s, n, &size) < 0 ||
if_err(size == SIZE_MAX, EOVERFLOW) ||
(rval = malloc(size + 1)) == NULL) {
if (dest != NULL)
*dest = NULL;
return -1;
}
memcpy(rval, s, size);
*(rval + size) = '\0';
*dest = rval;
return 0;
}
/* strict strcat */
int
scat(const char *s1, const char *s2,
size_t n, char **dest)
{
size_t size1;
size_t size2;
char *rval;
if (dest == NULL ||
slen(s1, n, &size1) < 0 ||
slen(s2, n, &size2) < 0 ||
if_err(size1 > SIZE_MAX - size2 - 1, EOVERFLOW) ||
(rval = malloc(size1 + size2 + 1)) == NULL) {
if (dest != NULL)
*dest = NULL;
return -1;
}
memcpy(rval, s1, size1);
memcpy(rval + size1, s2, size2);
*(rval + size1 + size2) = '\0';
*dest = rval;
return 0;
}
/* strict split/de-cat - off is where
2nd buffer will start from */
int
dcat(const char *s, size_t n,
size_t off, char **dest1,
char **dest2)
{
size_t size;
char *rval1 = NULL;
char *rval2 = NULL;
if (dest1 == NULL || dest2 == NULL ||
slen(s, n, &size) < 0 ||
if_err(size == SIZE_MAX, EOVERFLOW) ||
if_err(off >= size, EOVERFLOW) ||
(rval1 = malloc(off + 1)) == NULL ||
(rval2 = malloc(size - off + 1)) == NULL) {
goto err;
}
memcpy(rval1, s, off);
*(rval1 + off) = '\0';
memcpy(rval2, s + off, size - off);
*(rval2 + size - off) = '\0';
*dest1 = rval1;
*dest2 = rval2;
return 0;
err:
if (rval1 != NULL)
free(rval1);
if (rval2 != NULL)
free(rval2);
if (dest1 != NULL)
*dest1 = NULL;
if (dest2 != NULL)
*dest2 = NULL;
return -1;
}
/* the one for nvmutil state is in state.c */
/* this one just exits */
void
err_no_cleanup(int stfu, int nvm_errval, const char *msg, ...)
{
va_list args;
int saved_errno = errno;
const char *p;
#if defined(__OpenBSD__) && defined(OpenBSD)
#if (OpenBSD) >= 509
if (pledge("stdio", NULL) == -1)
fprintf(stderr, "pledge failure during exit");
#endif
#endif
if (!errno)
saved_errno = errno = ECANCELED;
if ((p = getnvmprogname()) != NULL)
fprintf(stderr, "%s: ", p);
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
if (p != NULL)
fprintf(stderr, ": %s\n", strerror(errno));
else
fprintf(stderr, "%s\n", strerror(errno));
exit(EXIT_FAILURE);
}
const char *
getnvmprogname(void)
{
static char *rval = NULL;
static char *p;
static int setname = 0;
if (!setname) {
if ((rval = lbgetprogname(NULL)) == NULL)
return NULL;
p = strrchr(rval, '/');
if (p)
rval = p + 1;
setname = 1;
}
return rval;
}
/* singleton. if string not null,
sets the string. after set,
will not set anymore. either
way, returns the string
*/
char *
lbgetprogname(char *argv0)
{
static int setname = 0;
static char *progname = NULL;
size_t len;
if (!setname) {
if (if_err(argv0 == NULL || *argv0 == '\0', EFAULT) ||
slen(argv0, 4096, &len) < 0 ||
(progname = malloc(len + 1)) == NULL)
return NULL;
memcpy(progname, argv0, len + 1);
setname = 1;
}
return progname;
}
+30
View File
@@ -0,0 +1,30 @@
/* 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 = getnvmprogname();
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);
b0rk(EINVAL, "Too few arguments");
}
+68
View File
@@ -0,0 +1,68 @@
/* 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)
b0rk(ECANCELED, "check_nvm_bound: out of bounds %lu",
(size_t)c);
}