use old nvmutil for now, in lbmk

i'm trying to make nvmutil work on openbsd. the new code
in lbutils is a bit buggy, likely somewhere in mkhtemp.
i'm still debugging it.

Signed-off-by: Leah Rowe <leah@libreboot.org>
This commit is contained in:
Leah Rowe
2026-04-21 13:39:29 +01:00
parent 4b5aca8ff8
commit 7f4f07fc40
16 changed files with 3712 additions and 3 deletions
+3 -3
View File
@@ -6,7 +6,7 @@
cbcfgsdir="config/coreboot" cbcfgsdir="config/coreboot"
tmpromdel="$XBMK_CACHE/DO_NOT_FLASH" tmpromdel="$XBMK_CACHE/DO_NOT_FLASH"
nvmutil="util/libreboot-utils/nvmutil" nvmutil="util/nvmutil/nvmutil"
ifdtool="elf/coreboot/default/ifdtool" ifdtool="elf/coreboot/default/ifdtool"
checkvars="CONFIG_GBE_BIN_PATH" checkvars="CONFIG_GBE_BIN_PATH"
@@ -197,8 +197,8 @@ modify_mac()
x_ cp "${CONFIG_GBE_BIN_PATH##*../}" "$xbtmp/gbe" x_ cp "${CONFIG_GBE_BIN_PATH##*../}" "$xbtmp/gbe"
if [ -n "$new_mac" ] && [ "$new_mac" != "restore" ]; then if [ -n "$new_mac" ] && [ "$new_mac" != "restore" ]; then
x_ make -C util/libreboot-utils clean x_ make -C util/nvmutil clean
x_ make -C util/libreboot-utils x_ make -C util/nvmutil
x_ "$nvmutil" "$xbtmp/gbe" setmac "$new_mac" x_ "$nvmutil" "$xbtmp/gbe" setmac "$new_mac"
fi fi
+5
View File
@@ -0,0 +1,5 @@
/nvm
/nvmutil
*.bin
*.o
*.d
+2
View File
@@ -0,0 +1,2 @@
Leah Rowe
Riku Viitanen
+21
View File
@@ -0,0 +1,21 @@
Copyright (C) 2022-2026 Leah Rowe <leah@libreboot.org>
Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+114
View File
@@ -0,0 +1,114 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2022,2026 Leah Rowe <leah@libreboot.org>
# Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com>
# Makefile for nvmutil, which is an application
# that modifies Intel GbE NVM configurations.
CC = cc
HELLCC = clang
CFLAGS =
LDFLAGS =
DESTDIR =
PREFIX = /usr/local
INSTALL = install
.SUFFIXES: .c .o
LDIR =
PORTABLE = $(LDIR) $(CFLAGS)
WARN = $(PORTABLE) -Wall -Wextra
STRICT = $(WARN) -std=c90 -pedantic -Werror
HELLFLAGS = $(STRICT) -Weverything
PROG = nvmutil
OBJS = \
obj/nvmutil.o \
obj/lib/state.o \
obj/lib/file.o \
obj/lib/string.o \
obj/lib/usage.o \
obj/lib/command.o \
obj/lib/num.o \
obj/lib/io.o \
obj/lib/checksum.o \
obj/lib/word.o
# default mode
CFLAGS_MODE = $(PORTABLE)
CC_MODE = $(CC)
all: $(PROG)
$(PROG): $(OBJS)
$(CC_MODE) $(OBJS) -o $(PROG) $(LDFLAGS)
# ensure obj directory exists
$(OBJS): obj
obj:
mkdir obj || true
mkdir obj/lib || true
# main program object
obj/nvmutil.o: nvmutil.c
$(CC_MODE) $(CFLAGS_MODE) -c nvmutil.c -o obj/nvmutil.o
# library/helper objects
obj/lib/state.o: lib/state.c
$(CC_MODE) $(CFLAGS_MODE) -c lib/state.c -o obj/lib/state.o
obj/lib/file.o: lib/file.c
$(CC_MODE) $(CFLAGS_MODE) -c lib/file.c -o obj/lib/file.o
obj/lib/string.o: lib/string.c
$(CC_MODE) $(CFLAGS_MODE) -c lib/string.c -o obj/lib/string.o
obj/lib/usage.o: lib/usage.c
$(CC_MODE) $(CFLAGS_MODE) -c lib/usage.c -o obj/lib/usage.o
obj/lib/command.o: lib/command.c
$(CC_MODE) $(CFLAGS_MODE) -c lib/command.c -o obj/lib/command.o
obj/lib/num.o: lib/num.c
$(CC_MODE) $(CFLAGS_MODE) -c lib/num.c -o obj/lib/num.o
obj/lib/io.o: lib/io.c
$(CC_MODE) $(CFLAGS_MODE) -c lib/io.c -o obj/lib/io.o
obj/lib/checksum.o: lib/checksum.c
$(CC_MODE) $(CFLAGS_MODE) -c lib/checksum.c -o obj/lib/checksum.o
obj/lib/word.o: lib/word.c
$(CC_MODE) $(CFLAGS_MODE) -c lib/word.c -o obj/lib/word.o
# install
install: $(PROG)
$(INSTALL) -d $(DESTDIR)$(PREFIX)/bin
$(INSTALL) $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG)
chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROG)
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG)
clean:
rm -f $(PROG) $(OBJS)
distclean: clean
# mode targets (portable replacement for ifeq)
warn:
$(MAKE) CFLAGS_MODE="$(WARN)"
strict:
$(MAKE) CFLAGS_MODE="$(STRICT)"
hell:
$(MAKE) CFLAGS_MODE="$(HELLFLAGS)" CC_MODE="$(HELLCC)"
+522
View File
@@ -0,0 +1,522 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
*/
#ifndef COMMON_H
#define COMMON_H
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
/* for linux getrandom
*/
#if defined(__linux__)
#include <errno.h>
#if defined(__has_include)
#if __has_include(<sys/random.h>)
#include <sys/random.h>
#define HAVE_GETRANDOM 1
#endif
#endif
#if !defined(HAVE_GETRANDOM)
#include <sys/syscall.h>
#if defined(SYS_getrandom)
#define HAVE_GETRANDOM_SYSCALL 1
#endif
#endif
#endif
#define items(x) (sizeof((x)) / sizeof((x)[0]))
/* system prototypes
*/
int fchmod(int fd, mode_t mode);
/* analog of SSIZE_MAX
*/
#ifndef X_LONG_MAX
#define X_LONG_MAX ((long)(~((long)1 << (sizeof(long)*CHAR_BIT-1))))
#endif
/* build config
*/
#ifndef NVMUTIL_H
#define NVMUTIL_H
#define MAX_CMD_LEN 50
#ifndef PATH_LEN
#define PATH_LEN 4096
#endif
#define OFF_ERR 0
#ifndef OFF_RESET
#define OFF_RESET 1
#endif
#ifndef S_ISVTX
#define S_ISVTX 01000
#endif
#if defined(S_IFMT) && ((S_ISVTX & S_IFMT) != 0)
#error "Unexpected bit layout"
#endif
#ifndef MAX_ZERO_RW_RETRY
#define MAX_ZERO_RW_RETRY 5
#endif
#ifndef HAVE_REAL_PREAD_PWRITE
#define HAVE_REAL_PREAD_PWRITE 0
#endif
#ifndef LOOP_EAGAIN
#define LOOP_EAGAIN 1
#endif
#ifndef LOOP_EINTR
#define LOOP_EINTR 1
#endif
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef O_ACCMODE
#define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR)
#endif
#ifndef O_BINARY
#define O_BINARY 0
#endif
#ifndef O_EXCL
#define O_EXCL 0
#endif
#ifndef O_CREAT
#define O_CREAT 0
#endif
#ifndef O_NONBLOCK
#define O_NONBLOCK 0
#endif
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
#ifndef O_NOFOLLOW
#define O_NOFOLLOW 0
#endif
#ifndef FD_CLOEXEC
#define FD_CLOEXEC 0
#endif
/* Sizes in bytes:
*/
#define SIZE_1KB 1024
#define SIZE_4KB (4 * SIZE_1KB)
#define SIZE_8KB (8 * SIZE_1KB)
#define SIZE_16KB (16 * SIZE_1KB)
#define SIZE_128KB (128 * SIZE_1KB)
#define GBE_BUF_SIZE (SIZE_128KB)
/* First 128 bytes of gbe.bin is NVM.
* Then extended area. All of NVM must
* add up to BABA, truncated (LE)
*
* First 4KB of each half of the file
* contains NVM+extended.
*/
#define GBE_WORK_SIZE (SIZE_8KB)
#define GBE_PART_SIZE (GBE_WORK_SIZE >> 1)
#define NVM_CHECKSUM 0xBABA
#define NVM_SIZE 128
#define NVM_WORDS (NVM_SIZE >> 1)
#define NVM_CHECKSUM_WORD (NVM_WORDS - 1)
/* argc minimum (dispatch)
*/
#define ARGC_3 3
#define ARGC_4 4
#define NO_LOOP_EAGAIN 0
#define NO_LOOP_EINTR 0
/* For checking if an fd is a normal file.
* Portable for old Unix e.g. v7 (S_IFREG),
* 4.2BSD (S_IFMT), POSIX (S_ISREG).
*
* IFREG: assumed 0100000 (classic bitmask)
*/
#ifndef S_ISREG
#if defined(S_IFMT) && defined(S_IFREG)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#elif defined(S_IFREG)
#define S_ISREG(m) (((m) & S_IFREG) != 0)
#else
#error "can't determine types with stat()"
#endif
#endif
#define IO_READ 0
#define IO_WRITE 1
#define IO_PREAD 2
#define IO_PWRITE 3
/* for nvmutil commands
*/
#define CMD_DUMP 0
#define CMD_SETMAC 1
#define CMD_SWAP 2
#define CMD_COPY 3
#define CMD_CAT 4
#define CMD_CAT16 5
#define CMD_CAT128 6
#define ARG_NOPART 0
#define ARG_PART 1
#define SKIP_CHECKSUM_READ 0
#define CHECKSUM_READ 1
#define SKIP_CHECKSUM_WRITE 0
#define CHECKSUM_WRITE 1
/* command table
*/
struct commands {
unsigned long chk;
char *str;
void (*run)(void);
int argc;
unsigned char arg_part;
unsigned char chksum_read;
unsigned char chksum_write;
unsigned long rw_size; /* within the 4KB GbE part */
int flags; /* e.g. O_RDWR or O_RDONLY */
};
/* mac address
*/
struct macaddr {
char *str; /* set to rmac, or argv string */
char rmac[18]; /* xx:xx:xx:xx:xx:xx */
unsigned short mac_buf[3];
};
/* gbe.bin and tmpfile
*/
struct xfile {
int gbe_fd;
struct stat gbe_st;
int tmp_fd;
struct stat tmp_st;
char *tname; /* path of tmp file */
char *fname; /* path of gbe file */
unsigned char *buf; /* work memory for files */
int io_err_gbe; /* intermediary write (verification) */
int io_err_gbe_bin; /* final write (real file) */
int rw_check_err_read[2];
int rw_check_partial_read[2];
int rw_check_bad_part[2];
int post_rw_checksum[2];
off_t gbe_file_size;
off_t gbe_tmp_size;
unsigned long part;
unsigned char part_modified[2];
unsigned char part_valid[2];
unsigned char real_buf[GBE_BUF_SIZE];
unsigned char bufcmp[GBE_BUF_SIZE]; /* compare gbe/tmp/reads */
unsigned char pad[GBE_WORK_SIZE]; /* the file that wouldn't die */
};
/* Command table, MAC address, files
*
* BE CAREFUL when editing this
* to ensure that you also update
* the tables in xstatus()
*/
struct xstate {
struct commands cmd[7];
struct macaddr mac;
struct xfile f;
char *argv0;
unsigned long i; /* index to cmd[] for current command */
int no_cmd;
/* Cat commands set this.
the cat cmd helpers check it */
int cat;
};
struct xstate *xstatus(int argc, char *argv[]);
/* Sanitize command tables.
*/
void sanitize_command_list(void);
void sanitize_command_index(unsigned long c);
/* Argument handling (user input)
*/
void set_cmd(int argc, char *argv[]);
void set_cmd_args(int argc, char *argv[]);
unsigned long conv_argv_part_num(const char *part_str);
int xstrxcmp(const char *a, const char *b, unsigned long maxlen);
/* Prep files for reading
*/
void open_gbe_file(void);
int lock_file(int fd, int flags);
int same_file(int fd, struct stat *st_old, int check_size);
void xopen(int *fd, const char *path, int flags, struct stat *st);
/* Read GbE file and verify checksums
*/
void copy_gbe(void);
void read_file(void);
void read_checksums(void);
int good_checksum(unsigned long partnum);
/* validate commands
*/
void check_command_num(unsigned long c);
unsigned char valid_command(unsigned long c);
/* Helper functions for command: setmac
*/
void cmd_helper_setmac(void);
void parse_mac_string(void);
unsigned long xstrxlen(const char *scmp, unsigned long maxlen);
void set_mac_byte(unsigned long mac_byte_pos);
void set_mac_nib(unsigned long mac_str_pos,
unsigned long mac_byte_pos, unsigned long mac_nib_pos);
unsigned short hextonum(char ch_s);
unsigned long rlong(void);
#if !(defined(FALLBACK_RAND_1989) && \
((FALLBACK_RAND_1989) > 0))
#if defined(__linux__)
#if defined(HAVE_GETRANDOM) || \
defined(HAVE_GETRANDOM_SYSCALL)
int fallback_rand_getrandom(void *buf, size_t len);
#endif
#endif
#else
unsigned long fallback_rand_1989(void);
unsigned long entropy_jitter(void);
#endif
void write_mac_part(unsigned long partnum);
/* Helper functions for command: dump
*/
void cmd_helper_dump(void);
void print_mac_from_nvm(unsigned long partnum);
void hexdump(unsigned long partnum);
/* Helper functions for command: swap
*/
void cmd_helper_swap(void);
/* Helper functions for command: copy
*/
void cmd_helper_copy(void);
/* Helper functions for commands:
* cat, cat16 and cat128
*/
void cmd_helper_cat(void);
void cmd_helper_cat16(void);
void cmd_helper_cat128(void);
void cat(unsigned long nff);
void cat_buf(unsigned char *b);
/* Command verification/control
*/
void check_cmd(void (*fn)(void), const char *name);
void cmd_helper_err(void);
/* Write GbE files to disk
*/
void write_gbe_file(void);
void set_checksum(unsigned long part);
unsigned short calculated_checksum(unsigned long p);
/* NVM read/write
*/
unsigned short nvm_word(unsigned long pos16, unsigned long part);
void set_nvm_word(unsigned long pos16,
unsigned long part, unsigned short val16);
void set_part_modified(unsigned long p);
void check_nvm_bound(unsigned long pos16, unsigned long part);
void check_bin(unsigned long a, const char *a_name);
/* GbE file read/write
*/
void rw_gbe_file_part(unsigned long p, int rw_type,
const char *rw_type_str);
void write_to_gbe_bin(void);
int gbe_mv(void);
void check_written_part(unsigned long p);
void report_io_err_rw(void);
unsigned char *gbe_mem_offset(unsigned long part, const char *f_op);
off_t gbe_file_offset(unsigned long part, const char *f_op);
off_t gbe_x_offset(unsigned long part, const char *f_op,
const char *d_type, off_t nsize, off_t ncmp);
long rw_gbe_file_exact(int fd, unsigned char *mem, unsigned long nrw,
off_t off, int rw_type);
/* Generic read/write
*/
int fsync_dir(const char *path);
long rw_file_exact(int fd, unsigned char *mem, unsigned long len,
off_t off, int rw_type, int loop_eagain, int loop_eintr,
unsigned long max_retries, int off_reset);
long prw(int fd, void *mem, unsigned long nrw,
off_t off, int rw_type, int loop_eagain, int loop_eintr,
int off_reset);
int io_args(int fd, void *mem, unsigned long nrw,
off_t off, int rw_type);
int check_file(int fd, struct stat *st);
long rw_over_nrw(long r, unsigned long nrw);
#if !defined(HAVE_REAL_PREAD_PWRITE) || \
HAVE_REAL_PREAD_PWRITE < 1
off_t lseek_on_eintr(int fd, off_t off,
int whence, int loop_eagain, int loop_eintr);
#endif
int try_err(int loop_err, int errval);
/* Error handling and cleanup
*/
void usage(void);
void err(int nvm_errval, const char *msg, ...);
int exit_cleanup(void);
const char *getnvmprogname(void);
/* Portable libc functions
*/
char *new_tmpfile(int *fd, int local, const char *path);
int mkstemp_n(char *template);
char *get_tmpdir(void);
int close_on_eintr(int fd);
int fsync_on_eintr(int fd);
/* asserts */
/* type asserts */
typedef char static_assert_char_is_8_bits[(CHAR_BIT == 8) ? 1 : -1];
typedef char static_assert_char_is_1[(sizeof(char) == 1) ? 1 : -1];
typedef char static_assert_unsigned_char_is_1[
(sizeof(unsigned char) == 1) ? 1 : -1];
typedef char static_assert_unsigned_short_is_2[
(sizeof(unsigned short) >= 2) ? 1 : -1];
typedef char static_assert_short_is_2[(sizeof(short) >= 2) ? 1 : -1];
typedef char static_assert_unsigned_int_is_4[
(sizeof(unsigned int) >= 4) ? 1 : -1];
typedef char static_assert_unsigned_long_is_4[
(sizeof(unsigned long) >= 4) ? 1 : -1];
typedef char static_assert_long_ulong[
(sizeof(unsigned long) == sizeof(long)) ? 1 : -1];
typedef char static_assert_int_ge_32[(sizeof(int) >= 4) ? 1 : -1];
typedef char static_assert_twos_complement[
((-1 & 3) == 3) ? 1 : -1
];
typedef char assert_unsigned_long_ptr[
(sizeof(unsigned long) >= sizeof(void *)) ? 1 : -1
];
/*
* We set _FILE_OFFSET_BITS 64, but we only handle
* but we only need smaller files, so require 4-bytes.
* Some operating systems ignore the define, hence assert:
*/
typedef char static_assert_off_t_is_32[(sizeof(off_t) >= 4) ? 1 : -1];
/*
* asserts (variables/defines sanity check)
*/
typedef char assert_argc3[(ARGC_3==3)?1:-1];
typedef char assert_argc4[(ARGC_4==4)?1:-1];
typedef char assert_read[(IO_READ==0)?1:-1];
typedef char assert_write[(IO_WRITE==1)?1:-1];
typedef char assert_pread[(IO_PREAD==2)?1:-1];
typedef char assert_pwrite[(IO_PWRITE==3)?1:-1];
typedef char assert_pathlen[(PATH_LEN>=256)?1:-1];
/* commands */
typedef char assert_cmd_dump[(CMD_DUMP==0)?1:-1];
typedef char assert_cmd_setmac[(CMD_SETMAC==1)?1:-1];
typedef char assert_cmd_swap[(CMD_SWAP==2)?1:-1];
typedef char assert_cmd_copy[(CMD_COPY==3)?1:-1];
typedef char assert_cmd_cat[(CMD_CAT==4)?1:-1];
typedef char assert_cmd_cat16[(CMD_CAT16==5)?1:-1];
typedef char assert_cmd_cat128[(CMD_CAT128==6)?1:-1];
/* bool */
typedef char bool_arg_nopart[(ARG_NOPART==0)?1:-1];
typedef char bool_arg_part[(ARG_PART==1)?1:-1];
typedef char bool_skip_checksum_read[(SKIP_CHECKSUM_READ==0)?1:-1];
typedef char bool_checksum_read[(CHECKSUM_READ==1)?1:-1];
typedef char bool_skip_checksum_write[(SKIP_CHECKSUM_WRITE==0)?1:-1];
typedef char bool_checksum_write[(CHECKSUM_WRITE==1)?1:-1];
typedef char bool_loop_eintr[(LOOP_EINTR==1||LOOP_EINTR==0)?1:-1];
typedef char bool_loop_eagain[(LOOP_EAGAIN==1||LOOP_EAGAIN==0)?1:-1];
typedef char bool_no_loop_eintr[(NO_LOOP_EINTR==0)?1:-1];
typedef char bool_no_loop_eagain[(NO_LOOP_EAGAIN==0)?1:-1];
typedef char bool_off_err[(OFF_ERR==0)?1:-1];
typedef char bool_off_reset[(OFF_RESET==0||OFF_RESET==1)?1:-1];
#endif
#endif
+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(0, NULL);
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
unsigned long _p;
unsigned long _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)
err(ECANCELED, "%s: part %lu has a bad checksum",
f->fname, (unsigned long)f->part);
err(ECANCELED, "%s: No valid checksum found in file",
f->fname);
}
}
int
good_checksum(unsigned long 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(unsigned long p)
{
check_bin(p, "part number");
set_nvm_word(NVM_CHECKSUM_WORD, p, calculated_checksum(p));
}
unsigned short
calculated_checksum(unsigned long p)
{
unsigned long 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);
}
+546
View File
@@ -0,0 +1,546 @@
/* 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(0, NULL);
unsigned long c;
unsigned long num_commands;
num_commands = items(x->cmd);
for (c = 0; c < num_commands; c++)
sanitize_command_index(c);
}
void
sanitize_command_index(unsigned long c)
{
struct xstate *x = xstatus(0, NULL);
struct commands *cmd = &x->cmd[c];
int _flag;
unsigned long gbe_rw_size;
check_command_num(c);
if (cmd->argc < 3)
err(EINVAL, "cmd index %lu: argc below 3, %d",
(unsigned long)c, cmd->argc);
if (cmd->str == NULL)
err(EINVAL, "cmd index %lu: NULL str",
(unsigned long)c);
if (*cmd->str == '\0')
err(EINVAL, "cmd index %lu: empty str",
(unsigned long)c);
if (xstrxlen(cmd->str, MAX_CMD_LEN + 1) >
MAX_CMD_LEN) {
err(EINVAL, "cmd index %lu: str too long: %s",
(unsigned long)c, cmd->str);
}
if (cmd->run == NULL)
err(EINVAL, "cmd index %lu: cmd ptr null",
(unsigned long)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:
err(EINVAL, "Unsupported rw_size: %lu",
(unsigned long)gbe_rw_size);
}
if (gbe_rw_size > GBE_PART_SIZE)
err(EINVAL, "rw_size larger than GbE part: %lu",
(unsigned long)gbe_rw_size);
_flag = (cmd->flags & O_ACCMODE);
if (_flag != O_RDONLY &&
_flag != O_RDWR)
err(EINVAL, "invalid cmd.flags setting");
}
void
set_cmd(int argc, char *argv[])
{
struct xstate *x = xstatus(0, NULL);
const char *cmd;
unsigned long c;
for (c = 0; c < items(x->cmd); c++) {
cmd = x->cmd[c].str;
/* not the right command */
if (xstrxcmp(argv[2], cmd, MAX_CMD_LEN) != 0)
continue;
/* valid command found */
if (argc >= x->cmd[c].argc) {
x->no_cmd = 0;
x->i = c; /* set command */
return;
}
err(EINVAL,
"Too few args on command '%s'", cmd);
}
x->no_cmd = 1;
}
void
set_cmd_args(int argc, char *argv[])
{
struct xstate *x = xstatus(0, NULL);
unsigned long 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)
err(EINVAL,
"arg_part set for command that needs argc4");
if (cmd->arg_part && i == CMD_SETMAC)
err(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]);
}
}
unsigned long
conv_argv_part_num(const char *part_str)
{
unsigned char ch;
if (part_str[0] == '\0' || part_str[1] != '\0')
err(EINVAL, "Partnum string '%s' wrong length", part_str);
/* char signedness is implementation-defined
*/
ch = (unsigned char)part_str[0];
if (ch < '0' || ch > '1')
err(EINVAL, "Bad part number (%c)", ch);
return (unsigned long)(ch - '0');
}
void
check_command_num(unsigned long c)
{
if (!valid_command(c))
err(EINVAL, "Invalid run_cmd arg: %lu",
(unsigned long)c);
}
unsigned char
valid_command(unsigned long c)
{
struct xstate *x = xstatus(0, NULL);
struct commands *cmd;
if (c >= items(x->cmd))
return 0;
cmd = &x->cmd[c];
if (c != cmd->chk)
err(EINVAL,
"Invalid cmd chk value (%lu) vs arg: %lu",
cmd->chk, c);
return 1;
}
void
cmd_helper_setmac(void)
{
struct xstate *x = xstatus(0, NULL);
struct macaddr *mac = &x->mac;
unsigned long 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(0, NULL);
struct macaddr *mac = &x->mac;
unsigned long mac_byte;
if (xstrxlen(x->mac.str, 18) != 17)
err(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)
err(EINVAL, "Must not specify all-zeroes MAC address");
if (mac->mac_buf[0] & 1)
err(EINVAL, "Must not specify multicast MAC address");
}
void
set_mac_byte(unsigned long mac_byte_pos)
{
struct xstate *x = xstatus(0, NULL);
struct macaddr *mac = &x->mac;
char separator;
unsigned long mac_str_pos;
unsigned long mac_nib_pos;
mac_str_pos = mac_byte_pos * 3;
if (mac_str_pos < 15) {
if ((separator = mac->str[mac_str_pos + 2]) != ':')
err(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(unsigned long mac_str_pos,
unsigned long mac_byte_pos, unsigned long mac_nib_pos)
{
struct xstate *x = xstatus(0, NULL);
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)
err(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(unsigned long partnum)
{
struct xstate *x = xstatus(0, NULL);
struct xfile *f = &x->f;
struct macaddr *mac = &x->mac;
unsigned long 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: ",
(unsigned long)partnum);
print_mac_from_nvm(partnum);
}
void
cmd_helper_dump(void)
{
struct xstate *x = xstatus(0, NULL);
struct xfile *f = &x->f;
unsigned long 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),
(unsigned long)p,
calculated_checksum(p));
}
printf("MAC (part %lu): ",
(unsigned long)p);
print_mac_from_nvm(p);
hexdump(p);
}
}
void
print_mac_from_nvm(unsigned long partnum)
{
unsigned long 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(unsigned long partnum)
{
unsigned long c;
unsigned long row;
unsigned short val16;
for (row = 0; row < 8; row++) {
printf("%08lx ",
(unsigned long)((unsigned long)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(0, NULL);
struct xfile *f = &x->f;
check_cmd(cmd_helper_swap, "swap");
memcpy(
f->buf + (unsigned long)GBE_WORK_SIZE,
f->buf,
GBE_PART_SIZE);
memcpy(
f->buf,
f->buf + (unsigned long)GBE_PART_SIZE,
GBE_PART_SIZE);
memcpy(
f->buf + (unsigned long)GBE_PART_SIZE,
f->buf + (unsigned long)GBE_WORK_SIZE,
GBE_PART_SIZE);
set_part_modified(0);
set_part_modified(1);
}
void
cmd_helper_copy(void)
{
struct xstate *x = xstatus(0, NULL);
struct xfile *f = &x->f;
check_cmd(cmd_helper_copy, "copy");
memcpy(
f->buf + (unsigned long)((f->part ^ 1) * GBE_PART_SIZE),
f->buf + (unsigned long)(f->part * GBE_PART_SIZE),
GBE_PART_SIZE);
set_part_modified(f->part ^ 1);
}
void
cmd_helper_cat(void)
{
struct xstate *x = xstatus(0, NULL);
check_cmd(cmd_helper_cat, "cat");
x->cat = 0;
cat(0);
}
void
cmd_helper_cat16(void)
{
struct xstate *x = xstatus(0, NULL);
check_cmd(cmd_helper_cat16, "cat16");
x->cat = 1;
cat(1);
}
void
cmd_helper_cat128(void)
{
struct xstate *x = xstatus(0, NULL);
check_cmd(cmd_helper_cat128, "cat128");
x->cat = 15;
cat(15);
}
void
cat(unsigned long nff)
{
struct xstate *x = xstatus(0, NULL);
struct xfile *f = &x->f;
unsigned long p;
unsigned long ff;
p = 0;
ff = 0;
if ((unsigned long)x->cat != nff) {
err(ECANCELED, "erroneous call to cat");
}
fflush(NULL);
memset(f->pad, 0xff, GBE_PART_SIZE);
for (p = 0; p < 2; p++) {
cat_buf(f->bufcmp +
(unsigned long)(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)
err(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)
err(errno, "stdout: cat");
}
void
check_cmd(void (*fn)(void),
const char *name)
{
struct xstate *x = xstatus(0, NULL);
unsigned long i = x->i;
if (x->cmd[i].run != fn)
err(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)
{
err(ECANCELED,
"Erroneously running command twice");
}
+890
View File
@@ -0,0 +1,890 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../include/common.h"
/* check that a file changed
*/
int
same_file(int fd, struct stat *st_old,
int check_size)
{
struct stat st;
int saved_errno = errno;
if (st_old == NULL || fd < 0)
goto err_same_file;
if (fstat(fd, &st) == -1)
return -1;
if (st.st_dev != st_old->st_dev ||
st.st_ino != st_old->st_ino ||
!S_ISREG(st.st_mode))
goto err_same_file;
if (check_size &&
st.st_size != st_old->st_size)
goto err_same_file;
errno = saved_errno;
return 0;
err_same_file:
errno = EIO;
return -1;
}
/* open() but with abort traps
*/
void
xopen(int *fd_ptr, const char *path, int flags, struct stat *st)
{
if ((*fd_ptr = open(path, flags)) == -1)
err(errno, "%s", path);
if (fstat(*fd_ptr, st) == -1)
err(errno, "%s: stat", path);
if (!S_ISREG(st->st_mode))
err(errno, "%s: not a regular file", path);
if (lseek(*fd_ptr, 0, SEEK_CUR) == (off_t)-1)
err(errno, "%s: file not seekable", path);
}
/* fsync() the directory of a file,
* useful for atomic writes
*/
int
fsync_dir(const char *path)
{
int saved_errno = errno;
unsigned long pathlen;
unsigned long maxlen;
char *dirbuf;
int dirfd;
char *slash;
struct stat st;
#if defined(PATH_LEN) && \
(PATH_LEN) >= 256
maxlen = PATH_LEN;
#else
maxlen = 1024;
#endif
dirbuf = NULL;
dirfd = -1;
pathlen = xstrxlen(path, maxlen);
if (pathlen >= maxlen) {
fprintf(stderr, "Path too long for fsync_parent_dir\n");
goto err_fsync_dir;
}
if (pathlen == 0)
{
errno = EINVAL;
goto err_fsync_dir;
}
dirbuf = malloc(pathlen + 1);
if (dirbuf == NULL)
goto err_fsync_dir;
memcpy(dirbuf, path, pathlen + 1);
slash = strrchr(dirbuf, '/');
if (slash != NULL) {
*slash = '\0';
if (*dirbuf == '\0') {
dirbuf[0] = '/';
dirbuf[1] = '\0';
}
} else {
dirbuf[0] = '.';
dirbuf[1] = '\0';
}
dirfd = open(dirbuf, O_RDONLY
#ifdef O_DIRECTORY
| O_DIRECTORY
#endif
#ifdef O_NOFOLLOW
| O_NOFOLLOW
#endif
);
if (dirfd == -1)
goto err_fsync_dir;
if (fstat(dirfd, &st) < 0)
goto err_fsync_dir;
if (!S_ISDIR(st.st_mode)) {
fprintf(stderr, "%s: not a directory\n", dirbuf);
goto err_fsync_dir;
}
/* sync file on disk */
if (fsync_on_eintr(dirfd) == -1)
goto err_fsync_dir;
if (close_on_eintr(dirfd) == -1)
goto err_fsync_dir;
if (dirbuf != NULL)
free(dirbuf);
errno = saved_errno;
return 0;
err_fsync_dir:
if (!errno)
errno = EIO;
if (errno != saved_errno)
fprintf(stderr, "%s: %s\n", path, strerror(errno));
if (dirbuf != NULL)
free(dirbuf);
if (dirfd > -1)
close_on_eintr(dirfd);
errno = saved_errno;
return -1;
}
/* returns ptr to path (string). if local>0:
* make tmpfile in the same directory as the
* file. if local==0, use TMPDIR
*
* if local==0, the 3rd argument is ignored
*/
char *
new_tmpfile(int *fd, int local, const char *path)
{
unsigned long maxlen;
struct stat st;
/* please do not modify the
* strings or I will get mad
*/
char tmp_none[] = "";
char tmp_default[] = "/tmp";
char default_tmpname[] = "tmpXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
char *tmpname;
char *base = NULL;
char *dest = NULL;
unsigned long tmpdir_len = 0;
unsigned long tmpname_len = 0;
unsigned long tmppath_len = 0;
int fd_tmp = -1;
int flags;
#if defined(PATH_LEN) && \
(PATH_LEN) >= 256
maxlen = PATH_LEN;
#else
maxlen = 1024;
#endif
tmpname = default_tmpname;
if (local) {
if (path == NULL)
goto err_new_tmpfile;
if (*path == '\0')
goto err_new_tmpfile;
if (stat(path, &st) == -1)
goto err_new_tmpfile;
if (!S_ISREG(st.st_mode))
goto err_new_tmpfile;
tmpname = (char *)path;
}
if (local) {
base = tmp_none;
/* appended to filename for tmp:
*/
tmpdir_len = xstrxlen(default_tmpname, maxlen);
} else {
base = get_tmpdir();
if (base == NULL)
base = tmp_default;
if (*base == '\0')
base = tmp_default;
tmpdir_len = xstrxlen(base, maxlen);
}
tmpname_len = xstrxlen(tmpname, maxlen);
tmppath_len = tmpdir_len + tmpname_len;
++tmppath_len; /* for '/' or '.' */
/* max length -1 of maxlen
* for termination
*/
if (tmpdir_len > maxlen - tmpname_len - 1)
goto err_new_tmpfile;
/* +1 for NULL */
dest = malloc(tmppath_len + 1);
if (dest == NULL)
goto err_new_tmpfile;
if (local) {
*dest = '.'; /* hidden file */
memcpy(dest + (unsigned long)1, tmpname, tmpname_len);
memcpy(dest + (unsigned long)1 + tmpname_len,
default_tmpname, tmpdir_len);
} else {
memcpy(dest, base, tmpdir_len);
dest[tmpdir_len] = '/';
memcpy(dest + tmpdir_len + 1, tmpname, tmpname_len);
}
dest[tmppath_len] = '\0';
fd_tmp = mkstemp_n(dest);
if (fd_tmp == -1)
goto err_new_tmpfile;
if (fchmod(fd_tmp, 0600) == -1)
goto err_new_tmpfile;
flags = fcntl(fd_tmp, F_GETFL);
if (flags == -1)
goto err_new_tmpfile;
/*
* O_APPEND would permit offsets
* to be ignored, which breaks
* positional read/write
*/
if (flags & O_APPEND)
goto err_new_tmpfile;
if (lock_file(fd_tmp, flags) == -1)
goto err_new_tmpfile;
if (fstat(fd_tmp, &st) == -1)
goto err_new_tmpfile;
/*
* Extremely defensive
* likely pointless checks
*/
/* check if it's a file */
if (!S_ISREG(st.st_mode))
goto err_new_tmpfile;
/* check if it's seekable */
if (lseek(fd_tmp, 0, SEEK_CUR) == (off_t)-1)
goto err_new_tmpfile;
/* tmpfile has >1 hardlinks */
if (st.st_nlink > 1)
goto err_new_tmpfile;
/* tmpfile unlinked while opened */
if (st.st_nlink == 0)
goto err_new_tmpfile;
*fd = fd_tmp;
return dest;
err_new_tmpfile:
if (dest != NULL)
free(dest);
if (fd_tmp > -1)
close_on_eintr(fd_tmp);
return NULL;
}
int
lock_file(int fd, int flags)
{
struct flock fl;
memset(&fl, 0, sizeof(fl));
if ((flags & O_ACCMODE) == O_RDONLY)
fl.l_type = F_RDLCK;
else
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
if (fcntl(fd, F_SETLK, &fl) == -1)
return -1;
return 0;
}
/* return TMPDIR, or fall back
* to portable defaults
*/
char *
get_tmpdir(void)
{
char *t;
struct stat st;
t = getenv("TMPDIR");
if (t && *t) {
if (stat(t, &st) == 0 && S_ISDIR(st.st_mode)) {
if ((st.st_mode & S_IWOTH) && !(st.st_mode & S_ISVTX))
return NULL;
return t;
}
}
if (stat("/tmp", &st) == 0 && S_ISDIR(st.st_mode))
return "/tmp";
if (stat("/var/tmp", &st) == 0 && S_ISDIR(st.st_mode))
return "/var/tmp";
return ".";
}
/* portable mkstemp
*/
int
mkstemp_n(char *template)
{
int fd;
unsigned long i, j;
unsigned long len;
char *p;
unsigned long xc = 0;
static char ch[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
unsigned long r;
#if defined(PATH_LEN) && \
(PATH_LEN) >= 256
unsigned long max_len = PATH_LEN;
#else
unsigned long max_len = 4096;
#endif
len = xstrxlen(template, max_len);
if (len < 6) {
errno = EINVAL;
return -1;
}
p = template + len;
while (p > template && p[-1] == 'X') {
--p;
++xc;
}
if (xc < 6) {
errno = EINVAL;
return -1;
}
for (i = 0; i < 200; i++) {
for (j = 0; j < xc; j++) {
r = rlong();
p[j] = ch[(unsigned long)(r >> 1) % (sizeof(ch) - 1)];
}
fd = open(template,
O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
if (fd >= 0)
return fd;
if (errno != EEXIST)
return -1;
}
errno = EEXIST;
return -1;
}
/*
* Safe I/O functions wrapping around
* read(), write() and providing a portable
* analog of both pread() and pwrite().
* These functions are designed for maximum
* robustness, checking NULL inputs, overflowed
* outputs, and all kinds of errors that the
* standard libc functions don't.
*
* Looping on EINTR and EAGAIN is supported.
* EINTR/EAGAIN looping is done indefinitely.
*/
/* rw_file_exact() - Read perfectly or die
*
* Read/write, and absolutely insist on an
* absolute read; e.g. if 100 bytes are
* requested, this MUST return 100.
*
* This function will never return zero.
* It will only return below (error),
* or above (success). On error, -1 is
* returned and errno is set accordingly.
*
* Zero-byte returns are not allowed.
* It will re-spin a finite number of
* times upon zero-return, to recover,
* otherwise it will return an error.
*/
long
rw_file_exact(int fd, unsigned char *mem, unsigned long nrw,
off_t off, int rw_type, int loop_eagain,
int loop_eintr, unsigned long max_retries,
int off_reset)
{
long rval;
long rc;
unsigned long nrw_cur;
off_t off_cur;
void *mem_cur;
unsigned long retries_on_zero;
rval = 0;
rc = 0;
retries_on_zero = 0;
if (io_args(fd, mem, nrw, off, rw_type) == -1)
return -1;
while (1) {
/* Prevent theoretical overflow */
if (rval >= 0 && (unsigned long)rval > (nrw - rc))
goto err_rw_file_exact;
rc += rval;
if ((unsigned long)rc >= nrw)
break;
mem_cur = (void *)(mem + (unsigned long)rc);
nrw_cur = (unsigned long)(nrw - (unsigned long)rc);
if (off < 0)
goto err_rw_file_exact;
off_cur = off + (off_t)rc;
rval = prw(fd, mem_cur, nrw_cur, off_cur,
rw_type, loop_eagain, loop_eintr,
off_reset);
if (rval < 0)
return -1;
if (rval == 0) {
if (retries_on_zero++ < max_retries)
continue;
goto err_rw_file_exact;
}
retries_on_zero = 0;
}
if ((unsigned long)rc != nrw)
goto err_rw_file_exact;
return rw_over_nrw(rc, nrw);
err_rw_file_exact:
errno = EIO;
return -1;
}
/* prw() - portable read-write with more
* safety checks than barebones libc
*
* portable pwrite/pread on request, or real
* pwrite/pread libc functions can be used.
* the portable (non-libc) pread/pwrite is not
* thread-safe, because it does not prevent or
* mitigate race conditions on file descriptors
*
* If you need real pwrite/pread, just compile
* with flag: HAVE_REAL_PREAD_PWRITE=1
*
* A fallback is provided for regular read/write.
* rw_type can be IO_READ (read), IO_WRITE (write),
* IO_PREAD (pread) or IO_PWRITE
*
* loop_eagain does a retry loop on EAGAIN if set
* loop_eintr does a retry loop on EINTR if set
*
* race conditions on non-libc pread/pwrite:
* if a file offset changes, abort, to mitage.
*
* off_reset 1: reset the file offset *once* if
* a change was detected, assuming
* nothing else is touching it now
* off_reset 0: never reset if changed
*/
long
prw(int fd, void *mem, unsigned long nrw,
off_t off, int rw_type,
int loop_eagain, int loop_eintr,
int off_reset)
{
long r;
int positional_rw;
struct stat st;
#if !defined(HAVE_REAL_PREAD_PWRITE) || \
HAVE_REAL_PREAD_PWRITE < 1
int saved_errno;
off_t verified;
off_t off_orig;
off_t off_last;
#endif
if (io_args(fd, mem, nrw, off, rw_type)
== -1) {
return -1;
}
r = -1;
/* do not use loop_eagain on
* normal files
*/
if (!loop_eagain) {
/* check whether the file
* changed
*/
if (check_file(fd, &st) == -1)
return -1;
}
if (rw_type >= IO_PREAD)
positional_rw = 1; /* pread/pwrite */
else
positional_rw = 0; /* read/write */
try_rw_again:
if (!positional_rw) {
#if defined(HAVE_REAL_PREAD_PWRITE) && \
HAVE_REAL_PREAD_PWRITE > 0
real_pread_pwrite:
#endif
if (rw_type == IO_WRITE)
r = write(fd, mem, nrw);
else if (rw_type == IO_READ)
r = read(fd, mem, nrw);
#if defined(HAVE_REAL_PREAD_PWRITE) && \
HAVE_REAL_PREAD_PWRITE > 0
else if (rw_type == IO_PWRITE)
r = pwrite(fd, mem, nrw, off);
else if (rw_type == IO_PREAD)
r = pread(fd, mem, nrw, off);
#endif
if (r == -1 && (errno == try_err(loop_eintr, EINTR)
|| errno == try_err(loop_eagain, EAGAIN)))
goto try_rw_again;
return rw_over_nrw(r, nrw);
}
#if defined(HAVE_REAL_PREAD_PWRITE) && \
HAVE_REAL_PREAD_PWRITE > 0
goto real_pread_pwrite;
#else
if ((off_orig = lseek_on_eintr(fd, (off_t)0, SEEK_CUR,
loop_eagain, loop_eintr)) == (off_t)-1) {
r = -1;
} else if (lseek_on_eintr(fd, off, SEEK_SET,
loop_eagain, loop_eintr) == (off_t)-1) {
r = -1;
} else {
verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR,
loop_eagain, loop_eintr);
/* abort if the offset changed,
* indicating race condition. if
* off_reset enabled, reset *ONCE*
*/
if (off_reset && off != verified)
lseek_on_eintr(fd, off, SEEK_SET,
loop_eagain, loop_eintr);
do {
/* check offset again, repeatedly.
* even if off_reset is set, this
* aborts if offsets change again
*/
verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR,
loop_eagain, loop_eintr);
if (off != verified)
goto err_prw;
if (rw_type == IO_PREAD)
r = read(fd, mem, nrw);
else if (rw_type == IO_PWRITE)
r = write(fd, mem, nrw);
if (rw_over_nrw(r, nrw) == -1) {
errno = EIO;
break;
}
} while (r == -1 &&
(errno == try_err(loop_eintr, EINTR) ||
errno == try_err(loop_eagain, EAGAIN)));
}
saved_errno = errno;
off_last = lseek_on_eintr(fd, off_orig, SEEK_SET,
loop_eagain, loop_eintr);
if (off_last != off_orig) {
errno = saved_errno;
goto err_prw;
}
errno = saved_errno;
return rw_over_nrw(r, nrw);
#endif
err_prw:
errno = EIO;
return -1;
}
int
io_args(int fd, void *mem, unsigned long nrw,
off_t off, int rw_type)
{
/* obviously */
if (mem == NULL)
goto err_io_args;
/* uninitialised fd */
if (fd < 0)
goto err_io_args;
/* negative offset */
if (off < 0)
goto err_io_args;
/* prevent zero-byte rw */
if (!nrw)
goto err_io_args;
/* prevent overflow */
if (nrw > (unsigned long)X_LONG_MAX)
goto err_io_args;
/* prevent overflow */
if (((unsigned long)off + nrw) < (unsigned long)off)
goto err_io_args;
if (rw_type > IO_PWRITE)
goto err_io_args;
return 0;
err_io_args:
errno = EIO;
return -1;
}
int
check_file(int fd, struct stat *st)
{
if (fstat(fd, st) == -1)
goto err_is_file;
if (!S_ISREG(st->st_mode))
goto err_is_file;
return 0;
err_is_file:
errno = EIO;
return -1;
}
/* POSIX can say whatever it wants.
* specification != implementation
*/
long
rw_over_nrw(long r, unsigned long nrw)
{
/* not a libc bug, but we
* don't like the number zero
*/
if (!nrw)
goto err_rw_over_nrw;
if (r == -1)
return r;
if ((unsigned long)
r > X_LONG_MAX) {
/* Theoretical buggy libc
* check. Extremely academic.
*
* Specifications never
* allow this return value
* to exceed SSIZE_T, but
* spec != implementation
*
* Check this after using
* [p]read() or [p]write()
*
* NOTE: here, we assume
* long integers are the
* same size as SSIZE_T
*/
goto err_rw_over_nrw;
}
/* Theoretical buggy libc:
* Should never return a number of
* bytes above the requested length.
*/
if ((unsigned long)r > nrw)
goto err_rw_over_nrw;
return r;
err_rw_over_nrw:
errno = EIO;
return -1;
}
#if !defined(HAVE_REAL_PREAD_PWRITE) || \
HAVE_REAL_PREAD_PWRITE < 1
off_t
lseek_on_eintr(int fd, off_t off, int whence,
int loop_eagain, int loop_eintr)
{
off_t old;
old = -1;
do {
old = lseek(fd, off, whence);
} while (old == (off_t)-1 && (
errno == try_err(loop_eintr, EINTR) ||
errno == try_err(loop_eagain, EAGAIN)));
return old;
}
#endif
int
try_err(int loop_err, int errval)
{
if (loop_err)
return errval;
return -1;
}
int
close_on_eintr(int fd)
{
int r;
int saved_errno = errno;
do {
r = close(fd);
} while (r == -1 && errno == EINTR);
if (r > -1)
errno = saved_errno;
return r;
}
int
fsync_on_eintr(int fd)
{
int r;
do {
r = fsync(fd);
} while (r == -1 && errno == EINTR);
return r;
}
+649
View File
@@ -0,0 +1,649 @@
/* 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(0, NULL);
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, &f->gbe_st);
if (f->gbe_st.st_nlink > 1)
err(EINVAL,
"%s: warning: file has multiple (%lu) hard links\n",
f->fname, (unsigned long)f->gbe_st.st_nlink);
if (f->gbe_st.st_nlink == 0)
err(EIO, "%s: file unlinked while open", f->fname);
_flags = fcntl(f->gbe_fd, F_GETFL);
if (_flags == -1)
err(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)
err(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:
err(EINVAL, "File size must be 8KB, 16KB or 128KB");
}
if (lock_file(f->gbe_fd, cmd->flags) == -1)
err(errno, "%s: can't lock", f->fname);
}
void
copy_gbe(void)
{
struct xstate *x = xstatus(0, NULL);
struct xfile *f = &x->f;
read_file();
if (f->gbe_file_size == SIZE_8KB)
return;
memcpy(f->buf + (unsigned long)GBE_PART_SIZE,
f->buf + (unsigned long)(f->gbe_file_size >> 1),
(unsigned long)GBE_PART_SIZE);
}
void
read_file(void)
{
struct xstate *x = xstatus(0, NULL);
struct xfile *f = &x->f;
struct stat _st;
long _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)
err(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)
err(errno, "%s: %s: copy failed",
f->fname, f->tname);
/* file size comparison
*/
if (fstat(f->tmp_fd, &_st) == -1)
err(errno, "%s: stat", f->tname);
f->gbe_tmp_size = _st.st_size;
if (f->gbe_tmp_size != f->gbe_file_size)
err(EIO, "%s: %s: not the same size",
f->fname, f->tname);
/* needs sync, for verification
*/
if (fsync_on_eintr(f->tmp_fd) == -1)
err(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)
err(errno, "%s: read failed (cmp)", f->tname);
if (memcmp(f->buf, f->bufcmp, f->gbe_file_size) != 0)
err(errno, "%s: %s: read contents differ (pre-test)",
f->fname, f->tname);
}
void
write_gbe_file(void)
{
struct xstate *x = xstatus(0, NULL);
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
unsigned long p;
unsigned char update_checksum;
if ((cmd->flags & O_ACCMODE) == O_RDONLY)
return;
if (same_file(f->tmp_fd, &f->tmp_st, 0) < 0)
err(errno, "%s: file inode/device changed", f->tname);
if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0)
err(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(unsigned long p, int rw_type,
const char *rw_type_str)
{
struct xstate *x = xstatus(0, NULL);
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
long rval;
off_t file_offset;
unsigned long gbe_rw_size;
unsigned char *mem_offset;
gbe_rw_size = cmd->rw_size;
if (rw_type < IO_PREAD || rw_type > IO_PWRITE)
err(errno, "%s: %s: part %lu: invalid rw_type, %d",
f->fname, rw_type_str, (unsigned long)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)
err(errno, "%s: %s: part %lu",
f->fname, rw_type_str, (unsigned long)p);
if ((unsigned long)rval != gbe_rw_size)
err(EIO, "%s: partial %s: part %lu",
f->fname, rw_type_str, (unsigned long)p);
}
void
write_to_gbe_bin(void)
{
struct xstate *x = xstatus(0, NULL);
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)
err(errno, "%s: fsync (pre-verification)",
f->tname);
check_written_part(0);
check_written_part(1);
report_io_err_rw();
if (f->io_err_gbe)
err(EIO, "%s: bad write", f->fname);
saved_errno = errno;
if (close_on_eintr(f->tmp_fd) == -1) {
fprintf(stderr, "FAIL: %s: close\n", f->tname);
f->io_err_gbe_bin = 1;
}
if (close_on_eintr(f->gbe_fd) == -1) {
fprintf(stderr, "FAIL: %s: close\n", f->fname);
f->io_err_gbe_bin = 1;
}
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
*/
if (f->tname != NULL)
free(f->tname);
f->tname = NULL;
}
}
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(unsigned long p)
{
struct xstate *x = xstatus(0, NULL);
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
long rval;
unsigned long 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)
err(errno, "%s: file inode/device changed", f->tname);
if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0)
err(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 ((unsigned long)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(0, NULL);
struct xfile *f = &x->f;
unsigned long 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, (unsigned long)p);
if (f->rw_check_partial_read[p])
fprintf(stderr,
"%s: partial pread: p%lu (post-verification)\n",
f->fname, (unsigned long)p);
if (f->rw_check_bad_part[p])
fprintf(stderr,
"%s: pwrite: corrupt write on p%lu\n",
f->fname, (unsigned long)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, (unsigned long)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",
(unsigned long)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(0, NULL);
struct xfile *f = &x->f;
int rval;
int saved_errno;
int tmp_gbe_bin_exists;
char *dest_tmp;
int dest_fd;
/* will be set 0 if it doesn't
*/
tmp_gbe_bin_exists = 1;
dest_tmp = NULL;
dest_fd = -1;
saved_errno = errno;
rval = rename(f->tname, f->fname);
if (rval > -1) {
/* rename on same filesystem
*/
tmp_gbe_bin_exists = 0;
if (fsync_dir(f->fname) < 0) {
f->io_err_gbe_bin = 1;
rval = -1;
}
goto ret_gbe_mv;
}
if (errno != EXDEV)
goto ret_gbe_mv;
/*
* OR, cross-filesystem rename:
*/
if ((rval = f->tmp_fd = open(f->tname,
O_RDONLY | O_BINARY)) == -1)
goto ret_gbe_mv;
/* create replacement temp in target directory
*/
dest_tmp = new_tmpfile(&dest_fd, 1, f->fname);
if (dest_tmp == NULL)
goto ret_gbe_mv;
/* copy data
*/
rval = 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 (rval < 0)
goto ret_gbe_mv;
rval = rw_file_exact(dest_fd, f->bufcmp,
f->gbe_file_size, 0, IO_PWRITE,
NO_LOOP_EAGAIN, LOOP_EINTR,
MAX_ZERO_RW_RETRY, OFF_ERR);
if (rval < 0)
goto ret_gbe_mv;
if (fsync_on_eintr(dest_fd) == -1)
goto ret_gbe_mv;
if (close_on_eintr(dest_fd) == -1)
goto ret_gbe_mv;
if (rename(dest_tmp, f->fname) == -1)
goto ret_gbe_mv;
if (fsync_dir(f->fname) < 0) {
f->io_err_gbe_bin = 1;
goto ret_gbe_mv;
}
free(dest_tmp);
dest_tmp = NULL;
ret_gbe_mv:
if (f->gbe_fd > -1) {
if (close_on_eintr(f->gbe_fd) < 0)
rval = -1;
if (fsync_dir(f->fname) < 0) {
f->io_err_gbe_bin = 1;
rval = -1;
}
f->gbe_fd = -1;
}
if (f->tmp_fd > -1) {
if (close_on_eintr(f->tmp_fd) < 0)
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(unsigned long p, const char *f_op)
{
struct xstate *x = xstatus(0, NULL);
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 + (unsigned long)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(unsigned long p, const char *f_op)
{
struct xstate *x = xstatus(0, NULL);
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(unsigned long p, const char *f_op, const char *d_type,
off_t nsize, off_t ncmp)
{
struct xstate *x = xstatus(0, NULL);
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)
err(ECANCELED, "%s: GbE %s %s out of bounds",
f->fname, d_type, f_op);
if (off != 0 && off != ncmp >> 1)
err(ECANCELED, "%s: GbE %s %s at bad offset",
f->fname, d_type, f_op);
return off;
}
long
rw_gbe_file_exact(int fd, unsigned char *mem, unsigned long nrw,
off_t off, int rw_type)
{
struct xstate *x = xstatus(0, NULL);
struct xfile *f = &x->f;
long 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 ((unsigned long)(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 > (unsigned long)(f->gbe_file_size - off))
goto err_rw_gbe_file_exact;
if (nrw > (unsigned long)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;
}
+349
View File
@@ -0,0 +1,349 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* Numerical functions.
*/
#ifdef __OpenBSD__
#include <sys/param.h>
#endif
#include <sys/types.h>
#if defined(FALLBACK_RAND_1989) && \
(FALLBACK_RAND_1989) > 0
#include <sys/time.h>
#endif
#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>
#if defined(FALLBACK_RAND_1989) && \
(FALLBACK_RAND_1989) > 0
#include <time.h>
#endif
#include <unistd.h>
#include "../include/common.h"
unsigned short
hextonum(char ch_s)
{
unsigned char ch;
ch = (unsigned char)ch_s;
if ((unsigned int)(ch - '0') <= 9)
return ch - '0';
ch |= 0x20;
if ((unsigned int)(ch - 'a') <= 5)
return ch - 'a' + 10;
if (ch == '?' || ch == 'x')
return (unsigned short)rlong() & 0xf;
return 16; /* invalid character */
}
/* Random numbers
*/
unsigned long
rlong(void)
{
#if !(defined(FALLBACK_RAND_1989) && \
((FALLBACK_RAND_1989) > 0))
#if (defined(__OpenBSD__) && (OpenBSD) >= 201) || \
defined(__FreeBSD__) || \
defined(__NetBSD__) || defined(__APPLE__)
unsigned long rval;
arc4random_buf(&rval, sizeof(unsigned long));
return rval;
#else
static int fd = -1;
static long nr = -1;
static unsigned long off = 0;
#if defined (BUFSIZ)
static char rbuf[BUFSIZ];
#else
#ifndef PORTABLE
static char rbuf[4096];
#elif ((PORTABLE) > 0)
static char rbuf[256]; /* scarce memory on old systems */
#else
static char rbuf[4096]; /* typical 32-bit BUFSIZ */
#endif
#endif
unsigned long rval;
long new_nr;
int retries = 0;
int max_retries = 100;
#if defined(__linux__)
#if defined(HAVE_GETRANDOM) || \
defined(HAVE_GETRANDOM_SYSCALL)
/* linux getrandom()
*
* we *can* use arc4random on
* modern linux, but not on
* every libc. better use the
* official linux function
*
* similar benefits to arc4random
* e.g. works in chroot, blocks
* until it has enough entropy,
* and works even when /dev/urandom
* is available (doesn't use it);
* it's generally more reliable
*/
if (fallback_rand_getrandom(&rval, sizeof(rval)) == 0)
return rval;
/*
* now fall back to urandom if getrandom failed:
*/
#endif
#endif
/* reading from urandom is inherently
* unreliable on old systems, even if
* newer systems make it more reliable
*
* modern linux/bsd make it safe, but
* we have to assume that someone is
* compiling this on linux from 1999
*
* this logic therefore applies various
* tricks to mitigate possible os bugs
*/
retry_urandom_read:
if (++retries > max_retries)
goto rlong_next;
if (nr < 0 || nr < (long)sizeof(unsigned long)) {
if (fd < 0) {
fd = open("/dev/urandom",
O_RDONLY | O_BINARY | O_NOFOLLOW |
O_CLOEXEC);
#ifdef USE_OLD_DEV_RANDOM
#if (USE_OLD_DEV_RANDOM) > 0
/* WARNING:
* /dev/random may block
* forever and does **NOT**
* guarantee better entropy
* on old systems
*
* only use it if needed
*/
if (fd < 0)
fd = open("/dev/random",
O_RDONLY | O_BINARY | O_NOFOLLOW |
O_CLOEXEC);
#endif
#endif
if (fd < 0)
goto retry_urandom_read;
retries = 0;
}
new_nr = rw_file_exact(fd, (unsigned char *)rbuf,
sizeof(rbuf), 0, IO_READ, LOOP_EAGAIN,
LOOP_EINTR, MAX_ZERO_RW_RETRY, OFF_ERR);
if (new_nr < 0 || new_nr < (long)sizeof(rbuf))
goto retry_urandom_read;
/* only reset buffer after successful refill */
nr = new_nr;
off = 0;
/* to mitigate file descriptor
* injection, we do not re-use
* the same descriptor each time
*/
(void) close_on_eintr(fd);
fd = -1;
}
fd = -1;
retries = 0;
memcpy(&rval, rbuf + off, sizeof(unsigned long));
nr -= (long)sizeof(unsigned long);
off += sizeof(unsigned long);
return rval;
rlong_next:
fd = -1;
off = 0;
nr = -1;
err(EIO, "Can't read from /dev/[ua]random");
return 0;
#endif
#else /* FALLBACK_RAND_1989 */
/* your computer is from a museum
*/
unsigned long mix = 0;
int nr;
/* 100 times, for entropy
*/
for (nr = 0; nr < 100; nr++)
mix ^= fallback_rand_1989();
/* 101 times ;)
*/
return fallback_rand_1989();
#endif
}
#if !(defined(FALLBACK_RAND_1989) && \
((FALLBACK_RAND_1989) > 0))
#if defined(__linux__)
#if defined(HAVE_GETRANDOM) || \
defined(HAVE_GETRANDOM_SYSCALL)
int
fallback_rand_getrandom(void *buf, unsigned long len)
{
unsigned long off = 0;
long rval = -1;
if (!len)
return -1;
if (buf == NULL)
return -1;
#if defined(HAVE_GETRANDOM) || \
defined(HAVE_GETRANDOM_SYSCALL)
while (off < len) {
#if defined(HAVE_GETRANDOM)
rval = (long)getrandom((char *)buf + off, len - off, 0);
#elif defined(HAVE_GETRANDOM_SYSCALL)
rval = (long)syscall(SYS_getrandom,
(char *)buf + off, len - off, 0);
#endif
if (rval < 0) {
if (errno == EINTR)
continue;
return -1; /* unsupported by kernel */
}
off += (unsigned long)rval;
}
return 0;
#else
(void)buf;
(void)len;
return -1;
#endif
}
#endif
#endif
#else
/* nobody should use this
* (not crypto-safe)
*/
unsigned long
fallback_rand_1989(void)
{
static unsigned long mix = 0;
static unsigned long counter = 0;
struct timeval tv;
gettimeofday(&tv, NULL);
mix ^= (unsigned long)tv.tv_sec
^ (unsigned long)tv.tv_usec
^ (unsigned long)getpid()
^ (unsigned long)&mix
^ counter++
^ entropy_jitter();
/*
* Stack addresses can vary between
* calls, thus increasing entropy.
*/
mix ^= (unsigned long)&mix;
mix ^= (unsigned long)&tv;
mix ^= (unsigned long)&counter;
return mix;
}
unsigned long
entropy_jitter(void)
{
unsigned long mix;
struct timeval a, b;
long mix_diff;
int c;
mix = 0;
gettimeofday(&a, NULL);
for (c = 0; c < 32; c++) {
getpid();
gettimeofday(&b, NULL);
/*
* prevent negative numbers to prevent overflow,
* which would bias rand to large numbers
*/
mix_diff = (long)(b.tv_usec - a.tv_usec);
if (mix_diff < 0)
mix_diff = -mix_diff;
mix ^= (unsigned long)(mix_diff);
mix ^= (unsigned long)&mix;
}
return mix;
}
#endif
void
check_bin(unsigned long a, const char *a_name)
{
if (a > 1)
err(EINVAL, "%s must be 0 or 1, but is %lu",
a_name, (unsigned long)a);
}
+280
View File
@@ -0,0 +1,280 @@
/* 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 *
xstatus(int argc, char *argv[])
{
static struct xstate us = {
/* DO NOT MESS THIS UP, OR THERE WILL BE DEMONS */
{
{
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
};
static int first_run = 1;
if (!first_run)
return &us;
us.f.buf = us.f.real_buf;
first_run = 0;
us.argv0 = argv[0];
if (argc > 1)
us.f.fname = argv[1];
if (argc < 3)
usage();
/* https://man.openbsd.org/pledge.2
https://man.openbsd.org/unveil.2 */
#if defined(__OpenBSD__) && defined(OpenBSD)
#if (OpenBSD) >= 604
if (pledge("stdio flock rpath wpath cpath unveil", NULL) == -1)
err(errno, "pledge plus unveil");
#elif (OpenBSD) >= 509
if (pledge("stdio flock rpath wpath cpath", NULL) == -1)
err(errno, "pledge");
#endif
#endif
#ifndef S_ISREG
err(ECANCELED, "Can't determine file types (S_ISREG undefined)");
#endif
#ifndef CHAR_BIT
err(ECANCELED, "Unknown char size");
#else
if (CHAR_BIT != 8)
err(EINVAL, "Unsupported char size");
#endif
#if defined(__OpenBSD__) && defined(OpenBSD) && \
(OpenBSD) >= 604
/* can only use local tmp on openbsd, due to unveil */
us.f.tname = new_tmpfile(&us.f.tmp_fd, 1, NULL);
#else
us.f.tname = new_tmpfile(&us.f.tmp_fd, 0, NULL);
#endif
if (us.f.tname == NULL)
err(errno, "Can't create tmpfile");
if (*us.f.tname == '\0')
err(errno, "tmp dir is an empty string");
#if defined(__OpenBSD__) && defined(OpenBSD) && \
OpenBSD >= 604
if (unveil(us.f.tname, "rwc") == -1)
err(errno, "unveil rwc: %s", us.f.tname);
#endif
if (fstat(us.f.tmp_fd, &us.f.tmp_st) < 0)
err(errno, "%s: stat", us.f.tname);
sanitize_command_list();
/* parse user command */
set_cmd(argc, argv);
set_cmd_args(argc, argv);
#if defined(__OpenBSD__) && defined(OpenBSD) && \
(OpenBSD) >= 604
if ((us.cmd[i].flags & O_ACCMODE) == O_RDONLY) {
if (unveil(us.f.fname, "r") == -1)
err(errno, "%s: unveil r", us.f.fname);
} else {
if (unveil(us.f.fname, "rwc") == -1)
err(errno, "%s: unveil rw", us.f.fname);
}
if (unveil(us.f.tname, "rwc") == -1)
err(errno, "%s: unveil rwc", us.f.tname);
if (unveil(NULL, NULL) == -1)
err(errno, "unveil block (rw)");
if (pledge("stdio flock rpath wpath cpath", NULL) == -1)
err(errno, "pledge (kill unveil)");
#endif
open_gbe_file();
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));
copy_gbe();
read_checksums();
return &us;
}
void
err(int nvm_errval, const char *msg, ...)
{
struct xstate *x = xstatus(0, NULL);
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);
}
const char *
getnvmprogname(void)
{
struct xstate *x = xstatus(0, NULL);
const char *p;
static char fallback[] = "nvmutil";
char *rval = fallback;
if (x != NULL) {
if (x->argv0 == NULL || *x->argv0 == '\0')
return "";
rval = x->argv0;
}
p = strrchr(rval, '/');
if (p)
return p + 1;
else
return rval;
}
int
exit_cleanup(void)
{
struct xstate *x = xstatus(0, NULL);
struct xfile *f;
int close_err;
int saved_errno;
close_err = 0;
saved_errno = errno;
if (x != NULL) {
f = &x->f;
if (f->gbe_fd > -1) {
if (close_on_eintr(f->gbe_fd) == -1)
close_err = 1;
f->gbe_fd = -1;
}
if (f->tmp_fd > -1) {
if (close_on_eintr(f->tmp_fd) == -1)
close_err = 1;
}
if (f->tname != NULL) {
if (unlink(f->tname) == -1)
close_err = 1;
}
f->tmp_fd = -1;
}
if (saved_errno)
errno = saved_errno;
if (close_err)
return -1;
return 0;
}
+75
View File
@@ -0,0 +1,75 @@
/* 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 <stddef.h>
#include <unistd.h>
#include "../include/common.h"
/* Portable strncmp() that blocks
* NULL/empty/unterminated strings
*/
int
xstrxcmp(const char *a, const char *b, unsigned long maxlen)
{
unsigned long i;
if (a == NULL || b == NULL)
err(EINVAL, "NULL input to xstrxcmp");
if (*a == '\0' || *b == '\0')
err(EINVAL, "Empty string in xstrxcmp");
for (i = 0; i < maxlen; i++) {
unsigned char ac = (unsigned char)a[i];
unsigned char bc = (unsigned char)b[i];
if (ac == '\0' || bc == '\0') {
if (ac == bc)
return 0;
return ac - bc;
}
if (ac != bc)
return ac - bc;
}
err(EINVAL, "Unterminated string in xstrxcmp");
errno = EINVAL;
return -1;
}
/* Portable strncmp() that blocks
* NULL/empty/unterminated strings
*/
unsigned long
xstrxlen(const char *scmp, unsigned long maxlen)
{
unsigned long xstr_index;
if (scmp == NULL)
err(EINVAL, "NULL input to xstrxlen");
if (*scmp == '\0')
err(EINVAL, "Empty string in xstrxlen");
for (xstr_index = 0;
xstr_index < maxlen && scmp[xstr_index] != '\0';
xstr_index++);
if (xstr_index == maxlen)
err(EINVAL, "Unterminated string in xstrxlen");
return xstr_index;
}
+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);
err(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(unsigned long pos16, unsigned long p)
{
struct xstate *x = xstatus(0, NULL);
struct xfile *f = &x->f;
unsigned long 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(unsigned long pos16, unsigned long p, unsigned short val16)
{
struct xstate *x = xstatus(0, NULL);
struct xfile *f = &x->f;
unsigned long 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(unsigned long p)
{
struct xstate *x = xstatus(0, NULL);
struct xfile *f = &x->f;
check_bin(p, "part number");
f->part_modified[p] = 1;
}
void
check_nvm_bound(unsigned long c, unsigned long p)
{
/* Block out of bound NVM access
*/
check_bin(p, "part number");
if (c >= NVM_WORDS)
err(ECANCELED, "check_nvm_bound: out of bounds %lu",
(unsigned long)c);
}
+50
View File
@@ -0,0 +1,50 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
*
* This tool lets you modify Intel GbE NVM (Gigabit Ethernet
* Non-Volatile Memory) images, e.g. change the MAC address.
* These images configure your Intel Gigabit Ethernet adapter.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include "include/common.h"
int
main(int argc, char *argv[])
{
struct xstate *x = xstatus(argc, argv);
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
unsigned long c;
if (cmd->run == NULL)
err(errno, "Command not set");
cmd->run();
for (c = 0; c < items(x->cmd); c++)
x->cmd[c].run = cmd_helper_err;
if ((cmd->flags & O_ACCMODE) == O_RDWR)
write_to_gbe_bin();
if (exit_cleanup() == -1)
err(EIO, "%s: close", f->fname);
if (f->io_err_gbe_bin)
err(EIO, "%s: error writing final file");
if (f->tname != NULL)
free(f->tname);
return EXIT_SUCCESS;
}