From 941bfe151e508e05d95e72e23efd39d19b1ec164 Mon Sep 17 00:00:00 2001 From: Cory Snider Date: Wed, 4 Feb 2026 18:47:07 -0500 Subject: [PATCH] WCOW: restore support for client-mounted roots Commit 00ca088f41b4de5d438d3d0ab7bdfbb948e44024 broke starting process-isolated containers when Root.Path is set in the container spec. Root.Path is set by clients such as Docker which mount and unmount the container's root filesystem themselves. Mounting is skipped, which also skips over setting coi.mountedWCOWLayers. The hcsoci.createWindowsContainerDocument function unconditionally dereferences that field, resulting in a nil-dereference panic when mounting is skipped. Restore the old behaviour by synthesizing the MountedWCOWLayers when Root.Path is set. Signed-off-by: Cory Snider --- internal/hcsoci/resources_wcow.go | 32 ++++++++++++++++---------- internal/layers/wcow_mount.go | 38 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/internal/hcsoci/resources_wcow.go b/internal/hcsoci/resources_wcow.go index 211b15f300..2430edca0b 100644 --- a/internal/hcsoci/resources_wcow.go +++ b/internal/hcsoci/resources_wcow.go @@ -34,18 +34,26 @@ func allocateWindowsResources(ctx context.Context, coi *createOptionsInternal, r coi.Spec.Root = &specs.Root{} } - if coi.Spec.Root.Path == "" && (coi.HostingSystem != nil || coi.Spec.Windows.HyperV == nil) { - log.G(ctx).Debug("hcsshim::allocateWindowsResources mounting storage") - mountedLayers, closer, err := layers.MountWCOWLayers(ctx, coi.actualID, coi.HostingSystem, coi.WCOWLayers) - if err != nil { - return errors.Wrap(err, "failed to mount container storage") - } - coi.Spec.Root.Path = mountedLayers.RootFS - coi.mountedWCOWLayers = mountedLayers - // If this is the pause container in a hypervisor-isolated pod, we can skip cleanup of - // layers, as that happens automatically when the UVM is terminated. - if !isSandbox || coi.HostingSystem == nil { - r.SetLayers(closer) + if coi.HostingSystem != nil || coi.Spec.Windows.HyperV == nil { + if coi.Spec.Root.Path == "" { + log.G(ctx).Debug("hcsshim::allocateWindowsResources mounting storage") + mountedLayers, closer, err := layers.MountWCOWLayers(ctx, coi.actualID, coi.HostingSystem, coi.WCOWLayers) + if err != nil { + return errors.Wrap(err, "failed to mount container storage") + } + coi.Spec.Root.Path = mountedLayers.RootFS + coi.mountedWCOWLayers = mountedLayers + // If this is the pause container in a hypervisor-isolated pod, we can skip cleanup of + // layers, as that happens automatically when the UVM is terminated. + if !isSandbox || coi.HostingSystem == nil { + r.SetLayers(closer) + } + } else { + l, err := layers.MakeMountedWCOWLayers(ctx, coi.WCOWLayers, coi.Spec.Root.Path) + if err != nil { + return errors.Wrap(err, "failed to use pre-mounted container storage") + } + coi.mountedWCOWLayers = l } } diff --git a/internal/layers/wcow_mount.go b/internal/layers/wcow_mount.go index 3bb78c09d6..9398eec1b1 100644 --- a/internal/layers/wcow_mount.go +++ b/internal/layers/wcow_mount.go @@ -49,6 +49,44 @@ func MountWCOWLayers(ctx context.Context, containerID string, vm *uvm.UtilityVM, } } +// MakeMountedWCOWLayers creates a MountedWCOWLayers structure from wl, like +// [MountWCOWLayers], without actually mounting anything. +func MakeMountedWCOWLayers(ctx context.Context, wl WCOWLayers, mountPath string) (*MountedWCOWLayers, error) { + switch l := wl.(type) { + case *wcowWCIFSLayers: + layersWithID := []MountedWCOWLayer{} + for _, l := range l.layerPaths { + layerID, err := wclayer.LayerID(ctx, l) + if err != nil { + return nil, err + } + layersWithID = append(layersWithID, MountedWCOWLayer{ + LayerID: layerID.String(), + MountedPath: l, + }) + } + + return &MountedWCOWLayers{ + RootFS: mountPath, + MountedLayerPaths: layersWithID, + }, nil + case *wcowForkedCIMLayers, *wcowBlockCIMLayers: + layerID, err := cimlayer.LayerID(mountPath) + if err != nil { + return nil, err + } + return &MountedWCOWLayers{ + RootFS: mountPath, + MountedLayerPaths: []MountedWCOWLayer{{ + LayerID: layerID, + MountedPath: mountPath, + }}, + }, nil + default: + return nil, fmt.Errorf("invalid layer type %T", wl) + } +} + // Represents a single layer that is mounted and ready to use. Depending on the type of // layers each individual layer may or may not be mounted. However, HCS still needs paths // of individual layers and a unique ID for each layer.