dotcloud/docker

View on GitHub
daemon/containerd/image_snapshot_unix.go

Summary

Maintainability
B
4 hrs
Test Coverage
//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
}