Skip to content

bug: silent data loss when multiple functions append to the same initrd cpio archive #995

Description

@Ankitdotraider

urunc builds the unikernel's initrd file in two separate steps during container startup, and each step opens the file, writes to it, and closes it independently. The problem is that closing a cpio archive writes a special "end of archive" marker. Whatever gets written after that marker is invisible to any real reader, including the Linux kernel when it unpacks the initrd at boot.
So here's the actual sequence: first, urunc appends any bind mounted files during setup and closes that write. Later, if the unikernel uses urunit as its init process, urunc reopens the same file and appends urunit's config (its command and environment variables), then closes again. Because the first close already wrote the end marker, this second write ends up sitting after it, and gets silently ignored when the kernel unpacks the archive.

No error shows up anywhere. The file write itself succeeds fine, it's just that nothing ever reads that part of the file back. In practice, this means: any Linux unikernel using initrd as its rootfs, running urunit as init, with at least one bind mount configured, boots with urunit missing its config.
System info

I traced this by reading through the relevant code on main, not by running a full urunc container (I don't have a VMM/unikernel setup to test that end to end right now). To confirm the actual behavior, I wrote a small standalone Go program using the same cpio library urunc depends on, doing the exact same thing urunc does: write one file, close, reopen the same file, write a second file, close again, then read it all back.
What I saw

step1: wrote bind mount entry + closed writer
step2: wrote urunit.conf entry + closed writer

--- Replaying archive via cpio.Reader ---
  found entry #1: etc/mounted-file.txt
Reader stopped: EOF

Total entries extracted: 1

Only the first file comes back out. The second one, written after the first close, just disappears.
Possible fix direction
Keep one writer open across both steps instead of closing in between, or make sure the archive only gets its final "end of archive" marker once everything has been written.

Disclosure: I used Claude (Anthropic) to help trace through the code and build the small reproduction above. I reviewed it myself and ran it on my own machine to confirm the output before writing this up.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions