From c87cc3137fbc56cbc155fce10d289520f74799fe Mon Sep 17 00:00:00 2001 From: Cory Snider Date: Tue, 10 Feb 2026 14:15:05 -0500 Subject: [PATCH] WCOW: restore support for client-mounted roots (#2595) 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 (cherry picked from commit fa457e62e68c2047b4f38df2a5c9ee36275166a3) Signed-off-by: Brian Goff --- 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 9df9f199eb..c13662fab1 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.