diff --git a/go.mod b/go.mod index 9660198081..abb41e6ce6 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/google/go-containerregistry v0.22.0 github.com/linuxkit/virtsock v0.0.0-20241009230534-cb6a20cc0422 github.com/mattn/go-shellwords v1.0.13 - github.com/moby/sys/user v0.4.1 + github.com/moby/sys/user v0.5.0 github.com/open-policy-agent/opa v1.20.1 github.com/opencontainers/cgroups v0.1.0 github.com/opencontainers/runc v1.4.3 diff --git a/go.sum b/go.sum index 9656d2afea..7cc79c3c85 100644 --- a/go.sum +++ b/go.sum @@ -829,8 +829,8 @@ github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCnd github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= -github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0= -github.com/moby/sys/user v0.4.1/go.mod h1:E9QsW5WRe1kUAf7kW8hXKwu1uhsZEAdPLYHYSDudF4Y= +github.com/moby/sys/user v0.5.0 h1:5+S4lyoavGyX1DGs68NW8xpx48T9+a7ogSUc0KjZg8Y= +github.com/moby/sys/user v0.5.0/go.mod h1:4mQzP5aTmaLjDLp/dc870oNkik6E9vjRtWYwI05gvow= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/test/go.mod b/test/go.mod index 2d00c6bf9f..0cc8d73b96 100644 --- a/test/go.mod +++ b/test/go.mod @@ -83,7 +83,7 @@ require ( github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/signal v0.7.1 // indirect - github.com/moby/sys/user v0.4.1 // indirect + github.com/moby/sys/user v0.5.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/mrunalp/fileutils v0.5.1 // indirect github.com/open-policy-agent/opa v1.20.1 // indirect diff --git a/test/go.sum b/test/go.sum index 935ac9bcb1..fece711bf1 100644 --- a/test/go.sum +++ b/test/go.sum @@ -169,8 +169,8 @@ github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7z github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= -github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0= -github.com/moby/sys/user v0.4.1/go.mod h1:E9QsW5WRe1kUAf7kW8hXKwu1uhsZEAdPLYHYSDudF4Y= +github.com/moby/sys/user v0.5.0 h1:5+S4lyoavGyX1DGs68NW8xpx48T9+a7ogSUc0KjZg8Y= +github.com/moby/sys/user v0.5.0/go.mod h1:4mQzP5aTmaLjDLp/dc870oNkik6E9vjRtWYwI05gvow= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/mrunalp/fileutils v0.5.1 h1:F+S7ZlNKnrwHfSwdlgNSkKo67ReVf8o9fel6C3dkm/Q= diff --git a/vendor/github.com/moby/sys/user/idtools.go b/vendor/github.com/moby/sys/user/idtools.go index 595b7a9272..1478153111 100644 --- a/vendor/github.com/moby/sys/user/idtools.go +++ b/vendor/github.com/moby/sys/user/idtools.go @@ -12,37 +12,39 @@ type mkdirOptions struct { onlyNew bool } -// WithOnlyNew is an option for MkdirAllAndChown that will only change ownership and permissions -// on newly created directories. If the directory already exists, it will not be modified +// WithOnlyNew configures [MkdirAllAndChown] and [MkdirAndChown] to leave +// the requested directory unchanged if it already exists. Newly created +// directories are still assigned the requested ownership and permissions. func WithOnlyNew(o *mkdirOptions) { o.onlyNew = true } -// MkdirAllAndChown creates a directory (include any along the path) and then modifies -// ownership to the requested uid/gid. By default, if the directory already exists, this -// function will still change ownership and permissions. If WithOnlyNew is passed as an -// option, then only the newly created directories will have ownership and permissions changed. +// MkdirAllAndChown creates a directory named path, along with any necessary +// parents, and applies the requested ownership and permission bits to all +// directories it creates. +// +// If path already exists as a directory, its ownership and permission bits +// are updated unless [WithOnlyNew] is specified. Existing parent directories +// are not modified. +// +// MkdirAllAndChown is similar to [os.MkdirAll], but also applies ownership and +// reapplies the requested permission bits after creation, so the resulting +// permissions are not affected by the process umask. func MkdirAllAndChown(path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error { - var options mkdirOptions - for _, opt := range opts { - opt(&options) - } - - return mkdirAs(path, mode, uid, gid, true, options.onlyNew) + return mkdirAs(path, mode, uid, gid, true, opts...) } -// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid. -// By default, if the directory already exists, this function still changes ownership and permissions. -// If WithOnlyNew is passed as an option, then only the newly created directory will have ownership -// and permissions changed. -// Note that unlike os.Mkdir(), this function does not return IsExist error -// in case path already exists. +// MkdirAndChown creates a directory named path and applies the requested +// ownership and permission bits. +// +// If path already exists as a directory, its ownership and permission bits +// are updated unless [WithOnlyNew] is specified. +// +// MkdirAndChown is similar to [os.Mkdir], but returns nil if path already +// exists as a directory. The resulting permission bits are not affected by +// the process umask. func MkdirAndChown(path string, mode os.FileMode, uid, gid int, opts ...MkdirOpt) error { - var options mkdirOptions - for _, opt := range opts { - opt(&options) - } - return mkdirAs(path, mode, uid, gid, false, options.onlyNew) + return mkdirAs(path, mode, uid, gid, false, opts...) } // getRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. @@ -106,22 +108,14 @@ func (i IdentityMapping) RootPair() (int, int) { return uid, gid } -// ToHost returns the host UID and GID for the container uid, gid. -// Remapping is only performed if the ids aren't already the remapped root ids +// ToHost returns the host UID and GID for the container uid and gid. +// An empty mapping is treated as identity. func (i IdentityMapping) ToHost(uid, gid int) (int, int, error) { - var err error - ruid, rgid := i.RootPair() - - if uid != ruid { - ruid, err = toHost(uid, i.UIDMaps) - if err != nil { - return ruid, rgid, err - } - } - - if gid != rgid { - rgid, err = toHost(gid, i.GIDMaps) + ruid, err := toHost(uid, i.UIDMaps) + if err != nil { + return -1, -1, err } + rgid, err := toHost(gid, i.GIDMaps) return ruid, rgid, err } diff --git a/vendor/github.com/moby/sys/user/idtools_unix.go b/vendor/github.com/moby/sys/user/idtools_unix.go index 4e39d2446b..679aaa3a26 100644 --- a/vendor/github.com/moby/sys/user/idtools_unix.go +++ b/vendor/github.com/moby/sys/user/idtools_unix.go @@ -10,7 +10,12 @@ import ( "syscall" ) -func mkdirAs(path string, mode os.FileMode, uid, gid int, mkAll, onlyNew bool) error { +func mkdirAs(path string, mode os.FileMode, uid, gid int, mkAll bool, opts ...MkdirOpt) error { + var options mkdirOptions + for _, opt := range opts { + opt(&options) + } + path, err := filepath.Abs(path) if err != nil { return err @@ -21,7 +26,7 @@ func mkdirAs(path string, mode os.FileMode, uid, gid int, mkAll, onlyNew bool) e if !stat.IsDir() { return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} } - if onlyNew { + if options.onlyNew { return nil } diff --git a/vendor/github.com/moby/sys/user/idtools_windows.go b/vendor/github.com/moby/sys/user/idtools_windows.go index 9de730cafb..3973d5593a 100644 --- a/vendor/github.com/moby/sys/user/idtools_windows.go +++ b/vendor/github.com/moby/sys/user/idtools_windows.go @@ -2,12 +2,25 @@ package user import ( "os" + "syscall" ) -// This is currently a wrapper around [os.MkdirAll] since currently -// permissions aren't set through this path, the identity isn't utilized. -// Ownership is handled elsewhere, but in the future could be support here -// too. -func mkdirAs(path string, _ os.FileMode, _, _ int, _, _ bool) error { - return os.MkdirAll(path, 0) +// mkdirAs creates path, optionally creating any missing parent directories. +// +// On Windows this is currently a thin wrapper around os.Mkdir and +// os.MkdirAll. Unlike the Unix implementation, ownership and permission +// bits are not applied. +func mkdirAs(path string, _ os.FileMode, _, _ int, mkAll bool, _ ...MkdirOpt) error { + if mkAll { + return os.MkdirAll(path, 0) + } + stat, err := os.Stat(path) + if err == nil { + if !stat.IsDir() { + return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} + } + return nil + } + + return os.Mkdir(path, 0) } diff --git a/vendor/github.com/moby/sys/user/user.go b/vendor/github.com/moby/sys/user/user.go index 857711ebfd..48c34a8b23 100644 --- a/vendor/github.com/moby/sys/user/user.go +++ b/vendor/github.com/moby/sys/user/user.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "os" + "slices" "strconv" "strings" ) @@ -17,9 +18,9 @@ const ( ) var ( - // ErrNoPasswdEntries is returned if no matching entries were found in /etc/group. + // ErrNoPasswdEntries is returned if no matching entries were found in /etc/passwd. ErrNoPasswdEntries = errors.New("no matching entries in passwd file") - // ErrNoGroupEntries is returned if no matching entries were found in /etc/passwd. + // ErrNoGroupEntries is returned if no matching entries were found in /etc/group. ErrNoGroupEntries = errors.New("no matching entries in group file") // ErrRange is returned if a UID or GID is outside of the valid range. ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minID, maxID) @@ -243,9 +244,6 @@ func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath // // See https://github.com/containerd/containerd/commit/de1341c201ffb0effebbf51d00376181968c8779 func parseNumeric(val string) (int, bool, error) { - if val == "" { - return 0, false, nil - } id, err := strconv.Atoi(val) if err != nil { if errors.Is(err, strconv.ErrSyntax) { @@ -296,51 +294,40 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) ( user := &ExecUser{ Uid: defaults.Uid, Gid: defaults.Gid, - Sgids: defaults.Sgids, + Sgids: append([]int{}, defaults.Sgids...), // Sgids slice *cannot* be nil. Home: defaults.Home, } - // Sgids slice *cannot* be nil. - if user.Sgids == nil { - user.Sgids = []int{} - } - - // Allow for userArg to have either "user" syntax, or optionally "user:group" syntax - var userArg, groupArg string - parseLine([]byte(userSpec), &userArg, &groupArg) + // Allow for userSpec to have either "user", or optionally "user:group" syntax. + usr, grp, _ := strings.Cut(userSpec, ":") - // Convert userArg and groupArg to be numeric, so we don't have to execute + // Convert usr and grp to be numeric, so we don't have to execute // Atoi *twice* for each iteration over lines. - uidArg, isUID, err := parseNumeric(userArg) + userArg, err := parseUserArg(usr) if err != nil { return nil, err } - gidArg, isGID, err := parseNumeric(groupArg) + groupArg, err := parseGroupArg(grp) if err != nil { return nil, err } // Find the matching user. users, err := ParsePasswdFilter(passwd, func(u User) bool { - if userArg == "" { + if userArg == nil { // Default to current state of the user. return u.Uid == user.Uid } - - if isUID { - // If the userArg is a valid numeric value, always treat it as a UID. - return uidArg == u.Uid - } - - return u.Name == userArg + return userArg.matches(u) }) // If we can't find the user, we have to bail. if err != nil && passwd != nil { - if userArg == "" { - userArg = strconv.Itoa(user.Uid) + name := usr + if userArg == nil { + name = strconv.Itoa(user.Uid) } - return nil, fmt.Errorf("unable to find user %s: %w", userArg, err) + return nil, fmt.Errorf("unable to find user %s: %w", name, err) } var matchedUserName string @@ -350,57 +337,42 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) ( user.Uid = users[0].Uid user.Gid = users[0].Gid user.Home = users[0].Home - } else if userArg != "" { + } else if userArg != nil { // If we can't find a user with the given username, the only other valid // option is if it's a numeric username with no associated entry in passwd. - - if !isUID { - // Not numeric. - return nil, fmt.Errorf("unable to find user %s: %w", userArg, ErrNoPasswdEntries) + if !userArg.isNumeric { + return nil, fmt.Errorf("unable to find user %s: %w", userArg.name, ErrNoPasswdEntries) } - user.Uid = uidArg + user.Uid = userArg.uid } // On to the groups. If we matched a username, we need to do this because of // the supplementary group IDs. - if groupArg != "" || matchedUserName != "" { + if groupArg != nil || matchedUserName != "" { groups, err := ParseGroupFilter(group, func(g Group) bool { // If the group argument isn't explicit, we'll just search for it. - if groupArg == "" { + if groupArg == nil { // Check if user is a member of this group. - for _, u := range g.List { - if u == matchedUserName { - return true - } - } - return false - } - - if isGID { - // If the groupArg is numeric, always treat it as a GID. - return gidArg == g.Gid + return slices.Contains(g.List, matchedUserName) } - - return g.Name == groupArg + return groupArg.matches(g) }) if err != nil && group != nil { return nil, fmt.Errorf("unable to find groups for spec %v: %w", matchedUserName, err) } // Only start modifying user.Gid if it is in explicit form. - if groupArg != "" { + if groupArg != nil { if len(groups) > 0 { // First match wins, even if there's more than one matching entry. user.Gid = groups[0].Gid } else { // If we can't find a group with the given name, the only other valid // option is if it's a numeric group name with no associated entry in group. - - if !isGID { - // Not numeric. - return nil, fmt.Errorf("unable to find group %s: %w", groupArg, ErrNoGroupEntries) + if !groupArg.isNumeric { + return nil, fmt.Errorf("unable to find group %s: %w", groupArg.name, ErrNoGroupEntries) } - user.Gid = gidArg + user.Gid = groupArg.gid } } else if len(groups) > 0 { // Supplementary group ids only make sense if in the implicit form. @@ -414,39 +386,19 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) ( return user, nil } -// groupArg is a parsed group argument for [GetAdditionalGroups]. -type groupArg struct { - name string - gid int - isNumeric bool -} - -// matches reports whether group g satisfies the argument. Numeric arguments -// are matched by GID only, others by name. -func (ag groupArg) matches(g Group) bool { - if ag.isNumeric { - return g.Gid == ag.gid - } - return g.Name == ag.name -} - // GetAdditionalGroups looks up a list of groups by name or group id // against the given /etc/group formatted data. If a group name cannot // be found, an error will be returned. If a group id cannot be found, // or the given group data is nil, the id will be returned as-is // provided it is in the legal range. func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) { - addtlGroups := make([]groupArg, len(additionalGroups)) - for i, ag := range additionalGroups { - gid, ok, err := parseNumeric(ag) + addtlGroups := make([]groupArg, 0, len(additionalGroups)) + for _, ag := range additionalGroups { + groupArg, err := parseGroupArg(ag) if err != nil { return nil, err } - addtlGroups[i] = groupArg{ - name: ag, - gid: gid, - isNumeric: ok, - } + addtlGroups = append(addtlGroups, *groupArg) } groups := []Group{} diff --git a/vendor/github.com/moby/sys/user/user_utils.go b/vendor/github.com/moby/sys/user/user_utils.go index 978aa927ed..8084d3b071 100644 --- a/vendor/github.com/moby/sys/user/user_utils.go +++ b/vendor/github.com/moby/sys/user/user_utils.go @@ -62,3 +62,65 @@ func (l *limitedFile) Read(p []byte) (int, error) { } return n, err } + +// userArg is a parsed user argument for [GetExecUser]. +type userArg struct { + name string + uid int + isNumeric bool +} + +// parseUserArg parses a user argument as either a user name or UID. +// +// If name is empty, parseUserArg returns "nil, nil" to indicate that no user +// argument was specified. +func parseUserArg(name string) (*userArg, error) { + if name == "" { + return nil, nil + } + uid, isNumeric, err := parseNumeric(name) + if err != nil { + return nil, err + } + return &userArg{name: name, uid: uid, isNumeric: isNumeric}, nil +} + +// matches reports whether user u satisfies the argument. Numeric arguments +// are matched by UID only, others by name. +func (ua *userArg) matches(u User) bool { + if ua.isNumeric { + return u.Uid == ua.uid + } + return u.Name == ua.name +} + +// groupArg is a parsed group argument for [GetAdditionalGroups] or [GetExecUser]. +type groupArg struct { + name string + gid int + isNumeric bool +} + +// parseGroupArg parses a group argument as either a group name or GID. +// +// If name is empty, parseGroupArg returns "nil, nil" to indicate that no group +// argument was specified. +func parseGroupArg(name string) (*groupArg, error) { + if name == "" { + return nil, nil + } + gid, isNumeric, err := parseNumeric(name) + if err != nil { + return nil, err + } + return &groupArg{name: name, gid: gid, isNumeric: isNumeric}, nil +} + +// matches reports whether group g satisfies the argument. Numeric arguments +// are matched by GID only, others by name. +func (ag groupArg) matches(g Group) bool { + if ag.isNumeric { + return g.Gid == ag.gid + } + return g.Name == ag.name +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 204964be3d..565f213492 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -360,8 +360,8 @@ github.com/moby/sys/capability # github.com/moby/sys/mountinfo v0.7.2 ## explicit; go 1.17 github.com/moby/sys/mountinfo -# github.com/moby/sys/user v0.4.1 -## explicit; go 1.18 +# github.com/moby/sys/user v0.5.0 +## explicit; go 1.24 github.com/moby/sys/user # github.com/moby/sys/userns v0.1.0 ## explicit; go 1.21