Skip to content

Commit 6a88cd5

Browse files
doug445claude
andcommitted
The integrity check is read-only for every filesystem: no repairing fallback, no stdin for a fix question, errors to the FORCE gate
The optional check offered before encryption is called read-only, and for f2fs it was not: fsck.f2fs --dry-run || fsck.f2fs -a. A fsck.f2fs without --dry-run, or one that found errors, went on to -a, a repairing mode. -a also exits 0 when it finds nothing it was told to fix, so on the real tools (f2fs-tools 1.16) a damaged image that --dry-run reported with exit 4 came back from the check as PASSED, every time - the FORCE gate never saw the errors. Audited, every filesystem's check: - ext2/3/4 e2fsck -f -n, btrfs check --readonly, xfs_repair -n, vfat fsck.fat -n: read-only already; unchanged but for stdin. - f2fs: fsck.f2fs --dry-run alone. Whether it has --dry-run is read from its usage first (a bare fsck.f2fs touches no device); without it the check is skipped with a warning saying why. No -a, ever. - ntfs: ntfsfix --no-action alone. ntfsfix || ntfsresize --info let ntfsresize overrule an ntfsfix that refused the volume; both are read-only, but the refusal was an error the FORCE gate never saw. Same usage probe; without --no-action, a skip. - every checker now runs with stdin from /dev/null. On damaged metadata fsck.f2fs --dry-run asks "restore lost files? [Y/N]"; answered y, f2fs- tools 1.16 segfaults; with stdin closed it exits 255, an error. - a missing tool, or a filesystem with no read-only checker, is a skip, reported as one - before, "cannot check" (exit 2) was shown as errors. The verdict goes to a new integrity_check: clean, PASSED; skipped, a loud warning that nothing was checked or repaired; errors, "Nothing was repaired", the FORCE gate as before - and, before FORCE, what the shrink that follows will do if you go on: ext2/3/4 run e2fsck -f -p then -y, f2fs fsck.f2fs -f, both repairing, unless an earlier run already shrank it. That shrink-phase fsck is unchanged; it is now stated, not implied. Each check was run against the real tools on clean and damaged image files first: none writes a byte (sha256 before and after), and the exit codes used here are theirs - e2fsck 0/4, fsck.fat 0/1, ntfsfix 0/1, fsck.f2fs --dry-run 0/4/255, xfs_repair and btrfs check 0. Tests, uki-fixture-test.sh section 12 (no root): fs_check_ro against fake checkers that record every call and whether stdin reached them - a fsck.f2fs that rejects --dry-run must lead to a skip and no -a, no checker may run anything but its read-only mode or be handed stdin, ntfsresize may never decide; integrity_check's skip, pass, errors-to-FORCE and its warning about the shrink; and fs_check_ro against the real tools on image files, damaged ones included: every image byte-identical, the verdict the checker's own. Against 1.6.6 it reports 15 failures, among them the -a call and both damaged f2fs images passed. CI installs the filesystem tools in the uki-fixtures job; no job is renamed. docs/FILESYSTEMS.md gains the check's table and what each outcome does, and its shrink cells say their fsck repairs; INSTALL and SECURITY match. uki 145/0 (1 skip) on the M2 Max, and again with every Asahi signal hidden in a user namespace; cmdline 71/0; bash -n and shellcheck -S warning clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 259256c commit 6a88cd5

6 files changed

Lines changed: 371 additions & 25 deletions

File tree

‎.github/workflows/lint.yml‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,12 @@ jobs:
6363
runs-on: ${{ matrix.runner }}
6464
steps:
6565
- uses: actions/checkout@v7
66-
- name: install binutils (objcopy, for .cmdline extraction)
67-
run: sudo apt-get update -q && sudo apt-get install -y -q binutils
66+
# binutils: objcopy, for .cmdline extraction. The filesystem tools: the
67+
# integrity check runs against real checkers on small image files.
68+
- name: install binutils + filesystem tools
69+
run: |
70+
sudo apt-get update -q
71+
sudo apt-get install -y -q binutils e2fsprogs f2fs-tools ntfs-3g dosfstools btrfs-progs xfsprogs
6872
- name: UKI fixture test
6973
run: bash tests/uki-fixture-test.sh
7074

‎SECURITY.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ interesting surface:
162162
redaction failing to apply when it was not disabled with `--no-redact`.
163163
* **A read-only tool that writes.** `luks-tune.sh` must never create or destroy
164164
a keyslot, change a passphrase, or touch data; `--dry-run` anywhere must reach
165-
no point of no return and write nothing to the target;
165+
no point of no return and write nothing to the target; the integrity check
166+
offered before encryption must write nothing and repair nothing;
166167
`extras/luks-fetch-cache` must read public header
167168
metadata and nothing else, and its cache at `/var/cache/luks-fetch.txt` is
168169
world-readable by design, so anything resembling key material appearing in it

‎bin/luks-deploy.sh‎

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -779,21 +779,93 @@ fs_bytes() {
779779
esac
780780
}
781781

782-
# fs_check_ro <dev> <fstype> — read-only integrity check. Returns the checker's
783-
# exit status; returns 2 (distinct "cannot check") when no checker exists.
782+
# fs_check_ro <dev> <fstype> — the optional pre-encryption check, read-only for
783+
# every filesystem: e2fsck -f -n, btrfs check --readonly, xfs_repair -n,
784+
# fsck.f2fs --dry-run, ntfsfix --no-action, fsck.fat -n — each writes nothing,
785+
# clean or damaged. Each runs with stdin from /dev/null, so none can put a fix
786+
# question to you (fsck.f2fs asks one on damaged metadata; answered, it can
787+
# crash). Returns the checker's status: 0 clean, anything else errors found.
788+
#
789+
# Nothing here repairs, and nothing falls back to a mode that does — until
790+
# 1.6.7 a refused or failed fsck.f2fs --dry-run went on to fsck.f2fs -a, a
791+
# repairing mode that also turned "errors found" into a pass, and ntfsfix's
792+
# refusal was overruled by ntfsresize --info. Where this machine has no
793+
# read-only checker for <fstype> (a fsck.f2fs without --dry-run, an ntfsfix
794+
# without --no-action, a missing tool), FS_CHECK_SKIP says why and it returns
795+
# 0 having checked nothing; integrity_check reports that as a skip.
796+
FS_CHECK_SKIP=""
784797
fs_check_ro() {
785-
local dev="$1" fstype="$2"
798+
local dev="$1" fstype="$2" usage
799+
FS_CHECK_SKIP=""
786800
case "$fstype" in
787-
ext2|ext3|ext4) e2fsck -f -n "$dev" ;;
788-
btrfs) btrfs check --readonly "$dev" 2>&1 | tail -3 ;;
789-
xfs) xfs_repair -n "$dev" ;;
790-
ntfs) ntfsfix --no-action "$dev" 2>&1 || ntfsresize --info --force "$dev" >/dev/null ;;
791-
f2fs) fsck.f2fs --dry-run "$dev" 2>&1 || fsck.f2fs -a "$dev" ;;
792-
vfat) fsck.fat -n "$dev" ;;
793-
*) return 2 ;;
801+
ext2|ext3|ext4)
802+
command -v e2fsck >/dev/null 2>&1 || { FS_CHECK_SKIP="e2fsck is not installed"; return 0; }
803+
e2fsck -f -n "$dev" </dev/null ;;
804+
btrfs)
805+
command -v btrfs >/dev/null 2>&1 || { FS_CHECK_SKIP="btrfs-progs is not installed"; return 0; }
806+
btrfs check --readonly "$dev" </dev/null 2>&1 | tail -3 ;;
807+
xfs)
808+
command -v xfs_repair >/dev/null 2>&1 || { FS_CHECK_SKIP="xfs_repair is not installed"; return 0; }
809+
xfs_repair -n "$dev" </dev/null ;;
810+
ntfs)
811+
command -v ntfsfix >/dev/null 2>&1 || { FS_CHECK_SKIP="ntfsfix is not installed"; return 0; }
812+
usage=$(ntfsfix --help </dev/null 2>&1 || true)
813+
case "$usage" in
814+
*--no-action*) ntfsfix --no-action "$dev" </dev/null 2>&1 ;;
815+
*) FS_CHECK_SKIP="this ntfsfix has no --no-action mode, so it cannot check without writing"; return 0 ;;
816+
esac ;;
817+
f2fs)
818+
command -v fsck.f2fs >/dev/null 2>&1 || { FS_CHECK_SKIP="fsck.f2fs is not installed"; return 0; }
819+
# A bare fsck.f2fs prints its usage and touches no device.
820+
usage=$(fsck.f2fs </dev/null 2>&1 || true)
821+
case "$usage" in
822+
*--dry-run*) fsck.f2fs --dry-run "$dev" </dev/null 2>&1 ;;
823+
*) FS_CHECK_SKIP="this fsck.f2fs has no --dry-run mode, so it cannot check without repairing"; return 0 ;;
824+
esac ;;
825+
vfat)
826+
command -v fsck.fat >/dev/null 2>&1 || { FS_CHECK_SKIP="fsck.fat is not installed"; return 0; }
827+
fsck.fat -n "$dev" </dev/null ;;
828+
*)
829+
FS_CHECK_SKIP="there is no read-only checker for $fstype"; return 0 ;;
794830
esac
795831
}
796832

833+
# --- integrity_check ---
834+
# integrity_check <dev> <fstype> — run fs_check_ro and act on what it found.
835+
# Clean: say so. No read-only checker here: say so, loudly, and go on without
836+
# a check — never a repairing fallback. Errors: report them, say what the
837+
# shrink that follows would do about them, and stop unless FORCE is typed.
838+
# Nothing in this step repairs anything.
839+
integrity_check() {
840+
local dev="$1" fstype="$2" fsck_override=""
841+
log " Running read-only $fstype check (this may take a while)..."
842+
if fs_check_ro "$dev" "$fstype"; then
843+
if [ -n "$FS_CHECK_SKIP" ]; then
844+
warn " Filesystem check SKIPPED — $FS_CHECK_SKIP."
845+
warn " Nothing was checked, and nothing was repaired. If you can, check $dev"
846+
warn " with a read-only tool yourself before going on."
847+
else
848+
log " Filesystem check: PASSED"
849+
fi
850+
return 0
851+
fi
852+
err " Filesystem check found errors! Nothing was repaired."
853+
if [ "${DEPLOY_MODE:-encrypt}" = "encrypt" ]; then
854+
case "$fstype" in
855+
ext2|ext3|ext4)
856+
warn " If you go on, the shrink before encryption (unless an earlier run already"
857+
warn " shrank it) runs e2fsck -f -p, then e2fsck -f -y: it repairs what it finds,"
858+
warn " answering yes to every fix." ;;
859+
f2fs)
860+
warn " If you go on, the shrink before encryption (unless an earlier run already"
861+
warn " shrank it) runs fsck.f2fs -f, which repairs what it finds." ;;
862+
esac
863+
fi
864+
ask fsck_override LUKS_FSCK_FORCE " Continue despite filesystem errors? (Type 'FORCE' to override): "
865+
[ "$fsck_override" = "FORCE" ] || fatal "Fix filesystem errors before encrypting."
866+
}
867+
# --- end integrity_check ---
868+
797869
# fs_shrink <dev> <fstype> — shrink the filesystem by SHRINK_MB so the LUKS2
798870
# header fits. Fatal on failure (nothing has been encrypted yet; aborting is
799871
# free). Caller has already verified the device is unmounted.
@@ -2464,15 +2536,7 @@ else
24642536
fi
24652537
case "$DO_CHECK" in [Nn]|[Nn][Oo]) DO_CHECK=n ;; esac
24662538
if [ "$DO_CHECK" != "n" ]; then
2467-
log " Running read-only $ORIG_FSTYPE check (this may take a while)..."
2468-
if fs_check_ro "$CHECK_DEV" "$ORIG_FSTYPE"; then
2469-
log " Filesystem check: PASSED"
2470-
else
2471-
err " Filesystem check found errors!"
2472-
fsck_override=""
2473-
ask fsck_override LUKS_FSCK_FORCE " Continue despite filesystem errors? (Type 'FORCE' to override): "
2474-
[ "$fsck_override" = "FORCE" ] || fatal "Fix filesystem errors before encrypting."
2475-
fi
2539+
integrity_check "$CHECK_DEV" "$ORIG_FSTYPE"
24762540
else
24772541
warn " Skipping filesystem check."
24782542
fi

‎docs/FILESYSTEMS.md‎

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,43 @@ Everything gnome-disks can format is classified below.
99

1010
| Filesystem | In place? | Shrink tool | Size probe (idempotency guard) | Notes |
1111
|---|---|---|---|---|
12-
| ext4 / ext3 / ext2 | ✅ | `resize2fs` (offline, after forced `e2fsck`) | `dumpe2fs` block count × block size | grow-back via `resize2fs` with no size argument |
12+
| ext4 / ext3 / ext2 | ✅ | `resize2fs` (offline, after a repairing `e2fsck -f -p`, then `-y`) | `dumpe2fs` block count × block size | grow-back via `resize2fs` with no size argument |
1313
| btrfs | ✅ | `btrfs filesystem resize -32M` (mounted) | `dump-super total_bytes` | subvolume layouts (`root`, `@`, `@rootfs`, flat) auto-detected; grow via `resize max` |
1414
| xfs | ⚠️ slack only | **none — XFS cannot shrink** | `xfs_db` dblocks × blocksize | accepted only when ≥ 32 MiB of slack already exists between filesystem end and partition end; grow via `xfs_growfs` |
15-
| f2fs | ✅ | `resize.f2fs -t` (needs f2fs-tools ≥ 1.16) | none — the script asks on re-runs whether a previous shrink completed | common on SBC/flash installs |
15+
| f2fs | ✅ | `resize.f2fs -t` (needs f2fs-tools ≥ 1.16), after a repairing `fsck.f2fs -f` | none — the script asks on re-runs whether a previous shrink completed | common on SBC/flash installs |
1616
| ntfs | ✅ | `ntfsresize --size` | `ntfsresize --info` | data partitions; the resize marks the volume for a consistency check on the next Windows boot (normal) |
1717
| vfat | ✅ | `fatresize` | none — asks on re-runs | data partitions / firmware copies |
1818
| exfat | ❌ | no shrink tool exists | — | back up → `mkfs.exfat` on a LUKS container → restore |
1919
| udf | ❌ | no shrink tool exists | — | same workaround as exfat |
2020
| swap | ❌ (by design) | — | — | swap holds no data worth an in-place conversion; see below |
2121

22+
## The integrity check
23+
24+
Before anything is changed, a real run offers to check the target
25+
filesystem (`LUKS_FSCK=yes|no`; a dry run skips it). The check is read-only
26+
for every filesystem: no checker runs in a mode that can repair, and none is
27+
handed your keyboard — stdin is closed, so no fix question can be answered.
28+
29+
| Filesystem | Check |
30+
|---|---|
31+
| ext4 / ext3 / ext2 | `e2fsck -f -n` |
32+
| btrfs | `btrfs check --readonly` |
33+
| xfs | `xfs_repair -n` |
34+
| f2fs | `fsck.f2fs --dry-run` |
35+
| ntfs | `ntfsfix --no-action` |
36+
| vfat | `fsck.fat -n` |
37+
38+
- **Errors** are reported and nothing is repaired; the run stops unless you
39+
type `FORCE` (`LUKS_FSCK_FORCE=FORCE`). If you go on, know that the shrink
40+
that follows runs a repairing check of its own, because its tools need a
41+
consistent filesystem — ext2/3/4 `e2fsck -f -p` and then `e2fsck -f -y`,
42+
f2fs `fsck.f2fs -f` — unless an earlier run already shrank it. Both repair
43+
what they find; the `FORCE` prompt says so.
44+
- **No read-only checker on this machine** — a `fsck.f2fs` without
45+
`--dry-run`, an `ntfsfix` without `--no-action`, a missing tool — and the
46+
check is skipped with a warning that says why. It never falls back to a
47+
repairing mode, as f2fs's did until 1.6.7 (`fsck.f2fs -a`).
48+
2249
## System vs data partitions
2350

2451
- A partition containing `/etc/fstab` is treated as an **installed system**:

‎docs/INSTALL.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ The deploy script then walks you through:
5656
- **Filesystem identification** — the filesystem's tools are auto-installed
5757
the moment the type is known (e.g. `e2fsprogs` for ext4, `ntfsprogs` for
5858
NTFS).
59-
- **Integrity check** — optional read-only fsck before anything changes.
59+
- **Integrity check** — optional, and read-only for every filesystem: errors
60+
stop the run unless you type `FORCE`, and where this machine has no
61+
read-only checker the check is skipped with a warning. Nothing is repaired
62+
here; see [FILESYSTEMS.md](FILESYSTEMS.md#the-integrity-check).
6063
- **KDF profile** — aggressive / moderate / fast, with unlock-time estimates
6164
benchmarked on the machine in front of you.
6265
- **The `ENCRYPT` gate** — a typed confirmation. Immediately before

0 commit comments

Comments
 (0)