Description
When a container's OCI spec declares more than one bind mount backed by a loop device for a filesystem the guest supports (e.g. two ext2 image files bind-mounted from the host), getBlockVolumes in pkg/unikontainers/block.go iterates the mounts and, for each supported one, clears the loop-device autoclear flag and calls mount.Unmount(mInfo.MountPoint) to detach it from the host so it can be handed to the guest as a block device.
If any iteration after the first fails (getMountInfo erroring on a later entry, setLoopAutoclear failing, or mount.Unmount failing), the function immediately does return nil, err, discarding the entire blkImgs slice built so far. The volumes already unmounted in earlier loop iterations are never returned to the caller and never recorded anywhere.
Root cause
pkg/unikontainers/block.go:196-241 (getBlockVolumes):
func getBlockVolumes(mounts []specs.Mount, ukernel types.Unikernel) ([]types.BlockDevParams, error) {
blkImgs := []types.BlockDevParams{}
for i, m := range mounts {
...
if ukernel.SupportsFS(mInfo.FsType) {
cleared, err := setLoopAutoclear(mInfo.Source, false)
if err != nil {
return nil, err // discards blkImgs already unmounted in earlier iterations
}
mInfo.LoopAutoclear = cleared
err = mount.Unmount(mInfo.MountPoint)
if err != nil {
return nil, err // same problem
}
...
blkImgs = append(blkImgs, mInfo)
}
}
return blkImgs, nil
}
This error propagates up through blockRootfs.getBlockDevs() and getMonitorResources() (pkg/unikontainers/unikontainers.go:363-392), causing InitialSetup() (pkg/unikontainers/unikontainers.go:145-233) to return before it ever writes state.json or the monitor-resources file. cmd/urunc/create.go's createUnikontainer() just propagates the error with no cleanup of its own.
The only code that restores unmounted block volumes is restoreBlockVolumes() (pkg/unikontainers/block.go:248-280), called exclusively from Unikontainer.Delete() (pkg/unikontainers/unikontainers.go:857) using monRes.BlockArgs loaded from the very state file InitialSetup never got to write. cmd/urunc/delete.go's os.ErrNotExist fallback (no state file found) just does os.RemoveAll on the container directory, and it has no Unikontainer/monRes to call restoreBlockVolumes with. So there is no code path, in either create or delete, that can put the earlier, successfully-unmounted volumes back.
Expected behavior
Either getBlockVolumes should roll back (re-mount and restore the autoclear flag) any volumes it already unmounted before returning an error, or InitialSetup/createUnikontainer should perform equivalent rollback whenever a later step fails, so a failed create never leaves pre-existing host mounts permanently detached.
Impact
A transient, single-volume failure during container creation permanently detaches unrelated, already-processed host bind mounts with no automatic recovery. This is host-state corruption, since these are pre-existing host mounts that other processes or data pipelines may depend on. It requires manual host intervention (mount by hand) to fix, and the impact scales with the number of block-backed bind mounts declared per container.
Suggested fix
Track already-unmounted entries during the loop in getBlockVolumes and remount/restore-autoclear them before returning an error (mirroring restoreBlockVolumes), or have InitialSetup/createUnikontainer invoke a rollback helper whenever InitialSetup fails after the unmount stage. Also make cmd/urunc/delete.go's missing-state-file fallback account for the possibility that partial unmount state still needs restoring, instead of assuming nothing needs cleanup.
System info
- Urunc version: main branch (HEAD as of 2026-07-29)
- Arch: any (x86_64 / arm64)
- VMM: any (bug is in the guest-rootfs/volume layer, independent of VMM backend)
- Unikernel: any block-capable guest using extra bind-mounted block volumes (e.g. Unikraft)
Steps to reproduce
- Configure an OCI bundle for a block-capable guest with two or more
bind mounts whose sources are loop-device-backed filesystems the guest supports (e.g. two separate ext2 image files).
- Arrange for the second (or later) mount's processing to fail, for example by revoking permission on that loop device so
setLoopAutoclear fails, or making mount.Unmount return EBUSY for that entry.
- Run
urunc create. The first volume's host mountpoint is already unmounted (visible in /proc/self/mountinfo before the failure returns). create fails and no state.json is written.
- Run
urunc delete <id>. It hits the os.ErrNotExist branch in cmd/urunc/delete.go and just removes the container directory; restoreBlockVolumes is never invoked.
- Inspect the host. The first volume's original mountpoint remains unmounted indefinitely.
Disclosure per the project's LLM policy: this report was drafted with help from an LLM (Claude, Anthropic, model claude-sonnet-5) to search the codebase and put the description together. I reviewed and confirmed the finding myself by reading the exact source lines in a fresh clone of the repository and by checking open/closed issues and open/merged PRs for duplicates before submitting.
Description
When a container's OCI spec declares more than one bind mount backed by a loop device for a filesystem the guest supports (e.g. two
ext2image files bind-mounted from the host),getBlockVolumesinpkg/unikontainers/block.goiterates the mounts and, for each supported one, clears the loop-device autoclear flag and callsmount.Unmount(mInfo.MountPoint)to detach it from the host so it can be handed to the guest as a block device.If any iteration after the first fails (
getMountInfoerroring on a later entry,setLoopAutoclearfailing, ormount.Unmountfailing), the function immediately doesreturn nil, err, discarding the entireblkImgsslice built so far. The volumes already unmounted in earlier loop iterations are never returned to the caller and never recorded anywhere.Root cause
pkg/unikontainers/block.go:196-241(getBlockVolumes):This error propagates up through
blockRootfs.getBlockDevs()andgetMonitorResources()(pkg/unikontainers/unikontainers.go:363-392), causingInitialSetup()(pkg/unikontainers/unikontainers.go:145-233) to return before it ever writesstate.jsonor the monitor-resources file.cmd/urunc/create.go'screateUnikontainer()just propagates the error with no cleanup of its own.The only code that restores unmounted block volumes is
restoreBlockVolumes()(pkg/unikontainers/block.go:248-280), called exclusively fromUnikontainer.Delete()(pkg/unikontainers/unikontainers.go:857) usingmonRes.BlockArgsloaded from the very state fileInitialSetupnever got to write.cmd/urunc/delete.go'sos.ErrNotExistfallback (no state file found) just doesos.RemoveAllon the container directory, and it has noUnikontainer/monResto callrestoreBlockVolumeswith. So there is no code path, in eithercreateordelete, that can put the earlier, successfully-unmounted volumes back.Expected behavior
Either
getBlockVolumesshould roll back (re-mount and restore the autoclear flag) any volumes it already unmounted before returning an error, orInitialSetup/createUnikontainershould perform equivalent rollback whenever a later step fails, so a failedcreatenever leaves pre-existing host mounts permanently detached.Impact
A transient, single-volume failure during container creation permanently detaches unrelated, already-processed host bind mounts with no automatic recovery. This is host-state corruption, since these are pre-existing host mounts that other processes or data pipelines may depend on. It requires manual host intervention (
mountby hand) to fix, and the impact scales with the number of block-backed bind mounts declared per container.Suggested fix
Track already-unmounted entries during the loop in
getBlockVolumesand remount/restore-autoclear them before returning an error (mirroringrestoreBlockVolumes), or haveInitialSetup/createUnikontainerinvoke a rollback helper wheneverInitialSetupfails after the unmount stage. Also makecmd/urunc/delete.go's missing-state-file fallback account for the possibility that partial unmount state still needs restoring, instead of assuming nothing needs cleanup.System info
Steps to reproduce
bindmounts whose sources are loop-device-backed filesystems the guest supports (e.g. two separateext2image files).setLoopAutoclearfails, or makingmount.UnmountreturnEBUSYfor that entry.urunc create. The first volume's host mountpoint is already unmounted (visible in/proc/self/mountinfobefore the failure returns).createfails and nostate.jsonis written.urunc delete <id>. It hits theos.ErrNotExistbranch incmd/urunc/delete.goand just removes the container directory;restoreBlockVolumesis never invoked.Disclosure per the project's LLM policy: this report was drafted with help from an LLM (Claude, Anthropic, model
claude-sonnet-5) to search the codebase and put the description together. I reviewed and confirmed the finding myself by reading the exact source lines in a fresh clone of the repository and by checking open/closed issues and open/merged PRs for duplicates before submitting.