| //go:build !windows | |
| package containerd | |
| import ( | |
| "context" | |
| "fmt" | |
| "os" | |
| "path/filepath" | |
| "syscall" | |
| "github.com/containerd/containerd/v2/core/mount" | |
| "github.com/containerd/containerd/v2/core/snapshots" | |
| "github.com/containerd/continuity/fs" | |
| "github.com/containerd/continuity/sysx" | |
| ) | |
| 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) | |
| } | |
| uid, gid, err := i.idMapping.ToHost(int(stat.Uid), int(stat.Gid)) | |
| if err != nil { | |
| return err | |
| } | |
| return chownWithCaps(path, uid, 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) | |
| } | |
| inos := make(map[uint64]struct{}) | |
| 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) | |
| } | |
| if _, ok := inos[stat.Ino]; ok { | |
| // Inode already processed, skip | |
| return nil | |
| } | |
| uid, gid, err := i.idMapping.ToContainer(int(stat.Uid), int(stat.Gid)) | |
| if err != nil { | |
| return err | |
| } | |
| inos[stat.Ino] = struct{}{} | |
| 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(int(stat.Uid), 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 | |
| } |