Skip to content

fix: Pin guest images to ghcr.io/nofireai and boot CLH guests on Linux 6.18.57 - #2

Open
safts wants to merge 3 commits into
nofirefrom
fix/clh-kernel-6.18.57
Open

safts wants to merge 3 commits into
nofirefrom
fix/clh-kernel-6.18.57

Conversation

@safts

@safts safts commented Nov 3, 2026

Copy link
Copy Markdown

Pins the Cloud Hypervisor guest kernel bunny injects to a build that has the CVE-2026-98432 fix (Linux IPv4 TCP connection-setup use-after-free, fixed in 6.18.56).

  • Mirror and pin (from Pull urunit and the guest kernel from ghcr.io/nofireai, pinned #1, rebased): urunit, the CLH kernel and libarchive come from ghcr.io/nofireai, pinned by digest, instead of harbor.nbfc.io/...:latest. nofire has since moved urunit to our own build, so urunit is pinned to ghcr.io/nofireai/urunit@sha256:239ab9c9… and no longer mirrored. This supersedes Pull urunit and the guest kernel from ghcr.io/nofireai, pinned #1, which conflicts with nofire.
  • Kernel 6.18.57: the previous kernel was harbor's hand-built 6.18.0 with no stable patches. It is replaced by ghcr.io/urunc-dev/unikernel-build/linux-kernel:generic 6.18.57, built from the signed kernel.org tarball. It has the same built-in drivers apart from SGX, and it is multi-arch. MPTCP stays off.

Verified: go vet and go test pass in golang:1.25; TestLLBBase depends on the host platform and fails the same way on nofire under macOS. A frontend built from this branch produced an image whose /.boot/kernel reports 6.18.57-urunc. Not yet booted under cloud-hypervisor.

After merge: the mirror workflow copies the images and the frontend rebuilds. ghcr.io/nofireai/bunny/linux-kernel-cloud-hypervisor must be readable by consumers (anonymous pulls 403 today).

🤖 Generated with Claude Code

panosmaurikos and others added 2 commits November 2, 2026 00:22
Every Containerfile build injects urunit and the Cloud Hypervisor kernel,
and bunny resolved both as harbor.nbfc.io/...:latest. So each build fetched
unpinned artifacts from a registry we do not run: the guest kernel could
change between two builds of the same commit, and a harbor outage failed the
build outright (a wrong-name certificate on 2026-08-26 broke brain-go's
urunc-e2e run 33002508492).

Mirror urunit, the Cloud Hypervisor kernel and libarchive into
ghcr.io/nofireai with digests preserved, and pin the defaults to those
digests. The new workflow copies them with skopeo and fails unless each copy
is byte-identical to its source.

Rebased on nofire 4aa6958: urunit is now our own build, so it is pinned to
ghcr.io/nofireai/urunit@sha256:239ab9c9 (its latest today) and no longer
mirrored from harbor.
The Cloud Hypervisor kernel bunny injects was harbor's
linux-kernel-cloud-hypervisor, a hand-built 6.18.0 with no stable
patches. It carries CVE-2026-98432, the IPv4 TCP connection-setup
use-after-free that a SYN with IP options reaches on any listening port;
the fix is in 6.18.56.

Mirror and pin urunc-dev/unikernel-build's generic kernel at 6.18.57
instead. It is built from the signed kernel.org tarball by that repo's
workflow, builds in the same drivers as the 6.18.0 kernel apart from
SGX, and is a multi-arch index, so an arm64 frontend now gets an arm64
kernel. MPTCP stays off, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Sergios Aftsidis <sergios@nofire.ai>
@safts

safts commented Nov 3, 2026

Copy link
Copy Markdown
Author

@claude-fleet review

@claude-fleet

claude-fleet Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Review task queued.

Task: db31ec84-30c2-442d-9bd3-f862cb6a384d
Commit: 21fdb03
Model: claude-opus-5 (effort medium)
Executor: ci-16gb-fsn1-1
Status: done. Review posted.

@claude-fleet claude-fleet Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes: two should_fix findings, one nit. The pin itself is sound — go vet and the full test suite pass, and TestLLBInitrd/Tool_state confirms the digest-pinned ref survives llb.Image unchanged — but nothing in the PR makes the mirrored packages pullable, and the new comment's rationale does not match the constants under it.

Findings:

  • should_fix, .github/workflows/mirror-nofire.yml:25 — mirror creates private GHCR packages; the new defaults 404/403 for every consumer
  • should_fix, hops/parse_file.go:30 — comment's premise is contradicted by lines 41-42 and llb.go:27
  • nit, .github/workflows/mirror-nofire.yml:20 — each digest lives in two files, synced only by convention, and the mirror trigger depends on that convention being followed

Verified:

  • The digest-pinned ref reaches LLB intact: TestLLBInitrd/Tool_state asserts docker-image:// + defaultBsdcpioImage — hops/llb_test.go:197, hops/llb.go:60
  • The CLH kernel is injected only when com.urunc.unikernel.hypervisor is cloud-hypervisor and com.urunc.unikernel.binary is empty — hops/parse_file.go:209-223
  • cloud-hypervisor is the default monitor, so the pinned kernel is the common path — hops/parse_file.go:200-202
  • The frontend rebuild does fire on this push: the go filter covers hops/** and gates build-bunny — .github/workflows/build-trigger-nofire.yml:39-41, 57-59
  • The tag expression yields sha- plus 12 hex characters, matching its comment — .github/workflows/mirror-nofire.yml:53
  • skopeo copy --all --preserve-digests plus the raw-manifest sha256 comparison is a real byte-identity check, so "its digest here is the upstream one" holds for the kernel — .github/workflows/mirror-nofire.yml:54-60

Checks run:

  • go test ./... → ok bunny/hops, no test files in cmd
  • go test ./hops/ -run 'Initrd|TestLLB' -v → TestLLBFiles, TestLLBInitrd, TestLLBCopy, TestLLBBase all PASS (TestLLBBase passes on linux/amd64; the macOS failure you describe is not reproducible here)
  • go vet ./... → clean
  • No revert check: the PR adds no test, so there is nothing to prove fails without the fix.

Smaller things:

  • defaultQemuKernelImage and defaultFirecrackerKernelImage (hops/parse_file.go:41-42) stay on harbor.nbfc.io at :latest. Pinning only the default monitor is a defensible scope, but then the comment above them should not claim the registry dependency is gone.
  • containerfileToPack has no test, so no assertion covers which kernel constant each hypervisor value selects. A table test over the three cases would pin the branch the PR changes.
  • The ordering argument in .github/workflows/mirror-nofire.yml:12-15 is an assumption, not a constraint: the two workflows have no needs: relation, and the frontend image can publish before the mirror finishes. The window is short and self-healing, but worth not stating as a guarantee.

Not checked:

  • CVE-2026-98432, and that 6.18.57 carries the fix released in 6.18.56. No reference reachable from here.
  • That sha256:20a3b24d… is the 6.18.57 generic kernel with the stated driver set (same built-ins minus SGX, MPTCP off, multi-arch), and that sha256:5244491f… is the current libarchive. No registry access in this workspace.
  • That /.boot/kernel reports 6.18.57-urunc, and the unperformed cloud-hypervisor boot.
  • Whether skopeo is preinstalled on the ubuntu-24.04 runner image; the step installs nothing.
  • The relationship to PR #1 and what it conflicted with.

Not checked:

  • CVE-2026-98432 and that Linux 6.18.57 carries the fix released in 6.18.56 - no reference reachable from this workspace
  • That sha256:20a3b24d is the 6.18.57 generic kernel with the stated built-in driver set (minus SGX), MPTCP off, and multi-arch manifests - no registry access
  • That sha256:5244491f is the current harbor libarchive image - no registry access
  • The author's claim that a frontend from this branch produces /.boot/kernel reporting 6.18.57-urunc, and the cloud-hypervisor boot the author states is not yet done
  • Whether skopeo is preinstalled on the ubuntu-24.04 runner image; the workflow installs nothing
  • The relationship to PR #1 and the conflict it is said to have with nofire

Task db31ec84-30c2-442d-9bd3-f862cb6a384d on ci-16gb-fsn1-1, model claude-opus-5, effort medium.


permissions:
contents: read
packages: write # create/update ghcr.io/nofireai/* packages

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should_fix: The mirror job creates the GHCR packages but never makes them readable, so the newly pinned defaults are unpullable by the builds that need them. Packages created by GITHUB_TOKEN default to private, and the PR description confirms anonymous pulls already 403 today.
Failure: after this merges, the rebuilt frontend resolves ghcr.io/nofireai/bunny/linux-kernel-cloud-hypervisor@sha256:20a3b24d… (hops/parse_file.go:43) and ghcr.io/nofireai/bunny/libarchive@sha256:5244491f… (hops/llb.go:27) for every Containerfile build; buildkit gets 403 on both and every build fails, until someone flips visibility by hand. This replaces an anonymous-readable harbor path with one that is not readable at all, so it is a regression for any consumer outside the org.
Evidence: .github/workflows/mirror-nofire.yml:23-25 grants only packages: write and the steps at :42-60 do only skopeo copy and skopeo inspect; nothing sets package visibility. hops/parse_file.go:209-223 and hops/llb.go:60 are the two injection sites.
Either add a step that sets the package public (GITHUB_TOKEN cannot do this; it needs an org PAT against PATCH /orgs/nofireai/packages/container/{name}), or hold the Go pin until the packages are confirmed public and say so in the PR rather than as after-merge work.

Comment thread hops/parse_file.go Outdated
"gopkg.in/yaml.v3"
)

// urunit and the Cloud Hypervisor kernel are the two artifacts every

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should_fix: The comment's stated reason is contradicted by the constants directly below it, on two counts.
Failure: it says urunit and the CLH kernel are "the two artifacts every Containerfile build injects" and that the change removes the harbor.nbfc.io failure mode, but a build with com.urunc.unikernel.hypervisor: qemu or firecracker still pulls harbor.nbfc.io/nubificus/bunny/linux-kernel-{qemu,firecracker}:latest — so a harbor outage still fails those builds, and a latest move still changes their guest kernel silently, which is exactly what the comment says is no longer possible. The CLH kernel is also not injected by every build: it is skipped whenever com.urunc.unikernel.binary is set. And libarchive is a third injected artifact that this PR mirrored, which the sentence omits.
Evidence: hops/parse_file.go:41-42 (harbor, :latest), hops/parse_file.go:209-223 (hypervisor switch, guarded by the binary == "" check at :208), hops/llb.go:27 and :60 (libarchive injected by InitrdLLB).
Rewording it to scope the claim to the default cloud-hypervisor path, and naming libarchive, keeps the rationale true for whoever reads it next.

on:
push:
branches: ["nofire"]
paths: [".github/workflows/mirror-nofire.yml"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Each digest is written in two files and kept in sync only by the comment asking for it, and the mirror's trigger depends on that convention holding.
Failure: a later kernel bump that edits defaultCLHKernelImage in hops/parse_file.go without editing the matrix here does not match paths: [".github/workflows/mirror-nofire.yml"], so the mirror never runs, while the go path filter in build-trigger-nofire.yml:39-41 does fire and ships a frontend pinned to a digest that was never copied into ghcr.io/nofireai. Every build then fails on an unresolvable digest, and the cause is two files away from the edit.
Evidence: the kernel digest at .github/workflows/mirror-nofire.yml:38 and hops/parse_file.go:43; the libarchive digest at .github/workflows/mirror-nofire.yml:41 and hops/llb.go:27; the path filter at .github/workflows/mirror-nofire.yml:20.
A grep in CI asserting each matrix digest appears in the Go source turns the convention into a failing check.

The comment claimed every build was off harbor; the qemu and firecracker
kernels still are, and libarchive is mirrored too. check-pins fails a PR
whose mirror matrix and hops/ pins disagree, which would otherwise ship a
frontend pinned to a digest that was never copied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Sergios Aftsidis <sergios@nofire.ai>
@safts

safts commented Nov 3, 2026

Copy link
Copy Markdown
Author

Fixed the comment and the nit in the latest commit: the rationale now scopes to the cloud-hypervisor path and names libarchive, and a check-pins job fails a PR whose mirror matrix and hops/ pins disagree. I tested it against a deliberately mismatched digest.

On visibility: agreed, and GITHUB_TOKEN can't set it. ghcr.io/nofireai/urunit and ghcr.io/nofireai/bunny are already public, and these two packages don't exist until the mirror first runs. The order is:

  1. Merge. mirror-nofire.yml runs and creates bunny/linux-kernel-cloud-hypervisor and bunny/libarchive.
  2. An org package admin sets both to public (package settings → Change visibility).
  3. Only then bump brain-go's BUILDKIT_SYNTAX to the rebuilt frontend.

brain-go pins the frontend by digest, so nothing it builds changes before step 3. Until step 2, an unpinned #syntax=ghcr.io/nofireai/bunny:latest (local builds per the urunc setup guide) 403s on the kernel, so steps 1 and 2 should happen together.

@cmainas cmainas left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @safts , thank you for the update. I am not sure I understand the new workflow and if I understand correctly,, the next time we update the kernel in the code, we will also need to update the workflow right? These two can easily drift.

Sign up for free to 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.

3 participants