1
0
mirror of https://github.com/golang/go synced 2024-11-26 19:51:17 -07:00

os/user: make user.LookupGroupId function work for large entries

The existing implementation of user.LookupGroupId function works
incorrectly with very large (>64K symbols) entries in /etc/group file.
This patch fixes this.

Fixes #43636

Change-Id: I453321f1ab15fd4d0002f97fcec7d0789e1e0da5
Reviewed-on: https://go-review.googlesource.com/c/go/+/283601
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Trust: Emmanuel Odeke <emmanuel@orijtech.com>
This commit is contained in:
Andrey Bokhanko 2021-01-14 15:30:14 +00:00 committed by Ian Lance Taylor
parent a826f7dc45
commit a98a0a75b4
2 changed files with 75 additions and 14 deletions

View File

@ -33,23 +33,72 @@ type lineFunc func(line []byte) (v interface{}, err error)
// readColonFile parses r as an /etc/group or /etc/passwd style file, running // readColonFile parses r as an /etc/group or /etc/passwd style file, running
// fn for each row. readColonFile returns a value, an error, or (nil, nil) if // fn for each row. readColonFile returns a value, an error, or (nil, nil) if
// the end of the file is reached without a match. // the end of the file is reached without a match.
func readColonFile(r io.Reader, fn lineFunc) (v interface{}, err error) { //
bs := bufio.NewScanner(r) // readCols is the minimum number of colon-separated fields that will be passed
for bs.Scan() { // to fn; in a long line additional fields may be silently discarded.
line := bs.Bytes() func readColonFile(r io.Reader, fn lineFunc, readCols int) (v interface{}, err error) {
rd := bufio.NewReader(r)
// Read the file line-by-line.
for {
var isPrefix bool
var wholeLine []byte
// Read the next line. We do so in chunks (as much as reader's
// buffer is able to keep), check if we read enough columns
// already on each step and store final result in wholeLine.
for {
var line []byte
line, isPrefix, err = rd.ReadLine()
if err != nil {
// We should return (nil, nil) if EOF is reached
// without a match.
if err == io.EOF {
err = nil
}
return nil, err
}
// Simple common case: line is short enough to fit in a
// single reader's buffer.
if !isPrefix && len(wholeLine) == 0 {
wholeLine = line
break
}
wholeLine = append(wholeLine, line...)
// Check if we read the whole line (or enough columns)
// already.
if !isPrefix || bytes.Count(wholeLine, []byte{':'}) >= readCols {
break
}
}
// There's no spec for /etc/passwd or /etc/group, but we try to follow // There's no spec for /etc/passwd or /etc/group, but we try to follow
// the same rules as the glibc parser, which allows comments and blank // the same rules as the glibc parser, which allows comments and blank
// space at the beginning of a line. // space at the beginning of a line.
line = bytes.TrimSpace(line) wholeLine = bytes.TrimSpace(wholeLine)
if len(line) == 0 || line[0] == '#' { if len(wholeLine) == 0 || wholeLine[0] == '#' {
continue continue
} }
v, err = fn(line) v, err = fn(wholeLine)
if v != nil || err != nil { if v != nil || err != nil {
return return
} }
// If necessary, skip the rest of the line
for ; isPrefix; _, isPrefix, err = rd.ReadLine() {
if err != nil {
// We should return (nil, nil) if EOF is reached without a match.
if err == io.EOF {
err = nil
}
return nil, err
}
}
} }
return nil, bs.Err()
} }
func matchGroupIndexValue(value string, idx int) lineFunc { func matchGroupIndexValue(value string, idx int) lineFunc {
@ -80,7 +129,7 @@ func matchGroupIndexValue(value string, idx int) lineFunc {
} }
func findGroupId(id string, r io.Reader) (*Group, error) { func findGroupId(id string, r io.Reader) (*Group, error) {
if v, err := readColonFile(r, matchGroupIndexValue(id, 2)); err != nil { if v, err := readColonFile(r, matchGroupIndexValue(id, 2), 3); err != nil {
return nil, err return nil, err
} else if v != nil { } else if v != nil {
return v.(*Group), nil return v.(*Group), nil
@ -89,7 +138,7 @@ func findGroupId(id string, r io.Reader) (*Group, error) {
} }
func findGroupName(name string, r io.Reader) (*Group, error) { func findGroupName(name string, r io.Reader) (*Group, error) {
if v, err := readColonFile(r, matchGroupIndexValue(name, 0)); err != nil { if v, err := readColonFile(r, matchGroupIndexValue(name, 0), 3); err != nil {
return nil, err return nil, err
} else if v != nil { } else if v != nil {
return v.(*Group), nil return v.(*Group), nil
@ -144,7 +193,7 @@ func findUserId(uid string, r io.Reader) (*User, error) {
if e != nil { if e != nil {
return nil, errors.New("user: invalid userid " + uid) return nil, errors.New("user: invalid userid " + uid)
} }
if v, err := readColonFile(r, matchUserIndexValue(uid, 2)); err != nil { if v, err := readColonFile(r, matchUserIndexValue(uid, 2), 6); err != nil {
return nil, err return nil, err
} else if v != nil { } else if v != nil {
return v.(*User), nil return v.(*User), nil
@ -153,7 +202,7 @@ func findUserId(uid string, r io.Reader) (*User, error) {
} }
func findUsername(name string, r io.Reader) (*User, error) { func findUsername(name string, r io.Reader) (*User, error) {
if v, err := readColonFile(r, matchUserIndexValue(name, 0)); err != nil { if v, err := readColonFile(r, matchUserIndexValue(name, 0), 6); err != nil {
return nil, err return nil, err
} else if v != nil { } else if v != nil {
return v.(*User), nil return v.(*User), nil

View File

@ -9,12 +9,13 @@
package user package user
import ( import (
"fmt"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
) )
const testGroupFile = `# See the opendirectoryd(8) man page for additional var testGroupFile = `# See the opendirectoryd(8) man page for additional
# information about Open Directory. # information about Open Directory.
## ##
nobody:*:-2: nobody:*:-2:
@ -30,7 +31,7 @@ daemon:*:1:root
# comment:*:4:found # comment:*:4:found
# comment:*:4:found # comment:*:4:found
kmem:*:2:root kmem:*:2:root
` ` + largeGroup()
var groupTests = []struct { var groupTests = []struct {
in string in string
@ -49,9 +50,20 @@ var groupTests = []struct {
{testGroupFile, "invalidgid", ""}, {testGroupFile, "invalidgid", ""},
{testGroupFile, "indented", "7"}, {testGroupFile, "indented", "7"},
{testGroupFile, "# comment", ""}, {testGroupFile, "# comment", ""},
{testGroupFile, "largegroup", "1000"},
{"", "emptyfile", ""}, {"", "emptyfile", ""},
} }
// Generate a proper "largegroup" entry for testGroupFile string
func largeGroup() (res string) {
var b strings.Builder
b.WriteString("largegroup:x:1000:user1")
for i := 2; i <= 7500; i++ {
fmt.Fprintf(&b, ",user%d", i)
}
return b.String()
}
func TestFindGroupName(t *testing.T) { func TestFindGroupName(t *testing.T) {
for _, tt := range groupTests { for _, tt := range groupTests {
got, err := findGroupName(tt.name, strings.NewReader(tt.in)) got, err := findGroupName(tt.name, strings.NewReader(tt.in))