Browse code

Update daemon to use moby sys/user identity mapping

Signed-off-by: Derek McGowan <derek@mcg.dev>

Derek McGowan authored on 2025/03/01 18:57:51
Showing 14 changed files
... ...
@@ -2,6 +2,7 @@ package daemon // import "github.com/docker/docker/daemon"
2 2
 
3 3
 import (
4 4
 	"github.com/docker/docker/pkg/archive"
5
+	"github.com/docker/docker/pkg/idtools"
5 6
 )
6 7
 
7 8
 // defaultTarCopyOptions is the setting that is used when unpacking an archive
... ...
@@ -9,6 +10,6 @@ import (
9 9
 func (daemon *Daemon) defaultTarCopyOptions(noOverwriteDirNonDir bool) *archive.TarOptions {
10 10
 	return &archive.TarOptions{
11 11
 		NoOverwriteDirNonDir: noOverwriteDirNonDir,
12
-		IDMap:                daemon.idMapping,
12
+		IDMap:                idtools.FromUserIdentityMapping(daemon.idMapping),
13 13
 	}
14 14
 }
... ...
@@ -23,6 +23,7 @@ import (
23 23
 	"github.com/docker/docker/pkg/process"
24 24
 	"github.com/docker/docker/pkg/stringid"
25 25
 	"github.com/moby/sys/mount"
26
+	"github.com/moby/sys/user"
26 27
 	"github.com/opencontainers/selinux/go-selinux/label"
27 28
 	"github.com/pkg/errors"
28 29
 	"go.opentelemetry.io/otel"
... ...
@@ -251,14 +252,14 @@ func (daemon *Daemon) setupIPCDirs(ctr *container.Container) error {
251 251
 		fallthrough
252 252
 
253 253
 	case ipcMode.IsShareable():
254
-		rootIDs := daemon.idMapping.RootPair()
254
+		uid, gid := daemon.idMapping.RootPair()
255 255
 		if !ctr.HasMountFor("/dev/shm") {
256 256
 			shmPath, err := ctr.ShmResourcePath()
257 257
 			if err != nil {
258 258
 				return err
259 259
 			}
260 260
 
261
-			if err := idtools.MkdirAllAndChown(shmPath, 0o700, rootIDs); err != nil {
261
+			if err := user.MkdirAllAndChown(shmPath, 0o700, uid, gid); err != nil {
262 262
 				return err
263 263
 			}
264 264
 
... ...
@@ -266,7 +267,7 @@ func (daemon *Daemon) setupIPCDirs(ctr *container.Container) error {
266 266
 			if err := unix.Mount("shm", shmPath, "tmpfs", uintptr(unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV), label.FormatMountLabel(shmproperty, ctr.GetMountLabel())); err != nil {
267 267
 				return fmt.Errorf("mounting shm tmpfs: %s", err)
268 268
 			}
269
-			if err := os.Chown(shmPath, rootIDs.UID, rootIDs.GID); err != nil {
269
+			if err := os.Chown(shmPath, uid, gid); err != nil {
270 270
 				return err
271 271
 			}
272 272
 			ctr.ShmPath = shmPath
... ...
@@ -298,7 +299,7 @@ func (daemon *Daemon) setupSecretDir(ctr *container.Container) (setupErr error)
298 298
 	}
299 299
 
300 300
 	// retrieve possible remapped range start for root UID, GID
301
-	rootIDs := daemon.idMapping.RootPair()
301
+	ruid, rgid := daemon.idMapping.RootPair()
302 302
 
303 303
 	for _, s := range ctr.SecretReferences {
304 304
 		// TODO (ehazlett): use type switch when more are supported
... ...
@@ -313,7 +314,7 @@ func (daemon *Daemon) setupSecretDir(ctr *container.Container) (setupErr error)
313 313
 		if err != nil {
314 314
 			return errors.Wrap(err, "error getting secret file path")
315 315
 		}
316
-		if err := idtools.MkdirAllAndChown(filepath.Dir(fPath), 0o700, rootIDs); err != nil {
316
+		if err := user.MkdirAllAndChown(filepath.Dir(fPath), 0o700, ruid, rgid); err != nil {
317 317
 			return errors.Wrap(err, "error creating secret mount path")
318 318
 		}
319 319
 
... ...
@@ -338,7 +339,7 @@ func (daemon *Daemon) setupSecretDir(ctr *container.Container) (setupErr error)
338 338
 			return err
339 339
 		}
340 340
 
341
-		if err := os.Chown(fPath, rootIDs.UID+uid, rootIDs.GID+gid); err != nil {
341
+		if err := os.Chown(fPath, ruid+uid, rgid+gid); err != nil {
342 342
 			return errors.Wrap(err, "error setting ownership for secret")
343 343
 		}
344 344
 		if err := os.Chmod(fPath, s.File.Mode); err != nil {
... ...
@@ -364,7 +365,7 @@ func (daemon *Daemon) setupSecretDir(ctr *container.Container) (setupErr error)
364 364
 		if err != nil {
365 365
 			return errors.Wrap(err, "error getting config file path for container")
366 366
 		}
367
-		if err := idtools.MkdirAllAndChown(filepath.Dir(fPath), 0o700, rootIDs); err != nil {
367
+		if err := user.MkdirAllAndChown(filepath.Dir(fPath), 0o700, ruid, rgid); err != nil {
368 368
 			return errors.Wrap(err, "error creating config mount path")
369 369
 		}
370 370
 
... ...
@@ -389,7 +390,7 @@ func (daemon *Daemon) setupSecretDir(ctr *container.Container) (setupErr error)
389 389
 			return err
390 390
 		}
391 391
 
392
-		if err := os.Chown(fPath, rootIDs.UID+uid, rootIDs.GID+gid); err != nil {
392
+		if err := os.Chown(fPath, ruid+uid, rgid+gid); err != nil {
393 393
 			return errors.Wrap(err, "error setting ownership for config")
394 394
 		}
395 395
 		if err := os.Chmod(fPath, configRef.File.Mode); err != nil {
... ...
@@ -404,18 +405,18 @@ func (daemon *Daemon) setupSecretDir(ctr *container.Container) (setupErr error)
404 404
 // In practice this is using a tmpfs mount and is used for both "configs" and "secrets"
405 405
 func (daemon *Daemon) createSecretsDir(ctr *container.Container) error {
406 406
 	// retrieve possible remapped range start for root UID, GID
407
-	rootIDs := daemon.idMapping.RootPair()
407
+	uid, gid := daemon.idMapping.RootPair()
408 408
 	dir, err := ctr.SecretMountPath()
409 409
 	if err != nil {
410 410
 		return errors.Wrap(err, "error getting container secrets dir")
411 411
 	}
412 412
 
413 413
 	// create tmpfs
414
-	if err := idtools.MkdirAllAndChown(dir, 0o700, rootIDs); err != nil {
414
+	if err := user.MkdirAllAndChown(dir, 0o700, uid, gid); err != nil {
415 415
 		return errors.Wrap(err, "error creating secret local mount path")
416 416
 	}
417 417
 
418
-	tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootIDs.UID, rootIDs.GID)
418
+	tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", uid, gid)
419 419
 	if err := mount.Mount("tmpfs", dir, "tmpfs", "nodev,nosuid,noexec,"+tmpfsOwnership); err != nil {
420 420
 		return errors.Wrap(err, "unable to setup secret mount")
421 421
 	}
... ...
@@ -430,8 +431,8 @@ func (daemon *Daemon) remountSecretDir(ctr *container.Container) error {
430 430
 	if err := label.Relabel(dir, ctr.MountLabel, false); err != nil {
431 431
 		log.G(context.TODO()).WithError(err).WithField("dir", dir).Warn("Error while attempting to set selinux label")
432 432
 	}
433
-	rootIDs := daemon.idMapping.RootPair()
434
-	tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootIDs.UID, rootIDs.GID)
433
+	uid, gid := daemon.idMapping.RootPair()
434
+	tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", uid, gid)
435 435
 
436 436
 	// remount secrets ro
437 437
 	if err := mount.Mount("tmpfs", dir, "tmpfs", "remount,ro,"+tmpfsOwnership); err != nil {
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"github.com/docker/docker/container"
15 15
 	"github.com/docker/docker/errdefs"
16 16
 	"github.com/docker/docker/oci"
17
+	"github.com/docker/docker/pkg/idtools"
17 18
 	volumemounts "github.com/docker/docker/volume/mounts"
18 19
 	volumeopts "github.com/docker/docker/volume/service/opts"
19 20
 	"github.com/opencontainers/selinux/go-selinux/label"
... ...
@@ -27,8 +28,8 @@ func (daemon *Daemon) createContainerOSSpecificSettings(ctx context.Context, con
27 27
 	}
28 28
 	defer daemon.Unmount(container)
29 29
 
30
-	rootIDs := daemon.idMapping.RootPair()
31
-	if err := container.SetupWorkingDirectory(rootIDs); err != nil {
30
+	uid, gid := daemon.idMapping.RootPair()
31
+	if err := container.SetupWorkingDirectory(idtools.Identity{UID: uid, GID: gid}); err != nil {
32 32
 		return err
33 33
 	}
34 34
 
... ...
@@ -107,8 +108,8 @@ func (daemon *Daemon) populateVolume(ctx context.Context, c *container.Container
107 107
 		}
108 108
 		return err
109 109
 	}
110
-
111
-	volumePath, cleanup, err := mnt.Setup(ctx, c.MountLabel, daemon.idMapping.RootPair(), nil)
110
+	uid, gid := daemon.idMapping.RootPair()
111
+	volumePath, cleanup, err := mnt.Setup(ctx, c.MountLabel, idtools.Identity{UID: uid, GID: gid}, nil)
112 112
 	if err != nil {
113 113
 		if errdefs.IsNotFound(err) {
114 114
 			return nil
... ...
@@ -75,6 +75,7 @@ import (
75 75
 	"github.com/moby/buildkit/util/grpcerrors"
76 76
 	"github.com/moby/buildkit/util/tracing"
77 77
 	"github.com/moby/locker"
78
+	"github.com/moby/sys/user"
78 79
 	"github.com/moby/sys/userns"
79 80
 	"github.com/pkg/errors"
80 81
 	"go.etcd.io/bbolt"
... ...
@@ -113,7 +114,7 @@ type Daemon struct {
113 113
 	sysInfoOnce       sync.Once
114 114
 	sysInfo           *sysinfo.SysInfo
115 115
 	shutdown          bool
116
-	idMapping         idtools.IdentityMapping
116
+	idMapping         user.IdentityMapping
117 117
 	PluginStore       *plugin.Store // TODO: remove
118 118
 	pluginManager     *plugin.Manager
119 119
 	linkIndex         *linkIndex
... ...
@@ -791,7 +792,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
791 791
 	if err != nil {
792 792
 		return nil, err
793 793
 	}
794
-	rootIDs := idMapping.RootPair()
794
+	uid, gid := idMapping.RootPair()
795 795
 
796 796
 	// set up the tmpDir to use a canonical path
797 797
 	tmp, err := prepareTempDir(config.Root)
... ...
@@ -878,10 +879,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
878 878
 	}
879 879
 
880 880
 	daemonRepo := filepath.Join(cfgStore.Root, "containers")
881
-	if err := idtools.MkdirAllAndChown(daemonRepo, 0o710, idtools.Identity{
882
-		UID: idtools.CurrentIdentity().UID,
883
-		GID: rootIDs.GID,
884
-	}); err != nil {
881
+	if err := user.MkdirAllAndChown(daemonRepo, 0o710, os.Getuid(), gid); err != nil {
885 882
 		return nil, err
886 883
 	}
887 884
 
... ...
@@ -999,7 +997,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
999 999
 	}
1000 1000
 	log.G(ctx).Debugf("Using default logging driver %s", d.defaultLogConfig.Type)
1001 1001
 
1002
-	d.volumes, err = volumesservice.NewVolumeService(cfgStore.Root, d.PluginStore, rootIDs, d)
1002
+	d.volumes, err = volumesservice.NewVolumeService(cfgStore.Root, d.PluginStore, idtools.Identity{UID: uid, GID: gid}, d)
1003 1003
 	if err != nil {
1004 1004
 		return nil, err
1005 1005
 	}
... ...
@@ -1074,15 +1072,15 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
1074 1074
 			RegistryHosts:   d.RegistryHosts,
1075 1075
 			Registry:        d.registryService,
1076 1076
 			EventsService:   d.EventsService,
1077
-			IDMapping:       idMapping,
1078
-			RefCountMounter: snapshotter.NewMounter(config.Root, driverName, idMapping),
1077
+			IDMapping:       idtools.FromUserIdentityMapping(idMapping),
1078
+			RefCountMounter: snapshotter.NewMounter(config.Root, driverName, idtools.FromUserIdentityMapping(idMapping)),
1079 1079
 		})
1080 1080
 	} else {
1081 1081
 		layerStore, err := layer.NewStoreFromOptions(layer.StoreOptions{
1082 1082
 			Root:               cfgStore.Root,
1083 1083
 			GraphDriver:        driverName,
1084 1084
 			GraphDriverOptions: cfgStore.GraphOptions,
1085
-			IDMapping:          idMapping,
1085
+			IDMapping:          idtools.FromUserIdentityMapping(idMapping),
1086 1086
 		})
1087 1087
 		if err != nil {
1088 1088
 			return nil, err
... ...
@@ -1423,7 +1421,7 @@ func prepareTempDir(rootDir string) (string, error) {
1423 1423
 			}
1424 1424
 		}
1425 1425
 	}
1426
-	return tmpDir, idtools.MkdirAllAndChown(tmpDir, 0o700, idtools.CurrentIdentity())
1426
+	return tmpDir, user.MkdirAllAndChown(tmpDir, 0o700, os.Getuid(), os.Getegid())
1427 1427
 }
1428 1428
 
1429 1429
 func (daemon *Daemon) setGenericResources(conf *config.Config) error {
... ...
@@ -1545,7 +1543,8 @@ func CreateDaemonRoot(config *config.Config) error {
1545 1545
 	if err != nil {
1546 1546
 		return err
1547 1547
 	}
1548
-	return setupDaemonRoot(config, realRoot, idMapping.RootPair())
1548
+	uid, gid := idMapping.RootPair()
1549
+	return setupDaemonRoot(config, realRoot, uid, gid)
1549 1550
 }
1550 1551
 
1551 1552
 // RemapContainerdNamespaces returns the right containerd namespaces to use:
... ...
@@ -1561,16 +1560,16 @@ func RemapContainerdNamespaces(config *config.Config) (ns string, pluginNs strin
1561 1561
 	if idMapping.Empty() {
1562 1562
 		return config.ContainerdNamespace, config.ContainerdPluginNamespace, nil
1563 1563
 	}
1564
-	root := idMapping.RootPair()
1564
+	uid, gid := idMapping.RootPair()
1565 1565
 
1566 1566
 	ns = config.ContainerdNamespace
1567 1567
 	if _, ok := config.ValuesSet["containerd-namespace"]; !ok {
1568
-		ns = fmt.Sprintf("%s-%d.%d", config.ContainerdNamespace, root.UID, root.GID)
1568
+		ns = fmt.Sprintf("%s-%d.%d", config.ContainerdNamespace, uid, gid)
1569 1569
 	}
1570 1570
 
1571 1571
 	pluginNs = config.ContainerdPluginNamespace
1572 1572
 	if _, ok := config.ValuesSet["containerd-plugin-namespace"]; !ok {
1573
-		pluginNs = fmt.Sprintf("%s-%d.%d", config.ContainerdPluginNamespace, root.UID, root.GID)
1573
+		pluginNs = fmt.Sprintf("%s-%d.%d", config.ContainerdPluginNamespace, uid, gid)
1574 1574
 	}
1575 1575
 
1576 1576
 	return ns, pluginNs, nil
... ...
@@ -1601,7 +1600,7 @@ func (daemon *Daemon) GetAttachmentStore() *network.AttachmentStore {
1601 1601
 
1602 1602
 // IdentityMapping returns uid/gid mapping or a SID (in the case of Windows) for the builder
1603 1603
 func (daemon *Daemon) IdentityMapping() idtools.IdentityMapping {
1604
-	return daemon.idMapping
1604
+	return idtools.FromUserIdentityMapping(daemon.idMapping)
1605 1605
 }
1606 1606
 
1607 1607
 // ImageService returns the Daemon's ImageService
... ...
@@ -45,6 +45,7 @@ import (
45 45
 	"github.com/docker/docker/runconfig"
46 46
 	volumemounts "github.com/docker/docker/volume/mounts"
47 47
 	"github.com/moby/sys/mount"
48
+	"github.com/moby/sys/user"
48 49
 	"github.com/opencontainers/runtime-spec/specs-go"
49 50
 	"github.com/opencontainers/selinux/go-selinux"
50 51
 	"github.com/opencontainers/selinux/go-selinux/label"
... ...
@@ -1250,9 +1251,10 @@ func removeDefaultBridgeInterface() {
1250 1250
 	}
1251 1251
 }
1252 1252
 
1253
-func setupInitLayer(idMapping idtools.IdentityMapping) func(string) error {
1253
+func setupInitLayer(idMapping user.IdentityMapping) func(string) error {
1254 1254
 	return func(initPath string) error {
1255
-		return initlayer.Setup(initPath, idMapping.RootPair())
1255
+		uid, gid := idMapping.RootPair()
1256
+		return initlayer.Setup(initPath, idtools.Identity{UID: uid, GID: gid})
1256 1257
 	}
1257 1258
 }
1258 1259
 
... ...
@@ -1349,9 +1351,9 @@ func parseRemappedRoot(usergrp string) (string, string, error) {
1349 1349
 	return username, groupname, nil
1350 1350
 }
1351 1351
 
1352
-func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) {
1352
+func setupRemappedRoot(config *config.Config) (user.IdentityMapping, error) {
1353 1353
 	if runtime.GOOS != "linux" && config.RemappedRoot != "" {
1354
-		return idtools.IdentityMapping{}, fmt.Errorf("User namespaces are only supported on Linux")
1354
+		return user.IdentityMapping{}, fmt.Errorf("User namespaces are only supported on Linux")
1355 1355
 	}
1356 1356
 
1357 1357
 	// if the daemon was started with remapped root option, parse
... ...
@@ -1359,13 +1361,13 @@ func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) {
1359 1359
 	if config.RemappedRoot != "" {
1360 1360
 		username, groupname, err := parseRemappedRoot(config.RemappedRoot)
1361 1361
 		if err != nil {
1362
-			return idtools.IdentityMapping{}, err
1362
+			return user.IdentityMapping{}, err
1363 1363
 		}
1364 1364
 		if username == "root" {
1365 1365
 			// Cannot setup user namespaces with a 1-to-1 mapping; "--root=0:0" is a no-op
1366 1366
 			// effectively
1367 1367
 			log.G(context.TODO()).Warn("User namespaces: root cannot be remapped with itself; user namespaces are OFF")
1368
-			return idtools.IdentityMapping{}, nil
1368
+			return user.IdentityMapping{}, nil
1369 1369
 		}
1370 1370
 		log.G(context.TODO()).Infof("User namespaces: ID ranges will be mapped to subuid/subgid ranges of: %s", username)
1371 1371
 		// update remapped root setting now that we have resolved them to actual names
... ...
@@ -1373,14 +1375,14 @@ func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) {
1373 1373
 
1374 1374
 		mappings, err := usergroup.LoadIdentityMapping(username)
1375 1375
 		if err != nil {
1376
-			return idtools.IdentityMapping{}, errors.Wrap(err, "Can't create ID mappings")
1376
+			return user.IdentityMapping{}, errors.Wrap(err, "Can't create ID mappings")
1377 1377
 		}
1378 1378
 		return mappings, nil
1379 1379
 	}
1380
-	return idtools.IdentityMapping{}, nil
1380
+	return user.IdentityMapping{}, nil
1381 1381
 }
1382 1382
 
1383
-func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools.Identity) error {
1383
+func setupDaemonRoot(config *config.Config, rootDir string, uid, gid int) error {
1384 1384
 	config.Root = rootDir
1385 1385
 	// the docker root metadata directory needs to have execute permissions for all users (g+x,o+x)
1386 1386
 	// so that syscalls executing as non-root, operating on subdirectories of the graph root
... ...
@@ -1400,9 +1402,9 @@ func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools
1400 1400
 		}
1401 1401
 	}
1402 1402
 
1403
-	id := idtools.Identity{UID: idtools.CurrentIdentity().UID, GID: remappedRoot.GID}
1403
+	curuid := os.Getuid()
1404 1404
 	// First make sure the current root dir has the correct perms.
1405
-	if err := idtools.MkdirAllAndChown(config.Root, 0o710, id); err != nil {
1405
+	if err := user.MkdirAllAndChown(config.Root, 0o710, curuid, gid); err != nil {
1406 1406
 		return errors.Wrapf(err, "could not create or set daemon root permissions: %s", config.Root)
1407 1407
 	}
1408 1408
 
... ...
@@ -1411,10 +1413,10 @@ func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools
1411 1411
 	// a new subdirectory with ownership set to the remapped uid/gid (so as to allow
1412 1412
 	// `chdir()` to work for containers namespaced to that uid/gid)
1413 1413
 	if config.RemappedRoot != "" {
1414
-		config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", remappedRoot.UID, remappedRoot.GID))
1414
+		config.Root = filepath.Join(rootDir, fmt.Sprintf("%d.%d", uid, gid))
1415 1415
 		log.G(context.TODO()).Debugf("Creating user namespaced daemon root: %s", config.Root)
1416 1416
 		// Create the root directory if it doesn't exist
1417
-		if err := idtools.MkdirAllAndChown(config.Root, 0o710, id); err != nil {
1417
+		if err := user.MkdirAllAndChown(config.Root, 0o710, curuid, gid); err != nil {
1418 1418
 			return fmt.Errorf("Cannot create daemon root: %s: %v", config.Root, err)
1419 1419
 		}
1420 1420
 		// we also need to verify that any pre-existing directories in the path to
... ...
@@ -1427,7 +1429,7 @@ func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools
1427 1427
 			if dirPath == "/" {
1428 1428
 				break
1429 1429
 			}
1430
-			if !canAccess(dirPath, remappedRoot) {
1430
+			if !canAccess(dirPath, uid, gid) {
1431 1431
 				return fmt.Errorf("a subdirectory in your graphroot path (%s) restricts access to the remapped root uid/gid; please fix by allowing 'o+x' permissions on existing directories", config.Root)
1432 1432
 			}
1433 1433
 		}
... ...
@@ -1445,7 +1447,7 @@ func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools
1445 1445
 // Note: this is a very rudimentary check, and may not produce accurate results,
1446 1446
 // so should not be used for anything other than the current use, see:
1447 1447
 // https://github.com/moby/moby/issues/43724
1448
-func canAccess(path string, pair idtools.Identity) bool {
1448
+func canAccess(path string, uid, gid int) bool {
1449 1449
 	statInfo, err := os.Stat(path)
1450 1450
 	if err != nil {
1451 1451
 		return false
... ...
@@ -1456,11 +1458,11 @@ func canAccess(path string, pair idtools.Identity) bool {
1456 1456
 		return true
1457 1457
 	}
1458 1458
 	ssi := statInfo.Sys().(*syscall.Stat_t)
1459
-	if ssi.Uid == uint32(pair.UID) && (perms&0o100 == 0o100) {
1459
+	if ssi.Uid == uint32(uid) && (perms&0o100 == 0o100) {
1460 1460
 		// owner access.
1461 1461
 		return true
1462 1462
 	}
1463
-	if ssi.Gid == uint32(pair.GID) && (perms&0o010 == 0o010) {
1463
+	if ssi.Gid == uint32(gid) && (perms&0o010 == 0o010) {
1464 1464
 		// group access.
1465 1465
 		return true
1466 1466
 	}
... ...
@@ -24,10 +24,10 @@ import (
24 24
 	"github.com/docker/docker/libnetwork/netlabel"
25 25
 	"github.com/docker/docker/libnetwork/options"
26 26
 	"github.com/docker/docker/libnetwork/scope"
27
-	"github.com/docker/docker/pkg/idtools"
28 27
 	"github.com/docker/docker/pkg/parsers/operatingsystem"
29 28
 	"github.com/docker/docker/pkg/sysinfo"
30 29
 	"github.com/docker/docker/pkg/system"
30
+	"github.com/moby/sys/user"
31 31
 	"github.com/pkg/errors"
32 32
 	"golang.org/x/sys/windows"
33 33
 	"golang.org/x/sys/windows/svc/mgr"
... ...
@@ -56,7 +56,7 @@ func (daemon *Daemon) parseSecurityOpt(daemonCfg *config.Config, securityOptions
56 56
 	return nil
57 57
 }
58 58
 
59
-func setupInitLayer(idMapping idtools.IdentityMapping) func(string) error {
59
+func setupInitLayer(idMapping user.IdentityMapping) func(string) error {
60 60
 	return nil
61 61
 }
62 62
 
... ...
@@ -462,11 +462,11 @@ func recursiveUnmount(_ string) error {
462 462
 	return nil
463 463
 }
464 464
 
465
-func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) {
466
-	return idtools.IdentityMapping{}, nil
465
+func setupRemappedRoot(config *config.Config) (user.IdentityMapping, error) {
466
+	return user.IdentityMapping{}, nil
467 467
 }
468 468
 
469
-func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools.Identity) error {
469
+func setupDaemonRoot(config *config.Config, rootDir string, uid, gid int) error {
470 470
 	config.Root = rootDir
471 471
 	// Create the root directory if it doesn't exists
472 472
 	if err := system.MkdirAllWithACL(config.Root, 0, system.SddlAdministratorsLocalSystem); err != nil {
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"github.com/docker/docker/errdefs"
12 12
 	"github.com/docker/docker/pkg/archive"
13 13
 	"github.com/docker/docker/pkg/chrootarchive"
14
+	"github.com/docker/docker/pkg/idtools"
14 15
 )
15 16
 
16 17
 // ContainerExport writes the contents of the container to the given
... ...
@@ -65,7 +66,7 @@ func (daemon *Daemon) containerExport(ctx context.Context, ctr *container.Contai
65 65
 
66 66
 	archv, err := chrootarchive.Tar(basefs, &archive.TarOptions{
67 67
 		Compression: archive.Uncompressed,
68
-		IDMap:       daemon.idMapping,
68
+		IDMap:       idtools.FromUserIdentityMapping(daemon.idMapping),
69 69
 	}, basefs)
70 70
 	if err != nil {
71 71
 		return err
... ...
@@ -191,7 +191,7 @@ func (daemon *Daemon) fillSecurityOptions(v *system.Info, sysInfo *sysinfo.SysIn
191 191
 	if selinux.GetEnabled() {
192 192
 		securityOptions = append(securityOptions, "name=selinux")
193 193
 	}
194
-	if rootIDs := daemon.idMapping.RootPair(); rootIDs.UID != 0 || rootIDs.GID != 0 {
194
+	if uid, gid := daemon.idMapping.RootPair(); uid != 0 || gid != 0 {
195 195
 		securityOptions = append(securityOptions, "name=userns")
196 196
 	}
197 197
 	if Rootless(cfg) {
... ...
@@ -360,13 +360,13 @@ func WithNamespaces(daemon *Daemon, c *container.Container) coci.SpecOpts {
360 360
 	}
361 361
 }
362 362
 
363
-func specMapping(s []idtools.IDMap) []specs.LinuxIDMapping {
363
+func specMapping(s []user.IDMap) []specs.LinuxIDMapping {
364 364
 	var ids []specs.LinuxIDMapping
365 365
 	for _, item := range s {
366 366
 		ids = append(ids, specs.LinuxIDMapping{
367
-			HostID:      uint32(item.HostID),
368
-			ContainerID: uint32(item.ContainerID),
369
-			Size:        uint32(item.Size),
367
+			HostID:      uint32(item.ParentID),
368
+			ContainerID: uint32(item.ID),
369
+			Size:        uint32(item.Count),
370 370
 		})
371 371
 	}
372 372
 	return ids
... ...
@@ -712,7 +712,8 @@ func withCommonOptions(daemon *Daemon, daemonCfg *dconfig.Config, c *container.C
712 712
 			Path:     c.BaseFS,
713 713
 			Readonly: c.HostConfig.ReadonlyRootfs,
714 714
 		}
715
-		if err := c.SetupWorkingDirectory(daemon.idMapping.RootPair()); err != nil {
715
+		uid, gid := daemon.idMapping.RootPair()
716
+		if err := c.SetupWorkingDirectory(idtools.Identity{UID: uid, GID: gid}); err != nil {
716 717
 			return err
717 718
 		}
718 719
 		cwd := c.Config.WorkingDir
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	mounttypes "github.com/docker/docker/api/types/mount"
15 15
 	"github.com/docker/docker/container"
16 16
 	"github.com/docker/docker/internal/cleanups"
17
+	"github.com/docker/docker/pkg/idtools"
17 18
 	volumemounts "github.com/docker/docker/volume/mounts"
18 19
 	"github.com/pkg/errors"
19 20
 )
... ...
@@ -61,7 +62,8 @@ func (daemon *Daemon) setupMounts(ctx context.Context, c *container.Container) (
61 61
 			return nil
62 62
 		}
63 63
 
64
-		path, clean, err := m.Setup(ctx, c.MountLabel, daemon.idMapping.RootPair(), checkfunc)
64
+		uid, gid := daemon.idMapping.RootPair()
65
+		path, clean, err := m.Setup(ctx, c.MountLabel, idtools.Identity{UID: uid, GID: gid}, checkfunc)
65 66
 		if err != nil {
66 67
 			return nil, nil, err
67 68
 		}
... ...
@@ -106,13 +108,13 @@ func (daemon *Daemon) setupMounts(ctx context.Context, c *container.Container) (
106 106
 	// if we are going to mount any of the network files from container
107 107
 	// metadata, the ownership must be set properly for potential container
108 108
 	// remapped root (user namespaces)
109
-	rootIDs := daemon.idMapping.RootPair()
109
+	uid, gid := daemon.idMapping.RootPair()
110 110
 	for _, mnt := range netMounts {
111 111
 		// we should only modify ownership of network files within our own container
112 112
 		// metadata repository. If the user specifies a mount path external, it is
113 113
 		// up to the user to make sure the file has proper ownership for userns
114 114
 		if strings.Index(mnt.Source, daemon.repository) == 0 {
115
-			if err := os.Chown(mnt.Source, rootIDs.UID, rootIDs.GID); err != nil {
115
+			if err := os.Chown(mnt.Source, uid, gid); err != nil {
116 116
 				return nil, nil, err
117 117
 			}
118 118
 		}
... ...
@@ -1,5 +1,7 @@
1 1
 package daemon // import "github.com/docker/docker/daemon"
2 2
 
3
+import "github.com/docker/docker/pkg/idtools"
4
+
3 5
 // ContainerCreateWorkdir creates the working directory. This solves the
4 6
 // issue arising from https://github.com/docker/docker/issues/27545,
5 7
 // which was initially fixed by https://github.com/docker/docker/pull/27884. But that fix
... ...
@@ -16,5 +18,6 @@ func (daemon *Daemon) ContainerCreateWorkdir(cID string) error {
16 16
 		return err
17 17
 	}
18 18
 	defer daemon.Unmount(container)
19
-	return container.SetupWorkingDirectory(daemon.idMapping.RootPair())
19
+	uid, gid := daemon.idMapping.RootPair()
20
+	return container.SetupWorkingDirectory(idtools.Identity{UID: uid, GID: gid})
20 21
 }
... ...
@@ -7,7 +7,7 @@ import (
7 7
 	"syscall"
8 8
 	"testing"
9 9
 
10
-	"github.com/docker/docker/pkg/idtools"
10
+	mobyuser "github.com/moby/sys/user"
11 11
 	"gotest.tools/v3/assert"
12 12
 	is "gotest.tools/v3/assert/cmp"
13 13
 	"gotest.tools/v3/skip"
... ...
@@ -29,14 +29,13 @@ func TestNewIDMappings(t *testing.T) {
29 29
 	idMapping, err := LoadIdentityMapping(tempUser.Username)
30 30
 	assert.Check(t, err)
31 31
 
32
-	rootUID, rootGID, err := idtools.GetRootUIDGID(idMapping.UIDMaps, idMapping.GIDMaps)
33
-	assert.Check(t, err)
32
+	rootUID, rootGID := idMapping.RootPair()
34 33
 
35 34
 	dirName, err := os.MkdirTemp("", "mkdirall")
36 35
 	assert.Check(t, err, "Couldn't create temp directory")
37 36
 	defer os.RemoveAll(dirName)
38 37
 
39
-	err = idtools.MkdirAllAndChown(dirName, 0o700, idtools.Identity{UID: rootUID, GID: rootGID})
38
+	err = mobyuser.MkdirAllAndChown(dirName, 0o700, rootUID, rootGID)
40 39
 	assert.Check(t, err, "Couldn't change ownership of file path. Got error")
41 40
 	cmd := exec.Command("ls", "-la", dirName)
42 41
 	cmd.SysProcAttr = &syscall.SysProcAttr{
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"strconv"
11 11
 	"syscall"
12 12
 
13
-	"github.com/docker/docker/pkg/idtools"
14 13
 	"github.com/moby/sys/user"
15 14
 )
16 15
 
... ...
@@ -140,28 +139,28 @@ func getExitCode(err error) (int, error) {
140 140
 // LoadIdentityMapping takes a requested username and
141 141
 // using the data from /etc/sub{uid,gid} ranges, creates the
142 142
 // proper uid and gid remapping ranges for that user/group pair
143
-func LoadIdentityMapping(name string) (idtools.IdentityMapping, error) {
143
+func LoadIdentityMapping(name string) (user.IdentityMapping, error) {
144 144
 	usr, err := LookupUser(name)
145 145
 	if err != nil {
146
-		return idtools.IdentityMapping{}, fmt.Errorf("could not get user for username %s: %v", name, err)
146
+		return user.IdentityMapping{}, fmt.Errorf("could not get user for username %s: %v", name, err)
147 147
 	}
148 148
 
149 149
 	subuidRanges, err := lookupSubRangesFile("/etc/subuid", usr)
150 150
 	if err != nil {
151
-		return idtools.IdentityMapping{}, err
151
+		return user.IdentityMapping{}, err
152 152
 	}
153 153
 	subgidRanges, err := lookupSubRangesFile("/etc/subgid", usr)
154 154
 	if err != nil {
155
-		return idtools.IdentityMapping{}, err
155
+		return user.IdentityMapping{}, err
156 156
 	}
157 157
 
158
-	return idtools.IdentityMapping{
158
+	return user.IdentityMapping{
159 159
 		UIDMaps: subuidRanges,
160 160
 		GIDMaps: subgidRanges,
161 161
 	}, nil
162 162
 }
163 163
 
164
-func lookupSubRangesFile(path string, usr user.User) ([]idtools.IDMap, error) {
164
+func lookupSubRangesFile(path string, usr user.User) ([]user.IDMap, error) {
165 165
 	uidstr := strconv.Itoa(usr.Uid)
166 166
 	rangeList, err := user.ParseSubIDFileFilter(path, func(sid user.SubID) bool {
167 167
 		return sid.Name == usr.Name || sid.Name == uidstr
... ...
@@ -173,16 +172,16 @@ func lookupSubRangesFile(path string, usr user.User) ([]idtools.IDMap, error) {
173 173
 		return nil, fmt.Errorf("no subuid ranges found for user %q", usr.Name)
174 174
 	}
175 175
 
176
-	idMap := []idtools.IDMap{}
176
+	idMap := []user.IDMap{}
177 177
 
178
-	containerID := 0
178
+	containerID := int64(0)
179 179
 	for _, idrange := range rangeList {
180
-		idMap = append(idMap, idtools.IDMap{
181
-			ContainerID: containerID,
182
-			HostID:      int(idrange.SubID),
183
-			Size:        int(idrange.Count),
180
+		idMap = append(idMap, user.IDMap{
181
+			ID:       containerID,
182
+			ParentID: idrange.SubID,
183
+			Count:    idrange.Count,
184 184
 		})
185
-		containerID = containerID + int(idrange.Count)
185
+		containerID = containerID + idrange.Count
186 186
 	}
187 187
 	return idMap, nil
188 188
 }
... ...
@@ -119,6 +119,31 @@ type IdentityMapping struct {
119 119
 	GIDMaps []IDMap `json:"GIDMaps"`
120 120
 }
121 121
 
122
+// FromUserIdentityMapping converts a [user.IdentityMapping] to an [idtools.IdentityMapping].
123
+//
124
+// Deprecated: use [user.IdentityMapping] directly, this is transitioning to user package.
125
+func FromUserIdentityMapping(u user.IdentityMapping) IdentityMapping {
126
+	return IdentityMapping{
127
+		UIDMaps: fromUserIDMap(u.UIDMaps),
128
+		GIDMaps: fromUserIDMap(u.GIDMaps),
129
+	}
130
+}
131
+
132
+func fromUserIDMap(u []user.IDMap) []IDMap {
133
+	if u == nil {
134
+		return nil
135
+	}
136
+	m := make([]IDMap, len(u))
137
+	for i := range u {
138
+		m[i] = IDMap{
139
+			ContainerID: int(u[i].ID),
140
+			HostID:      int(u[i].ParentID),
141
+			Size:        int(u[i].Count),
142
+		}
143
+	}
144
+	return m
145
+}
146
+
122 147
 // RootPair returns a uid and gid pair for the root user. The error is ignored
123 148
 // because a root user always exists, and the defaults are correct when the uid
124 149
 // and gid maps are empty.