Skip to content

Commit 6eb9f15

Browse files
thaJeztahkolyshkin
andcommitted
user: GetAdditionalGroups: treat numeric group arguments as GIDs only
Co-authored-by: Kir Kolyshkin <kolyshkin@gmail.com> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent c66bd2d commit 6eb9f15

2 files changed

Lines changed: 71 additions & 20 deletions

File tree

user/user.go

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -439,18 +439,47 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
439439
return user, nil
440440
}
441441

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+
442458
// GetAdditionalGroups looks up a list of groups by name or group id
443459
// against the given /etc/group formatted data. If a group name cannot
444460
// be found, an error will be returned. If a group id cannot be found,
445461
// or the given group data is nil, the id will be returned as-is
446462
// provided it is in the legal range.
447463
func 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+
448477
groups := []Group{}
449478
if group != nil {
450479
var err error
451480
groups, err = ParseGroupFilter(group, func(g Group) bool {
452-
for _, ag := range additionalGroups {
453-
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
481+
for _, ag := range addtlGroups {
482+
if ag.matches(g) {
454483
return true
455484
}
456485
}
@@ -462,32 +491,28 @@ func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, err
462491
}
463492

464493
gidMap := make(map[int]struct{})
465-
for _, ag := range additionalGroups {
494+
for _, ag := range addtlGroups {
466495
var found bool
467496
for _, g := range groups {
468-
// if we found a matched group either by name or gid, take the
469-
// first matched as correct
470-
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
471-
if _, ok := gidMap[g.Gid]; !ok {
472-
gidMap[g.Gid] = struct{}{}
473-
found = true
474-
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
475501
}
502+
gidMap[g.Gid] = struct{}{}
503+
found = true
504+
break
476505
}
477506
}
478-
// we asked for a group but didn't find it. let's check to see
479-
// 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.
480510
if !found {
481-
gid, err := strconv.ParseInt(ag, 10, 64)
482-
if err != nil {
511+
if !ag.isNumeric {
483512
// Not a numeric ID either.
484-
return nil, fmt.Errorf("unable to find group %s: %w", ag, ErrNoGroupEntries)
485-
}
486-
// Ensure gid is inside gid range.
487-
if gid < minID || gid > maxID {
488-
return nil, ErrRange
513+
return nil, fmt.Errorf("unable to find group %s: %w", ag.name, ErrNoGroupEntries)
489514
}
490-
gidMap[int(gid)] = struct{}{}
515+
gidMap[ag.gid] = struct{}{}
491516
}
492517
}
493518
gids := []int{}

user/user_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,9 @@ root:x:0:root
461461
adm:x:43:
462462
grp:x:1234:root,adm
463463
adm:x:4343:root,adm-duplicate
464+
2147483648:x:0:
465+
9223372036854775808:x:0:
466+
toolarge:x:2147483648:
464467
this is just some garbage data
465468
` + largeGroup()
466469
tests := []foo{
@@ -474,6 +477,11 @@ this is just some garbage data
474477
groups: []string{"adm"},
475478
expected: []int{43},
476479
},
480+
{
481+
// numeric group miss must continue checking remaining groups
482+
groups: []string{"10001", "adm"},
483+
expected: []int{43, 10001},
484+
},
477485
{
478486
// multiple groups
479487
groups: []string{"adm", "grp"},
@@ -517,6 +525,24 @@ this is just some garbage data
517525
groups: []string{"largegroup"},
518526
expected: []int{1000},
519527
},
528+
{
529+
// numeric group must not resolve as group name
530+
groups: []string{"2147483648"},
531+
expected: nil,
532+
hasError: true,
533+
},
534+
{
535+
// numeric group must not resolve as group name
536+
groups: []string{"9223372036854775808"},
537+
expected: nil,
538+
hasError: true,
539+
},
540+
{
541+
// group entry with out-of-range gid
542+
groups: []string{"toolarge"},
543+
expected: nil,
544+
hasError: true,
545+
},
520546
}
521547

522548
for _, tc := range tests {

0 commit comments

Comments
 (0)