Persist USB boot policy in Limine snapshot manifests
This commit is contained in:
1 parent
4ea410d6a1
commit
666800eb0c
2 files changed
+115
-6
No files matched your search
@@ -338,6 +338,75 @@ usb_authorization_rewrite_boot_cmdlines() {
|
||||
rm -f "$backup"
|
||||
}
|
||||
|
||||
usb_authorization_rewrite_snapshot_manifest() {
|
||||
local expected="$1" manifest="$2" temporary
|
||||
|
||||
[[ -e $manifest ]] || return 0
|
||||
temporary=$(mktemp "${manifest}.omarchy-usb.XXXXXXXXXX")
|
||||
if ! cp --preserve=mode,ownership "$manifest" "$temporary"; then
|
||||
rm -f "$temporary"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Limine uses the cached lines when present, and structured command lines
|
||||
# otherwise. Update both, including nested kernels and restore entries.
|
||||
if ! jq --arg expected "$expected" --arg parameter "$kernel_parameter" '
|
||||
def cmdline:
|
||||
if type != "string" then error("Invalid snapshot command line") else
|
||||
[splits("[[:space:]]+") | select(length > 0 and . != $parameter)]
|
||||
| if $expected == "enabled" then . + [$parameter] else . end
|
||||
| join(" ")
|
||||
end;
|
||||
def lines:
|
||||
map(if test("^\\s*(cmdline|kernel_cmdline):"; "i") then
|
||||
capture("^(?<prefix>\\s*(?:cmdline|kernel_cmdline):\\s*)(?<value>.*)$"; "i")
|
||||
| .prefix + (.value | cmdline)
|
||||
else . end);
|
||||
def kernel:
|
||||
.cmdlineDetails |= map(.cmdline |= cmdline | .snapshotCmdline |= cmdline)
|
||||
| .allInConfig |= lines
|
||||
| .allInSnapshotConfig |= lines
|
||||
| .subKernels |= map(kernel);
|
||||
if .jsonFormatVersion != "1.3.0" or (.snapshotEntries | type) != "array" then
|
||||
error("Unsupported Limine snapshot manifest format")
|
||||
else
|
||||
.snapshotEntries |= map(.kernelEntries |= map(kernel))
|
||||
end
|
||||
' "$manifest" >"$temporary"; then
|
||||
rm -f "$temporary"
|
||||
echo "Cannot update stored USB boot policy in $manifest." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
mv -f "$temporary" "$manifest"
|
||||
}
|
||||
|
||||
usb_authorization_sync_and_verify() (
|
||||
local expected="$1" config="$2" esp="$3"
|
||||
local machine_id="${4:-$(</etc/machine-id)}" manifest
|
||||
|
||||
# Share Limine's lock so a concurrent watcher cannot restore an old manifest
|
||||
# while we update it. The sync wrapper must not reacquire our lock.
|
||||
exec 9>/run/lock/boot-partition.lock
|
||||
flock --timeout 30 9 || return 1
|
||||
|
||||
# Recover/upgrade the manifest first, including backups if necessary.
|
||||
limine-snapper-sync --no-mutex --no-hooks || return 1
|
||||
for manifest in \
|
||||
"$esp/$machine_id/limine_history/snapshots.json" \
|
||||
"$esp/$machine_id/limine_history/snapshots.json.old" \
|
||||
"/var/cache/boot/$machine_id/lss/snapshots.json"; do
|
||||
usb_authorization_rewrite_snapshot_manifest "$expected" "$manifest" || return 1
|
||||
done
|
||||
|
||||
usb_authorization_rewrite_boot_cmdlines "$expected" "$config" "$machine_id" || return 1
|
||||
# Verify the regenerated menu, so synchronization cannot mask stale stored
|
||||
# command lines with a one-time edit to limine.conf.
|
||||
limine-snapper-sync --no-mutex --no-hooks || return 1
|
||||
limine-enroll-config || return 1
|
||||
usb_authorization_verify_boot_entries "$expected" "$config" "$esp" "$machine_id"
|
||||
)
|
||||
|
||||
main() {
|
||||
local had_drop_in=0 had_snapshot_setting=0 snapshot_status temporary
|
||||
|
||||
@@ -377,8 +446,7 @@ main() {
|
||||
fi
|
||||
|
||||
if ! limine-mkinitcpio ||
|
||||
! usb_authorization_rewrite_boot_cmdlines enabled "$limine_conf" ||
|
||||
! usb_authorization_verify_boot_entries enabled "$limine_conf" "$boot_path"; then
|
||||
! usb_authorization_sync_and_verify enabled "$limine_conf" "$boot_path"; then
|
||||
echo "Boot-time USB authorization remains configured in $drop_in; rerun this command after fixing the boot-image error." >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -401,14 +469,11 @@ main() {
|
||||
fi
|
||||
had_drop_in=1
|
||||
rm -f "$drop_in"
|
||||
elif (( ! had_snapshot_setting )) && usb_authorization_verify_boot_entries disabled "$limine_conf" "$boot_path"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
usb_authorization_disable_snapshot_setting "$limine_defaults"
|
||||
if ! limine-mkinitcpio ||
|
||||
! usb_authorization_rewrite_boot_cmdlines disabled "$limine_conf" ||
|
||||
! usb_authorization_verify_boot_entries disabled "$limine_conf" "$boot_path"; then
|
||||
! usb_authorization_sync_and_verify disabled "$limine_conf" "$boot_path"; then
|
||||
if (( had_drop_in )); then
|
||||
printf '%s\n' "$setting" >"$drop_in"
|
||||
chmod 644 "$drop_in"
|
||||
|
||||
@@ -269,6 +269,50 @@ usb_authorization_disable_snapshot_setting "$snapshot_config"
|
||||
fail "removing boot authorization restores future snapshot defaults"
|
||||
pass "boot authorization verifies generated entries despite Limine false-success exits"
|
||||
|
||||
manifest="$scratch/snapshots.json"
|
||||
jq -n --arg hash "$boot_hash" '
|
||||
def kernel:
|
||||
{cmdlineDetails: [{limineKey: "CMDLINE", cmdline: "quiet rootflags=subvol=@", snapshotCmdline: "quiet rootflags=subvol=/snapshot"}],
|
||||
allInConfig: [" cmdline: quiet rootflags=subvol=@"],
|
||||
allInSnapshotConfig: ["comment: kernel-id=linux", "path: boot():/vmlinuz-linux#" + $hash, "cmdline: quiet rootflags=subvol=/snapshot"],
|
||||
subKernels: [], properties: {untouched: "usbcore.authorized_default=0"}};
|
||||
{jsonFormatVersion: "1.3.0", snapshotEntries: [{kernelEntries: [kernel | .subKernels = [kernel | .allInSnapshotConfig = []]]}], uuid: "preserve-me"}
|
||||
' >"$manifest"
|
||||
cp "$manifest" "$scratch/manifest-original.json"
|
||||
for expected in enabled disabled; do
|
||||
usb_authorization_rewrite_snapshot_manifest "$expected" "$manifest"
|
||||
cp "$manifest" "$scratch/manifest-once.json"
|
||||
usb_authorization_rewrite_snapshot_manifest "$expected" "$manifest"
|
||||
cmp -s "$manifest" "$scratch/manifest-once.json" || fail "manifest updates are idempotent"
|
||||
# Model both Limine render paths: cached lines and structured details.
|
||||
for representation in cached structured; do
|
||||
{
|
||||
printf '/Omarchy\ncomment: machine-id=%s\n //linux\n' "$machine_id"
|
||||
if [[ $representation == "cached" ]]; then
|
||||
jq -r '.snapshotEntries[0].kernelEntries[0].allInSnapshotConfig[]' "$manifest"
|
||||
else
|
||||
printf 'comment: kernel-id=linux\npath: boot():/vmlinuz-linux#%s\n' "$boot_hash"
|
||||
jq -r '"cmdline: " + .snapshotEntries[0].kernelEntries[0].subKernels[0].cmdlineDetails[0].snapshotCmdline' "$manifest"
|
||||
fi
|
||||
} >"$scratch/regenerated.conf"
|
||||
usb_authorization_verify_boot_entries "$expected" "$scratch/regenerated.conf" "$scratch" "$machine_id" disabled ||
|
||||
fail "snapshot synchronization preserves $expected policy from $representation entries"
|
||||
done
|
||||
jq -e '.snapshotEntries[0].kernelEntries[0].properties.untouched == "usbcore.authorized_default=0"' "$manifest" >/dev/null ||
|
||||
fail "manifest changes preserve unrelated metadata"
|
||||
done
|
||||
jq -S . "$manifest" >"$scratch/manifest-after.json"
|
||||
jq -S . "$scratch/manifest-original.json" >"$scratch/manifest-before.json"
|
||||
cmp -s "$scratch/manifest-before.json" "$scratch/manifest-after.json" ||
|
||||
fail "disable restores all stored command lines without changing other snapshot data"
|
||||
printf '{bad json' >"$scratch/bad-manifest.json"
|
||||
if usb_authorization_rewrite_snapshot_manifest enabled "$scratch/bad-manifest.json" 2>/dev/null; then
|
||||
fail "invalid manifests must fail closed"
|
||||
fi
|
||||
[[ $(<"$scratch/bad-manifest.json") == '{bad json' ]] || fail "failed manifest updates preserve the original"
|
||||
pass "USB boot policy survives snapshot regeneration from cached and structured manifests"
|
||||
|
||||
|
||||
: >"$calls"
|
||||
"$ROOT/bin/omarchy-remove-security-usb-authorization" --boot-only --yes >"$scratch/remove-boot-output"
|
||||
grep -Fqx 'sudo <omarchy-usb-authorization-boot> <disable>' "$calls" ||
|
||||
|
||||
Reference in new issue
Block a user