Compare commits

...

19 Commits

Author SHA1 Message Date
Leah Rowe
6402a0fbe9 util/nvmutil: unified urandom/gbe file reading
like before, but with the newly correct logic

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-10 03:08:09 +00:00
Leah Rowe
4131402589 util/nvmutil: safer read_gbe_file_exact
it now retries infinitely on EINTR, except when the return
of pread is precisely zero, at which point it errs.

this is better than having an arbitrary maximum like before,
and increases robustness on unreliable file systems, e.g.
NFS shares.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-10 02:48:34 +00:00
Leah Rowe
0c23474322 util/nvmutil: report checksum in cmd_dump
as it should be!

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-10 01:51:18 +00:00
Leah Rowe
84a9e8f89b util/nvmutil: reduce checksum report verbosity
only print a message what arg_part is set. this
means that a checksum error message won't be printed
on cat commands.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-10 01:38:22 +00:00
Leah Rowe
007dece09e util/nvmutil: unified io flags
don't hardcode it per command logically. do it in
the command table instead.

this also fixes a bug where the cat commands did
not set the permissions read-only.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-10 01:29:12 +00:00
Leah Rowe
bb9010fdc1 util/nvmutil: require good checksum on cat
since the cat command can be used to create bad
gbe files, if the checksums don't match. my rule
is that nvmutil must never be used to destroy
data, only correct it (e.g. a file with just one
valid part can have it copied to the other part,
but you can't copy a bad part - and i removed
the "brick" command).

i *did* disable checksum requirements on the
dump command. with this, you can check the nvm
area and it tells you what the correct checksum
could be. then you could just correct it in a
hex editor if you wanted to, quite easily.

the idea is to slow down the act of destroying
or corrupting data as much as possible. someone
wily enough can use a hex editor to patch up some
files just fine.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-10 01:04:28 +00:00
Leah Rowe
08b1d95874 util/nvmutil: initialise w in gbe_cat_buf
no build error at the moment, nor would there be if
using clang or gcc, but i imagine some buggy compilers
might complain.

remember: portability. i also want this code to compile
on old, buggy compilers.

logically, this initialisation is redundant.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-10 00:53:53 +00:00
Leah Rowe
8f3bc13ac5 util/nvmutil: simplify the cat command
the current test is a bit over-engineered, so
i simplified it.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-10 00:46:18 +00:00
Leah Rowe
aa67cf087d util/nvmutil: add cat16 and cat128 commands
these take any file size of gbe file: 8KB, 16KB
or 128KB. so does the normal cat.

then you can use cat, cat16 or cat128. these
output to stdout, the corresponding size in KB.

0xFF padding used on the larger files. on the
larger files, the first 4KB of each half is the
GbE parts, and everything else is 0xFF padding.

now you can resize gbe files easily, example:

./nvmutil gbe128.bin > gbe8.bin

yes

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-09 23:47:22 +00:00
Leah Rowe
66f1640552 util/nvmutil: nope. rename out back to cat.
it *is* cat. it's catting two GbE parts. so its cat.

(two 4KB areas, plus padding when i add cat16/cat128)

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-09 22:54:38 +00:00
Leah Rowe
724c9bb36c util/nvmutil: rename cat to out
it doesn't cat. it outputs one file.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-09 22:34:28 +00:00
Leah Rowe
3d6e2637d6 util/nvmutil: remove stale comment
Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-09 22:25:39 +00:00
Leah Rowe
1e45d19f5d util/nvmutil: added a "cat" command
with this, you can read 16KB and 128KB files, and output
them to stdout, but it outputs 8KB

for example:

./nvmutil gbe128.bin > gbe8.bin

now you have a 8KB file

i could probably easily add cat16 and cat128 too.

nvmutil reads two 4KB parts regardless of GbE file
size (one from the first 4KB of each half of the
file), so this was easy to implement.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-09 22:18:51 +00:00
Leah Rowe
7d64b8ea8d util/nvmutil: allow dump without good checksums
Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-09 21:49:07 +00:00
Leah Rowe
45ea92a077 util/nvmutil: fix bad cast conversion
don't cast unsigned to signed.

no behaviour is changed, but this will prevent some
silly compilers complaining about -Wsign-conversion

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-09 21:12:59 +00:00
Leah Rowe
b5af1bf3ac util/nvmutil: add guard in rhex()
i removed this before, but it's good to put it
here defensively, in case i ever mess up
the urandom read function again.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-09 21:08:36 +00:00
Leah Rowe
f8ddb6ef84 util/nvmutil: fix EINTR detection on urandom read
i forgot to handle it in the previous refactor

not really a problem in practise, since the first
read probably succeeds anyway.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-09 21:06:18 +00:00
Leah Rowe
c59b3b7638 util/nvmutil: reorder some functions linearly
Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-09 20:47:45 +00:00
Leah Rowe
0f9ce929b4 util/nvmutil: tidy up gbe/urandom reading
split them up into their own functions, since they
no longer operate according to the same policy.

Signed-off-by: Leah Rowe <leah@libreboot.org>
2026-03-09 20:41:12 +00:00

View File

@@ -88,10 +88,6 @@ static void check_enum_bin(size_t a, const char *a_name,
static void set_cmd(int argc, char *argv[]);
static void set_cmd_args(int argc, char *argv[]);
static size_t conv_argv_part_num(const char *part_str);
static void set_io_flags(int argc, char *argv[]);
static void run_cmd(size_t c);
static void check_command_num(size_t c);
static uint8_t valid_command(size_t c);
static int xstrxcmp(const char *a, const char *b, size_t maxlen);
#ifndef NVMUTIL_ARC4RANDOM_BUF
static void open_dev_urandom(void);
@@ -102,6 +98,9 @@ static void read_gbe_file(void);
static void read_gbe_file_part(size_t part);
static void read_checksums(void);
static int good_checksum(size_t partnum);
static void run_cmd(size_t c);
static void check_command_num(size_t c);
static uint8_t valid_command(size_t c);
static void cmd_helper_setmac(void);
static void parse_mac_string(void);
static size_t xstrxlen(const char *scmp, size_t maxlen);
@@ -110,12 +109,14 @@ static void set_mac_nib(size_t mac_str_pos,
size_t mac_byte_pos, size_t mac_nib_pos);
static uint16_t hextonum(char ch_s);
static uint16_t rhex(void);
static ssize_t read_gbe_file_exact(int fd, void *buf, size_t len,
off_t off, const char *path, const char *op);
static void read_file_exact(int fd, uint8_t *mem, size_t len,
off_t off, uint8_t plesen, const char *path);
static void write_mac_part(size_t partnum);
static void cmd_helper_dump(void);
static void print_mac_from_nvm(size_t partnum);
static void hexdump(size_t partnum);
static void cmd_helper_cat(void);
static void gbe_cat_buf(uint8_t *b);
static void write_gbe_file(void);
static void override_part_modified(void);
static void set_checksum(size_t part);
@@ -127,7 +128,7 @@ static void check_nvm_bound(size_t pos16, size_t part);
static void check_bin(size_t a, const char *a_name);
static void write_gbe_file_part(size_t part);
static off_t gbe_file_offset(size_t part, const char *f_op);
static void *gbe_mem_offset(size_t part, const char *f_op);
static uint8_t *gbe_mem_offset(size_t part, const char *f_op);
static off_t gbe_x_offset(size_t part, const char *f_op,
const char *d_type, off_t nsize, off_t ncmp);
static void err(int nvm_errval, const char *msg, ...);
@@ -192,11 +193,11 @@ static const char *rname = NULL;
* The code will handle this properly.
*/
static uint8_t buf[GBE_FILE_SIZE];
static uint8_t pad[GBE_PART_SIZE];
static uint16_t mac_buf[3];
static off_t gbe_file_size;
static int gbe_flags;
#ifndef NVMUTIL_ARC4RANDOM_BUF
static int urandom_fd = -1;
#endif
@@ -232,6 +233,9 @@ enum {
CMD_SETMAC,
CMD_SWAP,
CMD_COPY,
CMD_CAT,
CMD_CAT16,
CMD_CAT128
};
/*
@@ -272,27 +276,24 @@ struct commands {
uint8_t chksum_read;
uint8_t chksum_write;
size_t rw_size; /* within the 4KB GbE part */
int flags; /* e.g. O_RDWR or O_RDONLY */
};
/*
* Command table, for nvmutil commands
*/
static const struct commands command[] = {
/*
* Unlike older versions, we require at least
* one checksum to be valid when running dump.
*/
{ CMD_DUMP, "dump", cmd_helper_dump, ARGC_3,
NO_INVERT, SET_MOD_OFF,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
NVM_SIZE },
SKIP_CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
NVM_SIZE, O_RDONLY },
{ CMD_SETMAC, "setmac", cmd_helper_setmac, ARGC_3,
NO_INVERT, SET_MOD_OFF,
ARG_NOPART,
CHECKSUM_READ, CHECKSUM_WRITE,
NVM_SIZE },
NVM_SIZE, O_RDWR },
/*
* OPTIMISATION: Read inverted, so no copying is needed.
@@ -301,7 +302,7 @@ static const struct commands command[] = {
PART_INVERT, SET_MOD_BOTH,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE },
GBE_PART_SIZE, O_RDWR },
/*
* OPTIMISATION: Read inverted, so no copying is needed.
@@ -311,7 +312,25 @@ static const struct commands command[] = {
PART_INVERT, SET_MOD_N,
ARG_PART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE },
GBE_PART_SIZE, O_RDWR },
{ CMD_CAT, "cat", cmd_helper_cat, ARGC_3,
NO_INVERT, SET_MOD_OFF,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDONLY },
{ CMD_CAT16, "cat16", cmd_helper_cat, ARGC_3,
NO_INVERT, SET_MOD_OFF,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDONLY },
{ CMD_CAT128, "cat128", cmd_helper_cat, ARGC_3,
NO_INVERT, SET_MOD_OFF,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDONLY },
};
#define MAX_CMD_LEN 50
@@ -348,11 +367,10 @@ main(int argc, char *argv[])
set_cmd(argc, argv);
set_cmd_args(argc, argv);
set_io_flags(argc, argv);
#ifdef NVMUTIL_PLEDGE
#ifdef NVMUTIL_UNVEIL
if (gbe_flags == O_RDONLY) {
if (command[cmd_index].flags == O_RDONLY) {
if (unveil(fname, "r") == -1)
err(ECANCELED, "%s: unveil ro", fname);
if (unveil(NULL, NULL) == -1)
@@ -368,7 +386,7 @@ main(int argc, char *argv[])
err(ECANCELED, "pledge rw (kill unveil)");
}
#else
if (gbe_flags == O_RDONLY) {
if (command[cmd_index].flags == O_RDONLY) {
if (pledge("stdio rpath", NULL) == -1)
err(ECANCELED, "pledge ro");
}
@@ -391,6 +409,11 @@ main(int argc, char *argv[])
err(ECANCELED, "pledge stdio (main)");
#endif
/*
* Used by CMD_CAT, for padding
*/
memset(pad, 0xff, sizeof(pad));
read_gbe_file();
read_checksums();
@@ -398,7 +421,7 @@ main(int argc, char *argv[])
if (errno)
err(errno, "%s: Unhandled error (WRITE SKIPPED)", fname);
else if (gbe_flags != O_RDONLY)
else if (command[cmd_index].flags == O_RDWR)
write_gbe_file();
close_files();
@@ -485,6 +508,10 @@ sanitize_command_index(size_t c)
if (gbe_rw_size > GBE_PART_SIZE)
err(EINVAL, "rw_size larger than GbE part: %zu",
gbe_rw_size);
if (command[c].flags != O_RDONLY &&
command[c].flags != O_RDWR)
err(EINVAL, "invalid cmd.flags setting");
}
static void
@@ -557,46 +584,6 @@ conv_argv_part_num(const char *part_str)
return (size_t)(ch - '0');
}
static void
set_io_flags(int argc, char *argv[])
{
gbe_flags = O_RDWR;
if (argc < 3)
return;
if (xstrxcmp(argv[2], "dump", MAX_CMD_LEN) == 0)
gbe_flags = O_RDONLY;
}
static void
run_cmd(size_t c)
{
check_command_num(c);
if (command[c].run)
command[c].run();
}
static void
check_command_num(size_t c)
{
if (!valid_command(c))
err(ECANCELED, "Invalid run_cmd arg: %zu", c);
}
static uint8_t
valid_command(size_t c)
{
if (c >= N_COMMANDS)
return 0;
if (c != command[c].chk)
err(ECANCELED, "Invalid cmd chk value (%zu) vs arg: %zu",
command[c].chk, c);
return 1;
}
/*
* Portable strcmp() but blocks NULL/empty/unterminated
* strings. Even stricter than strncmp().
@@ -661,7 +648,7 @@ open_gbe_file(void)
{
struct stat gbe_st;
xopen(&gbe_fd, fname, gbe_flags, &gbe_st);
xopen(&gbe_fd, fname, command[cmd_index].flags, &gbe_st);
gbe_file_size = gbe_st.st_size;
@@ -708,17 +695,12 @@ static void
read_gbe_file_part(size_t p)
{
size_t gbe_rw_size = command[cmd_index].rw_size;
void *mem_offset =
uint8_t *mem_offset =
gbe_mem_offset(p ^ command[cmd_index].invert, "pread");
if ((size_t)read_gbe_file_exact(gbe_fd, mem_offset,
gbe_rw_size, gbe_file_offset(p, "pread"), fname, "pread") !=
gbe_rw_size)
err(ECANCELED, "%s: Partial read from p%zu", fname, p);
printf("%s: Read %zu bytes from p%zu\n",
fname, gbe_rw_size, p);
read_file_exact(gbe_fd, mem_offset,
gbe_rw_size, gbe_file_offset(p, "pread"),
1, fname);
}
static void
@@ -764,9 +746,13 @@ read_checksums(void)
if (num_invalid < max_invalid)
errno = 0;
if (num_invalid >= max_invalid)
if (num_invalid >= max_invalid) {
if (max_invalid == 1)
err(ECANCELED, "%s: part %zu has a bad checksum",
fname, part);
err(ECANCELED, "%s: No valid checksum found in file",
fname);
}
}
static int
@@ -775,25 +761,41 @@ good_checksum(size_t partnum)
uint16_t expected_checksum = calculated_checksum(partnum);
uint16_t current_checksum = nvm_word(NVM_CHECKSUM_WORD, partnum);
size_t real_partnum = partnum ^ command[cmd_index].invert;
if (current_checksum == expected_checksum)
return 1;
fprintf(stderr,
"WARNING: BAD checksum in part %zu\n"
"EXPECTED checksum in part %zu: %04x\n"
"CURRENT checksum in part %zu: %04x\n",
real_partnum,
real_partnum,
expected_checksum,
real_partnum,
current_checksum);
set_err(ECANCELED);
return 0;
}
static void
run_cmd(size_t c)
{
check_command_num(c);
if (command[c].run)
command[c].run();
}
static void
check_command_num(size_t c)
{
if (!valid_command(c))
err(ECANCELED, "Invalid run_cmd arg: %zu", c);
}
static uint8_t
valid_command(size_t c)
{
if (c >= N_COMMANDS)
return 0;
if (c != command[c].chk)
err(ECANCELED, "Invalid cmd chk value (%zu) vs arg: %zu",
command[c].chk, c);
return 1;
}
static void
cmd_helper_setmac(void)
{
@@ -930,81 +932,46 @@ rhex(void)
{
static size_t n = 0;
static uint8_t rnum[12];
#ifndef NVMUTIL_ARC4RANDOM_BUF
int max_retries;
#endif
#ifdef NVMUTIL_ARC4RANDOM_BUF
if (!n) {
n = sizeof(rnum);
#ifdef NVMUTIL_ARC4RANDOM_BUF
arc4random_buf(rnum, n);
}
#else
for (max_retries = 0; max_retries < 50 && !n; max_retries++)
n = (size_t)read_gbe_file_exact(urandom_fd,
rnum, sizeof(rnum), 0, rname, NULL);
if (!n || n > sizeof(rnum))
err(ECANCELED, "Randomisation failure");
read_file_exact(urandom_fd, rnum, n, 0, 0, rname);
#endif
}
return (uint16_t)(rnum[--n] & 0xf);
}
static ssize_t
read_gbe_file_exact(int fd, void *buf, size_t len,
off_t off, const char *path, const char *op)
static void
read_file_exact(int fd, uint8_t *mem,
size_t len, off_t off, uint8_t plesen, const char *path)
{
int retry;
ssize_t rval;
ssize_t rval = -1;
ssize_t rc = 0;
if (fd == -1)
err(ECANCELED, "Trying to open bad fd: %s", path);
for (retry = 0; retry < MAX_RETRY_RW; retry++) {
if (op)
rval = pread(fd, buf, len, off);
for (rc = 0; rc != (ssize_t)len; rc += rval) {
if (plesen)
rval = pread(fd, mem + rc, len - rc, off + rc);
else
rval = read(fd, buf, len);
rval = read(fd, mem + rc, len - rc);
if (rval == (ssize_t)len) {
errno = 0;
return rval;
if (rval > -1) {
if (!rval) /* prevent infinite loop */
err(EIO, "%s: read of 0 bytes", path);
continue;
}
if (rval != -1) {
#ifndef NVMUTIL_ARC4RANDOM_BUF
if (fd == urandom_fd) {
/*
* /dev/[u]random reads can still return
* partial reads legally, on some weird
* Unix systems (especially older ones).
*
* We use a circular buffer for random
* bytes in rhex(), so we can just use
* the smaller amount of bytes and call
* read_gbe_file_exact again if necessary.
*/
if (rval > 0) {
errno = 0;
return rval;
}
}
#endif
err(ECANCELED,
"Short %s, %zd bytes, on file: %s",
op ? op : "read", rval, path);
}
if (errno != EINTR || rval < -1)
err(EIO, "%s", path);
if (errno != EINTR)
err(ECANCELED,
"Could not %s file: '%s'",
op ? op : "read", path);
errno = 0;
}
err(EINTR, "%s: max retries exceeded on file: %s",
op ? op : "read", path);
return -1;
}
static void
@@ -1028,7 +995,18 @@ cmd_helper_dump(void)
{
size_t partnum;
part_valid[0] = good_checksum(0);
part_valid[1] = good_checksum(1);
if (part_valid[0] || part_valid[1])
errno = 0;
for (partnum = 0; partnum < 2; partnum++) {
if (!part_valid[partnum])
fprintf(stderr,
"BAD checksum %04x in part %zu (expected %04x)\n",
nvm_word(NVM_CHECKSUM_WORD, partnum),
partnum, calculated_checksum(partnum));
printf("MAC (part %zu): ", partnum);
print_mac_from_nvm(partnum);
@@ -1070,6 +1048,41 @@ hexdump(size_t partnum)
}
}
static void
cmd_helper_cat(void)
{
size_t p;
size_t ff;
size_t n = 0;
if (cmd_index == CMD_CAT16)
n = 1;
else if (cmd_index == CMD_CAT128)
n = 15;
else if (cmd_index != CMD_CAT)
err(ECANCELED, "cmd_helper_cat called erroneously");
fflush(NULL);
for (p = 0; p < 2; p++) {
gbe_cat_buf(buf + (p * GBE_PART_SIZE));
for (ff = 0; ff < n; ff++)
gbe_cat_buf(pad);
}
}
static void
gbe_cat_buf(uint8_t *b)
{
size_t wc;
ssize_t w = 0;
for (wc = 0; wc < GBE_PART_SIZE; wc += w)
if ((w = write(STDOUT_FILENO, b + wc, GBE_PART_SIZE - wc)) < 1)
err(EIO, "%s: stdout", fname);
}
static void
write_gbe_file(void)
{
@@ -1077,7 +1090,7 @@ write_gbe_file(void)
size_t partnum;
uint8_t update_checksum;
if (gbe_flags == O_RDONLY)
if (command[cmd_index].flags == O_RDONLY)
return;
update_checksum = command[cmd_index].chksum_write;
@@ -1231,7 +1244,7 @@ write_gbe_file_part(size_t p)
if (rval != -1)
err(ECANCELED,
"%s: Short pwrite, %zd bytes",
"%s: Short pwrite of %zd bytes",
fname, rval);
if (errno != EINTR)
@@ -1264,13 +1277,13 @@ gbe_file_offset(size_t p, const char *f_op)
* but used to check Gbe bounds in memory,
* and it is *also* used during file I/O.
*/
static void *
static uint8_t *
gbe_mem_offset(size_t p, const char *f_op)
{
off_t gbe_off = gbe_x_offset(p, f_op, "mem",
GBE_PART_SIZE, GBE_FILE_SIZE);
return (void *)(buf + gbe_off);
return (uint8_t *)(buf + gbe_off);
}
static off_t
@@ -1375,8 +1388,12 @@ usage(uint8_t usage_exit)
"\t%s FILE dump\n"
"\t%s FILE setmac [MAC]\n"
"\t%s FILE swap\n"
"\t%s FILE copy 0|1\n",
util, util, util, util);
"\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);
if (usage_exit)
err(EINVAL, "Too few arguments");