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:
parent
a826f7dc45
commit
a98a0a75b4
@ -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
|
||||||
|
@ -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))
|
||||||
|
Loading…
Reference in New Issue
Block a user