WIP: convert to lwmk

Signed-off-by: Leah Rowe <leah@libreboot.org>
This commit is contained in:
Leah Rowe
2026-09-07 13:41:59 +01:00
parent 8d0ad21112
commit 2b34aeea5d
909 changed files with 6807 additions and 186660 deletions
-19
View File
@@ -1,19 +0,0 @@
Copyright (c) 2023 Nicholas Chin
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.
-16
View File
@@ -1,16 +0,0 @@
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2023 Nicholas Chin
CC=cc
CFLAGS=-Wall -Wextra -O2
SRCS=dell_flash_unlock.c accessors.c
all: $(SRCS) accessors.h
CFLAGS="$(CFLAGS)"; \
if [ $$(uname) = OpenBSD ] || [ $$(uname) = NetBSD ]; then \
CFLAGS="$$CFLAGS -l$$(uname -p)"; \
fi; \
$(CC) $$CFLAGS $(SRCS) -o dell_flash_unlock
clean:
rm -f dell_flash_unlock
-134
View File
@@ -1,134 +0,0 @@
# Dell Laptop Internal Flashing
This utility allows you to use flashprog's internal programmer to program the
entire BIOS flash chip from software while still running the original Dell
BIOS, which normally restricts software writes to the flash chip. It seems like
this works on any Dell laptop that has an EC similar to the SMSC MEC5035 on the
E6400, which mainly seem to be the Latitude and Precision lines starting from
around 2008 (E6400 era).
## TL;DR
### Linux specific
- On Linux, ensure you are booting with the `iomem=relaxed` kernel parameter.
- If you get a "Function not implemented" error, ensure that your kernel has
"CONFIG_X86_IOPL_IOPERM" set to "y". Here are several common locations for
the config and how to check them:
- `zcat /proc/config.gz | grep IOPL`
- `grep IOPL /boot/config`
- `grep IOPL /boot/config-$(uname -r)`
If it says it is not set, then you will need to install or compile a kernel
with that option set.
### OpenBSD/NetBSD/FreeBSD
- On OpenBSD/NetBSD/FreeBSD, ensure you are booting with securelevel set to -1.
### General
Make sure an AC adapter is plugged into your system
Run `make` to compile the utility, and then run `./dell_flash_unlock` with
root/superuser permissions and follow the directions it outputs.
## Confirmed supported devices
- Latitude E6400, E6500
- Latitude E6410, E4310
- Latitude E6420, E6520
- Latitude E6430, E6530, E5530
- Latitude E7240
- Precision M6800, M5800
It is likely that any other Latitude/Precision laptops from the same era as
devices specifically mentioned in the above list will work as Dell seems to use
the same ECs in one generation.
## Tested
These systems have been tested, but were reported as not working with
dell-flash-unlock. This could be due to user error, a bug in this utility, or
the feature not being implemented in Dell's firmware. If you have such a system,
please test the utility and report whether or not it actually works for you.
- Latitude E6220
- Latitude E6330
## Detailed device specific behavior
- On GM45 era laptops, the expected behavior is that you will run the utility
for the first time, which will tell the EC to set the descriptor override on
the next boot. Then you will need to shut down the system, after which the
system will automatically boot up. You should then re-run the utility to
disable SMM, after which you can run flashprog. Finally, you should run the
utility a third time to reenable SMM so that shutdown works properly
afterwards.
- On 1st Generation Intel Core systems such as the E6410 and newer, run the
utility and shutdown in the same way as the E6400. However, it seems like the
EC no longer automatically boots the system. In this case you should manually
power it on. It also seems that the firmware does not set the BIOS Lock bit
when the descriptor override is set, making the 2nd run after the reboot
technically unnecessary. There is no harm in rerunning it though, as the
utility can detect when the flash is unlocked and perform the correct steps
as necessary.
## How it works
There are several ways the firmware can protect itself from being overwritten.
One way is the Intel Flash Descriptor (IFD) permissions. On Intel systems, the
flash image is divided into several regions such as the IFD itself, Gigabit
Ethernet (GBE) non-volative memory, Management Engine (ME) firmware, Platform
Data (PD), and the BIOS. The IFD contains a section which specifies the
read/write permissions for each SPI controller (such as the host system) and
each region of the flash, which are enforced by the chipset.
On the Latitude E6400, the host has read-only access to the IFD, no access to
the ME region, and read-write access to the PD, GBE, and BIOS regions. In order
for flashprog to write to the entire flash internally, the host needs full
permissions to all of these regions. Since the IFD is read only, we cannot
change these permissions unless we directly access the chip using an external
programmer, which defeats the purpose of internal flashing.
However, Intel chipsets have a pin strap that allows the flash descriptor
permissions to be overridden depending on the value of the pin at power on,
granting RW permissions to all regions. On the ICH9M chipset on the E6400, this
pin is HDA\_DOCK\_EN/GPIO33, which will enable the override if it is sampled
low. This pin happens to be connected to a GPIO controlled by the Embedded
Controller (EC), a small microcontroller on the board which handles things like
the keyboard, touchpad, LEDs, and other system level tasks. Software can send a
certain command to the EC, which tells it to pull GPIO33 low on the next boot.
Although we now have full access according to the IFD permissions, we still
cannot flash the whole chip, due to another protection the firmware uses.
Before software can update the BIOS, it must change the BIOS Write Enable
(BIOSWE) bit in the chipset from 0 to 1. However, if the BIOS Lock Enable (BLE)
bit is also set to 1, then changing the BIOSWE bit triggers a System Management
Interrupt (SMI). This causes the processor to enter System Management Mode
(SMM), a highly privileged x86 execution state which operates transparently to
the operating system. The code that SMM runs is provided by the BIOS, which
checks the BIOSWE bit and sets it back to 0 before returning control to the OS.
This feature is intended to only allow SMM code to update the system firmware.
As the switch to SMM suspends the execution of the OS, it appears to the OS
that the BIOSWE bit was never set to 1. Unfortunately, the BLE bit cannot be
set back to 0 once it is set to 1, so this functionality cannot be disabled
after it is first enabled by the BIOS.
Older versions of the E6400 BIOS did not set the BLE bit, allowing flashprog to
flash the entire flash chip internally after only setting the descriptor
override. However, more recent versions do set it, so we may have hit a dead
end unless we force downgrade to an older version (though there is a more
convenient method, as we are about to see).
What if there was a way to sidestep the BIOS Lock entirely? As it turns out,
there is, and it's called the Global SMI Enable (GBL\_SMI\_EN) bit. If it's set
to 1, then the chipset will generate SMIs, such as when we change BIOSWE with
BLE set. If it's 0, then no SMI will be generated, even with the BLE bit set.
On the E6400, GBL\_SMI\_EN is set to 1, and it can be changed back to 0, unlike
the BLE bit. But there still might be one bit in the way, the SMI\_LOCK bit,
which prevents modifications to GBL\_SMI\_EN when SMI\_LOCK is 1. Like the BLE
bit, it cannot be changed back to 0 once it set to 1. But we are in luck, as
the vendor E6400 BIOS leaves SMI\_LOCK unset at 0, allowing us to clear
GBL\_SMI\_EN and disable SMIs, bypassing the BIOS Lock protections.
There are other possible protection mechanisms that the firmware can utilize,
such as Protected Range Register settings, which apply access permissions to
address ranges of the flash, similar to the IFD. However, the E6400 vendor
firmware does not utilize these, so they will not be discussed.
## References
- Open Security Training: Advanced x86: BIOS and SMM Internals - SMI Suppression
- https://opensecuritytraining.info/IntroBIOS_files/Day1_XX_Advanced%20x86%20-%20BIOS%20and%20SMM%20Internals%20-%20SMI%20Suppression.pdf
-151
View File
@@ -1,151 +0,0 @@
/* SPDX-License-Identifier: MIT */
/* SPDX-FileCopyrightText: 2023 Nicholas Chin */
#if defined(__linux__)
#include <sys/io.h>
#endif
#if defined(__OpenBSD__) || defined(__NetBSD__)
#include <sys/types.h>
#include <machine/sysarch.h>
#endif /* __OpenBSD__ || __NetBSD__ */
#if defined(__OpenBSD__)
#if defined(__amd64__)
#include <amd64/pio.h>
#elif defined(__i386__)
#include <i386/pio.h>
#endif /* __i386__ */
#endif /* __OpenBSD__ */
#if defined(__FreeBSD__)
#include <fcntl.h>
#include <sys/types.h>
#include <machine/cpufunc.h>
#include <unistd.h>
#endif /* __FreeBSD__ */
#include <errno.h>
#include "accessors.h"
uint32_t
pci_read_32(uint32_t dev, uint8_t reg)
{
sys_outl(PCI_CFG_ADDR, dev | reg);
return sys_inl(PCI_CFG_DATA);
}
void
pci_write_32(uint32_t dev, uint8_t reg, uint32_t value)
{
sys_outl(PCI_CFG_ADDR, dev | reg);
sys_outl(PCI_CFG_DATA, value);
}
void
sys_outb(unsigned int port, uint8_t data)
{
#if defined(__linux__)
outb(data, port);
#endif
#if defined(__OpenBSD__) || defined(__FreeBSD__)
outb(port, data);
#endif
#if defined(__NetBSD__)
__asm__ volatile ("outb %b0, %w1" : : "a"(data), "d"(port));
#endif
}
void
sys_outl(unsigned int port, uint32_t data)
{
#if defined(__linux__)
outl(data, port);
#endif
#if defined(__OpenBSD__) || defined(__FreeBSD__)
outl(port, data);
#endif
#if defined(__NetBSD__)
__asm__ volatile ("outl %0, %w1" : : "a"(data), "d"(port));
#endif
}
uint8_t
sys_inb(unsigned int port)
{
#if defined(__linux__) || defined (__OpenBSD__) \
|| defined(__FreeBSD__)
return inb(port);
#endif
#if defined(__NetBSD__)
uint8_t retval;
__asm__ volatile ("inb %w1, %b0" : "=a" (retval) : "d" (port));
return retval;
#endif
return 0;
}
uint32_t
sys_inl(unsigned int port)
{
#if defined(__linux__) || defined (__OpenBSD__) \
|| defined(__FreeBSD__)
return inl(port);
#endif
#if defined(__NetBSD__)
int retval;
__asm__ volatile ("inl %w1, %0" : "=a" (retval) : "d" (port));
return retval;
#endif
return 0;
}
int
sys_iopl(int level)
{
#if defined(__linux__)
return iopl(level);
#endif
#if defined(__OpenBSD__)
#if defined(__i386__)
return i386_iopl(level);
#elif defined(__amd64__)
return amd64_iopl(level);
#endif /* __amd64__ */
#endif /* __OpenBSD__ */
#if defined(__NetBSD__)
#if defined(__i386__)
return i386_iopl(level);
#elif defined(__amd64__)
return x86_64_iopl(level);
#endif /* __amd64__ */
#endif /* __NetBSD__ */
#if defined(__FreeBSD__)
/* Refer to io(4) manual page. This assumes the legacy behavior
* where opening /dev/io raises the IOPL of the process */
static int io_fd = -1;
/* Requesting privileged access */
if (level > 0) {
if (io_fd == -1) {
io_fd = open("/dev/io", O_RDONLY);
return (io_fd == -1) ? -1 : 0;
}
/* Lowering access to lowest level */
} else if (level == 0 && io_fd != -1) {
if (close(io_fd) == -1) {
return -1;
} else {
io_fd = -1;
}
}
return 0;
#endif /* __FreeBSD__ */
errno = ENOSYS;
return -1;
}
-17
View File
@@ -1,17 +0,0 @@
/* SPDX-License-Identifier: MIT */
/* SPDX-FileCopyrightText: 2023 Nicholas Chin */
#include <stdint.h>
#define PCI_CFG_ADDR 0xcf8
#define PCI_CFG_DATA 0xcfc
#define PCI_DEV(bus, dev, func) (1u << 31 | bus << 16 | dev << 11 | func << 8)
uint32_t pci_read_32(uint32_t dev, uint8_t reg);
void pci_write_32(uint32_t dev, uint8_t reg, uint32_t value);
int sys_iopl(int level);
void sys_outb(unsigned int port, uint8_t data);
void sys_outl(unsigned int port, uint32_t data);
uint8_t sys_inb(unsigned int port);
uint32_t sys_inl(unsigned int port);
-222
View File
@@ -1,222 +0,0 @@
/* SPDX-License-Identifier: MIT */
/* SPDX-FileCopyrightText: 2023 Nicholas Chin */
#include <sys/mman.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "accessors.h"
int get_fdo_status(void);
int check_lpc_decode(void);
void ec_set_fdo(void);
void write_ec_reg(uint8_t index, uint8_t data);
void send_ec_cmd(uint8_t cmd);
int wait_ec(void);
int check_bios_write_en(void);
int set_gbl_smi_en(int enable);
int get_gbl_smi_en(void);
#define EC_INDEX 0x910
#define EC_DATA 0x911
#define EC_ENABLE_FDO 2
#define LPC_DEV PCI_DEV(0, 0x1f, 0)
#define RCBA_MMIO_LEN 0x4000
/* Register offsets */
#define SPIBAR 0x3800
#define HSFS_REG 0x04
#define SMI_EN_REG 0x30
volatile uint8_t *rcba_mmio;
uint16_t pmbase;
int
main(int argc, char *argv[])
{
int devmemfd;
(void)argc;
(void)argv;
if (sys_iopl(3) == -1)
err(EXIT_FAILURE, "Could not access IO ports");
if ((devmemfd = open("/dev/mem", O_RDONLY)) == -1)
err(EXIT_FAILURE, "/dev/mem");
/* Read RCBA and PMBASE from the LPC config registers */
long int rcba = pci_read_32(LPC_DEV, 0xf0) & 0xffffc000;
pmbase = pci_read_32(LPC_DEV, 0x40) & 0xff80;
/* FDO pin-strap status bit is in RCBA mmio space */
rcba_mmio = mmap(0, RCBA_MMIO_LEN, PROT_READ, MAP_SHARED, devmemfd,
rcba);
if (rcba_mmio == MAP_FAILED)
err(EXIT_FAILURE, "Could not map RCBA");
if (get_fdo_status() == 1) { /* Descriptor not overridden */
if (check_lpc_decode() == -1)
err(EXIT_FAILURE, "Can't forward I/O to LPC");
printf("Sending FDO override command to EC:\n");
ec_set_fdo();
printf("Flash Descriptor Override enabled.\n"
"Shut down (don't reboot) now.\n\n"
"The EC may auto-boot on some systems; if not then "
"manually power on.\n When the system boots rerun "
"this utility to finish unlocking.\n");
} else if (check_bios_write_en() == 0) {
/* SMI locks in place, try disabling SMIs to bypass them */
if (set_gbl_smi_en(0)) {
printf("SMIs disabled. Internal flashing should work "
"now.\n After flashing, re-run this utility "
"to enable SMIs.\n (shutdown is buggy when "
"SMIs are disabled)\n");
} else {
errno = EIO;
err(EXIT_FAILURE, "Could not disable SMIs!");
}
} else { /* SMI locks not in place or bypassed */
if (get_gbl_smi_en()) {
/* SMIs are still enabled, assume this is an Exx10
* or newer which don't need the SMM bypass */
printf("Flash is unlocked.\n"
"Internal flashing should work.\n");
} else {
/* SMIs disabled, assume this is an Exx00 after
* unlocking and flashing */
set_gbl_smi_en(1);
printf("SMIs enabled.\n"
"You can now shutdown the system.\n");
}
}
sys_iopl(0);
return EXIT_SUCCESS;
}
int
get_fdo_status(void)
{
return (*(uint16_t*)(rcba_mmio + SPIBAR + HSFS_REG) >> 13) & 1;
}
int
check_lpc_decode(void)
{
/* Check that at a Generic Decode Range Register is set up to
* forward I/O ports 0x910 and 0x911 over LPC for the EC */
int i = 0;
int gen_dec_free = -1;
for (; i < 4; i++) {
uint32_t reg_val = pci_read_32(LPC_DEV, 0x84 + 4*i);
uint16_t base_addr = reg_val & 0xfffc;
uint16_t mask = ((reg_val >> 16) & 0xfffc) | 0x3;
/* Bit 0 is the enable for each decode range. If disabled, note
* this register as available to add our own range decode */
if ((reg_val & 1) == 0)
gen_dec_free = i;
/* Check if the current range register matches port 0x910.
* 0x911 doesn't need to be checked as the LPC bridge only
* decodes at the dword level, and thus a check is redundant */
if ((0x910 & ~mask) == base_addr) {
return 0;
}
}
/* No matching range found, try setting a range in a free register */
if (gen_dec_free != -1) {
/* Set up an I/O decode range from 0x910-0x913 */
pci_write_32(LPC_DEV, 0x84 + 4 * gen_dec_free, 0x911);
return 0;
} else {
errno = EIO;
return -1;
}
}
void
ec_set_fdo(void)
{
/* EC FDO command arguments for reference:
* 0 = Query EC FDO status
* 2 = Enable FDO for next boot
* 3 = Disable FDO for next boot */
write_ec_reg(0x12, EC_ENABLE_FDO);
send_ec_cmd(0xb8);
}
void
write_ec_reg(uint8_t index, uint8_t data)
{
sys_outb(EC_INDEX, index);
sys_outb(EC_DATA, data);
}
void
send_ec_cmd(uint8_t cmd)
{
sys_outb(EC_INDEX, 0);
sys_outb(EC_DATA, cmd);
if (wait_ec() == -1)
err(EXIT_FAILURE, "Timeout while waiting for EC!");
}
int
wait_ec(void)
{
uint8_t busy;
int timeout = 1000;
do {
sys_outb(EC_INDEX, 0);
busy = sys_inb(EC_DATA);
timeout--;
usleep(1000);
} while (busy && timeout > 0);
if (timeout <= 0)
errno = EIO;
return timeout > 0 ? 0 : -1;
}
int
check_bios_write_en(void)
{
uint8_t bios_cntl = pci_read_32(LPC_DEV, 0xdc) & 0xff;
/* Bit 5 = SMM BIOS Write Protect Disable (SMM_BWP)
* Bit 1 = BIOS Lock Enable (BLE)
* If both are 0, then there's no write protection */
if ((bios_cntl & 0x22) == 0)
return 1;
/* SMM protection is enabled, but try enabling writes
* anyway in case the vendor SMM code doesn't reset it */
pci_write_32(LPC_DEV, 0xdc, bios_cntl | 0x1);
return pci_read_32(LPC_DEV, 0xdc) & 0x1;
}
int
set_gbl_smi_en(int enable)
{
uint32_t smi_en = sys_inl(pmbase + SMI_EN_REG);
if (enable) {
smi_en |= 1;
} else {
smi_en &= ~1;
}
sys_outl(pmbase + SMI_EN_REG, smi_en);
return (get_gbl_smi_en() == enable);
}
int
get_gbl_smi_en(void)
{
return sys_inl(pmbase + SMI_EN_REG) & 1;
}
-68
View File
@@ -1,68 +0,0 @@
#!/bin/sh
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Leah Rowe
set -u -e
urlmain="https://www.mirrorservice.org/sites/libreboot.org/release/misc/grub"
urlbkup="https://mirror.math.princeton.edu/pub/libreboot/misc/grub"
# script to grab GNU gettext po files from translationproject.org -
# i noticed that the grub bootstrap script grabs these at build time,
# without actually checking signatures, and they could change on the
# server upstream at any time
# this means that the GRUB build process is currently non-deterministic,
# which is a violation of libreboot policy.
tmpdir="`mktemp -d`"
tmpmod="`mktemp -d`"
mkdir -p "$tmpdir" "$tmpmod" || exit 1
(
cd "$tmpdir" || exit 1
wget --mirror --level=1 -nd -nv -A.po -P 'po/.reference' \
https://translationproject.org/latest/grub/ || \
exit 1
find -type f > "$tmpmod/tmplist" || exit 1
while read -r f; do
printf "%s\n" "${f#./}" >> "$tmpmod/module.list"
# now make the actual config files, but don't use
# the main upstream, because those files can change
# at any time. we will, over time, manually update
# our mirrors
pkgname="${f##*/}"
[ -z "$pkgname" ] && printf "ERR\n" && exit 1
pkgsum="`sha512sum "$f" | awk '{print $1}'`"
mkdir -p "$tmpmod/$pkgname" || exit 1
printf "# SPDX-License-Identifier: GPL-3.0-or-later\n\n" >> \
"$tmpmod/$pkgname/module.cfg" || exit 1
printf "subcurl=\"%s/%s\"\n" "$urlmain" "$pkgname" >> \
"$tmpmod/$pkgname/module.cfg" || exit 1
printf "subcurl_bkup=\"%s/%s\"\n" "$urlbkup" "$pkgname" >> \
"$tmpmod/$pkgname/module.cfg" || exit 1
printf "subhash=\"%s\"\n" "$pkgsum" >> "$tmpmod/$pkgname/module.cfg"
done < "$tmpmod/tmplist" || exit 1; :
mv "$tmpmod/tmplist" "$tmpdir" || exit 1
)
printf "tmpdir for modules: '%s'\n" "$tmpmod"
rm -f "module.list" || exit 1
printf "Check directory for lbmk files: '%s'\n" "$tmpmod"
printf "This directory has the PO files: '%s'\n" "$tmpdir"
exit 0
+197
View File
@@ -0,0 +1,197 @@
#!/bin/sh
# Copyright 2026 Leah Lowe <leah@libreboot.org>
# SPDX-License-Identifier: MIT
# convert patches from librewolf source.git into git-am-compatible
# patches, for use in lwmk.git
# this script doesn't check the version. you must ensure that the
# patches match the correct firefox git tag
# example usage:
# import-patches /home/leah/firefox /home/leah/librewolf/source/patches newpatch
# NOTE: the third argument "newpatch" is optional, telling what directory
# to place the new patches in. otherwise it will output to $PWD
set -u -e
main()
{
if [ $# -lt 2 ]; then
err "too few args" "$@"
fi
ffdir="$1"
patches="$2"
newpatches="."
list=""
list_add=""
if [ ! -d "$ffdir" ] || [ ! -d "$patches" ]; then
err "invalid directories"
elif [ ! -e "$ffdir" ]; then
err "ffdir not a git repo"
fi
if [ $# -gt 2 ]; then
newpatches="$3"
fi
if [ ! -d "$newpatches" ]; then
err "third argument not a directory (new patch dir)" "$@"
fi
ffdir="`findpath "$ffdir"`"
patches="`findpath "$patches"`"
newpatches="`findpath "$newpatches"`"
base_commit="`git -C "$ffdir" describe --tags HEAD`"
list="`mktemp`"
list_add="`mktemp`"
if [ ! -f "$patches/../assets/patches.txt" ]; then
x_ find "$patches" -type f -name "*.patch" | sort > "$list" || \
err "cannot sort patchdir" "$@"
else
# librewolf has a system to enable patches or not, by only
# patching what's in assets/patches.txt in source.git
x_ cp "$patches/../assets/patches.txt" "$list"
sed -i -e "s,patches/,$patches/," "$list"
fi
while read -r patch; do
# some patches are made with format-patch,
# so nothing needs to be done:
#
gitam=1
git -C "$ffdir" am -3 "$patch" 2>/dev/null || gitam=0
if [ $gitam -gt 0 ]; then
echo "TEST: $patch" >> /home/leah/patchpatch.list
continue
fi
git am --abort 2>/dev/null || :
# not format-patch, so recreate patch:
#
(
x_ cd "$ffdir"
x_ patch -p1 -i "$patch"
) || err "can't apply patch: $patch"
x_ grep '+++' "$patch" | awk '{print $2}' | sed s/^b/./ \
> "$list_add" || \
err "can't list files on patch: $patch" "$@"
while read -r file_add; do
x_ git -C "$ffdir" add "$file_add"
done < "$list_add" || "can't read patch file: $patch" "$@"
x_ git commit -m "${patch#"$patches/"}" \
--author="LibreWolf Developers <noreply@librewolf.net>"
done < "$list" || err "can't read patch list" "$@"
x_ mkdir -p "$newpatches"
x_ git -C "$ffdir" format-patch $base_commit..HEAD -o "$newpatches"
x_ rm -f "$list" "$list_add"
}
findpath()
{
if [ $# -lt 1 ]; then
err "findpath: No arguments provided" "findpath" "$@"
fi
while [ $# -gt 0 ]; do
found="`readlink -f "$1" 2>/dev/null`" || return 1; :
if [ -z "$found" ]; then
found="`realpath "$1" 2>/dev/null`" || \
return 1
fi
printf "%s\n" "$found"
shift 1
done
}
x_()
{
if [ $# -lt 1 ]; then
return 0
elif [ -z "$1" ]; then
err "Empty first arg" "x_" "$@"
else
"$@" || err "Unhandled error" "x_" "$@"
fi
}
err()
{
if [ $# -eq 1 ]; then
printf "ERROR %s: %s\n" "$0" "$1" 1>&2 || :
elif [ $# -gt 1 ]; then
printf "ERROR %s: %s: in command with args: " "$0" "$1" 1>&2
shift 1
xprintf "$@" 1>&2
else
printf "ERROR, but no arguments provided to err\n" 1>&2
fi
exit 1
}
xprintf()
{
xprintfargs=0
while [ $# -gt 0 ]; do
printf "\"%s\"" "$1"
if [ $# -gt 1 ]; then
printf " "
fi
xprintfargs=1
shift 1
done
if [ $xprintfargs -gt 0 ]; then
printf "\n"
fi
}
main "$@"
-7
View File
@@ -1,7 +0,0 @@
/nvm
/nvmutil
/mkhtemp
/lottery
*.bin
*.o
*.d
-2
View File
@@ -1,2 +0,0 @@
Leah Rowe
Riku Viitanen
-21
View File
@@ -1,21 +0,0 @@
Copyright (C) 2022-2026 Leah Rowe <leah@libreboot.org>
Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-60
View File
@@ -1,60 +0,0 @@
# SPDX-License-Identifier: MIT
# Copyright (c) 2022,2026 Leah Rowe <leah@libreboot.org>
# Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com>
CC = cc
CFLAGS = -Os -Wall -Wextra -std=c99 -pedantic
LDFLAGS =
PREFIX = /usr/local
DESTDIR =
INSTALL = install
PROGS = nvmutil mkhtemp lottery
LIB_OBJS = \
lib/state.o \
lib/file.o \
lib/string.o \
lib/usage.o \
lib/command.o \
lib/num.o \
lib/io.o \
lib/checksum.o \
lib/word.o \
lib/mkhtemp.o \
lib/rand.o
OBJS_NVMUTIL = nvmutil.o $(LIB_OBJS)
OBJS_MKHTEMP = mkhtemp.o lib/file.o lib/string.o lib/num.o lib/mkhtemp.o lib/rand.o
OBJS_LOTTERY = lottery.o lib/file.o lib/string.o lib/num.o lib/mkhtemp.o lib/rand.o
all: $(PROGS)
nvmutil: $(OBJS_NVMUTIL)
$(CC) $(CFLAGS) $(OBJS_NVMUTIL) -o $@ $(LDFLAGS)
mkhtemp: $(OBJS_MKHTEMP)
$(CC) $(CFLAGS) $(OBJS_MKHTEMP) -o $@ $(LDFLAGS)
lottery: $(OBJS_LOTTERY)
$(CC) $(CFLAGS) $(OBJS_LOTTERY) -o $@ $(LDFLAGS)
.c.o:
$(CC) $(CFLAGS) -c $< -o $@
install: $(PROGS)
mkdir -p $(DESTDIR)$(PREFIX)/bin
for p in $(PROGS); do \
$(INSTALL) $$p $(DESTDIR)$(PREFIX)/bin/$$p; \
chmod 755 $(DESTDIR)$(PREFIX)/bin/$$p; \
done
uninstall:
for p in $(PROGS); do \
rm -f $(DESTDIR)$(PREFIX)/bin/$$p; \
done
clean:
rm -f $(PROGS) *.o lib/*.o
distclean: clean
-254
View File
@@ -1,254 +0,0 @@
Mkhtemp - Hardened mktemp
-------------------------
Just like normal mktemp, but hardened.
Create new files and directories randomly as determined by
the user's TMPDIR, or fallback. These temporary files and
directories can be generated from e.g. shell scripts, running
mkhtemp. There is also a library that you could use in your
program. Portable to Linux and BSD. **WORK IN PROGRESS.
This is a very new project. Expect bugs - a stable release
will be announced, when the code has matured.**
A brief summary of *why* mkhtemp is more secure (more
details provided later in this readme - please also
read the source code):
Detect and mitigate symlink attacks, directory access
race conditions, unsecure TMPDIR (e.g. bad enforce sticky
bit policy on world writeable dirs), implement in user
space a virtual sandbox (block directory escape and resolve
paths by walking from `/` manually instead of relying on
the kernel/system), voluntarily error out (halt all
operation) if accessing files you don't own - that's why
sticky bits are checked for example, even when you're root.
It... blocks symlinks, relative paths, attempts to prevent
directory escape (outside of the directory that the file
you're creating is in), basically implementing an analog
of something like e.g. unveil, but in userspace!
Mkhtemp is designed to be the most secure implementation
possible, of mktemp, offering a heavy amount of hardening
over traditional mktemp. Written in C99, and the plan is
very much to keep this code portable over time - patches
very much welcome.
i.e. please read the source code
```
/*
* WARNING: WORK IN PROGRESS.
* Do not use this software in
* your distro yet. It's ready
* when it's ready. Read the src.
*
* What you see is an early beta.
*
* Please do not merge this in
* your Linux distro package repo
* yet (unless maybe you're AUR).
*/
```
Supported mktemp flags:
```
mkhtemp: usage: mkhtemp [-d] [-p dir] [template]
-p DIR <-- set directory, overriding TMPDIR
-d <-- make a directory instead of a file
-q <-- silence errors (exit status unchanged)
```
The rest of them will be added later (the same ones
that GNU and BSD mktemp implement). With these options,
you can generate files/directories already.
You can also write a template at the end. e.g.
```
mkhtemp -d -p path/to/directory vickysomething_XXXXXXXXXXX
```
On most sane/normal setups, the program should already
actually work, but please know that it's very different
internally than every other mktemp implementation.
Read the source code if you're interested. As of this
time of writing, mkhtemp is very new, and under
development. A stable release will be announced when ready.
### What does mkhtemp do differently?
This software attempts to provide mitigation against
several TOCTOU-based
attacks e.g. directory rename / symlink / re-mount, and
generally provides much higher strictness than previous
implementations such as mktemp, mkstemp or even mkdtemp.
It uses several modern features by default, e.g. openat2
and `O_TMPFILE` (plus `O_EXCL`) on Linux, with additional
hardening; BSD projects only have openat so the code uses
that there, but some (not all) of the kinds of checks
Openat2 enforces are done manually (in userspace).
File system sandboxing in userspace (pathless discovery,
and operations are done only with FDs). At startup, the
root directory is opened, and then everything is relative
to that.
Many programs rely on mktemp, and they use TMPDIR in a way
that is quite insecure. Mkhtemp intends to change that,
quite dramatically, with: userspace sandbox (and use OS
level options e.g. OBSD pledge where available), constant
identity/ownership checks on files, MUCH stricter ownership
restrictions (e.g. enforce sticky bit policy on world-
writeable tmpdirs), preventing operation on other people's
files (only your own files) - even root is restricted,
depending on how the code is compiled. Please read the code.
Basically, the gist of it is that normal mktemp *trusts*
your system is set up properly. It will just run however
you tell it to, on whatever directory you tell it to, and
if you're able to write to it, it will write to it.
Some implementations (e.g. OpenBSD one) do some checks,
but not all of them do *all* checks. The purpose of
mkhtemp is to be as strict as possible, while still being
reliable enough that people can use it. Instead of catering
to legacy requirements, mkhtemp says that systems should
be secure. So if you're running in an insecure environment,
the goal of mkhtemp is to *exit* when you run it; better
this than files being corrupted.
Security and reliability are the same thing. They both
mean that your computer is behaving as it should, in a
manner that you can predict.
It doesn't matter how many containers you have, or how
memory-safe your programming language is, the same has
been true forever: code equals bugs, and code usually
has the same percentage of bugs, so more code equals
more bugs. Therefore, highly secure systems (such as
OpenBSD) typically try to keep their code as small and
clean as possible, so that they can audit it. Mkhtemp
assumes that your system is hostile, and is designed
accordingly.
What?
-----
This is the utility version, which makes use of the also-
included library. No docs yet - source code are the docs,
and the (ever evolving, and hardening) specification.
This was written from scratch, for use in nvmutil, and
it is designed to be portable (BSD, Linux). Patches
very much welcome.
Caution
-------
This is a new utility. Expect bugs.
```
WARNING: This is MUCH stricter than every other mktemp
implementation, even more so than mkdtemp or
the OpenBSD version of mkstemp. It *will* break,
or more specifically, reveal the flaws in, almost
every major critical infrastructure, because most
people already use mktemp extremely insecurely.
```
This tool is written by me, for me, and also Libreboot, but
it will be summitted for review to various Linux distros
and BSD projects once it has reached maturity.
### Why was this written?
Atomic writes were implemented in nvmutil (Libreboot's
Intel GbE NVM editor), but one element remained: the
program mktemp, itself, which has virtually no securitty
checks whatsoever. GNU and BSD implementations use
mkstemp now, which is a bit more secure, and they offer
additional hardening, but I wanted to be reasonably
assured that my GbE files were not being corrupted in
any way, and that naturally led to writing a hardened
tool. It was originally just going to be for nvmutil,
but then it became its own standard utility.
Existing implementations of mktemp just simply do not
have sufficient checks in place to prevent misuse. This
tool, mkhtemp, intentionally focuses on being secure
instead of easy. For individuals just running Linux on
their personal machine, it might not make much difference,
but corporations and projects running computers for lots
of big infrastructure need something reliable, since
mktemp is just one of those things everyone uses.
Every big program needs to make temporary files.
But the real reason I wrote this tool is because, it's
fun, and because I wanted to challenge myself.
Roadmap
-------
Some things that are in the near future for mkhtemp
development:
Thoroughly document every known case of CVEs in the wild,
and major attacks against individuals/projects/corporations
that were made possible by mktemp - that mkhtemp might
have prevented. There are several.
More hardening; still a lot more that can be done, depending
on OS. E.g. integrate FreeBSD capsicum.
Another example: although usually reliable, comparing the
inode and device of a file/directory isn't by itself sufficient.
There are other checks that mkhtemp does; for example I could
implement it so that directories are more aggressively re-
opened by mkhtemp itself, mid-operation. This re-opening
would be quite expensive computationally, but it would then
allow us to re-check everything, since we store state from
when the program starts.
Tidy up the code: the current code was thrown together in
a week, and needs tidying. A proper specification should be
written, to define how it works, and then the code should
be auditted for compliance. A lot of the functions are
also quite complex and do a lot; they could be split up.
Right now, mkhtemp mainly returns a file descriptor and
a path, after operation, ironic given the methods it uses
while opening your file/dir. After it's done, you then have
to handle everything again. Mkhtemp could keep everything
open instead, and continue to provide verification; in
other words, it could provide a completely unified way for
Linux/BSD programs to open files, write to them atomically,
and close. Programs like Vim will do this for example, or
other text editors, but every program has its own way. So
what mkhtemp could do is provide a well-defined API alongside
its mktemp hardening. Efforts would be made to avoid
feature creep, and ensure that the code remains small and
nimble.
Compatibility mode: another thing is that mkhtemp is a bit
too strict for some users, so it may break some setups. What
it could do is provide a compatibility mode, and in this
mode, behave like regular mktemp. That way, it could become
a drop-in replacement on Linux distros (and BSDs if they
want it), while providing a more hardened version and
recommending that where possible.
~~Rewrite it in rust~~ (nothing against it though, I just like C99 for some reason)
Also, generally document the history of mktemp, and how
mkhtemp works in comparison.
Also a manpage.
Once all this is done, and the project is fully polished,
then it will be ready for your Linux distro. For now, I
just use it in nvmutil (and I also use it on my personal
computer).
-607
View File
@@ -1,607 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
TODO: this file should be split, into headers for each
C source file specifically. it was originally just
for nvmutil, until i added mkhtemp to the mix
*/
#ifndef COMMON_H
#define COMMON_H
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
/* dangerously cool macros:
*/
#define SUCCESS(x) ((x) >= 0)
/* syscalls can set errno even on success; this
* is rare, but permitted. in various functions, we
* reset errno on success, to what the caller had,
* but we must still honour what was returned.
*
* lib/file.c is littered with examples
*/
#define reset_caller_errno(return_value) \
do { \
if (SUCCESS(return_value) && (!errno)) \
errno = saved_errno; \
} while (0)
#define items(x) (sizeof((x)) / sizeof((x)[0]))
#define MKHTEMP_RETRY_MAX 512
#define MKHTEMP_SPIN_THRESHOLD 32
#define MKHTEMP_FILE 0
#define MKHTEMP_DIR 1
/* if 1: on operations that
* check ownership, always
* permit root to access even
* if not the file/dir owner
*/
#ifndef ALLOW_ROOT_OVERRIDE
#define ALLOW_ROOT_OVERRIDE 0
#endif
/*
*/
#ifndef SSIZE_MAX
#define SSIZE_MAX ((ssize_t)(~((ssize_t)1 << (sizeof(ssize_t)*CHAR_BIT-1))))
#endif
/* build config
*/
#ifndef NVMUTIL_H
#define NVMUTIL_H
#define MAX_CMD_LEN 50
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
#ifndef PATH_MAX
#error PATH_MAX_undefined
#elif ((PATH_MAX) < 1024)
#error PATH_MAX_too_low
#endif
#ifndef S_ISVTX
#define S_ISVTX 01000
#endif
#if defined(S_IFMT) && ((S_ISVTX & S_IFMT) != 0)
#error "Unexpected bit layout"
#endif
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef O_NOCTTY
#define O_NOCTTY 0
#endif
#ifndef O_ACCMODE
#define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR)
#endif
#ifndef O_BINARY
#define O_BINARY 0
#endif
#ifndef O_EXCL
#define O_EXCL 0
#endif
#ifndef O_CREAT
#define O_CREAT 0
#endif
#ifndef O_NONBLOCK
#define O_NONBLOCK 0
#endif
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
#ifndef O_NOFOLLOW
#define O_NOFOLLOW 0
#endif
#ifndef FD_CLOEXEC
#define FD_CLOEXEC 0
#endif
/* Sizes in bytes:
*/
#define SIZE_1KB 1024
#define SIZE_4KB (4 * SIZE_1KB)
#define SIZE_8KB (8 * SIZE_1KB)
#define SIZE_16KB (16 * SIZE_1KB)
#define SIZE_128KB (128 * SIZE_1KB)
#define GBE_BUF_SIZE (SIZE_128KB)
/* First 128 bytes of gbe.bin is NVM.
* Then extended area. All of NVM must
* add up to BABA, truncated (LE)
*
* First 4KB of each half of the file
* contains NVM+extended.
*/
#define GBE_WORK_SIZE (SIZE_8KB)
#define GBE_PART_SIZE (GBE_WORK_SIZE >> 1)
#define NVM_CHECKSUM 0xBABA
#define NVM_SIZE 128
#define NVM_WORDS (NVM_SIZE >> 1)
#define NVM_CHECKSUM_WORD (NVM_WORDS - 1)
/* argc minimum (dispatch)
*/
#define ARGC_3 3
#define ARGC_4 4
/* For checking if an fd is a normal file.
* Portable for old Unix e.g. v7 (S_IFREG),
* 4.2BSD (S_IFMT), POSIX (S_ISREG).
*
* IFREG: assumed 0100000 (classic bitmask)
*/
#ifndef S_ISREG
#if defined(S_IFMT) && defined(S_IFREG)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#elif defined(S_IFREG)
#define S_ISREG(m) (((m) & S_IFREG) != 0)
#else
#error "can't determine types with stat()"
#endif
#endif
#define IO_READ 0
#define IO_WRITE 1
#define IO_PREAD 2
#define IO_PWRITE 3
/* for nvmutil commands
*/
#define CMD_DUMP 0
#define CMD_SETMAC 1
#define CMD_SWAP 2
#define CMD_COPY 3
#define CMD_CAT 4
#define CMD_CAT16 5
#define CMD_CAT128 6
#define ARG_NOPART 0
#define ARG_PART 1
#define SKIP_CHECKSUM_READ 0
#define CHECKSUM_READ 1
#define SKIP_CHECKSUM_WRITE 0
#define CHECKSUM_WRITE 1
/* command table
*/
typedef void (*func_t)(void);
struct commands {
size_t chk;
char *str;
func_t run;
int argc;
unsigned char arg_part;
unsigned char chksum_read;
unsigned char chksum_write;
size_t rw_size; /* within the 4KB GbE part */
int flags; /* e.g. O_RDWR or O_RDONLY */
};
/* mac address
*/
struct macaddr {
char *str; /* set to rmac, or argv string */
char rmac[18]; /* xx:xx:xx:xx:xx:xx */
unsigned short mac_buf[3];
};
/* gbe.bin and tmpfile
*/
struct xfile {
int gbe_fd;
struct stat gbe_st;
int tmp_fd;
struct stat tmp_st;
char *tname; /* path of tmp file */
char *fname; /* path of gbe file */
unsigned char *buf; /* work memory for files */
int io_err_gbe; /* intermediary write (verification) */
int io_err_gbe_bin; /* final write (real file) */
int rw_check_err_read[2];
int rw_check_partial_read[2];
int rw_check_bad_part[2];
int post_rw_checksum[2];
off_t gbe_file_size;
off_t gbe_tmp_size;
size_t part;
unsigned char part_modified[2];
unsigned char part_valid[2];
unsigned char real_buf[GBE_BUF_SIZE];
unsigned char bufcmp[GBE_BUF_SIZE]; /* compare gbe/tmp/reads */
unsigned char pad[GBE_WORK_SIZE]; /* the file that wouldn't die */
/* we later rename in-place, using old fd. renameat() */
int dirfd;
char *base;
char *tmpbase;
};
/* Command table, MAC address, files
*
* BE CAREFUL when editing this
* to ensure that you also update
* the tables in xstatus()
*/
struct xstate {
struct commands cmd[7];
struct macaddr mac;
struct xfile f;
size_t i; /* index to cmd[] for current command */
int no_cmd;
/* Cat commands set this.
the cat cmd helpers check it */
int cat;
};
struct filesystem {
int rootfd;
};
struct xstate *xstart(int argc, char *argv[]);
struct xstate *xstatus(void);
/* Sanitize command tables.
*/
void sanitize_command_list(void);
void sanitize_command_index(size_t c);
/* Argument handling (user input)
*/
void set_cmd(int argc, char *argv[]);
void set_cmd_args(int argc, char *argv[]);
size_t conv_argv_part_num(const char *part_str);
/* Prep files for reading
*/
void open_gbe_file(void);
int fd_verify_regular(int fd,
const struct stat *expected,
struct stat *out);
int fd_verify_identity(int fd,
const struct stat *expected,
struct stat *out);
int fd_verify_dir_identity(int fd,
const struct stat *expected);
int is_owner(struct stat *st);
int lock_file(int fd, int flags);
int same_file(int fd, struct stat *st_old, int check_size);
/* Read GbE file and verify checksums
*/
void copy_gbe(void);
void read_file(void);
void read_checksums(void);
int good_checksum(size_t partnum);
/* validate commands
*/
void check_command_num(size_t c);
unsigned char valid_command(size_t c);
/* Helper functions for command: setmac
*/
void cmd_helper_setmac(void);
void parse_mac_string(void);
void set_mac_byte(size_t mac_byte_pos);
void set_mac_nib(size_t mac_str_pos,
size_t mac_byte_pos, size_t mac_nib_pos);
void write_mac_part(size_t partnum);
/* string functions
*/
size_t page_remain(const void *p);
long pagesize(void);
char *smalloc(char **buf, size_t size);
void *vmalloc(void **buf, size_t size);
size_t slen(const char *scmp, size_t maxlen,
size_t *rval);
int vcmp(const void *s1, const void *s2, size_t n);
int scmp(const char *a, const char *b,
size_t maxlen, int *rval);
int ccmp(const char *a, const char *b, size_t i,
int *rval);
int dup_pair(char **dir, const char *d,
char **base, const char *b);
char *sdup(const char *s,
size_t n, char **dest);
char *scatn(ssize_t sc, const char **sv,
size_t max, char **rval);
char *scat(const char *s1, const char *s2,
size_t n, char **dest);
void dcat(const char *s, size_t n,
size_t off, char **dest1,
char **dest2);
/* numerical functions
*/
unsigned short hextonum(char ch_s);
void spew_hex(const void *data, size_t len);
void *rmalloc(size_t n);
void rset(void *buf, size_t n);
void *rmalloc(size_t n);
char *rchars(size_t n);
size_t rsize(size_t n);
/* Helper functions for command: dump
*/
void cmd_helper_dump(void);
void print_mac_from_nvm(size_t partnum);
/* Helper functions for command: swap
*/
void cmd_helper_swap(void);
/* Helper functions for command: copy
*/
void cmd_helper_copy(void);
/* Helper functions for commands:
* cat, cat16 and cat128
*/
void cmd_helper_cat(void);
void cmd_helper_cat16(void);
void cmd_helper_cat128(void);
void cat(size_t nff);
void cat_buf(unsigned char *b);
/* Command verification/control
*/
void check_cmd(void (*fn)(void), const char *name);
void cmd_helper_err(void);
/* Write GbE files to disk
*/
void write_gbe_file(void);
void set_checksum(size_t part);
unsigned short calculated_checksum(size_t p);
/* NVM read/write
*/
unsigned short nvm_word(size_t pos16, size_t part);
void set_nvm_word(size_t pos16,
size_t part, unsigned short val16);
void set_part_modified(size_t p);
void check_nvm_bound(size_t pos16, size_t part);
void check_bin(size_t a, const char *a_name);
/* GbE file read/write
*/
void rw_gbe_file_part(size_t p, int rw_type,
const char *rw_type_str);
void write_to_gbe_bin(void);
int gbe_mv(void);
void check_written_part(size_t p);
void report_io_err_rw(void);
unsigned char *gbe_mem_offset(size_t part, const char *f_op);
off_t gbe_file_offset(size_t part, const char *f_op);
off_t gbe_x_offset(size_t part, const char *f_op,
const char *d_type, off_t nsize, off_t ncmp);
ssize_t rw_gbe_file_exact(int fd, unsigned char *mem, size_t nrw,
off_t off, int rw_type);
/* Generic read/write
*/
int fsync_dir(const char *path);
ssize_t rw_exact(int fd, unsigned char *mem, size_t len,
off_t off, int rw_type);
ssize_t rw(int fd, void *mem, size_t nrw,
off_t off, int rw_type);
int io_args(int fd, void *mem, size_t nrw,
off_t off, int rw_type);
int check_file(int fd, struct stat *st);
ssize_t rw_over_nrw(ssize_t r, size_t nrw);
int sys_retry(int saved_errno, long rval);
int fs_retry(int saved_errno, int rval);
int rw_retry(int saved_errno, ssize_t rval);
/* Error handling and cleanup
*/
void usage(void);
int with_fallback_errno(int fallback);
void exitf(const char *msg, ...);
func_t errhook(func_t ptr); /* hook function for cleanup on err */
const char *lbgetprogname(void);
void no_op(void);
void err_mkhtemp(int errval, const char *msg, ...);
/* libc hardening
*/
int new_tmpfile(int *fd, char **path, char *tmpdir,
const char *template);
int new_tmpdir(int *fd, char **path, char *tmpdir,
const char *template);
int new_tmp_common(int *fd, char **path, int type,
char *tmpdir, const char *template);
int mkhtemp_try_create(int dirfd,
struct stat *st_dir_first,
char *fname_copy,
char *p,
size_t xc,
int *fd,
struct stat *st,
int type);
int
mkhtemp_tmpfile_linux(int dirfd,
struct stat *st_dir_first,
char *fname_copy,
char *p,
size_t xc,
int *fd,
struct stat *st);
int mkhtemp(int *fd, struct stat *st,
char *template, int dirfd, const char *fname,
struct stat *st_dir_first, int type);
int world_writeable_and_sticky(const char *s,
int sticky_allowed, int always_sticky);
int same_dir(const char *a, const char *b);
int tmpdir_policy(const char *path,
int *allow_noworld_unsticky);
char *env_tmpdir(int always_sticky, char **tmpdir,
char *override_tmpdir);
int secure_file(int *fd,
struct stat *st,
struct stat *expected,
int bad_flags,
int check_seek,
int do_lock,
mode_t mode);
void xclose(int *fd);
int fsync_on_eintr(int fd);
int fs_rename_at(int olddirfd, const char *old,
int newdirfd, const char *new);
int fs_open(const char *path, int flags);
void free_and_set_null(char **buf);
void open_file_on_eintr(const char *path, int *fd, int flags, mode_t mode,
struct stat *st);
struct filesystem *rootfs(void);
int fs_resolve_at(int dirfd, const char *path, int flags);
int fs_next_component(const char **p,
char *name, size_t namesz);
int fs_open_component(int dirfd, const char *name,
int flags, int is_last);
int fs_dirname_basename(const char *path,
char **dir, char **base, int allow_relative);
int openat_on_eintr(int dirfd, const char *path,
int flags, mode_t mode);
int mkdirat_on_eintr(int dirfd,
const char *pathname, mode_t mode);
int if_err(int condition, int errval);
int if_err_sys(int condition);
char *lbsetprogname(char *argv0);
/* asserts */
/* type asserts */
typedef char static_assert_char_is_8_bits[(CHAR_BIT == 8) ? 1 : -1];
typedef char static_assert_char_is_1[(sizeof(char) == 1) ? 1 : -1];
typedef char static_assert_unsigned_char_is_1[
(sizeof(unsigned char) == 1) ? 1 : -1];
typedef char static_assert_unsigned_short_is_2[
(sizeof(unsigned short) >= 2) ? 1 : -1];
typedef char static_assert_short_is_2[(sizeof(short) >= 2) ? 1 : -1];
typedef char static_assert_unsigned_int_is_4[
(sizeof(unsigned int) >= 4) ? 1 : -1];
typedef char static_assert_unsigned_ssize_t_is_4[
(sizeof(size_t) >= 4) ? 1 : -1];
typedef char static_assert_ssize_t_ussize_t[
(sizeof(size_t) == sizeof(ssize_t)) ? 1 : -1];
typedef char static_assert_int_ge_32[(sizeof(int) >= 4) ? 1 : -1];
typedef char static_assert_twos_complement[
((-1 & 3) == 3) ? 1 : -1
];
typedef char assert_unsigned_ssize_t_ptr[
(sizeof(size_t) >= sizeof(void *)) ? 1 : -1
];
/*
* We set _FILE_OFFSET_BITS 64, but we only handle
* but we only need smaller files, so require 4-bytes.
* Some operating systems ignore the define, hence assert:
*/
typedef char static_assert_off_t_is_32[(sizeof(off_t) >= 4) ? 1 : -1];
/*
* asserts (variables/defines sanity check)
*/
typedef char assert_argc3[(ARGC_3==3)?1:-1];
typedef char assert_argc4[(ARGC_4==4)?1:-1];
typedef char assert_read[(IO_READ==0)?1:-1];
typedef char assert_write[(IO_WRITE==1)?1:-1];
typedef char assert_pread[(IO_PREAD==2)?1:-1];
typedef char assert_pwrite[(IO_PWRITE==3)?1:-1];
typedef char assert_pathlen[(PATH_MAX>=1024)?1:-1];
/* commands */
typedef char assert_cmd_dump[(CMD_DUMP==0)?1:-1];
typedef char assert_cmd_setmac[(CMD_SETMAC==1)?1:-1];
typedef char assert_cmd_swap[(CMD_SWAP==2)?1:-1];
typedef char assert_cmd_copy[(CMD_COPY==3)?1:-1];
typedef char assert_cmd_cat[(CMD_CAT==4)?1:-1];
typedef char assert_cmd_cat16[(CMD_CAT16==5)?1:-1];
typedef char assert_cmd_cat128[(CMD_CAT128==6)?1:-1];
/* bool */
typedef char bool_arg_nopart[(ARG_NOPART==0)?1:-1];
typedef char bool_arg_part[(ARG_PART==1)?1:-1];
typedef char bool_skip_checksum_read[(SKIP_CHECKSUM_READ==0)?1:-1];
typedef char bool_checksum_read[(CHECKSUM_READ==1)?1:-1];
typedef char bool_skip_checksum_write[(SKIP_CHECKSUM_WRITE==0)?1:-1];
typedef char bool_checksum_write[(CHECKSUM_WRITE==1)?1:-1];
#endif
#endif
-108
View File
@@ -1,108 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
*
* Functions related to GbE NVM checksums.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include "../include/common.h"
void
read_checksums(void)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
size_t _p;
size_t _skip_part;
unsigned char _num_invalid;
unsigned char _max_invalid;
f->part_valid[0] = 0;
f->part_valid[1] = 0;
if (!cmd->chksum_read)
return;
_num_invalid = 0;
_max_invalid = 2;
if (cmd->arg_part)
_max_invalid = 1;
/* Skip verification on this part,
* but only when arg_part is set.
*/
_skip_part = f->part ^ 1;
for (_p = 0; _p < 2; _p++) {
/* Only verify a part if it was *read*
*/
if (cmd->arg_part && (_p == _skip_part))
continue;
f->part_valid[_p] = good_checksum(_p);
if (!f->part_valid[_p])
++_num_invalid;
}
if (_num_invalid >= _max_invalid) {
if (_max_invalid == 1)
exitf("%s: part %lu has a bad checksum",
f->fname, (size_t)f->part);
exitf("%s: No valid checksum found in file",
f->fname);
}
}
int
good_checksum(size_t partnum)
{
unsigned short expected_checksum;
unsigned short actual_checksum;
expected_checksum =
calculated_checksum(partnum);
actual_checksum =
nvm_word(NVM_CHECKSUM_WORD, partnum);
if (expected_checksum == actual_checksum) {
return 1;
} else {
return 0;
}
}
void
set_checksum(size_t p)
{
check_bin(p, "part number");
set_nvm_word(NVM_CHECKSUM_WORD, p, calculated_checksum(p));
}
unsigned short
calculated_checksum(size_t p)
{
size_t c;
unsigned int val16;
val16 = 0;
for (c = 0; c < NVM_CHECKSUM_WORD; c++)
val16 += (unsigned int)nvm_word(c, p);
return (unsigned short)((NVM_CHECKSUM - val16) & 0xffff);
}
-521
View File
@@ -1,521 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include "../include/common.h"
void
sanitize_command_list(void)
{
struct xstate *x = xstatus();
size_t c;
size_t num_commands;
num_commands = items(x->cmd);
for (c = 0; c < num_commands; c++)
sanitize_command_index(c);
}
void
sanitize_command_index(size_t c)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[c];
int _flag;
size_t gbe_rw_size;
size_t rval;
check_command_num(c);
if (cmd->argc < 3)
exitf("cmd index %lu: argc below 3, %d",
(size_t)c, cmd->argc);
if (cmd->str == NULL)
exitf("cmd index %lu: NULL str",
(size_t)c);
if (*cmd->str == '\0')
exitf("cmd index %lu: empty str",
(size_t)c);
if (slen(cmd->str, MAX_CMD_LEN +1, &rval) > MAX_CMD_LEN) {
exitf("cmd index %lu: str too long: %s",
(size_t)c, cmd->str);
}
if (cmd->run == NULL)
exitf("cmd index %lu: cmd ptr null",
(size_t)c);
check_bin(cmd->arg_part, "cmd.arg_part");
check_bin(cmd->chksum_read, "cmd.chksum_read");
check_bin(cmd->chksum_write, "cmd.chksum_write");
gbe_rw_size = cmd->rw_size;
switch (gbe_rw_size) {
case GBE_PART_SIZE:
case NVM_SIZE:
break;
default:
exitf("Unsupported rw_size: %lu",
(size_t)gbe_rw_size);
}
if (gbe_rw_size > GBE_PART_SIZE)
exitf("rw_size larger than GbE part: %lu",
(size_t)gbe_rw_size);
_flag = (cmd->flags & O_ACCMODE);
if (_flag != O_RDONLY &&
_flag != O_RDWR)
exitf("invalid cmd.flags setting");
}
void
set_cmd(int argc, char *argv[])
{
struct xstate *x = xstatus();
const char *cmd;
int rval;
size_t c;
for (c = 0; c < items(x->cmd); c++) {
cmd = x->cmd[c].str;
if (scmp(argv[2], cmd, MAX_CMD_LEN, &rval))
continue; /* not the right command */
/* valid command found */
if (argc >= x->cmd[c].argc) {
x->no_cmd = 0;
x->i = c; /* set command */
return;
}
exitf(
"Too few args on command '%s'", cmd);
}
x->no_cmd = 1;
}
void
set_cmd_args(int argc, char *argv[])
{
struct xstate *x = xstatus();
size_t i = x->i;
struct commands *cmd = &x->cmd[i];
struct xfile *f = &x->f;
if (!valid_command(i) || argc < 3)
usage();
if (x->no_cmd)
usage();
/* Maintainer bug
*/
if (cmd->arg_part && argc < 4)
exitf(
"arg_part set for command that needs argc4");
if (cmd->arg_part && i == CMD_SETMAC)
exitf(
"arg_part set on CMD_SETMAC");
if (i == CMD_SETMAC) {
if (argc >= 4)
x->mac.str = argv[3];
else
x->mac.str = x->mac.rmac;
} else if (cmd->arg_part) {
f->part = conv_argv_part_num(argv[3]);
}
}
size_t
conv_argv_part_num(const char *part_str)
{
unsigned char ch;
if (part_str[0] == '\0' || part_str[1] != '\0')
exitf("Partnum string '%s' wrong length", part_str);
/* char signedness is implementation-defined
*/
ch = (unsigned char)part_str[0];
if (ch < '0' || ch > '1')
exitf("Bad part number (%c)", ch);
return (size_t)(ch - '0');
}
void
check_command_num(size_t c)
{
if (!valid_command(c))
exitf("Invalid run_cmd arg: %lu",
(size_t)c);
}
unsigned char
valid_command(size_t c)
{
struct xstate *x = xstatus();
struct commands *cmd;
if (c >= items(x->cmd))
return 0;
cmd = &x->cmd[c];
if (c != cmd->chk)
exitf(
"Invalid cmd chk value (%lu) vs arg: %lu",
cmd->chk, c);
return 1;
}
void
cmd_helper_setmac(void)
{
struct xstate *x = xstatus();
struct macaddr *mac = &x->mac;
size_t partnum;
check_cmd(cmd_helper_setmac, "setmac");
printf("MAC address to be written: %s\n", mac->str);
parse_mac_string();
for (partnum = 0; partnum < 2; partnum++)
write_mac_part(partnum);
}
void
parse_mac_string(void)
{
struct xstate *x = xstatus();
struct macaddr *mac = &x->mac;
size_t mac_byte;
size_t rval;
if (slen(x->mac.str, 18, &rval) != 17)
exitf("MAC address is the wrong length");
memset(mac->mac_buf, 0, sizeof(mac->mac_buf));
for (mac_byte = 0; mac_byte < 6; mac_byte++)
set_mac_byte(mac_byte);
if ((mac->mac_buf[0] | mac->mac_buf[1] | mac->mac_buf[2]) == 0)
exitf("Must not specify all-zeroes MAC address");
if (mac->mac_buf[0] & 1)
exitf("Must not specify multicast MAC address");
}
void
set_mac_byte(size_t mac_byte_pos)
{
struct xstate *x = xstatus();
struct macaddr *mac = &x->mac;
char separator;
size_t mac_str_pos;
size_t mac_nib_pos;
mac_str_pos = mac_byte_pos * 3;
if (mac_str_pos < 15) {
if ((separator = mac->str[mac_str_pos + 2]) != ':')
exitf("Invalid MAC address separator '%c'",
separator);
}
for (mac_nib_pos = 0; mac_nib_pos < 2; mac_nib_pos++)
set_mac_nib(mac_str_pos, mac_byte_pos, mac_nib_pos);
}
void
set_mac_nib(size_t mac_str_pos,
size_t mac_byte_pos, size_t mac_nib_pos)
{
struct xstate *x = xstatus();
struct macaddr *mac = &x->mac;
char mac_ch;
unsigned short hex_num;
mac_ch = mac->str[mac_str_pos + mac_nib_pos];
if ((hex_num = hextonum(mac_ch)) > 15) {
if (hex_num >= 17)
exitf("Randomisation failure");
else
exitf("Invalid character '%c'",
mac->str[mac_str_pos + mac_nib_pos]);
}
/* If random, ensure that local/unicast bits are set.
*/
if ((mac_byte_pos == 0) && (mac_nib_pos == 1) &&
((mac_ch | 0x20) == 'x' ||
(mac_ch == '?')))
hex_num = (hex_num & 0xE) | 2; /* local, unicast */
/* MAC words stored big endian in-file, little-endian
* logically, so we reverse the order.
*/
mac->mac_buf[mac_byte_pos >> 1] |= hex_num <<
(((mac_byte_pos & 1) << 3) /* left or right byte? */
| ((mac_nib_pos ^ 1) << 2)); /* left or right nib? */
}
void
write_mac_part(size_t partnum)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
struct macaddr *mac = &x->mac;
size_t w;
check_bin(partnum, "part number");
if (!f->part_valid[partnum])
return;
for (w = 0; w < 3; w++)
set_nvm_word(w, partnum, mac->mac_buf[w]);
printf("Wrote MAC address to part %lu: ",
(size_t)partnum);
print_mac_from_nvm(partnum);
}
void
cmd_helper_dump(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
size_t p;
check_cmd(cmd_helper_dump, "dump");
f->part_valid[0] = good_checksum(0);
f->part_valid[1] = good_checksum(1);
for (p = 0; p < 2; p++) {
if (!f->part_valid[p]) {
fprintf(stderr,
"BAD checksum %04x in part %lu (expected %04x)\n",
nvm_word(NVM_CHECKSUM_WORD, p),
(size_t)p,
calculated_checksum(p));
}
printf("MAC (part %lu): ",
(size_t)p);
print_mac_from_nvm(p);
spew_hex(f->buf + (p * GBE_PART_SIZE), NVM_SIZE);
}
}
void
print_mac_from_nvm(size_t partnum)
{
size_t c;
unsigned short val16;
for (c = 0; c < 3; c++) {
val16 = nvm_word(c, partnum);
printf("%02x:%02x",
(unsigned int)(val16 & 0xff),
(unsigned int)(val16 >> 8));
if (c == 2)
printf("\n");
else
printf(":");
}
}
void
cmd_helper_swap(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
check_cmd(cmd_helper_swap, "swap");
memcpy(
f->buf + (size_t)GBE_WORK_SIZE,
f->buf,
GBE_PART_SIZE);
memcpy(
f->buf,
f->buf + (size_t)GBE_PART_SIZE,
GBE_PART_SIZE);
memcpy(
f->buf + (size_t)GBE_PART_SIZE,
f->buf + (size_t)GBE_WORK_SIZE,
GBE_PART_SIZE);
set_part_modified(0);
set_part_modified(1);
}
void
cmd_helper_copy(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
check_cmd(cmd_helper_copy, "copy");
memcpy(
f->buf + (size_t)((f->part ^ 1) * GBE_PART_SIZE),
f->buf + (size_t)(f->part * GBE_PART_SIZE),
GBE_PART_SIZE);
set_part_modified(f->part ^ 1);
}
void
cmd_helper_cat(void)
{
struct xstate *x = xstatus();
check_cmd(cmd_helper_cat, "cat");
x->cat = 0;
cat(0);
}
void
cmd_helper_cat16(void)
{
struct xstate *x = xstatus();
check_cmd(cmd_helper_cat16, "cat16");
x->cat = 1;
cat(1);
}
void
cmd_helper_cat128(void)
{
struct xstate *x = xstatus();
check_cmd(cmd_helper_cat128, "cat128");
x->cat = 15;
cat(15);
}
void
cat(size_t nff)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
size_t p;
size_t ff;
p = 0;
ff = 0;
if ((size_t)x->cat != nff) {
exitf("erroneous call to cat");
}
fflush(NULL);
memset(f->pad, 0xff, GBE_PART_SIZE);
for (p = 0; p < 2; p++) {
cat_buf(f->bufcmp +
(size_t)(p * (f->gbe_file_size >> 1)));
for (ff = 0; ff < nff; ff++) {
cat_buf(f->pad);
}
}
}
void
cat_buf(unsigned char *b)
{
if (b == NULL)
exitf("null pointer in cat command");
if (rw_exact(STDOUT_FILENO, b,
GBE_PART_SIZE, 0, IO_WRITE) < 0)
exitf("stdout: cat");
}
void
check_cmd(void (*fn)(void),
const char *name)
{
struct xstate *x = xstatus();
size_t i = x->i;
if (x->cmd[i].run != fn)
exitf("Running %s, but cmd %s is set",
name, x->cmd[i].str);
/* prevent second command
*/
for (i = 0; i < items(x->cmd); i++)
x->cmd[i].run = cmd_helper_err;
}
void
cmd_helper_err(void)
{
exitf(
"Erroneously running command twice");
}
-817
View File
@@ -1,817 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* Pathless i/o, and some stuff you
* probably never saw in userspace.
*
* Be nice to the demon.
*/
/*
TODO: putting it here just so it's somewhere:
PATH_MAX is not reliable as a limit for paths,
because the real length depends on mount point,
and specific file systems.
more correct usage example:
long max = pathconf("/", _PC_PATH_MAX);
*/
/* for openat2: */
#ifdef __linux__
#if !defined(USE_OPENAT) || \
((USE_OPENAT) < 1) /* if 1: use openat, not openat2 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <linux/openat2.h>
#include <sys/syscall.h>
#endif
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../include/common.h"
/* check that a file changed
*/
int
same_file(int fd, struct stat *st_old,
int check_size)
{
struct stat st;
int saved_errno = errno;
int rval = 0;
errno = 0;
if (if_err(st_old == NULL, EFAULT) ||
if_err(fd < 0, EBADF) ||
(rval = fstat(fd, &st)) < 0 ||
(rval = fd_verify_regular(fd, st_old, &st)) < 0 ||
if_err(check_size && st.st_size != st_old->st_size, ESTALE))
return with_fallback_errno(ESTALE);
reset_caller_errno(rval);
return 0;
}
int
fsync_dir(const char *path)
{
int saved_errno = errno;
size_t pathlen = 0;
char *dirbuf = NULL;
int dirfd = -1;
char *slash = NULL;
struct stat st = {0};
int rval = 0;
errno = 0;
if (if_err(slen(path, PATH_MAX, &pathlen) == 0, EINVAL))
goto err_fsync_dir;
memcpy(smalloc(&dirbuf, pathlen + 1),
path, pathlen + 1);
slash = strrchr(dirbuf, '/');
if (slash != NULL) {
*slash = '\0';
if (*dirbuf == '\0') {
dirbuf[0] = '/';
dirbuf[1] = '\0';
}
} else {
dirbuf[0] = '.';
dirbuf[1] = '\0';
}
dirfd = fs_open(dirbuf,
O_RDONLY | O_CLOEXEC | O_NOCTTY
#ifdef O_DIRECTORY
| O_DIRECTORY
#endif
#ifdef O_NOFOLLOW
| O_NOFOLLOW
#endif
);
if (if_err_sys(dirfd < 0) ||
if_err_sys((rval = fstat(dirfd, &st)) < 0) ||
if_err(!S_ISDIR(st.st_mode), ENOTDIR)
||
if_err_sys((rval = fsync_on_eintr(dirfd)) == -1))
goto err_fsync_dir;
xclose(&dirfd);
free_and_set_null(&dirbuf);
reset_caller_errno(rval);
return 0;
err_fsync_dir:
free_and_set_null(&dirbuf);
xclose(&dirfd);
return with_fallback_errno(EIO);
}
/* rw_exact() - Read perfectly or die
*
* Read/write, and absolutely insist on an
* absolute read; e.g. if 100 bytes are
* requested, this MUST return 100.
*
* This function will never return zero.
* It will only return below (error),
* or above (success). On error, -1 is
* returned and errno is set accordingly.
*
* Zero-byte returns are not allowed.
* It will re-spin a finite number of
* times upon zero-return, to recover,
* otherwise it will return an error.
*/
ssize_t
rw_exact(int fd, unsigned char *mem, size_t nrw,
off_t off, int rw_type)
{
int saved_errno = errno;
ssize_t rval = 0;
ssize_t rc = 0;
size_t nrw_cur;
off_t off_cur;
void *mem_cur;
errno = 0;
if (io_args(fd, mem, nrw, off, rw_type) == -1)
goto err_rw_exact;
while (1) {
/* Prevent theoretical overflow */
if (if_err(rval >= 0 && (size_t)rval > (nrw - (size_t)rc),
EOVERFLOW))
goto err_rw_exact;
rc += rval;
if ((size_t)rc >= nrw)
break;
mem_cur = (void *)(mem + (size_t)rc);
nrw_cur = (size_t)(nrw - (size_t)rc);
if (if_err(off < 0, EOVERFLOW))
goto err_rw_exact;
off_cur = off + (off_t)rc;
if ((rval = rw(fd, mem_cur, nrw_cur, off_cur, rw_type)) <= 0)
goto err_rw_exact;
}
if (if_err((size_t)rc != nrw, EIO) ||
(rval = rw_over_nrw(rc, nrw)) < 0)
goto err_rw_exact;
reset_caller_errno(rval);
return rval;
err_rw_exact:
return with_fallback_errno(EIO);
}
/**
* rw() - read-write but with more
* safety checks than barebones libc
*
* A fallback is provided for regular read/write.
* rw_type can be IO_READ (read), IO_WRITE (write),
* IO_PREAD (pread) or IO_PWRITE
*
* WARNING: this function allows zero-byte returns.
* this is intentional, to mimic libc behaviour.
* use rw_exact if you need to avoid this.
* (ditto partial writes/reads)
*
*/
ssize_t
rw(int fd, void *mem, size_t nrw,
off_t off, int rw_type)
{
ssize_t rval = 0;
ssize_t r = -1;
int saved_errno = errno;
errno = 0;
if (io_args(fd, mem, nrw, off, rw_type) == -1 ||
if_err(mem == NULL, EFAULT) ||
if_err(fd < 0, EBADF) ||
if_err(off < 0, EFAULT) ||
if_err(nrw == 0, EINVAL))
return with_fallback_errno(EIO);
do {
switch (rw_type) {
case IO_READ:
r = read(fd, mem, nrw);
break;
case IO_WRITE:
r = write(fd, mem, nrw);
break;
case IO_PREAD:
r = pread(fd, mem, nrw, off);
break;
case IO_PWRITE:
r = pwrite(fd, mem, nrw, off);
break;
default:
errno = EINVAL;
break;
}
} while (rw_retry(saved_errno, r));
if ((rval = rw_over_nrw(r, nrw)) < 0)
return with_fallback_errno(EIO);
reset_caller_errno(rval);
return rval;
}
int
io_args(int fd, void *mem, size_t nrw,
off_t off, int rw_type)
{
int saved_errno = errno;
errno = 0;
if (if_err(mem == NULL, EFAULT) ||
if_err(fd < 0, EBADF) ||
if_err(off < 0, ERANGE) ||
if_err(!nrw, EPERM) || /* TODO: toggle zero-byte check */
if_err(nrw > (size_t)SSIZE_MAX, ERANGE) ||
if_err(((size_t)off + nrw) < (size_t)off, ERANGE) ||
if_err(rw_type > IO_PWRITE, EINVAL))
goto err_io_args;
reset_caller_errno(0);
return 0;
err_io_args:
return with_fallback_errno(EINVAL);
}
int
check_file(int fd, struct stat *st)
{
int saved_errno = errno;
int rval = 0;
errno = 0;
if (if_err(fd < 0, EBADF) ||
if_err(st == NULL, EFAULT) ||
((rval = fstat(fd, st)) == -1) ||
if_err(!S_ISREG(st->st_mode), EBADF))
goto err_is_file;
reset_caller_errno(rval);
return 0;
err_is_file:
return with_fallback_errno(EINVAL);
}
/* POSIX can say whatever it wants.
* specification != implementation
*/
ssize_t
rw_over_nrw(ssize_t r, size_t nrw)
{
if (if_err(!nrw, EIO) ||
(r == -1) ||
if_err((size_t)r > SSIZE_MAX, ERANGE) ||
if_err((size_t)r > nrw, ERANGE))
return with_fallback_errno(EIO);
return r;
}
/* two functions that reduce sloccount by
* two hundred lines */
int
if_err(int condition, int errval)
{
if (!condition)
return 0;
if (errval)
errno = errval;
return 1;
}
int
if_err_sys(int condition)
{
if (!condition)
return 0;
return 1;
}
int
fs_rename_at(int olddirfd, const char *old,
int newdirfd, const char *new)
{
if (if_err(new == NULL || old == NULL, EFAULT) ||
if_err(olddirfd < 0 || newdirfd < 0, EBADF))
return -1;
return renameat(olddirfd, old, newdirfd, new);
}
/* secure open, based on relative path to root
*
* always a fixed fd for / see: rootfs()
* and fs_resolve_at()
*/
int
fs_open(const char *path, int flags)
{
struct filesystem *fs;
if (if_err(path == NULL, EFAULT) ||
if_err(path[0] != '/', EINVAL) ||
if_err_sys((fs = rootfs()) == NULL))
return -1;
return fs_resolve_at(fs->rootfd, path + 1, flags);
}
/* singleton function that returns a fixed descriptor of /
* used throughout, for repeated integrity checks
*/
struct filesystem *
rootfs(void)
{
static struct filesystem global_fs;
static int fs_initialised = 0;
if (!fs_initialised) {
global_fs.rootfd = -1;
open_file_on_eintr("/", &global_fs.rootfd,
O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0400, NULL);
if (global_fs.rootfd < 0)
return NULL;
fs_initialised = 1;
}
return &global_fs;
}
/* filesystem sandboxing in userspace
* TODO:
missing length bound check.
potential CPU DoS on very long paths, spammed repeatedly.
perhaps cap at MAX_PATH?
*/
int
fs_resolve_at(int dirfd, const char *path, int flags)
{
int nextfd = -1;
int curfd;
const char *p;
char name[PATH_MAX];
int saved_errno = errno;
int r;
int is_last;
errno = 0;
if (dirfd < 0 || path == NULL || *path == '\0') {
errno = EINVAL;
return -1;
}
p = path;
curfd = dirfd; /* start here */
for (;;) {
r = fs_next_component(&p, name, sizeof(name));
if (r < 0)
goto err;
if (r == 0)
break;
is_last = (*p == '\0');
nextfd = fs_open_component(curfd, name, flags, is_last);
if (nextfd < 0)
goto err;
/* close previous fd if not the original input */
if (curfd != dirfd)
xclose(&curfd);
curfd = nextfd;
nextfd = -1;
}
reset_caller_errno(0);
return curfd;
err:
saved_errno = errno;
if (nextfd >= 0)
xclose(&nextfd);
/* close curfd only if it's not the original */
if (curfd != dirfd && curfd >= 0)
xclose(&curfd);
errno = saved_errno;
return with_fallback_errno(EIO);
}
/* NOTE:
rejects . and .. but not empty strings
after normalisation. edge case:
//////
normalised implicitly, but might be good
to add a defensive check regardless. code
probably not exploitable in current state.
*/
int
fs_next_component(const char **p,
char *name, size_t namesz)
{
const char *s = *p;
size_t len = 0;
while (*s == '/')
s++;
if (*s == '\0') {
*p = s;
return 0;
}
while (s[len] != '/' && s[len] != '\0')
len++;
if (len == 0 || len >= namesz ||
len >= PATH_MAX) {
errno = ENAMETOOLONG;
return -1;
}
memcpy(name, s, len);
name[len] = '\0';
/* reject . and .. */
if (if_err((name[0] == '.' && name[1] == '\0') ||
(name[0] == '.' && name[1] == '.' && name[2] == '\0'), EPERM))
goto err;
*p = s + len;
return 1;
err:
return with_fallback_errno(EPERM);
}
int
fs_open_component(int dirfd, const char *name,
int flags, int is_last)
{
int saved_errno = errno;
int fd;
struct stat st;
errno = 0;
fd = openat_on_eintr(dirfd, name,
(is_last ? flags : (O_RDONLY | O_DIRECTORY)) |
O_NOFOLLOW | O_CLOEXEC, (flags & O_CREAT) ? 0600 : 0);
if (!is_last &&
(if_err(fd < 0, EBADF) ||
if_err_sys(fstat(fd, &st) < 0) ||
if_err(!S_ISDIR(st.st_mode), ENOTDIR)))
return with_fallback_errno(EIO);
reset_caller_errno(fd);
return fd;
}
int
fs_dirname_basename(const char *path,
char **dir, char **base,
int allow_relative)
{
int saved_errno = errno;
char *buf = NULL;
char *slash;
size_t len;
const char *d = NULL;
const char *b = NULL;
errno = 0;
if (if_err(path == NULL || dir == NULL || base == NULL, EFAULT))
goto err;
slen(path, PATH_MAX, &len);
memcpy(smalloc(&buf, len + 1),
path, len + 1);
/* strip trailing slashes */
while (len > 1 && buf[len - 1] == '/')
buf[--len] = '\0';
slash = strrchr(buf, '/');
if (slash) {
*slash = '\0';
d = buf;
b = slash + 1;
if (*d == '\0')
d = "/";
} else if (allow_relative) {
d = ".";
b = buf;
} else {
free_and_set_null(&buf);
goto err;
}
if (dup_pair(dir, d, base, b) < 0) {
free_and_set_null(&buf);
goto err;
}
free_and_set_null(&buf);
reset_caller_errno(0);
return 0;
err:
return with_fallback_errno(EINVAL);
}
/* TODO: why does this abort, but others
e.g. open_file_on_eintr, don't???
*/
void
open_file_on_eintr(const char *path,
int *fd, int flags, mode_t mode,
struct stat *st)
{
int saved_errno = errno;
int rval = 0;
errno = 0;
if (path == NULL)
exitf("open_file_on_eintr: null path");
if (fd == NULL)
exitf("%s: open_file_on_eintr: null fd ptr", path);
if (*fd >= 0)
exitf(
"%s: open_file_on_eintr: file already open", path);
errno = 0;
while (fs_retry(saved_errno,
rval = open(path, flags, mode)));
if (rval < 0)
exitf(
"%s: open_file_on_eintr: could not close", path);
reset_caller_errno(rval);
*fd = rval;
/* we don't care about edge case behaviour here,
even if the next operation sets errno on success,
because the open() call is our main concern.
however, we also must preserve the new errno,
assuming it changed above under the same edge case */
saved_errno = errno;
if (st != NULL) {
if (fstat(*fd, st) < 0)
exitf("%s: stat", path);
if (!S_ISREG(st->st_mode))
exitf("%s: not a regular file", path);
}
if (lseek(*fd, 0, SEEK_CUR) == (off_t)-1)
exitf("%s: file not seekable", path);
errno = saved_errno; /* see previous comment */
}
#if defined(__linux__) && \
(!defined(USE_OPENAT) || ((USE_OPENAT) < 1)) /* we use openat2 on linux */
int
openat_on_eintr(int dirfd, const char *path,
int flags, mode_t mode)
{
struct open_how how = {
.flags = (unsigned long long)flags,
.mode = mode,
.resolve =
RESOLVE_BENEATH |
RESOLVE_NO_SYMLINKS |
RESOLVE_NO_MAGICLINKS
};
int saved_errno = errno;
long rval = 0;
errno = 0;
if (if_err(dirfd < 0, EBADF) ||
if_err(path == NULL, EFAULT))
goto err;
errno = 0;
while (sys_retry(saved_errno,
rval = syscall(SYS_openat2, dirfd, path, &how, sizeof(how))));
if (rval == -1) /* avoid long->int UB for -1 */
goto err;
reset_caller_errno(rval);
return (int)rval;
err:
return with_fallback_errno(EIO); /* -1 */
}
#else /* regular openat on non-linux e.g. openbsd */
int
openat_on_eintr(int dirfd, const char *path,
int flags, mode_t mode)
{
int saved_errno = errno;
int rval = 0;
errno = 0;
if (if_err(dirfd < 0, EBADF) ||
if_err(path == NULL, EFAULT))
return with_fallback_errno(EIO);
while (fs_retry(saved_errno,
rval = openat(dirfd, path, flags, mode)));
reset_caller_errno(rval);
return rval;
}
#endif
int
mkdirat_on_eintr(int dirfd,
const char *path, mode_t mode)
{
int saved_errno = errno;
int rval = 0;
errno = 0;
if (if_err(dirfd < 0, EBADF) ||
if_err(path == NULL, EFAULT))
return with_fallback_errno(EIO);
while (fs_retry(saved_errno,
rval = mkdirat(dirfd, path, mode)));
reset_caller_errno(rval);
return rval;
}
int
fsync_on_eintr(int fd)
{
int saved_errno = errno;
int rval = 0;
errno = 0;
if (if_err(fd < 0, EBADF))
return with_fallback_errno(EIO);
while (fs_retry(saved_errno,
rval = fsync(fd)));
reset_caller_errno(rval);
return rval;
}
void
xclose(int *fd)
{
int saved_errno = errno;
int rval = 0;
if (fd == NULL)
exitf("xclose: null pointer");
if (*fd < 0)
return;
/* nuance regarding EINTR on close():
* EINTR can be set on error, but there's
* no guarantee whether the fd is then still
* open or closed. on some other commands, we
* loop EINTR, but for close, we instead skip
* aborting *if the errno is EINTR* - so don't
* loop it, but do regard EINTR with rval -1
* as essenitally a successful close()
*/
/* because we don't want to mess with someone
* elses file if that fd is then reassigned.
* if the operation truly did fail, we ignore
* it. just leave it flying in the wind */
errno = 0;
if ((rval = close(*fd)) < 0) {
if (errno != EINTR)
exitf("xclose: could not close");
/* regard EINTR as a successful close */
rval = 0;
}
*fd = -1;
reset_caller_errno(rval);
}
/* unified eintr looping.
* differently typed functions
* to avoid potential UB
*
* ONE MACRO TO RULE THEM ALL:
*/
#define fs_err_retry() \
do { \
if ((rval == -1) && \
(errno == EINTR)) \
return 1; \
if (rval >= 0 && !errno) \
errno = saved_errno; \
return 0; \
} while(0)
/*
* Regarding the errno logic above:
* on success, it is permitted that
* a syscall could still set errno.
* We reset errno after storingit
* for later preservation, in functions
* that call *_retry() functions.
*
* They rely ultimately on this
* macro for errno restoration. We
* assume therefore that errno was
* reset to zero before the retry
* loop. If errno is then *set* on
* success, we leave it alone. Otherwise,
* we restore the caller's saved errno.
*
* This offers some consistency, while
* complying with POSIX specification.
*/
/* retry switch for functions that
return long status e.g. linux syscall
*/
int
sys_retry(int saved_errno, long rval)
{
fs_err_retry();
}
/* retry switch for functions that
return int status e.g. mkdirat
*/
int
fs_retry(int saved_errno, int rval)
{
fs_err_retry();
}
/* retry switch for functions that
return rw count in ssize_t e.g. read()
*/
int
rw_retry(int saved_errno, ssize_t rval)
{
fs_err_retry();
}
-563
View File
@@ -1,563 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* I/O functions specific to nvmutil.
*/
/* TODO: local tmpfiles not being deleted
when flags==O_RDONLY e.g. dump command
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../include/common.h"
void
open_gbe_file(void)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
int saved_errno = errno;
errno = 0;
int _flags;
f->gbe_fd = -1;
open_file_on_eintr(f->fname, &f->gbe_fd,
O_NOFOLLOW | O_CLOEXEC | O_NOCTTY,
((cmd->flags & O_ACCMODE) == O_RDONLY) ? 0400 : 0600,
&f->gbe_st);
if (f->gbe_st.st_nlink > 1)
exitf(
"%s: warning: file has multiple (%lu) hard links\n",
f->fname, (size_t)f->gbe_st.st_nlink);
if (f->gbe_st.st_nlink == 0)
exitf("%s: file unlinked while open", f->fname);
if ((_flags = fcntl(f->gbe_fd, F_GETFL)) == -1)
exitf("%s: fcntl(F_GETFL)", f->fname);
/* O_APPEND allows POSIX write() to ignore
* the current write offset and write at EOF,
* which would break positional read/write
*/
if (_flags & O_APPEND)
exitf("%s: O_APPEND flag", f->fname);
f->gbe_file_size = f->gbe_st.st_size;
switch (f->gbe_file_size) {
case SIZE_8KB:
case SIZE_16KB:
case SIZE_128KB:
break;
default:
exitf("File size must be 8KB, 16KB or 128KB");
}
/* currently fails (EBADF), locks are advisory anyway: */
/*
if (lock_file(f->gbe_fd, cmd->flags) == -1)
exitf("%s: can't lock", f->fname);
*/
reset_caller_errno(0);
}
void
copy_gbe(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
read_file();
if (f->gbe_file_size == SIZE_8KB)
return;
memcpy(f->buf + (size_t)GBE_PART_SIZE,
f->buf + (size_t)(f->gbe_file_size >> 1),
(size_t)GBE_PART_SIZE);
}
void
read_file(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
struct stat _st;
ssize_t _r;
/* read main file
*/
_r = rw_exact(f->gbe_fd, f->buf, f->gbe_file_size,
0, IO_PREAD);
if (_r < 0)
exitf("%s: read failed", f->fname);
/* copy to tmpfile
*/
_r = rw_exact(f->tmp_fd, f->buf, f->gbe_file_size,
0, IO_PWRITE);
if (_r < 0)
exitf("%s: %s: copy failed",
f->fname, f->tname);
/* file size comparison
*/
if (fstat(f->tmp_fd, &_st) == -1)
exitf("%s: stat", f->tname);
f->gbe_tmp_size = _st.st_size;
if (f->gbe_tmp_size != f->gbe_file_size)
exitf("%s: %s: not the same size",
f->fname, f->tname);
/* needs sync, for verification
*/
if (fsync_on_eintr(f->tmp_fd) == -1)
exitf("%s: fsync (tmpfile copy)", f->tname);
_r = rw_exact(f->tmp_fd, f->bufcmp, f->gbe_file_size,
0, IO_PREAD);
if (_r < 0)
exitf("%s: read failed (cmp)", f->tname);
if (vcmp(f->buf, f->bufcmp, f->gbe_file_size) != 0)
exitf("%s: %s: read contents differ (pre-test)",
f->fname, f->tname);
}
void
write_gbe_file(void)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
size_t p;
unsigned char update_checksum;
if ((cmd->flags & O_ACCMODE) == O_RDONLY)
return;
if (same_file(f->tmp_fd, &f->tmp_st, 0) < 0)
exitf("%s: file inode/device changed", f->tname);
if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0)
exitf("%s: file has changed", f->fname);
update_checksum = cmd->chksum_write;
for (p = 0; p < 2; p++) {
if (!f->part_modified[p])
continue;
if (update_checksum)
set_checksum(p);
rw_gbe_file_part(p, IO_PWRITE, "pwrite");
}
}
void
rw_gbe_file_part(size_t p, int rw_type,
const char *rw_type_str)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
ssize_t rval;
off_t file_offset;
size_t gbe_rw_size;
unsigned char *mem_offset;
gbe_rw_size = cmd->rw_size;
if (rw_type < IO_PREAD || rw_type > IO_PWRITE)
exitf("%s: %s: part %lu: invalid rw_type, %d",
f->fname, rw_type_str, (size_t)p, rw_type);
mem_offset = gbe_mem_offset(p, rw_type_str);
file_offset = (off_t)gbe_file_offset(p, rw_type_str);
rval = rw_gbe_file_exact(f->tmp_fd, mem_offset,
gbe_rw_size, file_offset, rw_type);
if (rval == -1)
exitf("%s: %s: part %lu",
f->fname, rw_type_str, (size_t)p);
if ((size_t)rval != gbe_rw_size)
exitf("%s: partial %s: part %lu",
f->fname, rw_type_str, (size_t)p);
}
void
write_to_gbe_bin(void)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
int saved_errno;
int mv;
if ((cmd->flags & O_ACCMODE) != O_RDWR)
return;
write_gbe_file();
/* We may otherwise read from
* cache, so we must sync.
*/
if (fsync_on_eintr(f->tmp_fd) == -1)
exitf("%s: fsync (pre-verification)",
f->tname);
check_written_part(0);
check_written_part(1);
report_io_err_rw();
if (f->io_err_gbe)
exitf("%s: bad write", f->fname);
saved_errno = errno;
xclose(&f->tmp_fd);
xclose(&f->gbe_fd);
errno = saved_errno;
/* tmpfile written, now we
* rename it back to the main file
* (we do atomic writes)
*/
f->tmp_fd = -1;
f->gbe_fd = -1;
if (!f->io_err_gbe_bin) {
mv = gbe_mv();
if (mv < 0) {
f->io_err_gbe_bin = 1;
fprintf(stderr, "%s: %s\n",
f->fname, strerror(errno));
} else {
/* removed by rename
*/
free_and_set_null(&f->tname);
}
}
if (!f->io_err_gbe_bin)
return;
fprintf(stderr, "FAIL (rename): %s: skipping fsync\n",
f->fname);
if (errno)
fprintf(stderr,
"errno %d: %s\n", errno, strerror(errno));
}
void
check_written_part(size_t p)
{
struct xstate *x = xstatus();
struct commands *cmd = &x->cmd[x->i];
struct xfile *f = &x->f;
ssize_t rval;
size_t gbe_rw_size;
off_t file_offset;
unsigned char *mem_offset;
unsigned char *buf_restore;
if (!f->part_modified[p])
return;
gbe_rw_size = cmd->rw_size;
mem_offset = gbe_mem_offset(p, "pwrite");
file_offset = (off_t)gbe_file_offset(p, "pwrite");
memset(f->pad, 0xff, sizeof(f->pad));
if (same_file(f->tmp_fd, &f->tmp_st, 0) < 0)
exitf("%s: file inode/device changed", f->tname);
if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0)
exitf("%s: file changed during write", f->fname);
rval = rw_gbe_file_exact(f->tmp_fd, f->pad,
gbe_rw_size, file_offset, IO_PREAD);
if (rval == -1)
f->rw_check_err_read[p] = f->io_err_gbe = 1;
else if ((size_t)rval != gbe_rw_size)
f->rw_check_partial_read[p] = f->io_err_gbe = 1;
else if (vcmp(mem_offset, f->pad, gbe_rw_size) != 0)
f->rw_check_bad_part[p] = f->io_err_gbe = 1;
if (f->rw_check_err_read[p] ||
f->rw_check_partial_read[p])
return;
/* We only load one part on-file, into memory but
* always at offset zero, for post-write checks.
* That's why we hardcode good_checksum(0)
*/
buf_restore = f->buf;
/* good_checksum works on f->buf
* so let's change f->buf for now
*/
f->buf = f->pad;
if (good_checksum(0))
f->post_rw_checksum[p] = 1;
f->buf = buf_restore;
}
void
report_io_err_rw(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
size_t p;
if (!f->io_err_gbe)
return;
for (p = 0; p < 2; p++) {
if (!f->part_modified[p])
continue;
if (f->rw_check_err_read[p])
fprintf(stderr,
"%s: pread: p%lu (post-verification)\n",
f->fname, (size_t)p);
if (f->rw_check_partial_read[p])
fprintf(stderr,
"%s: partial pread: p%lu (post-verification)\n",
f->fname, (size_t)p);
if (f->rw_check_bad_part[p])
fprintf(stderr,
"%s: pwrite: corrupt write on p%lu\n",
f->fname, (size_t)p);
if (f->rw_check_err_read[p] ||
f->rw_check_partial_read[p]) {
fprintf(stderr,
"%s: p%lu: skipped checksum verification "
"(because read failed)\n",
f->fname, (size_t)p);
continue;
}
fprintf(stderr, "%s: ", f->fname);
if (f->post_rw_checksum[p])
fprintf(stderr, "GOOD");
else
fprintf(stderr, "BAD");
fprintf(stderr, " checksum in p%lu on-disk.\n",
(size_t)p);
if (f->post_rw_checksum[p]) {
fprintf(stderr,
" This does NOT mean it's safe. it may be\n"
" salvageable if you use the cat feature.\n");
}
}
}
int
gbe_mv(void)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
int rval;
int saved_errno;
int tmp_gbe_bin_exists;
/* will be set 0 if it doesn't
*/
tmp_gbe_bin_exists = 1;
saved_errno = errno;
rval = fs_rename_at(f->dirfd, f->tmpbase,
f->dirfd, f->base);
if (rval > -1)
tmp_gbe_bin_exists = 0;
if (f->gbe_fd > -1) {
xclose(&f->gbe_fd);
if (fsync_dir(f->fname) < 0) {
f->io_err_gbe_bin = 1;
rval = -1;
}
}
xclose(&f->tmp_fd);
/* before this function is called,
* tmp_fd may have been moved
*/
if (tmp_gbe_bin_exists) {
if (unlink(f->tname) < 0)
rval = -1;
else
tmp_gbe_bin_exists = 0;
}
if (rval >= 0)
goto out;
return with_fallback_errno(EIO);
out:
reset_caller_errno(rval);
return rval;
}
/* This one is similar to gbe_file_offset,
* but used to check Gbe bounds in memory,
* and it is *also* used during file I/O.
*/
unsigned char *
gbe_mem_offset(size_t p, const char *f_op)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
off_t gbe_off;
gbe_off = gbe_x_offset(p, f_op, "mem",
GBE_PART_SIZE, GBE_WORK_SIZE);
return (unsigned char *)
(f->buf + (size_t)gbe_off);
}
/* I/O operations filtered here. These operations must
* only write from the 0th position or the half position
* within the GbE file, and write 4KB of data.
*/
off_t
gbe_file_offset(size_t p, const char *f_op)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
off_t gbe_file_half_size;
gbe_file_half_size = f->gbe_file_size >> 1;
return gbe_x_offset(p, f_op, "file",
gbe_file_half_size, f->gbe_file_size);
}
off_t
gbe_x_offset(size_t p, const char *f_op, const char *d_type,
off_t nsize, off_t ncmp)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
off_t off;
check_bin(p, "part number");
off = ((off_t)p) * (off_t)nsize;
if (off > ncmp - GBE_PART_SIZE)
exitf("%s: GbE %s %s out of bounds",
f->fname, d_type, f_op);
if (off != 0 && off != ncmp >> 1)
exitf("%s: GbE %s %s at bad offset",
f->fname, d_type, f_op);
return off;
}
ssize_t
rw_gbe_file_exact(int fd, unsigned char *mem, size_t nrw,
off_t off, int rw_type)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
ssize_t r;
if (io_args(fd, mem, nrw, off, rw_type) == -1)
return -1;
if (mem != (void *)f->pad) {
if (mem < f->buf)
goto err_rw_gbe_file_exact;
if ((size_t)(mem - f->buf) >= GBE_WORK_SIZE)
goto err_rw_gbe_file_exact;
}
if (off < 0 || off >= f->gbe_file_size)
goto err_rw_gbe_file_exact;
if (nrw > (size_t)(f->gbe_file_size - off))
goto err_rw_gbe_file_exact;
if (nrw > (size_t)GBE_PART_SIZE)
goto err_rw_gbe_file_exact;
r = rw_exact(fd, mem, nrw, off, rw_type);
return rw_over_nrw(r, nrw);
err_rw_gbe_file_exact:
return with_fallback_errno(EIO);
}
-914
View File
@@ -1,914 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* Hardened mktemp (be nice to the demon).
*/
/* for openat2 / fast path: */
#ifdef __linux__
#if !defined(USE_OPENAT) || \
((USE_OPENAT) < 1) /* if 1: use openat, not openat2 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <sys/syscall.h>
#include <linux/openat2.h>
#ifndef O_TMPFILE
#define O_TMPFILE 020000000
#endif
#ifndef AT_EMPTY_PATH
#define AT_EMPTY_PATH 0x1000
#endif
#endif
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../include/common.h"
/* note: tmpdir is an override of TMPDIR or /tmp or /var/tmp */
int
new_tmpfile(int *fd, char **path, char *tmpdir,
const char *template)
{
return new_tmp_common(fd, path, MKHTEMP_FILE,
tmpdir, template);
}
/* note: tmpdir is an override of TMPDIR or /tmp or /var/tmp */
int
new_tmpdir(int *fd, char **path, char *tmpdir,
const char *template)
{
return new_tmp_common(fd, path, MKHTEMP_DIR,
tmpdir, template);
}
int
new_tmp_common(int *fd, char **path, int type,
char *tmpdir, const char *template)
{
struct stat st;
const char *templatestr;
size_t dirlen;
char *dest = NULL; /* final path (will be written into "path") */
int saved_errno = errno;
int dirfd = -1;
const char *fname = NULL;
struct stat st_dir_first;
char *fail_dir = NULL;
errno = 0;
if (if_err(path == NULL || fd == NULL, EFAULT) ||
if_err(*fd >= 0, EEXIST)) /* don't touch someone else's file */
goto err;
/* regarding **path:
* the pointer (to the pointer)
* must nott be null, but we don't
* care about the pointer it points
* to. you should expect it to be
* replaced upon successful return
*
* (on error, it will not be touched)
*/
*fd = -1;
if (tmpdir == NULL) { /* no user override */
#if defined(PERMIT_NON_STICKY_ALWAYS) && \
((PERMIT_NON_STICKY_ALWAYS) > 0)
tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS, &fail_dir, NULL);
#else
tmpdir = env_tmpdir(0, &fail_dir, NULL);
#endif
} else {
#if defined(PERMIT_NON_STICKY_ALWAYS) && \
((PERMIT_NON_STICKY_ALWAYS) > 0)
tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS, &fail_dir,
tmpdir);
#else
tmpdir = env_tmpdir(0, &fail_dir, tmpdir);
#endif
}
if (if_err(tmpdir ==NULL || *tmpdir == '\0' || *tmpdir != '/', EINVAL))
goto err;
if (template != NULL)
templatestr = template;
else
templatestr = "tmp.XXXXXXXXXX";
/* may as well calculate in advance */
dirlen = slen(tmpdir, PATH_MAX, &dirlen);
/* full path: */
dest = scatn(3, (const char *[]) { tmpdir, "/", templatestr },
PATH_MAX, &dest);
fname = dest + dirlen + 1;
dirfd = fs_open(tmpdir,
O_RDONLY | O_DIRECTORY);
if (dirfd < 0)
goto err;
if (fstat(dirfd, &st_dir_first) < 0)
goto err;
*fd = mkhtemp(fd, &st, dest, dirfd,
fname, &st_dir_first, type);
if (*fd < 0)
goto err;
xclose(&dirfd);
errno = saved_errno;
*path = dest;
reset_caller_errno(0);
return 0;
err:
free_and_set_null(&dest);
xclose(&dirfd);
xclose(fd);
/* where a TMPDIR isn't found, and we err,
* we pass this back through for the
* error message
*/
if (fail_dir != NULL)
*path = fail_dir;
errno = saved_errno;
return with_fallback_errno(EIO);
}
/* hardened TMPDIR parsing
*/
char *
env_tmpdir(int bypass_all_sticky_checks, char **tmpdir,
char *override_tmpdir)
{
char *t = NULL;
int allow_noworld_unsticky;
int saved_errno = errno;
static const char tmp[] = "/tmp";
static const char vartmp[] = "/var/tmp";
char *rval = NULL;
errno = 0;
/* tmpdir is a user override, if set */
if (override_tmpdir == NULL)
t = getenv("TMPDIR");
else
t = override_tmpdir;
if (t != NULL && *t != '\0') {
if (tmpdir_policy(t,
&allow_noworld_unsticky) < 0)
goto err;
if (!world_writeable_and_sticky(t,
allow_noworld_unsticky,
bypass_all_sticky_checks))
goto err;
rval = NULL;
if (t != NULL) {
if (sdup(t, PATH_MAX, &rval) == NULL)
goto err;
}
goto out;
}
allow_noworld_unsticky = 0;
if (world_writeable_and_sticky(tmp, allow_noworld_unsticky,
bypass_all_sticky_checks))
rval = (char *)tmp;
else if (world_writeable_and_sticky(vartmp,
allow_noworld_unsticky, bypass_all_sticky_checks))
rval = (char *)vartmp;
else
goto err;
out:
reset_caller_errno(0);
if (tmpdir != NULL)
*tmpdir = rval;
return rval;
err:
if (tmpdir != NULL && t != NULL)
*tmpdir = t;
(void) with_fallback_errno(EPERM);
return NULL;
}
int
tmpdir_policy(const char *path,
int *allow_noworld_unsticky)
{
int saved_errno = errno;
int r;
errno = 0;
if (if_err(path == NULL ||
allow_noworld_unsticky == NULL, EFAULT))
goto err_tmpdir_policy;
*allow_noworld_unsticky = 1;
r = same_dir(path, "/tmp");
if (r < 0)
goto err_tmpdir_policy;
if (r > 0)
*allow_noworld_unsticky = 0;
r = same_dir(path, "/var/tmp");
if (r < 0)
goto err_tmpdir_policy;
if (r > 0)
*allow_noworld_unsticky = 0;
reset_caller_errno(0);
return 0;
err_tmpdir_policy:
return with_fallback_errno(EPERM);
}
int
same_dir(const char *a, const char *b)
{
int fd_a = -1;
int fd_b = -1;
struct stat st_a;
struct stat st_b;
int saved_errno = errno;
int rval = 0; /* LOGICAL error, 0, if 0 is returned */
errno = 0;
/* optimisation: if both dirs
are the same, we don't need
to check anything. sehr schnell!
*/
/* bonus: scmp checks null for us */
if (!scmp(a, b, PATH_MAX, &rval))
goto success_same_dir;
else
rval = 0; /* reset */
if ((fd_a = fs_open(a, O_RDONLY | O_DIRECTORY | O_NOFOLLOW)) < 0 ||
(fd_b = fs_open(b, O_RDONLY | O_DIRECTORY | O_NOFOLLOW)) < 0 ||
fstat(fd_a, &st_a) < 0 ||
fstat(fd_b, &st_b) < 0)
goto err_same_dir;
if (st_a.st_dev == st_b.st_dev &&
st_a.st_ino == st_b.st_ino) {
success_same_dir:
rval = 1; /* SUCCESS */
}
xclose(&fd_a);
xclose(&fd_b);
/* we reset caller errno regardless
* of success, so long as it's not
* a syscall error
*/
reset_caller_errno(0);
return rval;
err_same_dir:
/* FAILURE (probably syscall) - returns -1
*/
xclose(&fd_a);
xclose(&fd_b);
return with_fallback_errno(EIO); /* -1 */
}
/* bypass_all_sticky_checks: if set,
disable stickiness checks (libc behaviour)
(if not set: leah behaviour)
allow_noworld_unsticky:
allow non-sticky files if not world-writeable
(still block non-sticky in standard TMPDIR)
*/
int
world_writeable_and_sticky(
const char *s,
int allow_noworld_unsticky,
int bypass_all_sticky_checks)
{
struct stat st;
int dirfd = -1;
int saved_errno = errno;
errno = 0;
if (if_err(s == NULL || *s == '\0', EINVAL) ||
(dirfd = fs_open(s, O_RDONLY | O_DIRECTORY)) < 0 ||
fstat(dirfd, &st) < 0 ||
if_err(!S_ISDIR(st.st_mode), ENOTDIR))
goto sticky_hell;
/* *normal-**ish mode (libc):
*/
if (bypass_all_sticky_checks)
goto sticky_heaven; /* normal == no security */
/* extremely not-libc mode:
* only require stickiness on world-writeable dirs:
*/
if (st.st_mode & S_IWOTH) { /* world writeable */
if (if_err(!(st.st_mode & S_ISVTX), EPERM))
goto sticky_hell; /* not sticky */
goto sticky_heaven; /* sticky! */
} else if (allow_noworld_unsticky) {
goto sticky_heaven; /* sticky visa */
} else {
goto sticky_hell; /* visa denied */
}
sticky_heaven:
if (faccessat(dirfd, ".", X_OK, AT_EACCESS) < 0)
goto sticky_hell; /* down you go! */
xclose(&dirfd);
reset_caller_errno(0);
return 1;
sticky_hell:
xclose(&dirfd);
(void) with_fallback_errno(EPERM);
return 0;
}
/* mk(h)temp - hardened mktemp.
* like mkstemp, but (MUCH) harder.
*
* designed to resist TOCTOU attacks
* e.g. directory race / symlink attack
*
* extremely strict and even implements
* some limited userspace-level sandboxing,
* similar in spirit to openbsd unveil,
* though unveil is from kernel space.
*
* supports both files and directories.
* file: type = MKHTEMP_FILE (0)
* dir: type = MKHTEMP_DIR (1)
*
* DESIGN NOTES:
*
* caller is expected to handle
* cleanup e.g. free(), on *st,
* *template, *fname (all of the
* pointers). ditto fd cleanup.
*
* some limited cleanup is
* performed here, e.g. directory/file
* cleanup on error in mkhtemp_try_create
*
* we only check if these are not NULL,
* and the caller is expected to take
* care; without too many conditions,
* these functions are more flexible,
* but some precauttions are taken:
*
* when used via the function new_tmpfile
* or new_tmpdir, thtis is extremely strict,
* much stricter than previous mktemp
* variants. for example, it is much
* stricter about stickiness on world
* writeable directories, and it enforces
* file ownership under hardened mode
* (only lets you touch your own files/dirs)
*/
/*
TODO:
some variables e.g. template vs suffix,
assumes they match.
we should test this explicitly,
but the way this is called is
currently safe - this would however
be nice for future library use
by outside projects.
this whole code needs to be reorganised
*/
int
mkhtemp(int *fd,
struct stat *st,
char *template,
int dirfd,
const char *fname,
struct stat *st_dir_first,
int type)
{
size_t template_len = 0;
size_t xc = 0;
size_t fname_len = 0;
char *fname_copy = NULL;
char *p;
size_t retries;
int saved_errno = errno;
int r;
char *end;
errno = 0;
if (if_err(fd == NULL || template == NULL || fname == NULL ||
st_dir_first == NULL, EFAULT) ||
if_err(*fd >= 0, EEXIST) ||
if_err(dirfd < 0, EBADF))
goto err;
/* count X */
for (end = template + slen(template, PATH_MAX, &template_len);
end > template && *--end == 'X'; xc++);
fname_len = slen(fname, PATH_MAX, &fname_len);
if (if_err(strrchr(fname, '/') != NULL, EINVAL))
goto err;
if (if_err(xc < 3 || xc > template_len, EINVAL) ||
if_err(fname_len > template_len, EOVERFLOW))
goto err;
if (if_err(vcmp(fname, template + template_len - fname_len,
fname_len) != 0, EINVAL))
goto err;
/* fname_copy = templatestr region only; p points to trailing XXXXXX */
memcpy(smalloc(&fname_copy, fname_len + 1),
template + template_len - fname_len,
fname_len + 1);
p = fname_copy + fname_len - xc;
for (retries = 0; retries < MKHTEMP_RETRY_MAX; retries++) {
r = mkhtemp_try_create(dirfd,
st_dir_first, fname_copy,
p, xc, fd, st, type);
if (r == 0)
continue;
if (r < 0)
goto err;
/* success: copy final name back */
memcpy(template + template_len - fname_len,
fname_copy, fname_len);
errno = saved_errno;
goto success;
}
errno = EEXIST;
err:
xclose(fd);
free_and_set_null(&fname_copy);
return with_fallback_errno(EIO);
success:
free_and_set_null(&fname_copy);
reset_caller_errno(0);
return *fd;
}
int
mkhtemp_try_create(int dirfd,
struct stat *st_dir_first,
char *fname_copy,
char *p,
size_t xc,
int *fd,
struct stat *st,
int type)
{
struct stat st_open;
int saved_errno = errno;
int rval = -1;
char *rstr = NULL;
int file_created = 0;
int dir_created = 0;
errno = 0;
if (if_err(fd == NULL || st == NULL || p ==NULL || fname_copy ==NULL ||
st_dir_first == NULL, EFAULT) ||
if_err(*fd >= 0, EEXIST))
goto err;
/* TODO: potential infinite loop under entropy failure.
* if attacker has control of rand - TODO: maybe add timeout
*/
memcpy(p, rstr = rchars(xc), xc);
free_and_set_null(&rstr);
if (if_err_sys(fd_verify_dir_identity(dirfd, st_dir_first) < 0))
goto err;
if (type == MKHTEMP_FILE) {
#if defined(__linux__) && \
(!defined(USE_OPENAT) || ((USE_OPENAT) < 1))
/* try O_TMPFILE fast path */
if (mkhtemp_tmpfile_linux(dirfd,
st_dir_first, fname_copy,
p, xc, fd, st) >= 0) {
errno = saved_errno;
rval = 1;
goto out;
}
#endif
*fd = openat_on_eintr(dirfd, fname_copy,
O_RDWR | O_CREAT | O_EXCL |
O_NOFOLLOW | O_CLOEXEC | O_NOCTTY, 0600);
/* O_CREAT and O_EXCL guarantees creation upon success
*/
if (*fd >= 0)
file_created = 1;
} else { /* dir: MKHTEMP_DIR */
if (mkdirat_on_eintr(dirfd, fname_copy, 0700) < 0)
goto err;
/* ^ NOTE: opening the directory here
will never set errno=EEXIST,
since we're not creating it */
dir_created = 1;
/* do it again (mitigate directory race) */
if (fd_verify_dir_identity(dirfd, st_dir_first) < 0)
goto err;
if ((*fd = openat_on_eintr(dirfd, fname_copy,
O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0)) < 0)
goto err;
if (if_err_sys(fstat(*fd, &st_open) < 0) ||
if_err(!S_ISDIR(st_open.st_mode), ENOTDIR))
goto err;
/* NOTE: pointless to check nlink here (only just opened) */
if (fd_verify_dir_identity(dirfd, st_dir_first) < 0)
goto err;
}
/* NOTE: openat_on_eintr and mkdirat_on_eintr
* already handled EINTR/EAGAIN looping
*/
if (*fd < 0) {
if (errno == EEXIST) {
rval = 0;
goto out;
}
goto err;
}
if (fstat(*fd, &st_open) < 0)
goto err;
if (type == MKHTEMP_FILE) {
if (fd_verify_dir_identity(dirfd, st_dir_first) < 0)
goto err;
if (secure_file(fd, st, &st_open,
O_APPEND, 1, 1, 0600) < 0) /* WARNING: only once */
goto err;
} else { /* dir: MKHTEMP_DIR */
if (fd_verify_identity(*fd, &st_open, st_dir_first) < 0)
goto err;
if (if_err(!S_ISDIR(st_open.st_mode), ENOTDIR) ||
if_err_sys(is_owner(&st_open) < 0) ||
if_err(st_open.st_mode & (S_IWGRP | S_IWOTH), EPERM))
goto err;
}
rval = 1;
out:
reset_caller_errno(0);
return rval;
err:
xclose(fd);
if (file_created)
(void) unlinkat(dirfd, fname_copy, 0);
if (dir_created)
(void) unlinkat(dirfd, fname_copy, AT_REMOVEDIR);
return with_fallback_errno(EPERM);
}
/* linux has its own special hardening
available specifically for tmpfiles,
which eliminates many race conditions.
we still use openat() on bsd, which is
still ok with our other mitigations
*/
#if defined(__linux__) && \
(!defined(USE_OPENAT) || ((USE_OPENAT) < 1))
int
mkhtemp_tmpfile_linux(int dirfd,
struct stat *st_dir_first,
char *fname_copy,
char *p,
size_t xc,
int *fd,
struct stat *st)
{
int saved_errno = errno;
int tmpfd = -1;
size_t retries;
int linked = 0;
char *rstr = NULL;
errno = 0;
if (if_err(fd == NULL || st == NULL ||
fname_copy == NULL || p == NULL ||
st_dir_first == NULL, EFAULT))
goto err;
/* create unnamed tmpfile */
tmpfd = openat_on_eintr(dirfd, ".",
O_TMPFILE | O_RDWR | O_CLOEXEC, 0600);
if (tmpfd < 0)
goto err;
if (fd_verify_dir_identity(dirfd, st_dir_first) < 0)
goto err;
for (retries = 0; retries < MKHTEMP_RETRY_MAX; retries++) {
memcpy(p, rstr = rchars(xc), xc);
free_and_set_null(&rstr);
if (fd_verify_dir_identity(dirfd,
st_dir_first) < 0)
goto err;
if (linkat(tmpfd, "", dirfd,
fname_copy, AT_EMPTY_PATH) == -1) {
if (errno == EEXIST)
continue; /* retry on collision */
else
goto err;
}
linked = 1; /* file created */
/* TODO: potential fd leak here.
* probably should only set *fd on successful
* return from this function (see below)
*/
if (fd_verify_dir_identity(dirfd, st_dir_first) < 0 ||
fstat(*fd = tmpfd, st) < 0 ||
secure_file(fd, st, st, O_APPEND, 1, 1, 0600) < 0)
goto err;
goto out;
}
if (!errno)
errno = EEXIST;
err:
if (linked)
(void) unlinkat(dirfd, fname_copy, 0);
xclose(&tmpfd);
return with_fallback_errno(EIO);
out:
reset_caller_errno(0);
return 0;
}
#endif
/* WARNING: **ONCE** per file.
*
* some of these checks will trip up
* if you do them twice; all of them
* only need to be done once anyway.
*/
int secure_file(int *fd,
struct stat *st,
struct stat *expected,
int bad_flags,
int check_seek,
int do_lock,
mode_t mode)
{
int flags = -1;
struct stat st_now;
int saved_errno = errno;
errno = 0;
if (if_err(fd == NULL || st == NULL, EFAULT) ||
if_err(*fd < 0, EBADF))
goto err_demons;
if ((flags = fcntl(*fd, F_GETFL)) == -1)
goto err_demons;
if (if_err(bad_flags > 0 && (flags & bad_flags), EPERM))
goto err_demons;
if (expected != NULL) {
if (fd_verify_regular(*fd, expected, st) < 0)
goto err_demons;
} else if (if_err_sys(fstat(*fd, &st_now) == -1) ||
if_err(!S_ISREG(st_now.st_mode), EBADF)) {
goto err_demons; /***********/
} else /* ( >:3 ) */
*st = st_now; /* /| |\ */ /* don't let him out */
/* / \ */
if (check_seek) { /***********/
if (lseek(*fd, 0, SEEK_CUR) == (off_t)-1)
goto err_demons;
} /* don't release the demon! */
if (if_err(st->st_nlink != 1, ELOOP) ||
if_err(st->st_uid != geteuid() && geteuid() != 0, EPERM) ||
if_err_sys(is_owner(st) < 0) ||
if_err(st->st_mode & (S_IWGRP | S_IWOTH), EPERM))
goto err_demons;
if (do_lock) {
if (lock_file(*fd, flags) == -1)
goto err_demons;
/* TODO: why would this be NULL? audit
* to find out. we should always verify! */
if (expected != NULL)
if (fd_verify_identity(*fd, expected, &st_now) < 0)
goto err_demons;
}
if (fchmod(*fd, mode) == -1)
goto err_demons;
reset_caller_errno(0);
return 0;
err_demons:
return with_fallback_errno(EIO);
}
int
fd_verify_regular(int fd,
const struct stat *expected,
struct stat *out)
{
int saved_errno = errno;
errno = 0;
if (if_err_sys(fd_verify_identity(fd, expected, out) < 0) ||
if_err(!S_ISREG(out->st_mode), EBADF)) {
return with_fallback_errno(EIO);
} else {
reset_caller_errno(0);
return 0; /* regular file */
}
}
int
fd_verify_identity(int fd,
const struct stat *expected,
struct stat *out)
{
struct stat st_now;
int saved_errno = errno;
errno = 0;
if( if_err(fd < 0 || expected == NULL, EFAULT) ||
if_err_sys(fstat(fd, &st_now)) ||
if_err(st_now.st_dev != expected->st_dev ||
st_now.st_ino != expected->st_ino, ESTALE))
return with_fallback_errno(EIO);
if (out != NULL)
*out = st_now;
reset_caller_errno(0);
return 0;
}
int
fd_verify_dir_identity(int fd,
const struct stat *expected)
{
struct stat st_now;
int saved_errno = errno;
errno = 0;
if (if_err(fd < 0 || expected == NULL, EFAULT) ||
if_err_sys(fstat(fd, &st_now) < 0) ||
if_err(st_now.st_dev != expected->st_dev, ESTALE) ||
if_err(st_now.st_ino != expected->st_ino, ESTALE) ||
if_err(!S_ISDIR(st_now.st_mode), ENOTDIR))
goto err;
reset_caller_errno(0);
return 0;
err:
return with_fallback_errno(EIO);
}
int
is_owner(struct stat *st)
{
int saved_errno = errno;
errno = 0;
if (if_err(st == NULL, EFAULT) ||
if_err(st->st_uid != geteuid() /* someone else's file */
#if defined(ALLOW_ROOT_OVERRIDE) && ((ALLOW_ROOT_OVERRIDE) > 0)
&& geteuid() != 0 /* override for root */
#endif
, EPERM)) return with_fallback_errno(EIO);
reset_caller_errno(0);
return 0;
}
int
lock_file(int fd, int flags)
{
struct flock fl;
int saved_errno = errno;
int fcntl_rval = -1;
errno = 0;
if (if_err(fd < 0, EBADF) ||
if_err(flags < 0, EINVAL))
goto err_lock_file;
memset(&fl, 0, sizeof(fl));
if ((flags & O_ACCMODE) == O_RDONLY)
fl.l_type = F_RDLCK;
else
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
if ((fcntl_rval = fcntl(fd, F_SETLK, &fl)) == -1)
goto err_lock_file;
reset_caller_errno(0);
return 0;
err_lock_file:
return with_fallback_errno(EIO);
}
-116
View File
@@ -1,116 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* Non-randomisation-related numerical functions.
* For rand functions, see: rand.c
*/
#ifdef __OpenBSD__
#include <sys/param.h>
#endif
#include <sys/types.h>
#include <errno.h>
#if !((defined(__OpenBSD__) && (OpenBSD) >= 201) || \
defined(__FreeBSD__) || \
defined(__NetBSD__) || defined(__APPLE__))
#include <fcntl.h> /* if not arc4random: /dev/urandom */
#endif
#include <ctype.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "../include/common.h"
unsigned short
hextonum(char ch_s)
{
unsigned char ch;
ch = (unsigned char)ch_s;
if ((unsigned int)(ch - '0') <= 9)
return ch - '0';
ch |= 0x20;
if ((unsigned int)(ch - 'a') <= 5)
return ch - 'a' + 10;
if (ch == '?' || ch == 'x') /* random */
return (short)rsize(16); /* <-- with rejection sampling! */
return 16;
}
/* basically hexdump -C */
/*
TODO: optimise this
write a full util for hexdump
how to optimise:
don't call print tens of thousands of times!
convert the numbers manually, and cache everything
in a BUFSIZ sized buffer, with everything properly
aligned. i worked out that i could fit 79 rows
in a 8KB buffer (1264 bytes of numbers represented
as strings in hex)
this depends on the OS, and would be calculated at
runtime.
then:
don't use printf. just write it to stdout (basically
a simple cat implementation)
*/
void
spew_hex(const void *data, size_t len)
{
const unsigned char *buf = (const unsigned char *)data;
unsigned char c;
size_t i;
size_t j;
if (buf == NULL ||
len == 0)
return;
for (i = 0; i < len; i += 16) {
if (len <= 4294967296) /* below 4GB */
printf("%08zx ", i);
else
printf("%16zu ", i);
for (j = 0; j < 16; j++) {
if (i + j < len)
printf("%02x ", buf[i + j]);
else
printf(" ");
if (j == 7)
printf(" ");
}
printf(" |");
for (j = 0; j < 16 && i + j < len; j++) {
c = buf[i + j];
printf("%c", isprint(c) ? c : '.');
}
printf("|\n");
}
printf("%08zx\n", len);
}
void
check_bin(size_t a, const char *a_name)
{
if (a > 1)
exitf("%s must be 0 or 1, but is %lu",
a_name, (size_t)a);
}
-200
View File
@@ -1,200 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* Random number generation
*/
#if defined(USE_ARC4) && \
((USE_ARC4) > 0)
#define _DEFAULT_SOURCE 1 /* for arc4random on *linux* */
/* (not needed on bsd - on bsd,
it is used automatically unless
overridden with USE_URANDOM */
#elif defined(USE_URANDOM) && \
((USE_URANDOM) > 0)
#include <fcntl.h> /* if not arc4random: /dev/urandom */
#elif defined(__linux__) && \
!(defined(USE_ARC4) && ((USE_ARC4) > 0))
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <sys/syscall.h>
#include <sys/random.h>
#endif
#ifdef __OpenBSD__
#include <sys/param.h>
#endif
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include "../include/common.h"
/* Regarding Linux getrandom/urandom:
*
* For maximum security guarantee, we *only*
* use getrandom via syscall, or /dev/urandom;
* use of urandom is ill advised. This is why
* we use the syscall, in case the libc version
* of getrandom() might defer to /dev/urandom
*
* We *abort* on error, for both /dev/urandom
* and getrandom(), because the BSD arc4random
* never returns with error; therefore, for the
* most parity in terms of behaviour, we abort,
* because otherwise the function would have two
* return modes: always successful (BSD), or only
* sometimes (Linux). The BSD arc4random could
* theoretically abort; it is extremely unlikely
* there, and just so on Linux, hence this design.
*
* This is important, because cryptographic code
* for example must not rely on weak randomness.
* We must therefore treat broken randomness as
* though the world is broken, and burn accordingly.
*
* Similarly, any invalid input (NULL, zero bytes
* requested) are treated as fatal errors; again,
* cryptographic code must be reliable. If your
* code erroneously requested zero bytes, you might
* then end up with a non-randomised buffer, where
* you likely intended otherwise.
*
* In other words: call rset() correctly, or your
* program dies, and rset will behave correctly,
* or your program dies.
*/
/* random string generator, with
* rejection sampling. NOTE: only
* uses ASCII-safe characters, for
* printing on a unix terminal
*
* you still shouldn't use this for
* password generation; open diceware
* passphrases are better for that
*
* NOTE: the generated strings must
* ALSO be safe for file/directory names
* on unix-like os e.g. linux/bsd
*/
char *
rchars(size_t n) /* emulates spkmodem-decode */
{
static char ch[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
char *s = NULL;
size_t i;
smalloc(&s, n + 1);
for (i = 0; i < n; i++)
s[i] = ch[rsize(sizeof(ch) - 1)];
*(s + n) = '\0';
return s;
}
size_t
rsize(size_t n)
{
size_t rval = SIZE_MAX;
if (!n)
exitf("rsize: division by zero");
/* rejection sampling (clamp rand to eliminate modulo bias) */
for (; rval >= SIZE_MAX - (SIZE_MAX % n); rset(&rval, sizeof(rval)));
return rval % n;
}
void *
rmalloc(size_t n)
{
void *buf = NULL;
rset(vmalloc(&buf, n), n);
return buf; /* basically malloc() but with rand */
}
void
rset(void *buf, size_t n)
{
int saved_errno = errno;
errno = 0;
if (if_err(buf == NULL, EFAULT))
goto err;
if (n == 0)
exitf("rset: zero-byte request");
/* on linux, getrandom is recommended,
but you can pass -DUSE_ARC4=1 to use arc4random.
useful for portability testing from linux.
*/
#if (defined(USE_ARC4) && ((USE_ARC4) > 0)) || \
((defined(__OpenBSD__) || defined(__FreeBSD__) || \
defined(__NetBSD__) || defined(__APPLE__) || \
defined(__DragonFly__)) && !(defined(USE_URANDOM) && \
((USE_URANDOM) > 0)))
arc4random_buf(buf, n);
#else
size_t off = 0;
retry_rand: {
#if defined(USE_URANDOM) && \
((USE_URANDOM) > 0)
ssize_t rval;
int fd = -1;
open_file_on_eintr("/dev/urandom", &fd, O_RDONLY, 0400, NULL);
while (rw_retry(saved_errno,
rval = rw(fd, (unsigned char *)buf + off, n - off, 0, IO_READ)));
#elif defined(__linux__)
long rval;
while (sys_retry(saved_errno,
rval = syscall(SYS_getrandom,
(unsigned char *)buf + off, n - off, 0)));
#else
#error Unsupported operating system (possibly unsecure randomisation)
#endif
if (rval < 0 || /* syscall fehler */
rval == 0) { /* prevent infinite loop on fatal err */
#if defined(USE_URANDOM) && \
((USE_URANDOM) > 0)
xclose(&fd);
#endif
goto err;
}
if ((off += (size_t)rval) < n)
goto retry_rand;
#if defined(USE_URANDOM) && \
((USE_URANDOM) > 0)
xclose(&fd);
#endif
}
#endif
reset_caller_errno(0);
return;
err:
(void) with_fallback_errno(ECANCELED);
exitf("Randomisierungsfehler");
exit(EXIT_FAILURE);
}
-164
View File
@@ -1,164 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
*
* State machine (singleton) for nvmutil data.
*/
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../include/common.h"
struct xstate *
xstart(int argc, char *argv[])
{
static int first_run = 1;
static char *dir = NULL;
static char *base = NULL;
char *realdir = NULL;
char *tmpdir = NULL;
char *tmpbase_local = NULL;
static struct xstate us = {
{
/* be careful when modifying xstate. you
* must set everything precisely */
{
CMD_DUMP, "dump", cmd_helper_dump, ARGC_3,
ARG_NOPART,
SKIP_CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
NVM_SIZE, O_RDONLY
}, {
CMD_SETMAC, "setmac", cmd_helper_setmac, ARGC_3,
ARG_NOPART,
CHECKSUM_READ, CHECKSUM_WRITE,
NVM_SIZE, O_RDWR
}, {
CMD_SWAP, "swap", cmd_helper_swap, ARGC_3,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDWR
}, {
CMD_COPY, "copy", cmd_helper_copy, ARGC_4,
ARG_PART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDWR
}, {
CMD_CAT, "cat", cmd_helper_cat, ARGC_3,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDONLY
}, {
CMD_CAT16, "cat16", cmd_helper_cat16, ARGC_3,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDONLY
}, {
CMD_CAT128, "cat128", cmd_helper_cat128, ARGC_3,
ARG_NOPART,
CHECKSUM_READ, SKIP_CHECKSUM_WRITE,
GBE_PART_SIZE, O_RDONLY
}
},
/* ->mac */
{NULL, "xx:xx:xx:xx:xx:xx", {0, 0, 0}}, /* .str, .rmac, .mac_buf */
/* .f */
{0},
/* ->i (index to cmd[]) */
0,
/* .no_cmd (set 0 when a command is found) */
1,
/* .cat (cat helpers set this) */
-1
};
if (!first_run)
return &us;
if (argc < 3)
exitf("xstart: Too few arguments");
if (argv == NULL)
exitf("xstart: NULL argv");
first_run = 0;
us.f.buf = us.f.real_buf;
us.f.fname = argv[1];
us.f.tmp_fd = -1;
us.f.tname = NULL;
if ((realdir = realpath(us.f.fname, NULL)) == NULL)
exitf("xstart: can't get realpath of %s",
us.f.fname);
if (fs_dirname_basename(realdir, &dir, &base, 0) < 0)
exitf("xstart: don't know CWD of %s",
us.f.fname);
sdup(base, PATH_MAX, &us.f.base);
us.f.dirfd = fs_open(dir,
O_RDONLY | O_DIRECTORY);
if (us.f.dirfd < 0)
exitf("%s: open dir", dir);
if (new_tmpfile(&us.f.tmp_fd, &us.f.tname, dir, ".gbe.XXXXXXXXXX") < 0)
exitf("%s", us.f.tname);
if (fs_dirname_basename(us.f.tname,
&tmpdir, &tmpbase_local, 0) < 0)
exitf("tmp basename");
sdup(tmpbase_local, PATH_MAX, &us.f.tmpbase);
free_and_set_null(&tmpdir);
if (us.f.tname == NULL)
exitf("x->f.tname null");
if (*us.f.tname == '\0')
exitf("x->f.tname empty");
if (fstat(us.f.tmp_fd, &us.f.tmp_st) < 0)
exitf("%s: stat", us.f.tname);
memset(us.f.real_buf, 0, sizeof(us.f.real_buf));
memset(us.f.bufcmp, 0, sizeof(us.f.bufcmp));
/* for good measure */
memset(us.f.pad, 0, sizeof(us.f.pad));
return &us;
}
struct xstate *
xstatus(void)
{
struct xstate *x = xstart(0, NULL);
if (x == NULL)
exitf("NULL pointer to xstate");
return x;
}
-643
View File
@@ -1,643 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*
* String functions
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <stdint.h>
#include "../include/common.h"
/* for null detection inside
* word-optimised string functions
*/
#define ff ((size_t)-1 / 0xFF)
#define high ((ff) * 0x80)
/* NOTE:
* do not assume that a match means
* both words have null at the same
* location. see how this is handled
* e.g. in scmp.
*/
#define zeroes(x) (((x) - (ff)) & ~(x) & (high))
size_t
page_remain(const void *p)
{
/* calling sysconf repeatedly
* is folly. cache it (static)
*/
static size_t pagesz = 0;
if (!pagesz)
pagesz = (size_t)pagesize();
return pagesz - ((uintptr_t)p & (pagesz - 1));
}
long
pagesize(void)
{
static long rval = 0;
static int set = 0;
int saved_errno = 0;
if (!set) {
if ((rval = sysconf(_SC_PAGESIZE)) < 0)
exitf("could not determine page size");
set = 1;
}
reset_caller_errno(0);
return rval;
}
void
free_and_set_null(char **buf)
{
if (buf == NULL)
exitf(
"null ptr (to ptr for freeing) in free_and_set_null");
if (*buf == NULL)
return;
free(*buf);
*buf = NULL;
}
/* safe(ish) malloc.
use this and free_and_set_null()
in your program, to reduce the
chance of use after frees!
if you use these functions in the
intended way, you will greatly reduce
the number of bugs in your code
*/
char *
smalloc(char **buf, size_t size)
{
return (char *)vmalloc((void **)buf, size);
}
void *
vmalloc(void **buf, size_t size)
{
int saved_errno = errno;
void *rval = NULL;
errno = 0;
if (size >= SIZE_MAX - 1)
exitf("integer overflow in vmalloc");
if (buf == NULL)
exitf("Bad pointer passed to vmalloc");
/* lots of programs will
* re-initialise a buffer
* that was allocated, without
* freeing or NULLing it. this
* is here intentionally, to
* force the programmer to behave
*/
if (*buf != NULL)
exitf("Non-null pointer given to vmalloc");
if (!size)
exitf(
"Tried to vmalloc(0) and that is very bad. Fix it now");
if ((rval = malloc(size)) == NULL)
exitf("malloc fail in vmalloc");
reset_caller_errno(0);
return *buf = rval;
}
/* strict word-based strcmp */
int
scmp(const char *a,
const char *b,
size_t maxlen,
int *rval)
{
size_t i = 0;
size_t j;
size_t wa;
size_t wb;
int saved_errno = errno;
errno = 0;
if (if_err(a == NULL || b == NULL || rval == NULL, EFAULT))
goto err;
for ( ; ((uintptr_t)(a + i) % sizeof(size_t)) != 0; i++) {
if (if_err(i >= maxlen, EOVERFLOW))
goto err;
else if (!ccmp(a, b, i, rval))
goto out;
}
for ( ; i + sizeof(size_t) <= maxlen;
i += sizeof(size_t)) {
/* prevent crossing page boundary on word check */
if (page_remain(a + i) < sizeof(size_t) ||
page_remain(b + i) < sizeof(size_t))
break;
memcpy(&wa, a + i, sizeof(size_t));
memcpy(&wb, b + i, sizeof(size_t));
if (wa != wb)
for (j = 0; j < sizeof(size_t); j++)
if (!ccmp(a, b, i + j, rval))
goto out;
if (!zeroes(wa))
continue;
*rval = 0;
goto out;
}
for ( ; i < maxlen; i++)
if (!ccmp(a, b, i, rval))
goto out;
err:
(void) with_fallback_errno(EFAULT);
if (rval != NULL)
*rval = -1;
exitf("scmp");
return -1;
out:
reset_caller_errno(0);
return *rval;
}
int ccmp(const char *a, const char *b,
size_t i, int *rval)
{
unsigned char ac;
unsigned char bc;
if (if_err(a == NULL || b == NULL || rval == NULL, EFAULT))
exitf("ccmp");
ac = (unsigned char)a[i];
bc = (unsigned char)b[i];
if (ac != bc) {
*rval = ac - bc;
return 0;
} else if (ac == '\0') {
*rval = 0;
return 0;
}
return 1;
}
/* strict word-based strlen */
size_t
slen(const char *s,
size_t maxlen,
size_t *rval)
{
int saved_errno = errno;
size_t i = 0;
size_t w;
size_t j;
errno = 0;
if (if_err(s == NULL || rval == NULL, EFAULT))
goto err;
for ( ; ((uintptr_t)(s + i) % sizeof(size_t)) != 0; i++) {
if (if_err(i >= maxlen, EOVERFLOW))
goto err;
if (s[i] == '\0') {
*rval = i;
goto out;
}
}
for ( ; i + sizeof(size_t) <= maxlen;
i += sizeof(size_t)) {
memcpy(&w, s + i, sizeof(size_t));
if (!zeroes(w))
continue;
for (j = 0; j < sizeof(size_t); j++) {
if (s[i + j] == '\0') {
*rval = i + j;
goto out;
}
}
}
for ( ; i < maxlen; i++) {
if (s[i] == '\0') {
*rval = i;
goto out;
}
}
err:
(void) with_fallback_errno(EFAULT);
if (rval != NULL)
*rval = 0;
exitf("slen"); /* abort */
return 0; /* gcc15 is happy */
out:
reset_caller_errno(0);
return *rval;
}
int
dup_pair(char **dir, const char *d,
char **base, const char *b)
{
char *dtmp = NULL;
char *btmp = NULL;
if (d && sdup(d, PATH_MAX, &dtmp) == NULL)
return -1;
if (b && sdup(b, PATH_MAX, &btmp) == NULL) {
free(dtmp);
return -1;
}
*dir = dtmp;
*base = btmp;
return 0;
}
/* strict word-based strdup */
char *
sdup(const char *s,
size_t max, char **dest)
{
size_t j;
size_t w;
size_t i = 0;
char *out = NULL;
int saved_errno = errno;
errno = 0;
if (if_err(dest == NULL || *dest != NULL || s == NULL, EFAULT))
goto err;
out = smalloc(dest, max);
for ( ; ((uintptr_t)(s + i) % sizeof(size_t)) != 0; i++) {
if (if_err(i >= max, EOVERFLOW))
goto err;
out[i] = s[i];
if (s[i] == '\0') {
*dest = out;
goto out;
}
}
for ( ; i + sizeof(size_t) <= max; i += sizeof(size_t)) {
if (page_remain(s + i) < sizeof(size_t))
break;
memcpy(&w, s + i, sizeof(size_t));
if (!zeroes(w)) {
memcpy(out + i, &w, sizeof(size_t));
continue;
}
for (j = 0; j < sizeof(size_t); j++) {
out[i + j] = s[i + j];
if (s[i + j] == '\0') {
*dest = out;
goto out;
}
}
}
for ( ; i < max; i++) {
out[i] = s[i];
if (s[i] == '\0') {
*dest = out;
goto out;
}
}
err:
free_and_set_null(&out);
if (dest != NULL)
*dest = NULL;
(void) with_fallback_errno(EFAULT);
exitf("sdup");
return NULL;
out:
reset_caller_errno(0);
return *dest;
}
/* concatenate N number of strings */
char *
scatn(ssize_t sc, const char **sv,
size_t max, char **rval)
{
int saved_errno = errno;
char *final = NULL;
char *rcur = NULL;
char *rtmp = NULL;
ssize_t i;
errno = 0;
if (if_err(sc < 2, EINVAL) ||
if_err(sv == NULL, EFAULT) ||
if_err(rval == NULL || *rval != NULL, EFAULT))
goto err;
for (i = 0; i < sc; i++) {
if (if_err(sv[i] == NULL, EFAULT))
goto err;
else if (i == 0) {
(void) sdup(sv[0], max, &final);
continue;
}
rtmp = NULL;
scat(final, sv[i], max, &rtmp);
free_and_set_null(&final);
final = rtmp;
rtmp = NULL;
}
reset_caller_errno(0);
*rval = final;
return *rval;
err:
free_and_set_null(&rcur);
free_and_set_null(&rtmp);
free_and_set_null(&final);
(void) with_fallback_errno(EFAULT);
exitf("scatn");
return NULL;
}
/* strict strcat */
char *
scat(const char *s1, const char *s2,
size_t n, char **dest)
{
size_t size1;
size_t size2;
char *rval = NULL;
int saved_errno = errno;
errno = 0;
if (if_err(dest == NULL || *dest != NULL, EFAULT))
goto err;
slen(s1, n, &size1);
slen(s2, n, &size2);
if (if_err(size1
> SIZE_MAX - size2 - 1, EOVERFLOW))
goto err;
smalloc(&rval, size1 + size2 + 1);
memcpy(rval, s1, size1);
memcpy(rval + size1, s2, size2);
*(rval + size1 + size2) = '\0';
reset_caller_errno(0);
*dest = rval;
return *dest;
err:
(void) with_fallback_errno(EINVAL);
if (dest != NULL)
*dest = NULL;
exitf("scat");
return NULL;
}
/* strict split/de-cat - off is where
2nd buffer will start from */
void
dcat(const char *s, size_t n,
size_t off, char **dest1,
char **dest2)
{
size_t size;
char *rval1 = NULL;
char *rval2 = NULL;
int saved_errno = errno;
errno = 0;
if (if_err(dest1 == NULL || dest2 == NULL, EFAULT))
goto err;
if (if_err(slen(s, n, &size) >= SIZE_MAX - 1, EOVERFLOW) ||
if_err(off >= size, EOVERFLOW))
goto err;
memcpy(smalloc(&rval1, off + 1),
s, off);
*(rval1 + off) = '\0';
memcpy(smalloc(&rval2, size - off +1),
s + off, size - off);
*(rval2 + size - off) = '\0';
*dest1 = rval1;
*dest2 = rval2;
reset_caller_errno(0);
return;
err:
*dest1 = *dest2 = NULL;
free_and_set_null(&rval1);
free_and_set_null(&rval2);
(void) with_fallback_errno(EINVAL);
exitf("dcat");
}
/* because no libc reimagination is complete
* without a reimplementation of memcmp. and
* no safe one is complete without null checks.
*/
int
vcmp(const void *s1, const void *s2, size_t n)
{
int saved_errno = errno;
size_t i = 0;
size_t a;
size_t b;
const unsigned char *x;
const unsigned char *y;
errno = 0;
if (if_err(s1 == NULL || s2 == NULL, EFAULT))
exitf("vcmp: null input");
x = s1;
y = s2;
for ( ; i + sizeof(size_t) <= n; i += sizeof(size_t)) {
memcpy(&a, x + i, sizeof(size_t));
memcpy(&b, y + i, sizeof(size_t));
if (a != b)
break;
}
for ( ; i < n; i++)
if (x[i] != y[i])
return (int)x[i] - (int)y[i];
reset_caller_errno(0);
return 0;
}
/* on functions that return with errno,
* i sometimes have a default fallback,
* which is set if errno wasn't changed,
* under error condition.
*/
int
with_fallback_errno(int fallback)
{
if (!errno)
errno = fallback;
return -1;
}
/* the one for nvmutil state is in state.c */
/* this one just exits */
void
exitf(const char *msg, ...)
{
va_list args;
int saved_errno = errno;
func_t err_cleanup = errhook(NULL);
err_cleanup();
reset_caller_errno(0);
saved_errno = errno;
if (!errno)
saved_errno = errno = ECANCELED;
fprintf(stderr, "%s: ", lbgetprogname());
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
errno = saved_errno;
fprintf(stderr, ": %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
/* the err function will
* call this upon exit, and
* cleanup will be performed
* e.g. you might want to
* close some files, depending
* on your program.
* see: exitf()
*/
func_t errhook(func_t ptr)
{
static int set = 0;
static func_t hook = NULL;
if (!set) {
set = 1;
if (ptr == NULL)
hook = no_op;
else
hook = ptr;
}
return hook;
}
void
no_op(void)
{
return;
}
const char *
lbgetprogname(void)
{
char *name = lbsetprogname(NULL);
char *p = NULL;
if (name)
p = strrchr(name, '/');
if (p)
return p + 1;
else if (name)
return name;
else
return "libreboot-utils";
}
/* singleton. if string not null,
sets the string. after set,
will not set anymore. either
way, returns the string
*/
char *
lbsetprogname(char *argv0)
{
static char *progname = NULL;
static int set = 0;
if (!set) {
if (argv0 == NULL)
return "libreboot-utils";
(void) sdup(argv0, PATH_MAX, &progname);
set = 1;
}
return progname;
}
-30
View File
@@ -1,30 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com>
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
*/
#include <errno.h>
#include <stdio.h>
#include "../include/common.h"
void
usage(void)
{
const char *util = lbgetprogname();
fprintf(stderr,
"Modify Intel GbE NVM images e.g. set MAC\n"
"USAGE:\n"
"\t%s FILE dump\n"
"\t%s FILE setmac [MAC]\n"
"\t%s FILE swap\n"
"\t%s FILE copy 0|1\n"
"\t%s FILE cat\n"
"\t%s FILE cat16\n"
"\t%s FILE cat128\n",
util, util, util, util,
util, util, util);
exitf("Too few arguments");
}
-68
View File
@@ -1,68 +0,0 @@
/* SPDX-License-Identifier: MIT
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
*
* Manipulate Intel GbE NVM words, which are 16-bit little
* endian in the files (MAC address words are big endian).
*/
#include <sys/types.h>
#include <errno.h>
#include <stddef.h>
#include "../include/common.h"
unsigned short
nvm_word(size_t pos16, size_t p)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
size_t pos;
check_nvm_bound(pos16, p);
pos = (pos16 << 1) + (p * GBE_PART_SIZE);
return (unsigned short)f->buf[pos] |
((unsigned short)f->buf[pos + 1] << 8);
}
void
set_nvm_word(size_t pos16, size_t p, unsigned short val16)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
size_t pos;
check_nvm_bound(pos16, p);
pos = (pos16 << 1) + (p * GBE_PART_SIZE);
f->buf[pos] = (unsigned char)(val16 & 0xff);
f->buf[pos + 1] = (unsigned char)(val16 >> 8);
set_part_modified(p);
}
void
set_part_modified(size_t p)
{
struct xstate *x = xstatus();
struct xfile *f = &x->f;
check_bin(p, "part number");
f->part_modified[p] = 1;
}
void
check_nvm_bound(size_t c, size_t p)
{
/* Block out of bound NVM access
*/
check_bin(p, "part number");
if (c >= NVM_WORDS)
exitf("check_nvm_bound: out of bounds %lu",
(size_t)c);
}
-74
View File
@@ -1,74 +0,0 @@
/* SPDX-License-Identifier: MIT ( >:3 )
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org> /| |\
Something something non-determinism / \ */
#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include "include/common.h"
static void
exit_cleanup(void);
int
main(int argc, char **argv)
{
#ifndef __linux__
#error This code is currently buggy on BSD systems. Only use on Linux.
#endif
int same = 0;
char *buf;
size_t size = BUFSIZ;
(void) argc, (void) argv;
(void) errhook(exit_cleanup);
(void) lbsetprogname(argv[0]);
#ifdef __OpenBSD__
/* https://man.openbsd.org/pledge.2 */
if (pledge("stdio", NULL) == -1)
exitf("pledge");
#endif
buf = rmalloc(size);
if (!vcmp(buf, buf + (size >> 1), size >> 1))
same = 1;
if (argc < 2) /* no spew */
spew_hex(buf, size);
free_and_set_null(&buf);
fprintf(stderr, "\n%s\n", same ? "You win!" : "You lose!");
return same ? EXIT_SUCCESS : EXIT_FAILURE;
}
static void
exit_cleanup(void)
{
#if defined(__OpenBSD__)
fprintf(stderr, "OpenBSD wins\n");
#elif defined(__FreeBSD__)
fprintf(stderr, "FreeBSD wins\n");
#elif defined(__NetBSD__)
fprintf(stderr, "NetBSD wins\n");
#elif defined(__APPLE__)
fprintf(stderr, "MacOS wins\n");
#elif defined(__DragonFly__)
fprintf(stderr, "DragonFly BSD wins\n");
#elif defined(__linux__)
#if defined(__GLIBC__)
fprintf(stderr, "GNU/Linux wins\n");
#elif defined(__MUSL__)
fprintf(stderr, "Rich Felker wins\n");
#else
fprintf(stderr, "Linux wins\n");
#endif
#else
fprintf(stderr, "Your operating system wins\n");
#endif
return;
}
-152
View File
@@ -1,152 +0,0 @@
/* SPDX-License-Identifier: MIT ( >:3 )
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org> /| |\
* / \
* Hardened mktemp (mkhtemp!)
*
* WORK IN PROGRESS (proof of concept), or, v0.0000001
* DO NOT PUT THIS IN YOUR LINUX DISTRO YET.
*
* In other words: for reference only -- PATCHES WELCOME!
*
* I will remove this notice when the code is mature, and
* probably contact several of your projects myself.
*
* See README. This is an ongoing project; no proper docs
* yet, and no manpage (yet!) - the code is documentation,
* while the specification that it implements evolves.
*/
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "include/common.h"
static void
exit_cleanup(void);
int
main(int argc, char *argv[])
{
#ifndef __linux__
#error This code is currently buggy on BSD systems. Only use on Linux.
#endif
size_t len;
size_t tlen;
size_t xc = 0;
char *tmpdir = NULL;
char *template = NULL;
char *p;
char *s = NULL;
char *rp;
char resolved[PATH_MAX];
char c;
int fd = -1;
int type = MKHTEMP_FILE;
(void) errhook(exit_cleanup);
(void) lbsetprogname(argv[0]);
#ifdef __OpenBSD__
/* https://man.openbsd.org/pledge.2 */
if (pledge("stdio flock rpath wpath cpath fattr", NULL) == -1)
exitf("pledge");
#endif
while ((c =
getopt(argc, argv, "qdp:")) != -1) {
switch (c) {
case 'd':
type = MKHTEMP_DIR;
break;
case 'p':
tmpdir = optarg;
break;
case 'q': /* don't print errors */
/* (exit status unchanged) */
break;
default:
goto err_usage;
}
}
if (optind < argc)
template = argv[optind];
if (optind + 1 < argc)
goto err_usage;
/* custom template e.g. foo.XXXXXXXXXXXXXXXXXXXXX */
if (template != NULL) {
for (p = template + slen(template, PATH_MAX, &tlen);
p > template && *--p == 'X'; xc++);
if (xc < 3) /* the gnu mktemp errs on less than 3 */
exitf(
"template must have 3 X or more on end (12+ advised");
}
/* user supplied -p PATH - WARNING:
* this permits symlinks, but only here,
* not in the library, so they are resolved
* here first, and *only here*. the mkhtemp
* library blocks them. be careful
* when using -p
*/
if (tmpdir != NULL) {
rp = realpath(tmpdir, resolved);
if (rp == NULL)
exitf("%s", tmpdir);
tmpdir = resolved;
}
if (new_tmp_common(&fd, &s, type,
tmpdir, template) < 0)
exitf("%s", s);
#ifdef __OpenBSD__
if (pledge("stdio", NULL) == -1)
exitf("pledge");
#endif
if (s == NULL)
exitf("bad string initialisation");
if (*s == '\0')
exitf("empty string initialisation");
slen(s, PATH_MAX, &len); /* Nullterminierung prüfen */
/* for good measure. (bonus: also re-checks length overflow) */
printf("%s\n", s);
return EXIT_SUCCESS;
err_usage:
exitf(
"usage: %s [-d] [-p dir] [template]\n", lbgetprogname());
}
static void
exit_cleanup(void)
{
return;
}
-134
View File
@@ -1,134 +0,0 @@
/* SPDX-License-Identifier: MIT ( >:3 )
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> /| |\
* / \
* This tool lets you modify Intel GbE NVM (Gigabit Ethernet
* Non-Volatile Memory) images, e.g. change the MAC address.
* These images configure your Intel Gigabit Ethernet adapter.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "include/common.h"
static void
exit_cleanup(void);
int
main(int argc, char *argv[])
{
#ifndef __linux__
#error This code is currently buggy on BSD systems. Only use on Linux.
#endif
struct xstate *x;
struct commands *cmd;
struct xfile *f;
size_t c;
(void) lbsetprogname(argv[0]);
if (argc < 3)
usage();
(void) errhook(exit_cleanup);
#ifdef __OpenBSD
/* https://man.openbsd.org/pledge.2 */
/* https://man.openbsd.org/unveil.2 */
if (pledge("stdio flock rpath wpath cpath unveil", NULL) == -1)
exitf("pledge");
if (unveil("/dev/urandom", "r") == -1)
exitf("unveil");
#endif
#ifndef S_ISREG
exitf(
"Can't determine file types (S_ISREG undefined)");
#endif
#if ((CHAR_BIT) != 8)
exitf("Unsupported char size");
#endif
if ((x = xstart(argc, argv)) == NULL)
exitf("NULL state on init");
/* parse user command */
/* TODO: CHECK ACCESSES VIA xstatus() */
set_cmd(argc, argv);
set_cmd_args(argc, argv);
cmd = &x->cmd[x->i];
f = &x->f;
#ifdef __OpenBSD__
if ((cmd->flags & O_ACCMODE) == O_RDONLY) {
if (unveil(f->fname, "r") == -1)
exitf("unveil");
} else {
if (unveil(f->fname, "rwc") == -1)
exitf("unveil");
}
if (unveil(f->tname, "rwc") == -1)
exitf("unveil");
if (unveil(NULL, NULL) == -1)
exitf("unveil");
if (pledge("stdio flock rpath wpath cpath", NULL) == -1)
exitf("pledge");
#endif
if (cmd->run == NULL)
exitf("Command not set");
sanitize_command_list();
open_gbe_file();
copy_gbe();
read_checksums();
cmd->run();
for (c = 0; c < items(x->cmd); c++)
x->cmd[c].run = cmd_helper_err;
if ((cmd->flags & O_ACCMODE) == O_RDWR)
write_to_gbe_bin();
exit_cleanup();
if (f->io_err_gbe_bin)
exitf("%s: error writing final file");
free_and_set_null(&f->tname);
return EXIT_SUCCESS;
}
static void
exit_cleanup(void)
{
struct xstate *x;
struct xfile *f;
x = xstatus();
if (x == NULL)
return;
f = &x->f;
/* close fds if still open */
xclose(&f->tmp_fd);
xclose(&f->gbe_fd);
/* unlink tmpfile if it exists */
if (f->tname != NULL) {
(void) unlink(f->tname);
free_and_set_null(&f->tname);
}
}
-616
View File
@@ -1,616 +0,0 @@
#!/usr/bin/env python3
"""ME7 Update binary parser."""
# Copyright (C) 2020 Tom Hiller <thrilleratplay@gmail.com>
# Copyright (C) 2016-2018 Nicola Corna <nicola@corna.info>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Based on the amazing me_cleaner, https://github.com/corna/me_cleaner, parses
# the required signed partition from an ME update file to generate a valid
# flashable ME binary.
#
# This was written for Heads ROM, https://github.com/osresearch/heads
# to allow continuous integration reproducible builds for Lenovo xx20 models
# (X220, T420, T520, etc).
#
# A full model list can be found:
# https://download.lenovo.com/ibmdl/pub/pc/pccbbs/mobiles/83rf46ww.txt
from struct import pack, unpack
from typing import List
import argparse
import sys
import hashlib
import binascii
import os.path
#############################################################################
FTPR_END = 0x76000
MINIFIED_FTPR_OFFSET = 0x400 # offset start of Factory Partition (FTPR)
ORIG_FTPR_OFFSET = 0xCC000
PARTITION_HEADER_OFFSET = 0x30 # size of partition header
DEFAULT_OUTPUT_FILE_NAME = "flashregion_2_intel_me.bin"
#############################################################################
class EntryFlags:
"""EntryFlag bitmap values."""
ExclBlockUse = 8192
WOPDisable = 4096
Logical = 2048
Execute = 1024
Write = 512
Read = 256
DirectAccess = 128
Type = 64
def generateHeader() -> bytes:
"""Generate Header."""
ROM_BYPASS_INSTR_0 = binascii.unhexlify("2020800F")
ROM_BYPASS_INSTR_1 = binascii.unhexlify("40000010")
ROM_BYPASS_INSTR_2 = pack("<I", 0)
ROM_BYPASS_INSTR_3 = pack("<I", 0)
# $FPT Partition table header
HEADER_TAG = "$FPT".encode()
HEADER_NUM_PARTITIONS = pack("<I", 1)
HEADER_VERSION = b"\x20" # version 2.0
HEADER_ENTRY_TYPE = b"\x10"
HEADER_LENGTH = b"\x30"
HEADER_CHECKSUM = pack("<B", 0)
HEADER_FLASH_CYCLE_LIFE = pack("<H", 7)
HEADER_FLASH_CYCLE_LIMIT = pack("<H", 100)
HEADER_UMA_SIZE = pack("<H", 32)
HEADER_FLAGS = binascii.unhexlify("000000FCFFFF")
HEADER_FITMAJOR = pack("<H", 0)
HEADER_FITMINOR = pack("<H", 0)
HEADER_FITHOTFIX = pack("<H", 0)
HEADER_FITBUILD = pack("<H", 0)
FTPR_header_layout = bytearray(
ROM_BYPASS_INSTR_0
+ ROM_BYPASS_INSTR_1
+ ROM_BYPASS_INSTR_2
+ ROM_BYPASS_INSTR_3
+ HEADER_TAG
+ HEADER_NUM_PARTITIONS
+ HEADER_VERSION
+ HEADER_ENTRY_TYPE
+ HEADER_LENGTH
+ HEADER_CHECKSUM
+ HEADER_FLASH_CYCLE_LIFE
+ HEADER_FLASH_CYCLE_LIMIT
+ HEADER_UMA_SIZE
+ HEADER_FLAGS
+ HEADER_FITMAJOR
+ HEADER_FITMINOR
+ HEADER_FITHOTFIX
+ HEADER_FITBUILD
)
# Update checksum
FTPR_header_layout[27] = (0x100 - sum(FTPR_header_layout) & 0xFF) & 0xFF
return FTPR_header_layout
def generateFtpPartition() -> bytes:
"""Partition table entry."""
ENTRY_NAME = binascii.unhexlify("46545052")
ENTRY_OWNER = binascii.unhexlify("FFFFFFFF") # "None"
ENTRY_OFFSET = binascii.unhexlify("00040000")
ENTRY_LENGTH = binascii.unhexlify("00600700")
ENTRY_START_TOKENS = pack("<I", 1)
ENTRY_MAX_TOKENS = pack("<I", 1)
ENTRY_SCRATCH_SECTORS = pack("<I", 0)
ENTRY_FLAGS = pack(
"<I",
(
EntryFlags.ExclBlockUse
+ EntryFlags.Execute
+ EntryFlags.Write
+ EntryFlags.Read
+ EntryFlags.DirectAccess
),
)
partition = (
ENTRY_NAME
+ ENTRY_OWNER
+ ENTRY_OFFSET
+ ENTRY_LENGTH
+ ENTRY_START_TOKENS
+ ENTRY_MAX_TOKENS
+ ENTRY_SCRATCH_SECTORS
+ ENTRY_FLAGS
)
# offset of the partition - length of partition entry -length of header
pad_len = MINIFIED_FTPR_OFFSET - (len(partition) + PARTITION_HEADER_OFFSET)
padding = b""
for i in range(0, pad_len):
padding += b"\xFF"
return partition + padding
############################################################################
class OutOfRegionException(Exception):
"""Out of Region Exception."""
pass
class clean_ftpr:
"""Clean Factory Parition (FTPR)."""
UNREMOVABLE_MODULES = ("ROMP", "BUP")
COMPRESSION_TYPE_NAME = ("uncomp.", "Huffman", "LZMA")
def __init__(self, ftpr: bytes):
"""Init."""
self.orig_ftpr = ftpr
self.ftpr = ftpr
self.mod_headers: List[bytes] = []
self.check_and_clean_ftpr()
#####################################################################
# tilities
#####################################################################
def slice(self, offset: int, size: int) -> bytes:
"""Copy data of a given size from FTPR starting from offset."""
offset_end = offset + size
return self.ftpr[offset:offset_end]
def unpack_next_int(self, offset: int) -> int:
"""Sugar syntax for unpacking a little-endian UINT at offset."""
return self.unpack_val(self.slice(offset, 4))
def unpack_val(self, data: bytes) -> int:
"""Sugar syntax for unpacking a little-endian unsigned integer."""
return unpack("<I", data)[0]
def bytes_to_ascii(self, data: bytes) -> str:
"""Decode bytes into ASCII."""
return data.rstrip(b"\x00").decode("ascii")
def clear_ftpr_data(self, start: int, end: int) -> None:
"""Replace values in range with 0xFF."""
empty_data = bytes()
for i in range(0, end - start):
empty_data += b"\xff"
self.write_ftpr_data(start, empty_data)
def write_ftpr_data(self, start: int, data: bytes) -> None:
"""Replace data in FTPR starting at a given offset."""
end = len(data) + start
new_partition = self.ftpr[:start]
new_partition += data
if end != FTPR_END:
new_partition += self.ftpr[end:]
self.ftpr = new_partition
######################################################################
# FTPR cleanig/checking functions
######################################################################
def get_chunks_offsets(self, llut: bytes):
"""Calculate Chunk offsets from LLUT."""
chunk_count = self.unpack_val(llut[0x04:0x08])
huffman_stream_end = sum(unpack("<II", llut[0x10:0x18]))
nonzero_offsets = [huffman_stream_end]
offsets = []
for i in range(0, chunk_count):
llut_start = 0x40 + (i * 4)
llut_end = 0x44 + (i * 4)
chunk = llut[llut_start:llut_end]
offset = 0
if chunk[3] != 0x80:
offset = self.unpack_val(chunk[0:3] + b"\x00")
offsets.append([offset, 0])
if offset != 0:
nonzero_offsets.append(offset)
nonzero_offsets.sort()
for i in offsets:
if i[0] != 0:
i[1] = nonzero_offsets[nonzero_offsets.index(i[0]) + 1]
return offsets
def relocate_partition(self) -> int:
"""Relocate partition."""
new_offset = MINIFIED_FTPR_OFFSET
name = self.bytes_to_ascii(self.slice(PARTITION_HEADER_OFFSET, 4))
old_offset, partition_size = unpack(
"<II", self.slice(PARTITION_HEADER_OFFSET + 0x8, 0x8)
)
llut_start = 0
for mod_header in self.mod_headers:
if (self.unpack_val(mod_header[0x50:0x54]) >> 4) & 7 == 0x01:
llut_start = self.unpack_val(mod_header[0x38:0x3C])
llut_start += old_offset
break
if self.mod_headers and llut_start != 0:
# Bytes 0x9:0xb of the LLUT (bytes 0x1:0x3 of the AddrBase) are
# added to the SpiBase (bytes 0xc:0x10 of the LLUT) to compute the
# final start of the LLUT. Since AddrBase is not modifiable, we can
# act only on SpiBase and here we compute the minimum allowed
# new_offset.
llut_start_corr = unpack("<H", self.slice(llut_start + 0x9, 2))[0]
new_offset = max(
new_offset, llut_start_corr - llut_start - 0x40 + old_offset
)
new_offset = ((new_offset + 0x1F) // 0x20) * 0x20
offset_diff = new_offset - old_offset
print(
"Relocating {} from {:#x} - {:#x} to {:#x} - {:#x}...".format(
name,
old_offset,
old_offset + partition_size,
new_offset,
new_offset + partition_size,
)
)
print(" Adjusting FPT entry...")
self.write_ftpr_data(
PARTITION_HEADER_OFFSET + 0x08,
pack("<I", new_offset),
)
if self.mod_headers:
if llut_start != 0:
if self.slice(llut_start, 4) == b"LLUT":
print(" Adjusting LUT start offset...")
llut_offset = pack(
"<I", llut_start + offset_diff + 0x40 - llut_start_corr
)
self.write_ftpr_data(llut_start + 0x0C, llut_offset)
print(" Adjusting Huffman start offset...")
old_huff_offset = self.unpack_next_int(llut_start + 0x14)
ftpr_offset_diff = MINIFIED_FTPR_OFFSET - ORIG_FTPR_OFFSET
self.write_ftpr_data(
llut_start + 0x14,
pack("<I", old_huff_offset + ftpr_offset_diff),
)
print(" Adjusting chunks offsets...")
chunk_count = self.unpack_next_int(llut_start + 0x4)
offset = llut_start + 0x40
offset_end = chunk_count * 4
chunks = bytearray(self.slice(offset, offset_end))
for i in range(0, offset_end, 4):
i_plus_3 = i + 3
if chunks[i_plus_3] != 0x80:
chunks[i:i_plus_3] = pack(
"<I",
self.unpack_val(chunks[i:i_plus_3] + b"\x00")
+ (MINIFIED_FTPR_OFFSET - ORIG_FTPR_OFFSET),
)[0:3]
self.write_ftpr_data(offset, bytes(chunks))
else:
sys.exit("Huffman modules present but no LLUT found!")
else:
print(" No Huffman modules found")
print(" Moving data...")
partition_size = min(partition_size, FTPR_END - old_offset)
if (
old_offset + partition_size <= FTPR_END
and new_offset + partition_size <= FTPR_END
):
for i in range(0, partition_size, 4096):
block_length = min(partition_size - i, 4096)
block = self.slice(old_offset + i, block_length)
self.clear_ftpr_data(old_offset + i, len(block))
self.write_ftpr_data(new_offset + i, block)
else:
raise OutOfRegionException()
return new_offset
def remove_modules(self) -> int:
"""Remove modules."""
unremovable_huff_chunks = []
chunks_offsets = []
base = 0
chunk_size = 0
end_addr = 0
for mod_header in self.mod_headers:
name = self.bytes_to_ascii(mod_header[0x04:0x14])
offset = self.unpack_val(mod_header[0x38:0x3C])
size = self.unpack_val(mod_header[0x40:0x44])
flags = self.unpack_val(mod_header[0x50:0x54])
comp_type = (flags >> 4) & 7
comp_type_name = self.COMPRESSION_TYPE_NAME[comp_type]
print(" {:<16} ({:<7}, ".format(name, comp_type_name), end="")
# If compresion type uncompressed or LZMA
if comp_type == 0x00 or comp_type == 0x02:
offset_end = offset + size
range_msg = "0x{:06x} - 0x{:06x} ): "
print(range_msg.format(offset, offset_end), end="")
if name in self.UNREMOVABLE_MODULES:
end_addr = max(end_addr, offset + size)
print("NOT removed, essential")
else:
offset_end = min(offset + size, FTPR_END)
self.clear_ftpr_data(offset, offset_end)
print("removed")
# Else if compression type huffman
elif comp_type == 0x01:
if not chunks_offsets:
# Check if Local Look Up Table (LLUT) is present
if self.slice(offset, 4) == b"LLUT":
llut = self.slice(offset, 0x40)
chunk_count = self.unpack_val(llut[0x4:0x8])
base = self.unpack_val(llut[0x8:0xC]) + 0x10000000
chunk_size = self.unpack_val(llut[0x30:0x34])
llut = self.slice(offset, (chunk_count * 4) + 0x40)
# calculate offsets of chunks from LLUT
chunks_offsets = self.get_chunks_offsets(llut)
else:
no_llut_msg = "Huffman modules found,"
no_llut_msg += "but LLUT is not present."
sys.exit(no_llut_msg)
module_base = self.unpack_val(mod_header[0x34:0x38])
module_size = self.unpack_val(mod_header[0x3C:0x40])
first_chunk_num = (module_base - base) // chunk_size
last_chunk_num = first_chunk_num + module_size // chunk_size
huff_size = 0
chunk_length = last_chunk_num + 1
for chunk in chunks_offsets[first_chunk_num:chunk_length]:
huff_size += chunk[1] - chunk[0]
size_in_kiB = "~" + str(int(round(huff_size / 1024))) + " KiB"
print(
"fragmented data, {:<9}): ".format(size_in_kiB),
end="",
)
# Check if module is in the unremovable list
if name in self.UNREMOVABLE_MODULES:
print("NOT removed, essential")
# add to list of unremovable chunks
for x in chunks_offsets[first_chunk_num:chunk_length]:
if x[0] != 0:
unremovable_huff_chunks.append(x)
else:
print("removed")
# Else unknown compression type
else:
unkwn_comp_msg = " 0x{:06x} - 0x{:06x}): "
unkwn_comp_msg += "unknown compression, skipping"
print(unkwn_comp_msg.format(offset, offset + size), end="")
if chunks_offsets:
removable_huff_chunks = []
for chunk in chunks_offsets:
# if chunk is not in a unremovable chunk, it must be removable
if all(
not (
unremovable_chk[0] <= chunk[0] < unremovable_chk[1]
or unremovable_chk[0] < chunk[1] <= unremovable_chk[1]
)
for unremovable_chk in unremovable_huff_chunks
):
removable_huff_chunks.append(chunk)
for removable_chunk in removable_huff_chunks:
if removable_chunk[1] > removable_chunk[0]:
chunk_start = removable_chunk[0] - ORIG_FTPR_OFFSET
chunk_end = removable_chunk[1] - ORIG_FTPR_OFFSET
self.clear_ftpr_data(chunk_start, chunk_end)
end_addr = max(
end_addr, max(unremovable_huff_chunks, key=lambda x: x[1])[1]
)
end_addr -= ORIG_FTPR_OFFSET
return end_addr
def find_mod_header_size(self) -> None:
"""Find module header size."""
self.mod_header_size = 0
data = self.slice(0x290, 0x84)
# check header size
if data[0x0:0x4] == b"$MME":
if data[0x60:0x64] == b"$MME" or self.num_modules == 1:
self.mod_header_size = 0x60
elif data[0x80:0x84] == b"$MME":
self.mod_header_size = 0x80
def find_mod_headers(self) -> None:
"""Find module headers."""
data = self.slice(0x290, self.mod_header_size * self.num_modules)
for i in range(0, self.num_modules):
header_start = i * self.mod_header_size
header_end = (i + 1) * self.mod_header_size
self.mod_headers.append(data[header_start:header_end])
def resize_partition(self, end_addr: int) -> None:
"""Resize partition."""
spared_blocks = 4
if end_addr > 0:
end_addr = (end_addr // 0x1000 + 1) * 0x1000
end_addr += spared_blocks * 0x1000
# partition header not added yet
# remove trailing data the same size as the header.
end_addr -= MINIFIED_FTPR_OFFSET
me_size_msg = "The ME minimum size should be {0} "
me_size_msg += "bytes ({0:#x} bytes)"
print(me_size_msg.format(end_addr))
print("Truncating file at {:#x}...".format(end_addr))
self.ftpr = self.ftpr[:end_addr]
def check_and_clean_ftpr(self) -> None:
"""Check and clean FTPR (factory partition)."""
self.num_modules = self.unpack_next_int(0x20)
self.find_mod_header_size()
if self.mod_header_size != 0:
self.find_mod_headers()
# ensure all of the headers begin with b'$MME'
if all(hdr.startswith(b"$MME") for hdr in self.mod_headers):
end_addr = self.remove_modules()
new_offset = self.relocate_partition()
end_addr += new_offset
self.resize_partition(end_addr)
# flip bit
# XXX: I have no idea why this works and passes RSA signiture
self.write_ftpr_data(0x39, b"\x00")
else:
sys.exit(
"Found less modules than expected in the FTPR "
"partition; skipping modules removal and exiting."
)
else:
sys.exit(
"Can't find the module header size; skipping modules"
"removal and exiting."
)
##########################################################################
def check_partition_signature(f, offset) -> bool:
"""check_partition_signature copied/shamelessly stolen from me_cleaner."""
f.seek(offset)
header = f.read(0x80)
modulus = int(binascii.hexlify(f.read(0x100)[::-1]), 16)
public_exponent = unpack("<I", f.read(4))[0]
signature = int(binascii.hexlify(f.read(0x100)[::-1]), 16)
header_len = unpack("<I", header[0x4:0x8])[0] * 4
manifest_len = unpack("<I", header[0x18:0x1C])[0] * 4
f.seek(offset + header_len)
sha256 = hashlib.sha256()
sha256.update(header)
tmp = f.read(manifest_len - header_len)
sha256.update(tmp)
decrypted_sig = pow(signature, public_exponent, modulus)
return "{:#x}".format(decrypted_sig).endswith(sha256.hexdigest()) # FIXME
##########################################################################
def generate_me_blob(input_file: str, output_file: str) -> None:
"""Generate ME blob."""
print("Starting ME 7.x Update parser.")
orig_f = open(input_file, "rb")
cleaned_ftpr = clean_ftpr(orig_f.read(FTPR_END))
orig_f.close()
fo = open(output_file, "wb")
fo.write(generateHeader())
fo.write(generateFtpPartition())
fo.write(cleaned_ftpr.ftpr)
fo.close()
def verify_output(output_file: str) -> None:
"""Verify Generated ME file."""
file_verifiy = open(output_file, "rb")
if check_partition_signature(file_verifiy, MINIFIED_FTPR_OFFSET):
print(output_file + " is VALID")
file_verifiy.close()
else:
print(output_file + " is INVALID!!")
file_verifiy.close()
sys.exit("The FTPR partition signature is not valid.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Tool to remove as much code "
"as possible from Intel ME/TXE 7.x firmware "
"update and create paratition for a flashable ME parition."
)
parser.add_argument("file", help="ME/TXE image or full dump")
parser.add_argument(
"-O",
"--output",
metavar="output_file",
help="save "
"save file name other than the default '" + DEFAULT_OUTPUT_FILE_NAME + "'",
)
args = parser.parse_args()
output_file_name = DEFAULT_OUTPUT_FILE_NAME if not args.output else args.output
# Check if output file exists, ask to overwrite or exit
if os.path.isfile(output_file_name):
input_msg = output_file_name
input_msg += " exists. Do you want to overwrite? [y/N]: "
if not str(input(input_msg)).lower().startswith("y"):
sys.exit("Not overwriting file. Exiting.")
generate_me_blob(args.file, output_file_name)
verify_output(output_file_name)
-7
View File
@@ -1,7 +0,0 @@
/nvm
/nvmutil
/mkhtemp
/lottery
*.bin
*.o
*.d
-2
View File
@@ -1,2 +0,0 @@
Leah Rowe
Riku Viitanen
-21
View File
@@ -1,21 +0,0 @@
Copyright (C) 2022-2025 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.
-8
View File
@@ -1,8 +0,0 @@
This change log has moved. Please refer here for historical pre-osboot-merge
changes:
<https://libreboot.org/docs/install/nvmutilimport.html>
Osboot merged with Libreboot on November 17th, 2022. For nvmutil changes after
this date, please check regular Libreboot release announcements which shall
now specify any such changes.
-24
View File
@@ -1,24 +0,0 @@
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2022,2025 Leah Rowe <leah@libreboot.org>
# SPDX-FileCopyrightText: 2023 Riku Viitanen <riku.viitanen@protonmail.com>
CC?=cc
CFLAGS?=-Os -Wall -Wextra
DESTDIR?=
PREFIX?=/usr/local
INSTALL?=install
nvmutil: nvmutil.c
$(CC) $(CFLAGS) nvmutil.c -o nvmutil
install:
$(INSTALL) nvmutil $(DESTDIR)$(PREFIX)/bin/nvmutil
uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/nvmutil
distclean:
rm -f nvmutil
clean:
rm -f nvmutil
-4
View File
@@ -1,4 +0,0 @@
This documentation has become part of lbwww. See:
<https://libreboot.org/docs/install/nvmutil.html>
-764
View File
@@ -1,764 +0,0 @@
/* SPDX-License-Identifier: MIT */
/* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> */
/* Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com> */
#include <sys/types.h>
#include <sys/stat.h>
#include <err.h>
#include <errno.h>
#include <fcntl.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>
void cmd_setchecksum(void), cmd_brick(void), swap(int partnum), writeGbe(void),
cmd_dump(void), cmd_setmac(void), readGbe(void),
macf(int partnum), hexdump(int partnum), openFiles(const char *path),
cmd_copy(void), parseMacString(const char *strMac, uint16_t *mac),
cmd_swap(void), xclose(int *fd);
int goodChecksum(int partnum), open_on_eintr(const char *pathname, int flags),
fs_retry(int saved_errno, int rval),
rw_retry(int saved_errno, ssize_t rval), if_err(int condition, int errval),
if_err_sys(int condition);
ssize_t rw_exact(int fd, unsigned char *mem, size_t nrw,
off_t off, int rw_type);
ssize_t rw(int fd, void *mem, size_t nrw,
off_t off, int rw_type);
int io_args(int fd, void *mem, size_t nrw,
off_t off, int rw_type);
int with_fallback_errno(int fallback);
ssize_t rw_over_nrw(ssize_t r, size_t nrw);
uint8_t hextonum(char chs), rhex(void);
#define COMMAND argv[2]
#define MAC_ADDRESS argv[3]
#define PARTN argv[3]
#define NVM_CHECKSUM 0xBABA
#define NVM_CHECKSUM_WORD 0x3F
#define NVM_SIZE 128
#define SIZE_4KB 0x1000
#define SIZE_8KB 0x2000
#define SIZE_16KB 0x4000
#define SIZE_128KB 0x20000
#define IO_READ 0
#define IO_WRITE 1
#define IO_PREAD 2
#define IO_PWRITE 3
uint16_t mac[3] = {0, 0, 0};
ssize_t nf;
size_t partsize;
uintptr_t gbe[2];
uint8_t nvmPartChanged[2] = {0, 0}, do_read[2] = {1, 1};
int flags, rfd, fd, part;
const char *strMac = NULL, *strRMac = "xx:xx:xx:xx:xx:xx", *filename = NULL;
typedef struct op {
char *str;
void (*cmd)(void);
int args;
} op_t;
op_t op[] = {
{ .str = "dump", .cmd = cmd_dump, .args = 3},
{ .str = "setmac", .cmd = cmd_setmac, .args = 3},
{ .str = "swap", .cmd = cmd_swap, .args = 3},
{ .str = "copy", .cmd = cmd_copy, .args = 4},
{ .str = "brick", .cmd = cmd_brick, .args = 4},
{ .str = "setchecksum", .cmd = cmd_setchecksum, .args = 4},
};
void (*cmd)(void) = NULL;
#define err_if(x) \
do { \
if (x) { \
err(EXIT_FAILURE, "%s", filename); \
} \
} while(0)
#define xopen(f,l,p) \
do { \
if ((f = open_on_eintr(l, p)) == -1) { \
err(EXIT_FAILURE, "%s", l); \
} \
if (fstat(f, &st) == -1) { \
err(EXIT_FAILURE, "%s", l); \
} \
} while(0)
#define word(pos16, partnum) \
(((uint16_t *) gbe[partnum])[pos16])
#define setWord(pos16, p, val16) \
do { \
if (word(pos16, p) != val16) { \
nvmPartChanged[p] = 1 | (word(pos16, p) = val16); \
} \
} while(0)
#define SUCCESS(x) \
((x) >= 0)
#define reset_caller_errno(return_value) \
do { \
if (SUCCESS(return_value) && (!errno)) { \
errno = saved_errno; \
} \
} while (0)
int
main(int argc, char *argv[])
{
#ifdef __OpenBSD__
err_if(pledge("stdio rpath wpath unveil", NULL) == -1);
#endif
if (argc < 2) {
#ifdef __OpenBSD__
err_if(pledge("stdio", NULL) == -1);
#endif
fprintf(stderr, "Modify Intel GbE NVM images e.g. set MAC\n");
fprintf(stderr, "USAGE:\n");
fprintf(stderr, " %s FILE dump\n", argv[0]);
fprintf(stderr, " %s FILE\n # same as setmac without arg\n",
argv[0]);
fprintf(stderr, " %s FILE setmac [MAC]\n", argv[0]);
fprintf(stderr, " %s FILE swap\n", argv[0]);
fprintf(stderr, " %s FILE copy 0|1\n", argv[0]);
fprintf(stderr, " %s FILE brick 0|1\n", argv[0]);
fprintf(stderr, " %s FILE setchecksum 0|1\n", argv[0]);
errno = EINVAL;
err(EXIT_FAILURE, "Too few arguments");
}
filename = argv[1];
flags = O_RDWR;
if (argc > 2) {
if (strcmp(COMMAND, "dump") == 0) {
flags = O_RDONLY;
#ifdef __OpenBSD__
err_if(pledge("stdio rpath unveil", NULL) == -1);
#endif
}
}
#ifdef __OpenBSD__
err_if(unveil("/dev/urandom", "r") == -1);
if (flags == O_RDONLY) {
err_if(unveil(filename, "r") == -1);
err_if(unveil(NULL, NULL) == -1);
err_if(pledge("stdio rpath", NULL) == -1);
} else {
err_if(unveil(filename, "rw") == -1);
err_if(unveil(NULL, NULL) == -1);
err_if(pledge("stdio rpath wpath", NULL) == -1);
}
#endif
openFiles(filename);
#ifdef __OpenBSD__
err_if(pledge("stdio", NULL) == -1);
#endif
if (argc > 2) {
for (int i = 0; (i < 6) && (cmd == NULL); i++) {
if (strcmp(COMMAND, op[i].str) != 0)
continue;
if (argc >= op[i].args) {
cmd = op[i].cmd;
break;
}
errno = EINVAL;
err(EXIT_FAILURE, "Too few args on command '%s'",
op[i].str);
}
} else {
cmd = cmd_setmac;
}
if ((cmd == NULL) && (argc > 2)) { /* nvm gbe [MAC] */
strMac = COMMAND;
cmd = cmd_setmac;
} else if (cmd == cmd_setmac) { /* nvm gbe setmac [MAC] */
strMac = strRMac; /* random MAC */
if (argc > 3)
strMac = MAC_ADDRESS;
} else if ((cmd != NULL) && (argc > 3)) { /* user-supplied partnum */
err_if((errno = (!((part = PARTN[0] - '0') == 0 || part == 1))
|| PARTN[1] ? EINVAL : 0)); /* only allow '0' or '1' */
}
if (cmd == NULL) {
errno = EINVAL;
err(EXIT_FAILURE, "Bad command");
}
readGbe();
(*cmd)();
writeGbe();
return EXIT_SUCCESS;
}
void
openFiles(const char *path)
{
struct stat st;
xopen(rfd, "/dev/urandom", O_RDONLY);
xopen(fd, path, flags);
switch(st.st_size) {
case SIZE_8KB:
case SIZE_16KB:
case SIZE_128KB:
partsize = st.st_size >> 1;
break;
default:
errno = ECANCELED;
err(EXIT_FAILURE, "Invalid file size (not 8/16/128KiB)");
break;
}
if (if_err(!S_ISREG(st.st_mode), EBADF))
err(EXIT_FAILURE, "Not a GbE file");
}
void
readGbe(void)
{
if ((cmd == cmd_swap) || (cmd == cmd_copy))
nf = SIZE_4KB;
else
nf = NVM_SIZE;
if ((cmd == cmd_copy) || (cmd == cmd_setchecksum) || (cmd == cmd_brick))
do_read[part ^ 1] = 0;
char *buf = malloc(nf << (do_read[0] & do_read[1]));
if (buf == NULL)
err(EXIT_FAILURE, "malloc");
gbe[0] = (uintptr_t) buf;
gbe[1] = gbe[0] + (nf * (do_read[0] & do_read[1]));
ssize_t tnr = 0;
for (int p = 0; p < 2; p++) {
if (!do_read[p])
continue;
ssize_t nr = rw_exact(fd, (uint8_t *) gbe[p],
nf, p * partsize, IO_PREAD);
err_if(nr == -1);
if (nr != nf)
err(EXIT_FAILURE,
"%ld bytes read from '%s', expected %ld bytes\n",
nr, filename, nf);
tnr += nr;
swap(p); /* handle big-endian host CPU */
}
printf("%ld bytes read from file '%s'\n", tnr, filename);
}
void
cmd_setmac(void)
{
int mac_updated = 0;
parseMacString(strMac, mac);
printf("MAC address to be written: %s\n", strMac);
for (int partnum = 0; partnum < 2; partnum++) {
if (!goodChecksum(part = partnum))
continue;
for (int w = 0; w < 3; w++)
setWord(w, partnum, mac[w]);
printf("Wrote MAC address to part %d: ", partnum);
macf(partnum);
cmd_setchecksum();
mac_updated = 1;
}
if (mac_updated)
return;
errno = ECANCELED;
err(EXIT_FAILURE, "Error updating MAC address");
}
void
parseMacString(const char *strMac, uint16_t *mac)
{
uint64_t total = 0;
if (strnlen(strMac, 20) != 17) {
errno = EINVAL;
err(EXIT_FAILURE, "Invalid MAC address string length");
}
for (uint8_t h, i = 0; i < 16; i += 3) {
if (i != 15)
if (strMac[i + 2] != ':') {
errno = EINVAL;
err(EXIT_FAILURE,
"Invalid MAC address separator '%c'",
strMac[i + 2]);
}
int byte = i / 3;
for (int nib = 0; nib < 2; nib++, total += h) {
if ((h = hextonum(strMac[i + nib])) > 15) {
errno = EINVAL;
err(EXIT_FAILURE, "Invalid character '%c'",
strMac[i + nib]);
}
/* If random, ensure that local/unicast bits are set */
if ((byte == 0) && (nib == 1))
if ((strMac[i + nib] == '?') ||
(strMac[i + nib] == 'x') ||
(strMac[i + nib] == 'X')) /* random */
h = (h & 0xE) | 2; /* local, unicast */
mac[byte >> 1] |= ((uint16_t ) h)
<< ((8 * (byte % 2)) + (4 * (nib ^ 1)));
}
}
if (!((total == 0) || (mac[0] & 1)))
return;
errno = EINVAL;
if (total == 0)
err(EXIT_FAILURE, "Invalid MAC (all-zero MAC address)");
if (mac[0] & 1)
err(EXIT_FAILURE, "Invalid MAC (multicast bit set)");
}
uint8_t
hextonum(char ch)
{
if ((ch >= '0') && (ch <= '9'))
return ch - '0';
else if ((ch >= 'A') && (ch <= 'F'))
return ch - 'A' + 10;
else if ((ch >= 'a') && (ch <= 'f'))
return ch - 'a' + 10;
else if ((ch == '?') || (ch == 'x') || (ch == 'X'))
return rhex(); /* random hex value */
else
return 16; /* error: invalid character */
}
uint8_t
rhex(void)
{
static uint8_t n = 0, rnum[16];
if (!n)
err_if(rw_exact(rfd, (uint8_t *) &rnum,
(n = 15) + 1, 0, IO_READ) == -1);
return rnum[n--] & 0xf;
}
void
cmd_dump(void)
{
for (int partnum = 0, numInvalid = 0; partnum < 2; partnum++) {
if ((cmd != cmd_dump) && (flags != O_RDONLY) &&
(!nvmPartChanged[partnum]))
continue;
if (!goodChecksum(partnum))
++numInvalid;
printf("MAC (part %d): ", partnum);
macf(partnum);
hexdump(partnum);
if (numInvalid > 1) {
errno = EINVAL;
err(EXIT_FAILURE, "dump: No valid checksums");
}
}
}
void
macf(int partnum)
{
for (int c = 0; c < 3; c++) {
uint16_t val16 = word(c, partnum);
printf("%02x:%02x", val16 & 0xff, val16 >> 8);
if (c == 2)
printf("\n");
else
printf(":");
}
}
void
hexdump(int partnum)
{
for (int row = 0; row < 8; row++) {
printf("%08x ", row << 4);
for (int c = 0; c < 8; c++) {
uint16_t val16 = word((row << 3) + c, partnum);
if (c == 4)
printf(" ");
printf(" %02x %02x", val16 & 0xff, val16 >> 8);
}
printf("\n");
}
}
/* WARNING: Cannot fail. Make sure the file is valid. */
void
cmd_setchecksum(void)
{
uint16_t val16 = 0;
for (int c = 0; c < NVM_CHECKSUM_WORD; c++)
val16 += word(c, part);
setWord(NVM_CHECKSUM_WORD, part, NVM_CHECKSUM - val16);
}
void
cmd_brick(void)
{
if (goodChecksum(part)) {
setWord(NVM_CHECKSUM_WORD, part,
((word(NVM_CHECKSUM_WORD, part)) ^ 0xFF));
} else {
errno = ECANCELED;
err(EXIT_FAILURE, "brick: Bad checksum in part %d", part);
}
}
void
cmd_copy(void)
{
nvmPartChanged[part ^ 1] = goodChecksum(part);
if (!nvmPartChanged[part ^ 1]) {
errno = ECANCELED;
err(EXIT_FAILURE, "copy: Bad checksum in part %d", part);
}
}
void
cmd_swap(void) {
if(!(goodChecksum(0) || goodChecksum(1))) {
errno = ECANCELED;
err(EXIT_FAILURE, "swap: Bad checksums");
}
gbe[0] ^= gbe[1];
gbe[1] ^= gbe[0];
gbe[0] ^= gbe[1];
nvmPartChanged[0] = nvmPartChanged[1] = 1;
}
int
goodChecksum(int partnum)
{
uint16_t total = 0;
for(int w = 0; w <= NVM_CHECKSUM_WORD; w++)
total += word(w, partnum);
if (total == NVM_CHECKSUM)
return 1;
fprintf(stderr, "WARNING: BAD checksum in part %d\n", partnum);
return 0;
}
void
writeGbe(void)
{
ssize_t tnw = 0;
for (int p = 0; p < 2; p++) {
if ((!nvmPartChanged[p]) || (flags == O_RDONLY))
continue;
swap(p); /* swap bytes on big-endian host CPUs */
ssize_t nw = rw_exact(fd, (uint8_t *) gbe[p], nf,
p * partsize, IO_PWRITE);
err_if(nw == -1);
if (nw != nf) {
errno = ECANCELED;
err(EXIT_FAILURE,
"%ld bytes written to '%s', expected %ld bytes\n",
nw, filename, nf);
}
tnw += nf;
}
if ((flags != O_RDONLY) && (cmd != cmd_dump)) {
if (nvmPartChanged[0] || nvmPartChanged[1])
printf("The following nvm words were written:\n");
cmd_dump();
}
if ((!tnw) && (flags != O_RDONLY))
fprintf(stderr, "No changes needed on file '%s'\n", filename);
else if (tnw)
printf("%ld bytes written to file '%s'\n", tnw, filename);
xclose(&fd);
}
void
xclose(int *fd)
{
int saved_errno = errno;
int rval = 0;
if (fd == NULL) {
errno = EBADF;
err(EXIT_FAILURE, "xclose: null pointer");
}
if (*fd < 0)
return;
errno = 0;
if ((rval = close(*fd)) < 0) {
if (errno != EINTR)
err(EXIT_FAILURE, "xclose: could not close");
/* regard EINTR as a successful close */
rval = 0;
}
*fd = -1;
reset_caller_errno(rval);
}
void
swap(int partnum)
{
size_t w, x;
uint8_t *n = (uint8_t *) gbe[partnum];
int e = 1;
for (w = NVM_SIZE * ((uint8_t *) &e)[0], x = 1; w < NVM_SIZE;
w += 2, x += 2) {
n[w] ^= n[x];
n[x] ^= n[w];
n[w] ^= n[x];
}
}
int
open_on_eintr(const char *pathname,
int flags)
{
int saved_errno = errno;
int rval = 0;
errno = 0;
while (fs_retry(saved_errno,
rval = open(pathname, flags)));
reset_caller_errno(rval);
return rval;
}
ssize_t
rw_exact(int fd, unsigned char *mem, size_t nrw,
off_t off, int rw_type)
{
int saved_errno = errno;
ssize_t rval = 0;
ssize_t rc = 0;
size_t nrw_cur;
off_t off_cur;
void *mem_cur;
errno = 0;
if (io_args(fd, mem, nrw, off, rw_type) == -1)
goto err_rw_exact;
while (1) {
/* Prevent theoretical overflow */
if (if_err(rval >= 0 && (size_t)rval > (nrw - (size_t)rc),
EOVERFLOW))
goto err_rw_exact;
rc += rval;
if ((size_t)rc >= nrw)
break;
mem_cur = (void *)(mem + (size_t)rc);
nrw_cur = (size_t)(nrw - (size_t)rc);
if (if_err(off < 0, EOVERFLOW))
goto err_rw_exact;
off_cur = off + (off_t)rc;
if ((rval = rw(fd, mem_cur, nrw_cur, off_cur, rw_type)) <= 0)
goto err_rw_exact;
}
if (if_err((size_t)rc != nrw, EIO) ||
(rval = rw_over_nrw(rc, nrw)) < 0)
goto err_rw_exact;
reset_caller_errno(rval);
return rval;
err_rw_exact:
return with_fallback_errno(EIO);
}
ssize_t
rw(int fd, void *mem, size_t nrw,
off_t off, int rw_type)
{
ssize_t rval = 0;
ssize_t r = -1;
int saved_errno = errno;
errno = 0;
if (io_args(fd, mem, nrw, off, rw_type) == -1 ||
if_err(mem == NULL, EFAULT) ||
if_err(fd < 0, EBADF) ||
if_err(off < 0, EFAULT) ||
if_err(nrw == 0, EINVAL))
return with_fallback_errno(EIO);
do {
switch (rw_type) {
case IO_READ:
r = read(fd, mem, nrw);
break;
case IO_WRITE:
r = write(fd, mem, nrw);
break;
case IO_PREAD:
r = pread(fd, mem, nrw, off);
break;
case IO_PWRITE:
r = pwrite(fd, mem, nrw, off);
break;
default:
errno = EINVAL;
break;
}
} while (rw_retry(saved_errno, r));
if ((rval = rw_over_nrw(r, nrw)) < 0)
return with_fallback_errno(EIO);
reset_caller_errno(rval);
return rval;
}
int
io_args(int fd, void *mem, size_t nrw,
off_t off, int rw_type)
{
int saved_errno = errno;
errno = 0;
if (if_err(mem == NULL, EFAULT) ||
if_err(fd < 0, EBADF) ||
if_err(off < 0, ERANGE) ||
if_err(!nrw, EPERM) || /* TODO: toggle zero-byte check */
if_err(nrw > (size_t)SSIZE_MAX, ERANGE) ||
if_err(((size_t)off + nrw) < (size_t)off, ERANGE) ||
if_err(rw_type > IO_PWRITE, EINVAL))
goto err_io_args;
reset_caller_errno(0);
return 0;
err_io_args:
return with_fallback_errno(EINVAL);
}
ssize_t
rw_over_nrw(ssize_t r, size_t nrw)
{
if (if_err(!nrw, EIO) ||
(r == -1) ||
if_err((size_t)r > SSIZE_MAX, ERANGE) ||
if_err((size_t)r > nrw, ERANGE))
return with_fallback_errno(EIO);
return r;
}
int
with_fallback_errno(int fallback)
{
if (!errno)
errno = fallback;
return -1;
}
/* two functions that reduce sloccount by
* two hundred lines */
int
if_err(int condition, int errval)
{
if (!condition)
return 0;
if (errval)
errno = errval;
return 1;
}
int
if_err_sys(int condition)
{
if (!condition)
return 0;
return 1;
}
#define fs_err_retry() \
do { \
if ((rval == -1) && \
(errno == EINTR)) { \
return 1; \
} \
if (rval >= 0 && !errno) { \
errno = saved_errno; \
} \
} while(0)
int
fs_retry(int saved_errno, int rval)
{
fs_err_retry();
return 0;
}
int
rw_retry(int saved_errno, ssize_t rval)
{
fs_err_retry();
return 0;
}
-1
View File
@@ -1 +0,0 @@
spkmodem-recv
-14
View File
@@ -1,14 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-or-later
CC?=cc
CFLAGS?=-Os -Wall -Wextra
DESTDIR?=
PREFIX?=/usr/local
INSTALL?=install
spkmodem-recv:
$(CC) $(CFLAGS) -o $@ $@.c
install: spkmodem-recv
$(INSTALL) -d $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) $< -t $(DESTDIR)$(PREFIX)/bin/
clean:
rm -f spkmodem-recv
-123
View File
@@ -1,123 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* SPDX-FileCopyrightText: 2013 Free Software Foundation, Inc. */
/* Usage: parec --channels=1 --rate=48000 --format=s16le | ./spkmodem-recv */
/* Forked from coreboot's version, at util/spkmodem_recv/ in coreboot.git,
* revision 5c2b5fcf2f9c9259938fd03cfa3ea06b36a007f0 as of 3 January 2022.
* This version is heavily modified, re-written based on OpenBSD Kernel Source
* File Style Guide (KNF); this change is Copyright 2023 Leah Rowe. */
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SAMPLES_PER_FRAME 240
#define MAX_SAMPLES (2 * SAMPLES_PER_FRAME)
#define FREQ_SEP_MIN 5
#define FREQ_SEP_MAX 15
#define FREQ_DATA_MIN 15
#define FREQ_DATA_THRESHOLD 25
#define FREQ_DATA_MAX 60
#define THRESHOLD 500
#define reset_char() ascii = 0, ascii_bit = 7
signed short frame[MAX_SAMPLES], pulse[MAX_SAMPLES];
int ringpos, debug, freq_data, freq_separator, sample_count, ascii_bit = 7;
char ascii = 0;
void handle_audio(void);
void decode_pulse(void);
int set_ascii_bit(void);
void print_char(void);
void print_stats(void);
int
main(int argc, char *argv[])
{
int c;
#ifdef __OpenBSD__
if (pledge("stdio", NULL) == -1)
err(EXIT_FAILURE, "pledge");
#endif
while ((c = getopt(argc, argv, "d")) != -1)
if (!(debug = (c == 'd'))) {
errno = EINVAL;
err(EXIT_FAILURE, "%c: Invalid option", c);
}
setvbuf(stdout, NULL, _IONBF, 0);
while (!feof(stdin))
handle_audio();
return EXIT_SUCCESS;
}
void
handle_audio(void)
{
if (sample_count > (3 * SAMPLES_PER_FRAME))
sample_count = reset_char();
if ((freq_separator <= FREQ_SEP_MIN) || (freq_separator >= FREQ_SEP_MAX)
|| (freq_data <= FREQ_DATA_MIN) || (freq_data >= FREQ_DATA_MAX)) {
decode_pulse();
return;
}
if (set_ascii_bit() < 0)
print_char();
sample_count = 0;
for (int sample = 0; sample < SAMPLES_PER_FRAME; sample++)
decode_pulse();
}
void
decode_pulse(void)
{
int next_ringpos = (ringpos + SAMPLES_PER_FRAME) % MAX_SAMPLES;
freq_data -= pulse[ringpos];
freq_data += pulse[next_ringpos];
freq_separator -= pulse[next_ringpos];
fread(frame + ringpos, 1, sizeof(frame[0]), stdin);
if (ferror(stdin) != 0)
err(EXIT_FAILURE, "Could not read from frame.");
if ((pulse[ringpos] = (abs(frame[ringpos]) > THRESHOLD) ? 1 : 0))
++freq_separator;
++ringpos;
ringpos %= MAX_SAMPLES;
++sample_count;
}
int
set_ascii_bit(void)
{
if (debug)
print_stats();
if (freq_data < FREQ_DATA_THRESHOLD)
ascii |= (1 << ascii_bit);
--ascii_bit;
return ascii_bit;
}
void
print_char(void)
{
if (debug)
printf("<%c, %x>", ascii, ascii);
else
printf("%c", ascii);
reset_char();
}
void
print_stats(void)
{
long stdin_pos;
if ((stdin_pos = ftell(stdin)) == -1)
err(EXIT_FAILURE, NULL);
printf ("%d %d %d @%ld\n", freq_data, freq_separator,
FREQ_DATA_THRESHOLD, stdin_pos - sizeof(frame));
}