Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 86 additions & 37 deletions user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,38 @@ func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath
return GetExecUser(userSpec, defaults, passwd, group)
}

// parseNumeric parses the given UID or GID value to an integer and within
// the minID - maxID range.
//
// While the Linux kernel allows the max UID to be MaxUint32 - 2,
// and the OCI Runtime Spec has no definition about the max UID, we require
// the UID to be <= MaxInt32.
//
// 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) {
// Discard the error, because non-numeric values are expected
// when passing a username or group-name.
return 0, false, nil
}
if errors.Is(err, strconv.ErrRange) {
return 0, true, ErrRange
}
// Other errors ("invalid base", "invalid bit size"); should never
// happen with strconv.Atoi.
return 0, false, err
}
if id < minID || id > maxID {
return 0, true, ErrRange
}
return id, true, nil
}

// GetExecUser parses a user specification string (using the passwd and group
// readers as sources for /etc/passwd and /etc/group data, respectively). In
// the case of blank fields or missing data from the sources, the values in
Expand Down Expand Up @@ -315,8 +347,14 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (

// Convert userArg and groupArg to be numeric, so we don't have to execute
// Atoi *twice* for each iteration over lines.
uidArg, uidErr := strconv.Atoi(userArg)
gidArg, gidErr := strconv.Atoi(groupArg)
uidArg, isUID, err := parseNumeric(userArg)
if err != nil {
return nil, err
}
gidArg, isGID, err := parseNumeric(groupArg)
if err != nil {
return nil, err
}

// Find the matching user.
users, err := ParsePasswdFilter(passwd, func(u User) bool {
Expand All @@ -325,8 +363,8 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
return u.Uid == user.Uid
}

if uidErr == nil {
// If the userArg is numeric, always treat it as a UID.
if isUID {
// If the userArg is a valid numeric value, always treat it as a UID.
return uidArg == u.Uid
}

Expand All @@ -352,18 +390,11 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
// 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 uidErr != nil {
if !isUID {
// Not numeric.
return nil, fmt.Errorf("unable to find user %s: %w", userArg, ErrNoPasswdEntries)
}
user.Uid = uidArg

// Must be inside valid uid range.
if user.Uid < minID || user.Uid > maxID {
return nil, ErrRange
}

// Okay, so it's numeric. We can just roll with this.
}

// On to the groups. If we matched a username, we need to do this because of
Expand All @@ -381,7 +412,7 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
return false
}

if gidErr == nil {
if isGID {
// If the groupArg is numeric, always treat it as a GID.
return gidArg == g.Gid
}
Expand All @@ -401,18 +432,11 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
// 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 gidErr != nil {
if !isGID {
// Not numeric.
return nil, fmt.Errorf("unable to find group %s: %w", groupArg, ErrNoGroupEntries)
}
user.Gid = gidArg

// Must be inside valid gid range.
if user.Gid < minID || user.Gid > maxID {
return nil, ErrRange
}

// Okay, so it's numeric. We can just roll with this.
}
} else if len(groups) > 0 {
// Supplementary group ids only make sense if in the implicit form.
Expand All @@ -432,12 +456,35 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
// 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) {
type groupArg struct {
name string
gid int
isNumeric bool
}

addtlGroups := make([]groupArg, len(additionalGroups))
for i, ag := range additionalGroups {
gid, ok, err := parseNumeric(ag)
if err != nil {
return nil, err
}
addtlGroups[i] = groupArg{
name: ag,
gid: gid,
isNumeric: ok,
}
}

groups := []Group{}
if group != nil {
var err error
groups, err = ParseGroupFilter(group, func(g Group) bool {
for _, ag := range additionalGroups {
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
for _, ag := range addtlGroups {
if ag.isNumeric {
if g.Gid == ag.gid {
return true
}
} else if g.Name == ag.name {
return true
}
}
Expand All @@ -449,32 +496,34 @@ func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, err
}

gidMap := make(map[int]struct{})
for _, ag := range additionalGroups {
for _, ag := range addtlGroups {
var found bool
for _, g := range groups {
// if we found a matched group either by name or gid, take the
// first matched as correct
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
if _, ok := gidMap[g.Gid]; !ok {
gidMap[g.Gid] = struct{}{}
found = true
break
if ag.isNumeric {
if g.Gid != ag.gid {
continue
}
} else if g.Name != ag.name {
continue
}

if g.Gid < minID || g.Gid > maxID {
return nil, ErrRange
}
gidMap[g.Gid] = struct{}{}
found = true
break
}
// we asked for a group but didn't find it. let's check to see
// if we wanted a numeric group
if !found {
gid, err := strconv.ParseInt(ag, 10, 64)
if err != nil {
if !ag.isNumeric {
// Not a numeric ID either.
return nil, fmt.Errorf("unable to find group %s: %w", ag, ErrNoGroupEntries)
}
// Ensure gid is inside gid range.
if gid < minID || gid > maxID {
return nil, ErrRange
return nil, fmt.Errorf("unable to find group %s: %w", ag.name, ErrNoGroupEntries)
}
gidMap[int(gid)] = struct{}{}
gidMap[ag.gid] = struct{}{}
}
}
gids := []int{}
Expand Down
Loading
Loading