//go:build !windows package containerd import ( "context" "fmt" "os" "path/filepath" "syscall" "github.com/containerd/containerd/mount" "github.com/containerd/containerd/snapshots" "github.com/containerd/continuity/fs" "github.com/containerd/continuity/sysx" "github.com/docker/docker/pkg/idtools" ) const ( // Values based on linux/include/uapi/linux/capability.h xattrCapsSz2 = 20 versionOffset = 3 vfsCapRevision2 = 2 vfsCapRevision3 = 3 remapSuffix = "-remap" ) func (i *ImageService) remapSnapshot(ctx context.Context, snapshotter snapshots.Snapshotter, id string, parentSnapshot string) error { _, err := snapshotter.Prepare(ctx, id, parentSnapshot) if err != nil { return err } mounts, err := snapshotter.Mounts(ctx, id) if err != nil { return err } if err := i.remapRootFS(ctx, mounts); err != nil { return err } return err } func (i *ImageService) remapRootFS(ctx context.Context, mounts []mount.Mount) error { return mount.WithTempMount(ctx, mounts, func(root string) error { return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } stat := info.Sys().(*syscall.Stat_t) if stat == nil { return fmt.Errorf("cannot get underlying data for %s", path) } ids, err := i.idMapping.ToHost(idtools.Identity{UID: int(stat.Uid), GID: int(stat.Gid)}) if err != nil { return err } return chownWithCaps(path, ids.UID, ids.GID) }) }) } func (i *ImageService) copyAndUnremapRootFS(ctx context.Context, dst, src []mount.Mount) error { return mount.WithTempMount(ctx, src, func(source string) error { return mount.WithTempMount(ctx, dst, func(root string) error { // TODO: Update CopyDir to support remap directly if err := fs.CopyDir(root, source); err != nil { return fmt.Errorf("failed to copy: %w", err) } return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } stat := info.Sys().(*syscall.Stat_t) if stat == nil { return fmt.Errorf("cannot get underlying data for %s", path) } uid, gid, err := i.idMapping.ToContainer(idtools.Identity{UID: int(stat.Uid), GID: int(stat.Gid)}) if err != nil { return err } return chownWithCaps(path, uid, gid) }) }) }) } func (i *ImageService) unremapRootFS(ctx context.Context, mounts []mount.Mount) error { return mount.WithTempMount(ctx, mounts, func(root string) error { return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } stat := info.Sys().(*syscall.Stat_t) if stat == nil { return fmt.Errorf("cannot get underlying data for %s", path) } uid, gid, err := i.idMapping.ToContainer(idtools.Identity{UID: int(stat.Uid), GID: int(stat.Gid)}) if err != nil { return err } return chownWithCaps(path, uid, gid) }) }) } // chownWithCaps will chown path and preserve the extended attributes. // chowning a file will remove the capabilities, so we need to first get all of // them, chown the file, and then set the extended attributes func chownWithCaps(path string, uid int, gid int) error { xattrKeys, err := sysx.LListxattr(path) if err != nil { return err } xattrs := make(map[string][]byte, len(xattrKeys)) for _, xattr := range xattrKeys { data, err := sysx.LGetxattr(path, xattr) if err != nil { return err } xattrs[xattr] = data } if err := os.Lchown(path, uid, gid); err != nil { return err } for xattrKey, xattrValue := range xattrs { length := len(xattrValue) // make sure the capabilities are version 2, // capabilities version 3 also store the root uid of the namespace, // we don't want this when we are in userns-remap mode // see: https://github.com/moby/moby/pull/41724 if xattrKey == "security.capability" && xattrValue[versionOffset] == vfsCapRevision3 { xattrValue[versionOffset] = vfsCapRevision2 length = xattrCapsSz2 } if err := sysx.LSetxattr(path, xattrKey, xattrValue[:length], 0); err != nil { return err } } return nil }