diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 20b08e75e2..f15dff0af9 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -15,6 +15,7 @@ import ( "strconv" "strings" "sync" + "syscall" "time" "github.com/containerd/cgroups" @@ -1247,7 +1248,7 @@ func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools if dirPath == "/" { break } - if !idtools.CanAccess(dirPath, remappedRoot) { + if !canAccess(dirPath, remappedRoot) { return fmt.Errorf("a subdirectory in your graphroot path (%s) restricts access to the remapped root uid/gid; please fix by allowing 'o+x' permissions on existing directories", config.Root) } } @@ -1259,6 +1260,34 @@ func setupDaemonRoot(config *config.Config, rootDir string, remappedRoot idtools return nil } +// canAccess takes a valid (existing) directory and a uid, gid pair and determines +// if that uid, gid pair has access (execute bit) to the directory. +// +// Note: this is a very rudimentary check, and may not produce accurate results, +// so should not be used for anything other than the current use, see: +// https://github.com/moby/moby/issues/43724 +func canAccess(path string, pair idtools.Identity) bool { + statInfo, err := os.Stat(path) + if err != nil { + return false + } + perms := statInfo.Mode().Perm() + if perms&0o001 == 0o001 { + // world access + return true + } + ssi := statInfo.Sys().(*syscall.Stat_t) + if ssi.Uid == uint32(pair.UID) && (perms&0o100 == 0o100) { + // owner access. + return true + } + if ssi.Gid == uint32(pair.GID) && (perms&0o010 == 0o010) { + // group access. + return true + } + return false +} + func setupDaemonRootPropagation(cfg *config.Config) error { rootParentMount, mountOptions, err := getSourceMount(cfg.Root) if err != nil { diff --git a/pkg/idtools/idtools_unix.go b/pkg/idtools/idtools_unix.go index 2bd8140baa..7d31c69dae 100644 --- a/pkg/idtools/idtools_unix.go +++ b/pkg/idtools/idtools_unix.go @@ -79,30 +79,6 @@ func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting return nil } -// CanAccess takes a valid (existing) directory and a uid, gid pair and determines -// if that uid, gid pair has access (execute bit) to the directory -func CanAccess(path string, pair Identity) bool { - statInfo, err := os.Stat(path) - if err != nil { - return false - } - perms := statInfo.Mode().Perm() - if perms&0o001 == 0o001 { - // world access - return true - } - ssi := statInfo.Sys().(*syscall.Stat_t) - if ssi.Uid == uint32(pair.UID) && (perms&0o100 == 0o100) { - // owner access. - return true - } - if ssi.Gid == uint32(pair.GID) && (perms&0o010 == 0o010) { - // group access. - return true - } - return false -} - // LookupUser uses traditional local system files lookup (from libcontainer/user) on a username, // followed by a call to `getent` for supporting host configured non-files passwd and group dbs func LookupUser(name string) (user.User, error) { diff --git a/pkg/idtools/idtools_unix_test.go b/pkg/idtools/idtools_unix_test.go index 0979daf9e4..1e65e2af08 100644 --- a/pkg/idtools/idtools_unix_test.go +++ b/pkg/idtools/idtools_unix_test.go @@ -6,8 +6,10 @@ package idtools // import "github.com/docker/docker/pkg/idtools" import ( "fmt" "os" + "os/exec" "os/user" "path/filepath" + "syscall" "testing" "golang.org/x/sys/unix" @@ -425,7 +427,12 @@ func TestNewIDMappings(t *testing.T) { err = MkdirAllAndChown(dirName, 0o700, Identity{UID: rootUID, GID: rootGID}) assert.Check(t, err, "Couldn't change ownership of file path. Got error") - assert.Check(t, CanAccess(dirName, idMapping.RootPair()), "Unable to access %s directory with user UID:%d and GID:%d", dirName, rootUID, rootGID) + cmd := exec.Command("ls", "-la", dirName) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Credential: &syscall.Credential{Uid: uint32(rootUID), Gid: uint32(rootGID)}, + } + out, err := cmd.CombinedOutput() + assert.Check(t, err, "Unable to access %s directory with user UID:%d and GID:%d:\n%s", dirName, rootUID, rootGID, string(out)) } func TestLookupUserAndGroup(t *testing.T) {