Skip to content

Commit d023d47

Browse files
committed
storage: fix UID/GID map handling for shifting containers
Remove the canUseShifting branch from CreateContainer and aways store the caller's IDMappingOptions. In layers.go, when the driver supports shifting, set the rechown target to identity instead of the container's maps. This avoids unnecessary UpdateLayerIDMap calls (which break overlay with idmapped mounts) while still handling the migration case where a layer was created on a system without shifting support. Closes: https://redhat.atlassian.net/browse/RHEL-160859 Assisted-by: Claude Opus 4.6 Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
1 parent 7ae6c5c commit d023d47

2 files changed

Lines changed: 105 additions & 2 deletions

File tree

storage/layers.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,9 +1674,13 @@ func (r *layerStore) create(id string, parentLayer *Layer, names []string, mount
16741674
}
16751675
}
16761676

1677+
targetMappings := idMappings
1678+
if r.driver.SupportsShifting(moreOptions.UIDMap, moreOptions.GIDMap) {
1679+
targetMappings = &idtools.IDMappings{}
1680+
}
16771681
if oldMappings != nil &&
1678-
(!reflect.DeepEqual(oldMappings.UIDs(), idMappings.UIDs()) || !reflect.DeepEqual(oldMappings.GIDs(), idMappings.GIDs())) {
1679-
if err = r.driver.UpdateLayerIDMap(id, oldMappings, idMappings, mountLabel); err != nil {
1682+
(!reflect.DeepEqual(oldMappings.UIDs(), targetMappings.UIDs()) || !reflect.DeepEqual(oldMappings.GIDs(), targetMappings.GIDs())) {
1683+
if err = r.driver.UpdateLayerIDMap(id, oldMappings, targetMappings, mountLabel); err != nil {
16801684
cleanupFailureContext = "in UpdateLayerIDMap"
16811685
return nil, -1, err
16821686
}

storage/store_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package storage
22

33
import (
4+
"archive/tar"
5+
"io"
46
"os"
57
"path/filepath"
68
"testing"
@@ -629,3 +631,100 @@ func TestStoreDelete(t *testing.T) {
629631

630632
store.Free()
631633
}
634+
635+
// TestCreateContainerShifting verifies that when the overlay driver supports
636+
// shifting (idmapped mounts), CreateContainer stores the UID/GID maps on the
637+
// container layer so that Diff() can reverse-map host UIDs back to container
638+
// UIDs. It also verifies that UpdateLayerIDMap is not called (no diff1
639+
// directory is created).
640+
func TestCreateContainerShifting(t *testing.T) {
641+
reexec.Init()
642+
643+
if os.Getuid() != 0 {
644+
t.Skip("test requires root")
645+
}
646+
647+
uidMap := []idtools.IDMap{{ContainerID: 0, HostID: 1000000, Size: 65536}}
648+
gidMap := []idtools.IDMap{{ContainerID: 0, HostID: 1000000, Size: 65536}}
649+
650+
store := newTestStore(t, StoreOptions{
651+
GraphDriverName: "overlay",
652+
UIDMap: uidMap,
653+
GIDMap: gidMap,
654+
})
655+
defer func() {
656+
_, _ = store.Shutdown(true)
657+
store.Free()
658+
}()
659+
660+
driver, err := store.GraphDriver()
661+
require.NoError(t, err)
662+
if !driver.SupportsShifting(uidMap, gidMap) {
663+
t.Skip("overlay driver does not support shifting on this kernel")
664+
}
665+
666+
// Create a base layer with a file owned by container UID 0.
667+
baseLayer, err := store.CreateLayer("", "", nil, "", false, nil)
668+
require.NoError(t, err)
669+
670+
image, err := store.CreateImage("", nil, baseLayer.ID, "", nil)
671+
require.NoError(t, err)
672+
673+
// Create a container from the image with UID mappings.
674+
// Pass the maps via ContainerOptions, as buildah does.
675+
container, err := store.CreateContainer("", nil, image.ID, "", "", &ContainerOptions{
676+
IDMappingOptions: IDMappingOptions{
677+
UIDMap: uidMap,
678+
GIDMap: gidMap,
679+
},
680+
})
681+
require.NoError(t, err)
682+
683+
// Verify the container layer has the UID/GID maps stored.
684+
containerLayer, err := store.Layer(container.LayerID)
685+
require.NoError(t, err)
686+
assert.Equal(t, uidMap, containerLayer.UIDMap, "container layer should have UID maps stored")
687+
assert.Equal(t, gidMap, containerLayer.GIDMap, "container layer should have GID maps stored")
688+
689+
// Verify that UpdateLayerIDMap was NOT called: the overlay driver creates
690+
// a "diff1" directory when rotating diff dirs during UpdateLayerIDMap.
691+
graphRoot := store.GraphRoot()
692+
diff1Path := filepath.Join(graphRoot, "overlay", containerLayer.ID, "diff1")
693+
_, err = os.Stat(diff1Path)
694+
assert.True(t, os.IsNotExist(err), "diff1 should not exist (UpdateLayerIDMap should not have been called)")
695+
696+
// Mount the container, write a file with host UID, unmount.
697+
mountPoint, err := store.Mount(container.ID, "")
698+
require.NoError(t, err)
699+
700+
testFile := filepath.Join(mountPoint, "testfile")
701+
require.NoError(t, os.WriteFile(testFile, []byte("hello"), 0o644))
702+
// Simulate what happens in a user namespace: the file gets the host UID.
703+
require.NoError(t, os.Chown(testFile, 1000000, 1000000))
704+
705+
_, err = store.Unmount(container.ID, false)
706+
require.NoError(t, err)
707+
708+
// Generate a diff and verify that Diff() maps host UIDs back to
709+
// container UIDs using the stored maps (ToContainer translation).
710+
rc, err := store.Diff("", containerLayer.ID, nil)
711+
require.NoError(t, err)
712+
defer rc.Close()
713+
714+
tr := tar.NewReader(rc)
715+
found := false
716+
for {
717+
hdr, err := tr.Next()
718+
if err == io.EOF {
719+
break
720+
}
721+
require.NoError(t, err)
722+
if hdr.Name == "testfile" {
723+
found = true
724+
assert.Equal(t, 0, hdr.Uid, "Diff() should translate host UID back to container UID 0")
725+
assert.Equal(t, 0, hdr.Gid, "Diff() should translate host GID back to container GID 0")
726+
break
727+
}
728+
}
729+
assert.True(t, found, "testfile should be present in the diff")
730+
}

0 commit comments

Comments
 (0)