github.com/moby/docker@v26.1.3+incompatible/internal/safepath/join_linux.go (about) 1 package safepath 2 3 import ( 4 "context" 5 "os" 6 "path/filepath" 7 "runtime" 8 "strconv" 9 10 "github.com/containerd/log" 11 "github.com/docker/docker/internal/unix_noeintr" 12 "github.com/pkg/errors" 13 "golang.org/x/sys/unix" 14 ) 15 16 // Join makes sure that the concatenation of path and subpath doesn't 17 // resolve to a path outside of path and returns a path to a temporary file that is 18 // a bind mount to the exact same file/directory that was validated. 19 // 20 // After use, it is the caller's responsibility to call Close on the returned 21 // SafePath object, which will unmount the temporary file/directory 22 // and remove it. 23 func Join(_ context.Context, path, subpath string) (*SafePath, error) { 24 base, subpart, err := evaluatePath(path, subpath) 25 if err != nil { 26 return nil, err 27 } 28 29 runtime.LockOSThread() 30 defer runtime.UnlockOSThread() 31 fd, err := safeOpenFd(base, subpart) 32 if err != nil { 33 return nil, err 34 } 35 36 defer unix_noeintr.Close(fd) 37 38 tmpMount, err := tempMountPoint(fd) 39 if err != nil { 40 return nil, errors.Wrap(err, "failed to create temporary file for safe mount") 41 } 42 43 pid := strconv.Itoa(unix.Gettid()) 44 // Using explicit pid path, because /proc/self/fd/<fd> fails with EACCES 45 // when running under "Enhanced Container Isolation" in Docker Desktop 46 // which uses sysbox runtime under the hood. 47 // TODO(vvoland): Investigate. 48 mountSource := "/proc/" + pid + "/fd/" + strconv.Itoa(fd) 49 50 if err := unix_noeintr.Mount(mountSource, tmpMount, "none", unix.MS_BIND, ""); err != nil { 51 os.Remove(tmpMount) 52 return nil, errors.Wrap(err, "failed to mount resolved path") 53 } 54 55 return &SafePath{ 56 path: tmpMount, 57 sourceBase: base, 58 sourceSubpath: subpart, 59 cleanup: cleanupSafePath(tmpMount), 60 }, nil 61 } 62 63 // safeOpenFd opens the file at filepath.Join(path, subpath) in O_PATH 64 // mode and returns the file descriptor if subpath is within the subtree 65 // rooted at path. It is an error if any of components of path or subpath 66 // are symbolic links. 67 // 68 // It is a caller's responsibility to close the returned file descriptor, if no 69 // error was returned. 70 func safeOpenFd(path, subpath string) (int, error) { 71 // Open base volume path (_data directory). 72 prevFd, err := unix_noeintr.Open(path, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0) 73 if err != nil { 74 return -1, &ErrNotAccessible{Path: path, Cause: err} 75 } 76 defer unix_noeintr.Close(prevFd) 77 78 // Try to use the Openat2 syscall first (available on Linux 5.6+). 79 fd, err := unix_noeintr.Openat2(prevFd, subpath, &unix.OpenHow{ 80 Flags: unix.O_PATH | unix.O_CLOEXEC, 81 Mode: 0, 82 Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_MAGICLINKS | unix.RESOLVE_NO_SYMLINKS, 83 }) 84 85 switch { 86 case errors.Is(err, unix.ENOSYS): 87 // Openat2 is not available, fallback to Openat loop. 88 return kubernetesSafeOpen(path, subpath) 89 case errors.Is(err, unix.EXDEV): 90 return -1, &ErrEscapesBase{Base: path, Subpath: subpath} 91 case errors.Is(err, unix.ENOENT), errors.Is(err, unix.ELOOP): 92 return -1, &ErrNotAccessible{Path: filepath.Join(path, subpath), Cause: err} 93 case err != nil: 94 return -1, &os.PathError{Op: "openat2", Path: subpath, Err: err} 95 } 96 97 // Openat2 is available and succeeded. 98 return fd, nil 99 } 100 101 // tempMountPoint creates a temporary file/directory to act as mount 102 // point for the file descriptor. 103 func tempMountPoint(sourceFd int) (string, error) { 104 var stat unix.Stat_t 105 err := unix_noeintr.Fstat(sourceFd, &stat) 106 if err != nil { 107 return "", errors.Wrap(err, "failed to Fstat mount source fd") 108 } 109 110 isDir := (stat.Mode & unix.S_IFMT) == unix.S_IFDIR 111 if isDir { 112 return os.MkdirTemp("", "safe-mount") 113 } 114 115 f, err := os.CreateTemp("", "safe-mount") 116 if err != nil { 117 return "", err 118 } 119 120 p := f.Name() 121 if err := f.Close(); err != nil { 122 return "", err 123 } 124 return p, nil 125 } 126 127 // cleanupSafePaths returns a function that unmounts the path and removes the 128 // mountpoint. 129 func cleanupSafePath(path string) func(context.Context) error { 130 return func(ctx context.Context) error { 131 log.G(ctx).WithField("path", path).Debug("removing safe temp mount") 132 133 if err := unix_noeintr.Unmount(path, unix.MNT_DETACH); err != nil { 134 if errors.Is(err, unix.EINVAL) { 135 log.G(ctx).WithField("path", path).Warn("safe temp mount no longer exists?") 136 return nil 137 } 138 return errors.Wrapf(err, "error unmounting safe mount %s", path) 139 } 140 if err := os.Remove(path); err != nil { 141 if errors.Is(err, os.ErrNotExist) { 142 log.G(ctx).WithField("path", path).Warn("safe temp mount no longer exists?") 143 return nil 144 } 145 return errors.Wrapf(err, "failed to delete temporary safe mount") 146 } 147 148 return nil 149 } 150 }