mirror of
https://codeberg.org/libreboot/lbmk.git
synced 2026-07-19 00:03:45 +02:00
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:
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user