github.com/moby/docker@v26.1.3+incompatible/daemon/containerd/image_snapshot_unix.go (about)

     1  //go:build !windows
     2  
     3  package containerd
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"syscall"
    11  
    12  	"github.com/containerd/containerd/mount"
    13  	"github.com/containerd/containerd/snapshots"
    14  	"github.com/containerd/continuity/fs"
    15  	"github.com/containerd/continuity/sysx"
    16  	"github.com/docker/docker/pkg/idtools"
    17  )
    18  
    19  const (
    20  	// Values based on linux/include/uapi/linux/capability.h
    21  	xattrCapsSz2    = 20
    22  	versionOffset   = 3
    23  	vfsCapRevision2 = 2
    24  	vfsCapRevision3 = 3
    25  	remapSuffix     = "-remap"
    26  )
    27  
    28  func (i *ImageService) remapSnapshot(ctx context.Context, snapshotter snapshots.Snapshotter, id string, parentSnapshot string) error {
    29  	_, err := snapshotter.Prepare(ctx, id, parentSnapshot)
    30  	if err != nil {
    31  		return err
    32  	}
    33  	mounts, err := snapshotter.Mounts(ctx, id)
    34  	if err != nil {
    35  		return err
    36  	}
    37  
    38  	if err := i.remapRootFS(ctx, mounts); err != nil {
    39  		return err
    40  	}
    41  
    42  	return err
    43  }
    44  
    45  func (i *ImageService) remapRootFS(ctx context.Context, mounts []mount.Mount) error {
    46  	return mount.WithTempMount(ctx, mounts, func(root string) error {
    47  		return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
    48  			if err != nil {
    49  				return err
    50  			}
    51  
    52  			stat := info.Sys().(*syscall.Stat_t)
    53  			if stat == nil {
    54  				return fmt.Errorf("cannot get underlying data for %s", path)
    55  			}
    56  
    57  			ids, err := i.idMapping.ToHost(idtools.Identity{UID: int(stat.Uid), GID: int(stat.Gid)})
    58  			if err != nil {
    59  				return err
    60  			}
    61  
    62  			return chownWithCaps(path, ids.UID, ids.GID)
    63  		})
    64  	})
    65  }
    66  
    67  func (i *ImageService) copyAndUnremapRootFS(ctx context.Context, dst, src []mount.Mount) error {
    68  	return mount.WithTempMount(ctx, src, func(source string) error {
    69  		return mount.WithTempMount(ctx, dst, func(root string) error {
    70  			// TODO: Update CopyDir to support remap directly
    71  			if err := fs.CopyDir(root, source); err != nil {
    72  				return fmt.Errorf("failed to copy: %w", err)
    73  			}
    74  
    75  			return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
    76  				if err != nil {
    77  					return err
    78  				}
    79  
    80  				stat := info.Sys().(*syscall.Stat_t)
    81  				if stat == nil {
    82  					return fmt.Errorf("cannot get underlying data for %s", path)
    83  				}
    84  
    85  				uid, gid, err := i.idMapping.ToContainer(idtools.Identity{UID: int(stat.Uid), GID: int(stat.Gid)})
    86  				if err != nil {
    87  					return err
    88  				}
    89  
    90  				return chownWithCaps(path, uid, gid)
    91  			})
    92  		})
    93  	})
    94  }
    95  
    96  func (i *ImageService) unremapRootFS(ctx context.Context, mounts []mount.Mount) error {
    97  	return mount.WithTempMount(ctx, mounts, func(root string) error {
    98  		return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
    99  			if err != nil {
   100  				return err
   101  			}
   102  
   103  			stat := info.Sys().(*syscall.Stat_t)
   104  			if stat == nil {
   105  				return fmt.Errorf("cannot get underlying data for %s", path)
   106  			}
   107  
   108  			uid, gid, err := i.idMapping.ToContainer(idtools.Identity{UID: int(stat.Uid), GID: int(stat.Gid)})
   109  			if err != nil {
   110  				return err
   111  			}
   112  
   113  			return chownWithCaps(path, uid, gid)
   114  		})
   115  	})
   116  }
   117  
   118  // chownWithCaps will chown path and preserve the extended attributes.
   119  // chowning a file will remove the capabilities, so we need to first get all of
   120  // them, chown the file, and then set the extended attributes
   121  func chownWithCaps(path string, uid int, gid int) error {
   122  	xattrKeys, err := sysx.LListxattr(path)
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	xattrs := make(map[string][]byte, len(xattrKeys))
   128  
   129  	for _, xattr := range xattrKeys {
   130  		data, err := sysx.LGetxattr(path, xattr)
   131  		if err != nil {
   132  			return err
   133  		}
   134  		xattrs[xattr] = data
   135  	}
   136  
   137  	if err := os.Lchown(path, uid, gid); err != nil {
   138  		return err
   139  	}
   140  
   141  	for xattrKey, xattrValue := range xattrs {
   142  		length := len(xattrValue)
   143  		// make sure the capabilities are version 2,
   144  		// capabilities version 3 also store the root uid of the namespace,
   145  		// we don't want this when we are in userns-remap mode
   146  		// see: https://github.com/moby/moby/pull/41724
   147  		if xattrKey == "security.capability" && xattrValue[versionOffset] == vfsCapRevision3 {
   148  			xattrValue[versionOffset] = vfsCapRevision2
   149  			length = xattrCapsSz2
   150  		}
   151  		if err := sysx.LSetxattr(path, xattrKey, xattrValue[:length], 0); err != nil {
   152  			return err
   153  		}
   154  	}
   155  
   156  	return nil
   157  }