stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling - #1

Merged
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg
Jul 14, 2026
Merged

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling#1
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg

Conversation

@HarryR

Copy link
Copy Markdown
Collaborator

Summary

Reworks stage2 /data persistence and the payload-forging tooling. Net change from main:

  • /data: dm-crypt aes-xts-plain64 (AES-256-XTS) instead of the planned authenticated dm-integrity + AEAD. Authenticated encryption (no internal hash) makes a never-written sector EIO on read, and ext4 online-resize does read-modify-write of new-group metadata in the region it grows into, so the resize aborts unless the whole grow region is pre-wiped (O(volume), unviable on large volumes). XTS decrypts unwritten sectors to garbage, so resize needs no wipe. Trade: at-rest tamper garbles rather than being detected; media integrity is left to the storage layer, raw-disk adversary out of scope. Key drift still fails closed.
  • PID 1 owns failure: a fatal boot error logs, waits 60s, then ACPI power-offs, instead of a returning init panicking the kernel and spinning a vCPU.
  • meta_bg ext4 base for unbounded online growth (no resize= cap / journal bump).
  • mkruntime.sh built out: env knobs, tar-on-stdin / tar / dir / binary inputs (docker export as an example, not a dependency), <out> as a file or - for stdout, and --bootstrap to emit a ready-to-run packed stage2. Makefile pack uses it.

Verification

  • make ci green (fmt + clippy -D warnings + tests)
  • PIVOT_TEST=PASS (config passthrough, attest chain, resize, persist-across-reboot, ephemeral overlay)
  • GROW_TEST=PASS -- grew /data to ~12 GiB across a meta_bg boundary, no wipe, sparse hole reads zero

Review notes / open threads

  • meta_bg gives unbounded growth; resize_inode + a resize= cap is the bounded alternative if a hard ceiling is ever wanted.
  • The dm-verity salt is a single fixed value namespacing the whole tree (not per-node); a Merkle per-node domain-separation review is parked as a separate long-thought.
  • Backlog (not in this PR): real stage2 TPM quote, docker->erofs make front-end, aarch64, digest-pinned erofs-builder.

Draft for review.

🤖 Generated with Claude Code

/data: replace the planned authenticated dm-integrity + dm-crypt AEAD stack with
confidentiality-only dm-crypt aes-xts-plain64 (AES-256-XTS). Authenticated
encryption has no internal hash, so a never-written sector EIOs on read; ext4
online-resize does read-modify-write of new-group metadata (backup GDT via
ext4_setup_new_descs) in the region it grows into, so the resize aborts unless
the whole grow region is pre-wiped first -- O(volume), unacceptable on large
volumes. XTS decrypts unwritten sectors to garbage instead of erroring, so the
resize needs no wipe (the standard LUKS/dm-crypt pattern) and cold-boot cost is
independent of volume size. Trade: at-rest tampering garbles plaintext rather
than being cryptographically detected -- media integrity is left to the storage
layer (EBS et al.), and a raw-disk-write adversary is out of scope. Key drift
still fails closed (superblock decrypts to garbage -> no ext4 magic -> reinit).
64-byte TPM-derived key, new key label.
loader: as PID 1, own fatal boot errors instead of returning. A returning init
panics the kernel ("Attempted to kill init") and spins a vCPU at 100% forever
(in prod and the QEMU harness). Now: log, 60s grace for console capture, then a
clean ACPI power-off.
ext4 /data base: meta_bg (drop resize_inode) for unbounded online growth; no
resize= ceiling or journal bump needed. Verified growing to ~12 GiB across a
meta_bg boundary with no wipe.
mkruntime.sh: env knobs (SOURCE_DATE_EPOCH, DATA_SIZE_MB, ROOTFS_UUID,
VERITY_SALT) with reproducible defaults; rootfs input now accepts a tar stream
on stdin, a tar file, a directory, or a single binary (docker export documented
as an example, not a dependency); <out> is a file path or - for stdout; and
--bootstrap prepends the loader ELF to emit a ready-to-run packed stage2. The
Makefile pack target uses --bootstrap and forwards the knobs.
tests: pivot-init drops the tamper-detection check (property removed with the
AEAD stack); add grow-probe (large-volume meta_bg growth + sparse-hole). Prune
unused probe examples. make ci green; PIVOT_TEST=PASS and GROW_TEST=PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR marked this pull request as ready for review July 13, 2026 18:03
…E restructure
Make lockboot:erofs-builder a one-shot CLI: bake tools/mkruntime.sh in as the
ENTRYPOINT, so packaging no longer needs a repo bind-mount -- mount just the loader
ELF, pipe a rootfs in on stdin, get the stage2 out on stdout. Drop coreutils
(busybox covers touch -d @epoch, stat -c, tar, install, awk); output stays
byte-identical (same root hash), image 20.3 -> 18.2 MB. Makefile pack passes args
straight to the entrypoint.
Restructure the README to lead with what it is and how to use it (the mkruntime
one-liner with the built bootstrap), and move how-it-boots / security model /
testing into a Technical details section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HarryR added a commit to lockboot/stage0 that referenced this pull request Jul 13, 2026
## What
Bring the stage0 build image and disk layout in line with the Fedora 44
chain bump, plus stable disk identity.
## Changes
- **rpm + gnupg** added to `Dockerfile.build` so the UKI build can
verify Fedora package GPG signatures (image parity with stage1, kept
byte-identical). No effect on compiled artifacts.
- **Stable lockboot GPT GUIDs + FAT volume-id.** `DISK_GUID` /
`PART_GUID` are now constant across releases (`4c4f434b-424f-4f54-...`,
"LOCKBOOT" + a role index), so the initial GPT is byte-stable (stable
pre-stage2 PCR5) and the boot disk is identifiable by a fixed GUID
rather than by hashing stage0.efi (which changed per release). This is
the stable identity stage2's `find_boot_device` can key on. (stage2
still rewrites the GPT at runtime to add p2/p3, which is why it excludes
PCR5 from its key binding.)
- **Pin `vaportpm-attest` to v0.3.0** (rev + version guard), matching
stage1.
- **Extract the shared `DOCKER_RUN` harness** into `build.mk` (same
refactor as stage1).
## Verification
- Reproducible: GUIDs/timestamps fixed; build output byte-stable.
- No change to the runtime boot path. Pairs with lockboot/stage1#19
(Fedora 44 kernel/stub) and lockboot/stage2#1.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Clean check-context names (no spaces/parens) so the uniform branch ruleset across
the lockboot repos can require the same names everywhere.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR merged commit fd1fa37 into mainJul 14, 2026
4 checks passed
@HarryR
HarryR deleted the data-xts-meta_bg branch July 14, 2026 11:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling - #1

Merged
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg
Jul 14, 2026
Merged

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling#1
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg

Conversation

@HarryR

Copy link
Copy Markdown
Collaborator

Summary

Reworks stage2 /data persistence and the payload-forging tooling. Net change from main:

  • /data: dm-crypt aes-xts-plain64 (AES-256-XTS) instead of the planned authenticated dm-integrity + AEAD. Authenticated encryption (no internal hash) makes a never-written sector EIO on read, and ext4 online-resize does read-modify-write of new-group metadata in the region it grows into, so the resize aborts unless the whole grow region is pre-wiped (O(volume), unviable on large volumes). XTS decrypts unwritten sectors to garbage, so resize needs no wipe. Trade: at-rest tamper garbles rather than being detected; media integrity is left to the storage layer, raw-disk adversary out of scope. Key drift still fails closed.
  • PID 1 owns failure: a fatal boot error logs, waits 60s, then ACPI power-offs, instead of a returning init panicking the kernel and spinning a vCPU.
  • meta_bg ext4 base for unbounded online growth (no resize= cap / journal bump).
  • mkruntime.sh built out: env knobs, tar-on-stdin / tar / dir / binary inputs (docker export as an example, not a dependency), <out> as a file or - for stdout, and --bootstrap to emit a ready-to-run packed stage2. Makefile pack uses it.

Verification

  • make ci green (fmt + clippy -D warnings + tests)
  • PIVOT_TEST=PASS (config passthrough, attest chain, resize, persist-across-reboot, ephemeral overlay)
  • GROW_TEST=PASS -- grew /data to ~12 GiB across a meta_bg boundary, no wipe, sparse hole reads zero

Review notes / open threads

  • meta_bg gives unbounded growth; resize_inode + a resize= cap is the bounded alternative if a hard ceiling is ever wanted.
  • The dm-verity salt is a single fixed value namespacing the whole tree (not per-node); a Merkle per-node domain-separation review is parked as a separate long-thought.
  • Backlog (not in this PR): real stage2 TPM quote, docker->erofs make front-end, aarch64, digest-pinned erofs-builder.

Draft for review.

🤖 Generated with Claude Code

/data: replace the planned authenticated dm-integrity + dm-crypt AEAD stack with
confidentiality-only dm-crypt aes-xts-plain64 (AES-256-XTS). Authenticated
encryption has no internal hash, so a never-written sector EIOs on read; ext4
online-resize does read-modify-write of new-group metadata (backup GDT via
ext4_setup_new_descs) in the region it grows into, so the resize aborts unless
the whole grow region is pre-wiped first -- O(volume), unacceptable on large
volumes. XTS decrypts unwritten sectors to garbage instead of erroring, so the
resize needs no wipe (the standard LUKS/dm-crypt pattern) and cold-boot cost is
independent of volume size. Trade: at-rest tampering garbles plaintext rather
than being cryptographically detected -- media integrity is left to the storage
layer (EBS et al.), and a raw-disk-write adversary is out of scope. Key drift
still fails closed (superblock decrypts to garbage -> no ext4 magic -> reinit).
64-byte TPM-derived key, new key label.
loader: as PID 1, own fatal boot errors instead of returning. A returning init
panics the kernel ("Attempted to kill init") and spins a vCPU at 100% forever
(in prod and the QEMU harness). Now: log, 60s grace for console capture, then a
clean ACPI power-off.
ext4 /data base: meta_bg (drop resize_inode) for unbounded online growth; no
resize= ceiling or journal bump needed. Verified growing to ~12 GiB across a
meta_bg boundary with no wipe.
mkruntime.sh: env knobs (SOURCE_DATE_EPOCH, DATA_SIZE_MB, ROOTFS_UUID,
VERITY_SALT) with reproducible defaults; rootfs input now accepts a tar stream
on stdin, a tar file, a directory, or a single binary (docker export documented
as an example, not a dependency); <out> is a file path or - for stdout; and
--bootstrap prepends the loader ELF to emit a ready-to-run packed stage2. The
Makefile pack target uses --bootstrap and forwards the knobs.
tests: pivot-init drops the tamper-detection check (property removed with the
AEAD stack); add grow-probe (large-volume meta_bg growth + sparse-hole). Prune
unused probe examples. make ci green; PIVOT_TEST=PASS and GROW_TEST=PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR marked this pull request as ready for review July 13, 2026 18:03
…E restructure
Make lockboot:erofs-builder a one-shot CLI: bake tools/mkruntime.sh in as the
ENTRYPOINT, so packaging no longer needs a repo bind-mount -- mount just the loader
ELF, pipe a rootfs in on stdin, get the stage2 out on stdout. Drop coreutils
(busybox covers touch -d @epoch, stat -c, tar, install, awk); output stays
byte-identical (same root hash), image 20.3 -> 18.2 MB. Makefile pack passes args
straight to the entrypoint.
Restructure the README to lead with what it is and how to use it (the mkruntime
one-liner with the built bootstrap), and move how-it-boots / security model /
testing into a Technical details section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HarryR added a commit to lockboot/stage0 that referenced this pull request Jul 13, 2026
## What
Bring the stage0 build image and disk layout in line with the Fedora 44
chain bump, plus stable disk identity.
## Changes
- **rpm + gnupg** added to `Dockerfile.build` so the UKI build can
verify Fedora package GPG signatures (image parity with stage1, kept
byte-identical). No effect on compiled artifacts.
- **Stable lockboot GPT GUIDs + FAT volume-id.** `DISK_GUID` /
`PART_GUID` are now constant across releases (`4c4f434b-424f-4f54-...`,
"LOCKBOOT" + a role index), so the initial GPT is byte-stable (stable
pre-stage2 PCR5) and the boot disk is identifiable by a fixed GUID
rather than by hashing stage0.efi (which changed per release). This is
the stable identity stage2's `find_boot_device` can key on. (stage2
still rewrites the GPT at runtime to add p2/p3, which is why it excludes
PCR5 from its key binding.)
- **Pin `vaportpm-attest` to v0.3.0** (rev + version guard), matching
stage1.
- **Extract the shared `DOCKER_RUN` harness** into `build.mk` (same
refactor as stage1).
## Verification
- Reproducible: GUIDs/timestamps fixed; build output byte-stable.
- No change to the runtime boot path. Pairs with lockboot/stage1#19
(Fedora 44 kernel/stub) and lockboot/stage2#1.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Clean check-context names (no spaces/parens) so the uniform branch ruleset across
the lockboot repos can require the same names everywhere.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR merged commit fd1fa37 into mainJul 14, 2026
4 checks passed
@HarryR
HarryR deleted the data-xts-meta_bg branch July 14, 2026 11:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling - #1

Merged
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg
Jul 14, 2026
Merged

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling#1
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg

Conversation

@HarryR

Copy link
Copy Markdown
Collaborator

Summary

Reworks stage2 /data persistence and the payload-forging tooling. Net change from main:

  • /data: dm-crypt aes-xts-plain64 (AES-256-XTS) instead of the planned authenticated dm-integrity + AEAD. Authenticated encryption (no internal hash) makes a never-written sector EIO on read, and ext4 online-resize does read-modify-write of new-group metadata in the region it grows into, so the resize aborts unless the whole grow region is pre-wiped (O(volume), unviable on large volumes). XTS decrypts unwritten sectors to garbage, so resize needs no wipe. Trade: at-rest tamper garbles rather than being detected; media integrity is left to the storage layer, raw-disk adversary out of scope. Key drift still fails closed.
  • PID 1 owns failure: a fatal boot error logs, waits 60s, then ACPI power-offs, instead of a returning init panicking the kernel and spinning a vCPU.
  • meta_bg ext4 base for unbounded online growth (no resize= cap / journal bump).
  • mkruntime.sh built out: env knobs, tar-on-stdin / tar / dir / binary inputs (docker export as an example, not a dependency), <out> as a file or - for stdout, and --bootstrap to emit a ready-to-run packed stage2. Makefile pack uses it.

Verification

  • make ci green (fmt + clippy -D warnings + tests)
  • PIVOT_TEST=PASS (config passthrough, attest chain, resize, persist-across-reboot, ephemeral overlay)
  • GROW_TEST=PASS -- grew /data to ~12 GiB across a meta_bg boundary, no wipe, sparse hole reads zero

Review notes / open threads

  • meta_bg gives unbounded growth; resize_inode + a resize= cap is the bounded alternative if a hard ceiling is ever wanted.
  • The dm-verity salt is a single fixed value namespacing the whole tree (not per-node); a Merkle per-node domain-separation review is parked as a separate long-thought.
  • Backlog (not in this PR): real stage2 TPM quote, docker->erofs make front-end, aarch64, digest-pinned erofs-builder.

Draft for review.

🤖 Generated with Claude Code

/data: replace the planned authenticated dm-integrity + dm-crypt AEAD stack with
confidentiality-only dm-crypt aes-xts-plain64 (AES-256-XTS). Authenticated
encryption has no internal hash, so a never-written sector EIOs on read; ext4
online-resize does read-modify-write of new-group metadata (backup GDT via
ext4_setup_new_descs) in the region it grows into, so the resize aborts unless
the whole grow region is pre-wiped first -- O(volume), unacceptable on large
volumes. XTS decrypts unwritten sectors to garbage instead of erroring, so the
resize needs no wipe (the standard LUKS/dm-crypt pattern) and cold-boot cost is
independent of volume size. Trade: at-rest tampering garbles plaintext rather
than being cryptographically detected -- media integrity is left to the storage
layer (EBS et al.), and a raw-disk-write adversary is out of scope. Key drift
still fails closed (superblock decrypts to garbage -> no ext4 magic -> reinit).
64-byte TPM-derived key, new key label.
loader: as PID 1, own fatal boot errors instead of returning. A returning init
panics the kernel ("Attempted to kill init") and spins a vCPU at 100% forever
(in prod and the QEMU harness). Now: log, 60s grace for console capture, then a
clean ACPI power-off.
ext4 /data base: meta_bg (drop resize_inode) for unbounded online growth; no
resize= ceiling or journal bump needed. Verified growing to ~12 GiB across a
meta_bg boundary with no wipe.
mkruntime.sh: env knobs (SOURCE_DATE_EPOCH, DATA_SIZE_MB, ROOTFS_UUID,
VERITY_SALT) with reproducible defaults; rootfs input now accepts a tar stream
on stdin, a tar file, a directory, or a single binary (docker export documented
as an example, not a dependency); <out> is a file path or - for stdout; and
--bootstrap prepends the loader ELF to emit a ready-to-run packed stage2. The
Makefile pack target uses --bootstrap and forwards the knobs.
tests: pivot-init drops the tamper-detection check (property removed with the
AEAD stack); add grow-probe (large-volume meta_bg growth + sparse-hole). Prune
unused probe examples. make ci green; PIVOT_TEST=PASS and GROW_TEST=PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR marked this pull request as ready for review July 13, 2026 18:03
…E restructure
Make lockboot:erofs-builder a one-shot CLI: bake tools/mkruntime.sh in as the
ENTRYPOINT, so packaging no longer needs a repo bind-mount -- mount just the loader
ELF, pipe a rootfs in on stdin, get the stage2 out on stdout. Drop coreutils
(busybox covers touch -d @epoch, stat -c, tar, install, awk); output stays
byte-identical (same root hash), image 20.3 -> 18.2 MB. Makefile pack passes args
straight to the entrypoint.
Restructure the README to lead with what it is and how to use it (the mkruntime
one-liner with the built bootstrap), and move how-it-boots / security model /
testing into a Technical details section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HarryR added a commit to lockboot/stage0 that referenced this pull request Jul 13, 2026
## What
Bring the stage0 build image and disk layout in line with the Fedora 44
chain bump, plus stable disk identity.
## Changes
- **rpm + gnupg** added to `Dockerfile.build` so the UKI build can
verify Fedora package GPG signatures (image parity with stage1, kept
byte-identical). No effect on compiled artifacts.
- **Stable lockboot GPT GUIDs + FAT volume-id.** `DISK_GUID` /
`PART_GUID` are now constant across releases (`4c4f434b-424f-4f54-...`,
"LOCKBOOT" + a role index), so the initial GPT is byte-stable (stable
pre-stage2 PCR5) and the boot disk is identifiable by a fixed GUID
rather than by hashing stage0.efi (which changed per release). This is
the stable identity stage2's `find_boot_device` can key on. (stage2
still rewrites the GPT at runtime to add p2/p3, which is why it excludes
PCR5 from its key binding.)
- **Pin `vaportpm-attest` to v0.3.0** (rev + version guard), matching
stage1.
- **Extract the shared `DOCKER_RUN` harness** into `build.mk` (same
refactor as stage1).
## Verification
- Reproducible: GUIDs/timestamps fixed; build output byte-stable.
- No change to the runtime boot path. Pairs with lockboot/stage1#19
(Fedora 44 kernel/stub) and lockboot/stage2#1.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Clean check-context names (no spaces/parens) so the uniform branch ruleset across
the lockboot repos can require the same names everywhere.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR merged commit fd1fa37 into mainJul 14, 2026
4 checks passed
@HarryR
HarryR deleted the data-xts-meta_bg branch July 14, 2026 11:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling - #1

Merged
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg
Jul 14, 2026
Merged

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling#1
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg

Conversation

@HarryR

Copy link
Copy Markdown
Collaborator

Summary

Reworks stage2 /data persistence and the payload-forging tooling. Net change from main:

  • /data: dm-crypt aes-xts-plain64 (AES-256-XTS) instead of the planned authenticated dm-integrity + AEAD. Authenticated encryption (no internal hash) makes a never-written sector EIO on read, and ext4 online-resize does read-modify-write of new-group metadata in the region it grows into, so the resize aborts unless the whole grow region is pre-wiped (O(volume), unviable on large volumes). XTS decrypts unwritten sectors to garbage, so resize needs no wipe. Trade: at-rest tamper garbles rather than being detected; media integrity is left to the storage layer, raw-disk adversary out of scope. Key drift still fails closed.
  • PID 1 owns failure: a fatal boot error logs, waits 60s, then ACPI power-offs, instead of a returning init panicking the kernel and spinning a vCPU.
  • meta_bg ext4 base for unbounded online growth (no resize= cap / journal bump).
  • mkruntime.sh built out: env knobs, tar-on-stdin / tar / dir / binary inputs (docker export as an example, not a dependency), <out> as a file or - for stdout, and --bootstrap to emit a ready-to-run packed stage2. Makefile pack uses it.

Verification

  • make ci green (fmt + clippy -D warnings + tests)
  • PIVOT_TEST=PASS (config passthrough, attest chain, resize, persist-across-reboot, ephemeral overlay)
  • GROW_TEST=PASS -- grew /data to ~12 GiB across a meta_bg boundary, no wipe, sparse hole reads zero

Review notes / open threads

  • meta_bg gives unbounded growth; resize_inode + a resize= cap is the bounded alternative if a hard ceiling is ever wanted.
  • The dm-verity salt is a single fixed value namespacing the whole tree (not per-node); a Merkle per-node domain-separation review is parked as a separate long-thought.
  • Backlog (not in this PR): real stage2 TPM quote, docker->erofs make front-end, aarch64, digest-pinned erofs-builder.

Draft for review.

🤖 Generated with Claude Code

/data: replace the planned authenticated dm-integrity + dm-crypt AEAD stack with
confidentiality-only dm-crypt aes-xts-plain64 (AES-256-XTS). Authenticated
encryption has no internal hash, so a never-written sector EIOs on read; ext4
online-resize does read-modify-write of new-group metadata (backup GDT via
ext4_setup_new_descs) in the region it grows into, so the resize aborts unless
the whole grow region is pre-wiped first -- O(volume), unacceptable on large
volumes. XTS decrypts unwritten sectors to garbage instead of erroring, so the
resize needs no wipe (the standard LUKS/dm-crypt pattern) and cold-boot cost is
independent of volume size. Trade: at-rest tampering garbles plaintext rather
than being cryptographically detected -- media integrity is left to the storage
layer (EBS et al.), and a raw-disk-write adversary is out of scope. Key drift
still fails closed (superblock decrypts to garbage -> no ext4 magic -> reinit).
64-byte TPM-derived key, new key label.
loader: as PID 1, own fatal boot errors instead of returning. A returning init
panics the kernel ("Attempted to kill init") and spins a vCPU at 100% forever
(in prod and the QEMU harness). Now: log, 60s grace for console capture, then a
clean ACPI power-off.
ext4 /data base: meta_bg (drop resize_inode) for unbounded online growth; no
resize= ceiling or journal bump needed. Verified growing to ~12 GiB across a
meta_bg boundary with no wipe.
mkruntime.sh: env knobs (SOURCE_DATE_EPOCH, DATA_SIZE_MB, ROOTFS_UUID,
VERITY_SALT) with reproducible defaults; rootfs input now accepts a tar stream
on stdin, a tar file, a directory, or a single binary (docker export documented
as an example, not a dependency); <out> is a file path or - for stdout; and
--bootstrap prepends the loader ELF to emit a ready-to-run packed stage2. The
Makefile pack target uses --bootstrap and forwards the knobs.
tests: pivot-init drops the tamper-detection check (property removed with the
AEAD stack); add grow-probe (large-volume meta_bg growth + sparse-hole). Prune
unused probe examples. make ci green; PIVOT_TEST=PASS and GROW_TEST=PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR marked this pull request as ready for review July 13, 2026 18:03
…E restructure
Make lockboot:erofs-builder a one-shot CLI: bake tools/mkruntime.sh in as the
ENTRYPOINT, so packaging no longer needs a repo bind-mount -- mount just the loader
ELF, pipe a rootfs in on stdin, get the stage2 out on stdout. Drop coreutils
(busybox covers touch -d @epoch, stat -c, tar, install, awk); output stays
byte-identical (same root hash), image 20.3 -> 18.2 MB. Makefile pack passes args
straight to the entrypoint.
Restructure the README to lead with what it is and how to use it (the mkruntime
one-liner with the built bootstrap), and move how-it-boots / security model /
testing into a Technical details section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HarryR added a commit to lockboot/stage0 that referenced this pull request Jul 13, 2026
## What
Bring the stage0 build image and disk layout in line with the Fedora 44
chain bump, plus stable disk identity.
## Changes
- **rpm + gnupg** added to `Dockerfile.build` so the UKI build can
verify Fedora package GPG signatures (image parity with stage1, kept
byte-identical). No effect on compiled artifacts.
- **Stable lockboot GPT GUIDs + FAT volume-id.** `DISK_GUID` /
`PART_GUID` are now constant across releases (`4c4f434b-424f-4f54-...`,
"LOCKBOOT" + a role index), so the initial GPT is byte-stable (stable
pre-stage2 PCR5) and the boot disk is identifiable by a fixed GUID
rather than by hashing stage0.efi (which changed per release). This is
the stable identity stage2's `find_boot_device` can key on. (stage2
still rewrites the GPT at runtime to add p2/p3, which is why it excludes
PCR5 from its key binding.)
- **Pin `vaportpm-attest` to v0.3.0** (rev + version guard), matching
stage1.
- **Extract the shared `DOCKER_RUN` harness** into `build.mk` (same
refactor as stage1).
## Verification
- Reproducible: GUIDs/timestamps fixed; build output byte-stable.
- No change to the runtime boot path. Pairs with lockboot/stage1#19
(Fedora 44 kernel/stub) and lockboot/stage2#1.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Clean check-context names (no spaces/parens) so the uniform branch ruleset across
the lockboot repos can require the same names everywhere.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR merged commit fd1fa37 into mainJul 14, 2026
4 checks passed
@HarryR
HarryR deleted the data-xts-meta_bg branch July 14, 2026 11:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling - #1

Merged
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg
Jul 14, 2026
Merged

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling#1
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg

Conversation

@HarryR

Copy link
Copy Markdown
Collaborator

Summary

Reworks stage2 /data persistence and the payload-forging tooling. Net change from main:

  • /data: dm-crypt aes-xts-plain64 (AES-256-XTS) instead of the planned authenticated dm-integrity + AEAD. Authenticated encryption (no internal hash) makes a never-written sector EIO on read, and ext4 online-resize does read-modify-write of new-group metadata in the region it grows into, so the resize aborts unless the whole grow region is pre-wiped (O(volume), unviable on large volumes). XTS decrypts unwritten sectors to garbage, so resize needs no wipe. Trade: at-rest tamper garbles rather than being detected; media integrity is left to the storage layer, raw-disk adversary out of scope. Key drift still fails closed.
  • PID 1 owns failure: a fatal boot error logs, waits 60s, then ACPI power-offs, instead of a returning init panicking the kernel and spinning a vCPU.
  • meta_bg ext4 base for unbounded online growth (no resize= cap / journal bump).
  • mkruntime.sh built out: env knobs, tar-on-stdin / tar / dir / binary inputs (docker export as an example, not a dependency), <out> as a file or - for stdout, and --bootstrap to emit a ready-to-run packed stage2. Makefile pack uses it.

Verification

  • make ci green (fmt + clippy -D warnings + tests)
  • PIVOT_TEST=PASS (config passthrough, attest chain, resize, persist-across-reboot, ephemeral overlay)
  • GROW_TEST=PASS -- grew /data to ~12 GiB across a meta_bg boundary, no wipe, sparse hole reads zero

Review notes / open threads

  • meta_bg gives unbounded growth; resize_inode + a resize= cap is the bounded alternative if a hard ceiling is ever wanted.
  • The dm-verity salt is a single fixed value namespacing the whole tree (not per-node); a Merkle per-node domain-separation review is parked as a separate long-thought.
  • Backlog (not in this PR): real stage2 TPM quote, docker->erofs make front-end, aarch64, digest-pinned erofs-builder.

Draft for review.

🤖 Generated with Claude Code

/data: replace the planned authenticated dm-integrity + dm-crypt AEAD stack with
confidentiality-only dm-crypt aes-xts-plain64 (AES-256-XTS). Authenticated
encryption has no internal hash, so a never-written sector EIOs on read; ext4
online-resize does read-modify-write of new-group metadata (backup GDT via
ext4_setup_new_descs) in the region it grows into, so the resize aborts unless
the whole grow region is pre-wiped first -- O(volume), unacceptable on large
volumes. XTS decrypts unwritten sectors to garbage instead of erroring, so the
resize needs no wipe (the standard LUKS/dm-crypt pattern) and cold-boot cost is
independent of volume size. Trade: at-rest tampering garbles plaintext rather
than being cryptographically detected -- media integrity is left to the storage
layer (EBS et al.), and a raw-disk-write adversary is out of scope. Key drift
still fails closed (superblock decrypts to garbage -> no ext4 magic -> reinit).
64-byte TPM-derived key, new key label.
loader: as PID 1, own fatal boot errors instead of returning. A returning init
panics the kernel ("Attempted to kill init") and spins a vCPU at 100% forever
(in prod and the QEMU harness). Now: log, 60s grace for console capture, then a
clean ACPI power-off.
ext4 /data base: meta_bg (drop resize_inode) for unbounded online growth; no
resize= ceiling or journal bump needed. Verified growing to ~12 GiB across a
meta_bg boundary with no wipe.
mkruntime.sh: env knobs (SOURCE_DATE_EPOCH, DATA_SIZE_MB, ROOTFS_UUID,
VERITY_SALT) with reproducible defaults; rootfs input now accepts a tar stream
on stdin, a tar file, a directory, or a single binary (docker export documented
as an example, not a dependency); <out> is a file path or - for stdout; and
--bootstrap prepends the loader ELF to emit a ready-to-run packed stage2. The
Makefile pack target uses --bootstrap and forwards the knobs.
tests: pivot-init drops the tamper-detection check (property removed with the
AEAD stack); add grow-probe (large-volume meta_bg growth + sparse-hole). Prune
unused probe examples. make ci green; PIVOT_TEST=PASS and GROW_TEST=PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR marked this pull request as ready for review July 13, 2026 18:03
…E restructure
Make lockboot:erofs-builder a one-shot CLI: bake tools/mkruntime.sh in as the
ENTRYPOINT, so packaging no longer needs a repo bind-mount -- mount just the loader
ELF, pipe a rootfs in on stdin, get the stage2 out on stdout. Drop coreutils
(busybox covers touch -d @epoch, stat -c, tar, install, awk); output stays
byte-identical (same root hash), image 20.3 -> 18.2 MB. Makefile pack passes args
straight to the entrypoint.
Restructure the README to lead with what it is and how to use it (the mkruntime
one-liner with the built bootstrap), and move how-it-boots / security model /
testing into a Technical details section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HarryR added a commit to lockboot/stage0 that referenced this pull request Jul 13, 2026
## What
Bring the stage0 build image and disk layout in line with the Fedora 44
chain bump, plus stable disk identity.
## Changes
- **rpm + gnupg** added to `Dockerfile.build` so the UKI build can
verify Fedora package GPG signatures (image parity with stage1, kept
byte-identical). No effect on compiled artifacts.
- **Stable lockboot GPT GUIDs + FAT volume-id.** `DISK_GUID` /
`PART_GUID` are now constant across releases (`4c4f434b-424f-4f54-...`,
"LOCKBOOT" + a role index), so the initial GPT is byte-stable (stable
pre-stage2 PCR5) and the boot disk is identifiable by a fixed GUID
rather than by hashing stage0.efi (which changed per release). This is
the stable identity stage2's `find_boot_device` can key on. (stage2
still rewrites the GPT at runtime to add p2/p3, which is why it excludes
PCR5 from its key binding.)
- **Pin `vaportpm-attest` to v0.3.0** (rev + version guard), matching
stage1.
- **Extract the shared `DOCKER_RUN` harness** into `build.mk` (same
refactor as stage1).
## Verification
- Reproducible: GUIDs/timestamps fixed; build output byte-stable.
- No change to the runtime boot path. Pairs with lockboot/stage1#19
(Fedora 44 kernel/stub) and lockboot/stage2#1.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Clean check-context names (no spaces/parens) so the uniform branch ruleset across
the lockboot repos can require the same names everywhere.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR merged commit fd1fa37 into mainJul 14, 2026
4 checks passed
@HarryR
HarryR deleted the data-xts-meta_bg branch July 14, 2026 11:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling - #1

Merged
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg
Jul 14, 2026
Merged

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling#1
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg

Conversation

@HarryR

Copy link
Copy Markdown
Collaborator

Summary

Reworks stage2 /data persistence and the payload-forging tooling. Net change from main:

  • /data: dm-crypt aes-xts-plain64 (AES-256-XTS) instead of the planned authenticated dm-integrity + AEAD. Authenticated encryption (no internal hash) makes a never-written sector EIO on read, and ext4 online-resize does read-modify-write of new-group metadata in the region it grows into, so the resize aborts unless the whole grow region is pre-wiped (O(volume), unviable on large volumes). XTS decrypts unwritten sectors to garbage, so resize needs no wipe. Trade: at-rest tamper garbles rather than being detected; media integrity is left to the storage layer, raw-disk adversary out of scope. Key drift still fails closed.
  • PID 1 owns failure: a fatal boot error logs, waits 60s, then ACPI power-offs, instead of a returning init panicking the kernel and spinning a vCPU.
  • meta_bg ext4 base for unbounded online growth (no resize= cap / journal bump).
  • mkruntime.sh built out: env knobs, tar-on-stdin / tar / dir / binary inputs (docker export as an example, not a dependency), <out> as a file or - for stdout, and --bootstrap to emit a ready-to-run packed stage2. Makefile pack uses it.

Verification

  • make ci green (fmt + clippy -D warnings + tests)
  • PIVOT_TEST=PASS (config passthrough, attest chain, resize, persist-across-reboot, ephemeral overlay)
  • GROW_TEST=PASS -- grew /data to ~12 GiB across a meta_bg boundary, no wipe, sparse hole reads zero

Review notes / open threads

  • meta_bg gives unbounded growth; resize_inode + a resize= cap is the bounded alternative if a hard ceiling is ever wanted.
  • The dm-verity salt is a single fixed value namespacing the whole tree (not per-node); a Merkle per-node domain-separation review is parked as a separate long-thought.
  • Backlog (not in this PR): real stage2 TPM quote, docker->erofs make front-end, aarch64, digest-pinned erofs-builder.

Draft for review.

🤖 Generated with Claude Code

/data: replace the planned authenticated dm-integrity + dm-crypt AEAD stack with
confidentiality-only dm-crypt aes-xts-plain64 (AES-256-XTS). Authenticated
encryption has no internal hash, so a never-written sector EIOs on read; ext4
online-resize does read-modify-write of new-group metadata (backup GDT via
ext4_setup_new_descs) in the region it grows into, so the resize aborts unless
the whole grow region is pre-wiped first -- O(volume), unacceptable on large
volumes. XTS decrypts unwritten sectors to garbage instead of erroring, so the
resize needs no wipe (the standard LUKS/dm-crypt pattern) and cold-boot cost is
independent of volume size. Trade: at-rest tampering garbles plaintext rather
than being cryptographically detected -- media integrity is left to the storage
layer (EBS et al.), and a raw-disk-write adversary is out of scope. Key drift
still fails closed (superblock decrypts to garbage -> no ext4 magic -> reinit).
64-byte TPM-derived key, new key label.
loader: as PID 1, own fatal boot errors instead of returning. A returning init
panics the kernel ("Attempted to kill init") and spins a vCPU at 100% forever
(in prod and the QEMU harness). Now: log, 60s grace for console capture, then a
clean ACPI power-off.
ext4 /data base: meta_bg (drop resize_inode) for unbounded online growth; no
resize= ceiling or journal bump needed. Verified growing to ~12 GiB across a
meta_bg boundary with no wipe.
mkruntime.sh: env knobs (SOURCE_DATE_EPOCH, DATA_SIZE_MB, ROOTFS_UUID,
VERITY_SALT) with reproducible defaults; rootfs input now accepts a tar stream
on stdin, a tar file, a directory, or a single binary (docker export documented
as an example, not a dependency); <out> is a file path or - for stdout; and
--bootstrap prepends the loader ELF to emit a ready-to-run packed stage2. The
Makefile pack target uses --bootstrap and forwards the knobs.
tests: pivot-init drops the tamper-detection check (property removed with the
AEAD stack); add grow-probe (large-volume meta_bg growth + sparse-hole). Prune
unused probe examples. make ci green; PIVOT_TEST=PASS and GROW_TEST=PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR marked this pull request as ready for review July 13, 2026 18:03
…E restructure
Make lockboot:erofs-builder a one-shot CLI: bake tools/mkruntime.sh in as the
ENTRYPOINT, so packaging no longer needs a repo bind-mount -- mount just the loader
ELF, pipe a rootfs in on stdin, get the stage2 out on stdout. Drop coreutils
(busybox covers touch -d @epoch, stat -c, tar, install, awk); output stays
byte-identical (same root hash), image 20.3 -> 18.2 MB. Makefile pack passes args
straight to the entrypoint.
Restructure the README to lead with what it is and how to use it (the mkruntime
one-liner with the built bootstrap), and move how-it-boots / security model /
testing into a Technical details section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HarryR added a commit to lockboot/stage0 that referenced this pull request Jul 13, 2026
## What
Bring the stage0 build image and disk layout in line with the Fedora 44
chain bump, plus stable disk identity.
## Changes
- **rpm + gnupg** added to `Dockerfile.build` so the UKI build can
verify Fedora package GPG signatures (image parity with stage1, kept
byte-identical). No effect on compiled artifacts.
- **Stable lockboot GPT GUIDs + FAT volume-id.** `DISK_GUID` /
`PART_GUID` are now constant across releases (`4c4f434b-424f-4f54-...`,
"LOCKBOOT" + a role index), so the initial GPT is byte-stable (stable
pre-stage2 PCR5) and the boot disk is identifiable by a fixed GUID
rather than by hashing stage0.efi (which changed per release). This is
the stable identity stage2's `find_boot_device` can key on. (stage2
still rewrites the GPT at runtime to add p2/p3, which is why it excludes
PCR5 from its key binding.)
- **Pin `vaportpm-attest` to v0.3.0** (rev + version guard), matching
stage1.
- **Extract the shared `DOCKER_RUN` harness** into `build.mk` (same
refactor as stage1).
## Verification
- Reproducible: GUIDs/timestamps fixed; build output byte-stable.
- No change to the runtime boot path. Pairs with lockboot/stage1#19
(Fedora 44 kernel/stub) and lockboot/stage2#1.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Clean check-context names (no spaces/parens) so the uniform branch ruleset across
the lockboot repos can require the same names everywhere.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR merged commit fd1fa37 into mainJul 14, 2026
4 checks passed
@HarryR
HarryR deleted the data-xts-meta_bg branch July 14, 2026 11:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling - #1

Merged
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg
Jul 14, 2026
Merged

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling#1
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg

Conversation

@HarryR

Copy link
Copy Markdown
Collaborator

Summary

Reworks stage2 /data persistence and the payload-forging tooling. Net change from main:

  • /data: dm-crypt aes-xts-plain64 (AES-256-XTS) instead of the planned authenticated dm-integrity + AEAD. Authenticated encryption (no internal hash) makes a never-written sector EIO on read, and ext4 online-resize does read-modify-write of new-group metadata in the region it grows into, so the resize aborts unless the whole grow region is pre-wiped (O(volume), unviable on large volumes). XTS decrypts unwritten sectors to garbage, so resize needs no wipe. Trade: at-rest tamper garbles rather than being detected; media integrity is left to the storage layer, raw-disk adversary out of scope. Key drift still fails closed.
  • PID 1 owns failure: a fatal boot error logs, waits 60s, then ACPI power-offs, instead of a returning init panicking the kernel and spinning a vCPU.
  • meta_bg ext4 base for unbounded online growth (no resize= cap / journal bump).
  • mkruntime.sh built out: env knobs, tar-on-stdin / tar / dir / binary inputs (docker export as an example, not a dependency), <out> as a file or - for stdout, and --bootstrap to emit a ready-to-run packed stage2. Makefile pack uses it.

Verification

  • make ci green (fmt + clippy -D warnings + tests)
  • PIVOT_TEST=PASS (config passthrough, attest chain, resize, persist-across-reboot, ephemeral overlay)
  • GROW_TEST=PASS -- grew /data to ~12 GiB across a meta_bg boundary, no wipe, sparse hole reads zero

Review notes / open threads

  • meta_bg gives unbounded growth; resize_inode + a resize= cap is the bounded alternative if a hard ceiling is ever wanted.
  • The dm-verity salt is a single fixed value namespacing the whole tree (not per-node); a Merkle per-node domain-separation review is parked as a separate long-thought.
  • Backlog (not in this PR): real stage2 TPM quote, docker->erofs make front-end, aarch64, digest-pinned erofs-builder.

Draft for review.

🤖 Generated with Claude Code

/data: replace the planned authenticated dm-integrity + dm-crypt AEAD stack with
confidentiality-only dm-crypt aes-xts-plain64 (AES-256-XTS). Authenticated
encryption has no internal hash, so a never-written sector EIOs on read; ext4
online-resize does read-modify-write of new-group metadata (backup GDT via
ext4_setup_new_descs) in the region it grows into, so the resize aborts unless
the whole grow region is pre-wiped first -- O(volume), unacceptable on large
volumes. XTS decrypts unwritten sectors to garbage instead of erroring, so the
resize needs no wipe (the standard LUKS/dm-crypt pattern) and cold-boot cost is
independent of volume size. Trade: at-rest tampering garbles plaintext rather
than being cryptographically detected -- media integrity is left to the storage
layer (EBS et al.), and a raw-disk-write adversary is out of scope. Key drift
still fails closed (superblock decrypts to garbage -> no ext4 magic -> reinit).
64-byte TPM-derived key, new key label.
loader: as PID 1, own fatal boot errors instead of returning. A returning init
panics the kernel ("Attempted to kill init") and spins a vCPU at 100% forever
(in prod and the QEMU harness). Now: log, 60s grace for console capture, then a
clean ACPI power-off.
ext4 /data base: meta_bg (drop resize_inode) for unbounded online growth; no
resize= ceiling or journal bump needed. Verified growing to ~12 GiB across a
meta_bg boundary with no wipe.
mkruntime.sh: env knobs (SOURCE_DATE_EPOCH, DATA_SIZE_MB, ROOTFS_UUID,
VERITY_SALT) with reproducible defaults; rootfs input now accepts a tar stream
on stdin, a tar file, a directory, or a single binary (docker export documented
as an example, not a dependency); <out> is a file path or - for stdout; and
--bootstrap prepends the loader ELF to emit a ready-to-run packed stage2. The
Makefile pack target uses --bootstrap and forwards the knobs.
tests: pivot-init drops the tamper-detection check (property removed with the
AEAD stack); add grow-probe (large-volume meta_bg growth + sparse-hole). Prune
unused probe examples. make ci green; PIVOT_TEST=PASS and GROW_TEST=PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR marked this pull request as ready for review July 13, 2026 18:03
…E restructure
Make lockboot:erofs-builder a one-shot CLI: bake tools/mkruntime.sh in as the
ENTRYPOINT, so packaging no longer needs a repo bind-mount -- mount just the loader
ELF, pipe a rootfs in on stdin, get the stage2 out on stdout. Drop coreutils
(busybox covers touch -d @epoch, stat -c, tar, install, awk); output stays
byte-identical (same root hash), image 20.3 -> 18.2 MB. Makefile pack passes args
straight to the entrypoint.
Restructure the README to lead with what it is and how to use it (the mkruntime
one-liner with the built bootstrap), and move how-it-boots / security model /
testing into a Technical details section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HarryR added a commit to lockboot/stage0 that referenced this pull request Jul 13, 2026
## What
Bring the stage0 build image and disk layout in line with the Fedora 44
chain bump, plus stable disk identity.
## Changes
- **rpm + gnupg** added to `Dockerfile.build` so the UKI build can
verify Fedora package GPG signatures (image parity with stage1, kept
byte-identical). No effect on compiled artifacts.
- **Stable lockboot GPT GUIDs + FAT volume-id.** `DISK_GUID` /
`PART_GUID` are now constant across releases (`4c4f434b-424f-4f54-...`,
"LOCKBOOT" + a role index), so the initial GPT is byte-stable (stable
pre-stage2 PCR5) and the boot disk is identifiable by a fixed GUID
rather than by hashing stage0.efi (which changed per release). This is
the stable identity stage2's `find_boot_device` can key on. (stage2
still rewrites the GPT at runtime to add p2/p3, which is why it excludes
PCR5 from its key binding.)
- **Pin `vaportpm-attest` to v0.3.0** (rev + version guard), matching
stage1.
- **Extract the shared `DOCKER_RUN` harness** into `build.mk` (same
refactor as stage1).
## Verification
- Reproducible: GUIDs/timestamps fixed; build output byte-stable.
- No change to the runtime boot path. Pairs with lockboot/stage1#19
(Fedora 44 kernel/stub) and lockboot/stage2#1.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Clean check-context names (no spaces/parens) so the uniform branch ruleset across
the lockboot repos can require the same names everywhere.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR merged commit fd1fa37 into mainJul 14, 2026
4 checks passed
@HarryR
HarryR deleted the data-xts-meta_bg branch July 14, 2026 11:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling - #1

Merged
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg
Jul 14, 2026
Merged

stage2: rework /data persistence (XTS, meta_bg) + mkruntime tooling#1
HarryR merged 3 commits into
mainfrom
data-xts-meta_bg

Conversation

@HarryR

Copy link
Copy Markdown
Collaborator

Summary

Reworks stage2 /data persistence and the payload-forging tooling. Net change from main:

  • /data: dm-crypt aes-xts-plain64 (AES-256-XTS) instead of the planned authenticated dm-integrity + AEAD. Authenticated encryption (no internal hash) makes a never-written sector EIO on read, and ext4 online-resize does read-modify-write of new-group metadata in the region it grows into, so the resize aborts unless the whole grow region is pre-wiped (O(volume), unviable on large volumes). XTS decrypts unwritten sectors to garbage, so resize needs no wipe. Trade: at-rest tamper garbles rather than being detected; media integrity is left to the storage layer, raw-disk adversary out of scope. Key drift still fails closed.
  • PID 1 owns failure: a fatal boot error logs, waits 60s, then ACPI power-offs, instead of a returning init panicking the kernel and spinning a vCPU.
  • meta_bg ext4 base for unbounded online growth (no resize= cap / journal bump).
  • mkruntime.sh built out: env knobs, tar-on-stdin / tar / dir / binary inputs (docker export as an example, not a dependency), <out> as a file or - for stdout, and --bootstrap to emit a ready-to-run packed stage2. Makefile pack uses it.

Verification

  • make ci green (fmt + clippy -D warnings + tests)
  • PIVOT_TEST=PASS (config passthrough, attest chain, resize, persist-across-reboot, ephemeral overlay)
  • GROW_TEST=PASS -- grew /data to ~12 GiB across a meta_bg boundary, no wipe, sparse hole reads zero

Review notes / open threads

  • meta_bg gives unbounded growth; resize_inode + a resize= cap is the bounded alternative if a hard ceiling is ever wanted.
  • The dm-verity salt is a single fixed value namespacing the whole tree (not per-node); a Merkle per-node domain-separation review is parked as a separate long-thought.
  • Backlog (not in this PR): real stage2 TPM quote, docker->erofs make front-end, aarch64, digest-pinned erofs-builder.

Draft for review.

🤖 Generated with Claude Code

/data: replace the planned authenticated dm-integrity + dm-crypt AEAD stack with
confidentiality-only dm-crypt aes-xts-plain64 (AES-256-XTS). Authenticated
encryption has no internal hash, so a never-written sector EIOs on read; ext4
online-resize does read-modify-write of new-group metadata (backup GDT via
ext4_setup_new_descs) in the region it grows into, so the resize aborts unless
the whole grow region is pre-wiped first -- O(volume), unacceptable on large
volumes. XTS decrypts unwritten sectors to garbage instead of erroring, so the
resize needs no wipe (the standard LUKS/dm-crypt pattern) and cold-boot cost is
independent of volume size. Trade: at-rest tampering garbles plaintext rather
than being cryptographically detected -- media integrity is left to the storage
layer (EBS et al.), and a raw-disk-write adversary is out of scope. Key drift
still fails closed (superblock decrypts to garbage -> no ext4 magic -> reinit).
64-byte TPM-derived key, new key label.
loader: as PID 1, own fatal boot errors instead of returning. A returning init
panics the kernel ("Attempted to kill init") and spins a vCPU at 100% forever
(in prod and the QEMU harness). Now: log, 60s grace for console capture, then a
clean ACPI power-off.
ext4 /data base: meta_bg (drop resize_inode) for unbounded online growth; no
resize= ceiling or journal bump needed. Verified growing to ~12 GiB across a
meta_bg boundary with no wipe.
mkruntime.sh: env knobs (SOURCE_DATE_EPOCH, DATA_SIZE_MB, ROOTFS_UUID,
VERITY_SALT) with reproducible defaults; rootfs input now accepts a tar stream
on stdin, a tar file, a directory, or a single binary (docker export documented
as an example, not a dependency); <out> is a file path or - for stdout; and
--bootstrap prepends the loader ELF to emit a ready-to-run packed stage2. The
Makefile pack target uses --bootstrap and forwards the knobs.
tests: pivot-init drops the tamper-detection check (property removed with the
AEAD stack); add grow-probe (large-volume meta_bg growth + sparse-hole). Prune
unused probe examples. make ci green; PIVOT_TEST=PASS and GROW_TEST=PASS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR marked this pull request as ready for review July 13, 2026 18:03
…E restructure
Make lockboot:erofs-builder a one-shot CLI: bake tools/mkruntime.sh in as the
ENTRYPOINT, so packaging no longer needs a repo bind-mount -- mount just the loader
ELF, pipe a rootfs in on stdin, get the stage2 out on stdout. Drop coreutils
(busybox covers touch -d @epoch, stat -c, tar, install, awk); output stays
byte-identical (same root hash), image 20.3 -> 18.2 MB. Makefile pack passes args
straight to the entrypoint.
Restructure the README to lead with what it is and how to use it (the mkruntime
one-liner with the built bootstrap), and move how-it-boots / security model /
testing into a Technical details section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HarryR added a commit to lockboot/stage0 that referenced this pull request Jul 13, 2026
## What
Bring the stage0 build image and disk layout in line with the Fedora 44
chain bump, plus stable disk identity.
## Changes
- **rpm + gnupg** added to `Dockerfile.build` so the UKI build can
verify Fedora package GPG signatures (image parity with stage1, kept
byte-identical). No effect on compiled artifacts.
- **Stable lockboot GPT GUIDs + FAT volume-id.** `DISK_GUID` /
`PART_GUID` are now constant across releases (`4c4f434b-424f-4f54-...`,
"LOCKBOOT" + a role index), so the initial GPT is byte-stable (stable
pre-stage2 PCR5) and the boot disk is identifiable by a fixed GUID
rather than by hashing stage0.efi (which changed per release). This is
the stable identity stage2's `find_boot_device` can key on. (stage2
still rewrites the GPT at runtime to add p2/p3, which is why it excludes
PCR5 from its key binding.)
- **Pin `vaportpm-attest` to v0.3.0** (rev + version guard), matching
stage1.
- **Extract the shared `DOCKER_RUN` harness** into `build.mk` (same
refactor as stage1).
## Verification
- Reproducible: GUIDs/timestamps fixed; build output byte-stable.
- No change to the runtime boot path. Pairs with lockboot/stage1#19
(Fedora 44 kernel/stub) and lockboot/stage2#1.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Clean check-context names (no spaces/parens) so the uniform branch ruleset across
the lockboot repos can require the same names everywhere.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@HarryR
HarryR merged commit fd1fa37 into mainJul 14, 2026
4 checks passed
@HarryR
HarryR deleted the data-xts-meta_bg branch July 14, 2026 11:00
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@HarryR