@@ -259,6 +259,38 @@ func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath
259259 return GetExecUser (userSpec , defaults , passwd , group )
260260}
261261
262+ // parseNumeric parses the given UID or GID value to an integer and within
263+ // the minID - maxID range.
264+ //
265+ // While the Linux kernel allows the max UID to be MaxUint32 - 2,
266+ // and the OCI Runtime Spec has no definition about the max UID, we require
267+ // the UID to be <= MaxInt32.
268+ //
269+ // See https://github.com/containerd/containerd/commit/de1341c201ffb0effebbf51d00376181968c8779
270+ func parseNumeric (val string ) (int , bool , error ) {
271+ if val == "" {
272+ return 0 , false , nil
273+ }
274+ id , err := strconv .Atoi (val )
275+ if err != nil {
276+ if errors .Is (err , strconv .ErrSyntax ) {
277+ // Discard the error, because non-numeric values are expected
278+ // when passing a username or group-name.
279+ return 0 , false , nil
280+ }
281+ if errors .Is (err , strconv .ErrRange ) {
282+ return 0 , true , ErrRange
283+ }
284+ // Other errors ("invalid base", "invalid bit size"); should never
285+ // happen with strconv.Atoi.
286+ return 0 , false , err
287+ }
288+ if id < minID || id > maxID {
289+ return 0 , true , ErrRange
290+ }
291+ return id , true , nil
292+ }
293+
262294// GetExecUser parses a user specification string (using the passwd and group
263295// readers as sources for /etc/passwd and /etc/group data, respectively). In
264296// the case of blank fields or missing data from the sources, the values in
@@ -304,8 +336,14 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
304336
305337 // Convert userArg and groupArg to be numeric, so we don't have to execute
306338 // Atoi *twice* for each iteration over lines.
307- uidArg , uidErr := strconv .Atoi (userArg )
308- gidArg , gidErr := strconv .Atoi (groupArg )
339+ uidArg , isUID , err := parseNumeric (userArg )
340+ if err != nil {
341+ return nil , err
342+ }
343+ gidArg , isGID , err := parseNumeric (groupArg )
344+ if err != nil {
345+ return nil , err
346+ }
309347
310348 // Find the matching user.
311349 users , err := ParsePasswdFilter (passwd , func (u User ) bool {
@@ -314,8 +352,8 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
314352 return u .Uid == user .Uid
315353 }
316354
317- if uidErr == nil {
318- // If the userArg is numeric, always treat it as a UID.
355+ if isUID {
356+ // If the userArg is a valid numeric value , always treat it as a UID.
319357 return uidArg == u .Uid
320358 }
321359
@@ -341,18 +379,11 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
341379 // If we can't find a user with the given username, the only other valid
342380 // option is if it's a numeric username with no associated entry in passwd.
343381
344- if uidErr != nil {
382+ if ! isUID {
345383 // Not numeric.
346384 return nil , fmt .Errorf ("unable to find user %s: %w" , userArg , ErrNoPasswdEntries )
347385 }
348386 user .Uid = uidArg
349-
350- // Must be inside valid uid range.
351- if user .Uid < minID || user .Uid > maxID {
352- return nil , ErrRange
353- }
354-
355- // Okay, so it's numeric. We can just roll with this.
356387 }
357388
358389 // On to the groups. If we matched a username, we need to do this because of
@@ -370,7 +401,7 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
370401 return false
371402 }
372403
373- if gidErr == nil {
404+ if isGID {
374405 // If the groupArg is numeric, always treat it as a GID.
375406 return gidArg == g .Gid
376407 }
@@ -390,18 +421,11 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
390421 // If we can't find a group with the given name, the only other valid
391422 // option is if it's a numeric group name with no associated entry in group.
392423
393- if gidErr != nil {
424+ if ! isGID {
394425 // Not numeric.
395426 return nil , fmt .Errorf ("unable to find group %s: %w" , groupArg , ErrNoGroupEntries )
396427 }
397428 user .Gid = gidArg
398-
399- // Must be inside valid gid range.
400- if user .Gid < minID || user .Gid > maxID {
401- return nil , ErrRange
402- }
403-
404- // Okay, so it's numeric. We can just roll with this.
405429 }
406430 } else if len (groups ) > 0 {
407431 // Supplementary group ids only make sense if in the implicit form.
@@ -415,18 +439,47 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
415439 return user , nil
416440}
417441
442+ // groupArg is a parsed group argument for [GetAdditionalGroups].
443+ type groupArg struct {
444+ name string
445+ gid int
446+ isNumeric bool
447+ }
448+
449+ // matches reports whether group g satisfies the argument. Numeric arguments
450+ // are matched by GID only, others by name.
451+ func (ag groupArg ) matches (g Group ) bool {
452+ if ag .isNumeric {
453+ return g .Gid == ag .gid
454+ }
455+ return g .Name == ag .name
456+ }
457+
418458// GetAdditionalGroups looks up a list of groups by name or group id
419459// against the given /etc/group formatted data. If a group name cannot
420460// be found, an error will be returned. If a group id cannot be found,
421461// or the given group data is nil, the id will be returned as-is
422462// provided it is in the legal range.
423463func GetAdditionalGroups (additionalGroups []string , group io.Reader ) ([]int , error ) {
464+ addtlGroups := make ([]groupArg , len (additionalGroups ))
465+ for i , ag := range additionalGroups {
466+ gid , ok , err := parseNumeric (ag )
467+ if err != nil {
468+ return nil , err
469+ }
470+ addtlGroups [i ] = groupArg {
471+ name : ag ,
472+ gid : gid ,
473+ isNumeric : ok ,
474+ }
475+ }
476+
424477 groups := []Group {}
425478 if group != nil {
426479 var err error
427480 groups , err = ParseGroupFilter (group , func (g Group ) bool {
428- for _ , ag := range additionalGroups {
429- if g . Name == ag || strconv . Itoa ( g . Gid ) == ag {
481+ for _ , ag := range addtlGroups {
482+ if ag . matches ( g ) {
430483 return true
431484 }
432485 }
@@ -438,32 +491,28 @@ func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, err
438491 }
439492
440493 gidMap := make (map [int ]struct {})
441- for _ , ag := range additionalGroups {
494+ for _ , ag := range addtlGroups {
442495 var found bool
443496 for _ , g := range groups {
444- // if we found a matched group either by name or gid, take the
445- // first matched as correct
446- if g .Name == ag || strconv .Itoa (g .Gid ) == ag {
447- if _ , ok := gidMap [g .Gid ]; ! ok {
448- gidMap [g .Gid ] = struct {}{}
449- found = true
450- break
497+ if ag .matches (g ) {
498+ // take the first matched group as correct
499+ if g .Gid < minID || g .Gid > maxID {
500+ return nil , ErrRange
451501 }
502+ gidMap [g .Gid ] = struct {}{}
503+ found = true
504+ break
452505 }
453506 }
454- // we asked for a group but didn't find it. let's check to see
455- // if we wanted a numeric group
507+ // We asked for a group but didn't find it. Numeric group IDs may be
508+ // used as-is even when they are not present in /etc/group; non-numeric
509+ // group names must be found.
456510 if ! found {
457- gid , err := strconv .ParseInt (ag , 10 , 64 )
458- if err != nil {
511+ if ! ag .isNumeric {
459512 // Not a numeric ID either.
460- return nil , fmt .Errorf ("unable to find group %s: %w" , ag , ErrNoGroupEntries )
461- }
462- // Ensure gid is inside gid range.
463- if gid < minID || gid > maxID {
464- return nil , ErrRange
513+ return nil , fmt .Errorf ("unable to find group %s: %w" , ag .name , ErrNoGroupEntries )
465514 }
466- gidMap [int ( gid ) ] = struct {}{}
515+ gidMap [ag . gid ] = struct {}{}
467516 }
468517 }
469518 gids := []int {}
0 commit comments