Internal change.

PiperOrigin-RevId: 658853348
This commit is contained in:
gVisor bot
2024-08-02 11:39:57 -07:00
parent 742b914fcb
commit b04d0e9293
4 changed files with 259 additions and 94 deletions
+1 -5
View File
@@ -215,11 +215,7 @@ func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error {
if uid != 0 || gid != 0 {
return fmt.Errorf("container spec specified both an explicit UID/GID and a user name, only one or the other may be provided")
}
var err error
uid, gid, err = user.GetExecUIDGIDFromUser(l.Kernel.SupervisorContext(), mntns, args.User)
if err != nil {
return fmt.Errorf("couldn't retrieve UID and GID for user %v, err: %v", args.User, err)
}
uid, gid = user.GetExecUIDGIDFromUser(l.Kernel.SupervisorContext(), mntns, args.User)
}
creds := auth.NewUserCredentials(
+1
View File
@@ -33,6 +33,7 @@ go_test(
deps = [
"//pkg/abi/linux",
"//pkg/context",
"//pkg/errors/linuxerr",
"//pkg/fspath",
"//pkg/sentry/fsimpl/tmpfs",
"//pkg/sentry/kernel/auth",
+124 -36
View File
@@ -26,11 +26,17 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/usermem"
)
const (
defaultUID = auth.KUID(0)
defaultGID = auth.KGID(0)
)
type fileReader struct {
ctx context.Context
fd *vfs.FileDescription
@@ -162,9 +168,80 @@ func findHomeInPasswd(uid uint32, passwd io.Reader, defaultHome string) (string,
return defaultHome, nil
}
func findUIDGIDInPasswd(passwd io.Reader, user string) (auth.KUID, auth.KGID, error) {
defaultUID := auth.KUID(auth.OverflowUID)
defaultGID := auth.KGID(auth.OverflowGID)
func openFile(ctx context.Context, mns *vfs.MountNamespace, path string) (*vfs.FileDescription, error) {
log.Infof("Opening %q", path)
root := mns.Root(ctx)
defer root.DecRef(ctx)
creds := auth.CredentialsFromContext(ctx)
target := &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse(path),
}
fd, err := root.Mount().Filesystem().VirtualFilesystem().OpenAt(ctx, creds, target, &vfs.OpenOptions{Flags: linux.O_RDONLY})
if err != nil {
log.Warningf("Failed to open %q, error: %v", path, err)
return nil, err
}
return fd, nil
}
// findGroupInGroupFile parses a group file and returns the given group's
// gid. If the gid is a number, we don't need to read the file.
//
// If we don't find the group, we return 0.
func findGroupInGroupFile(ctx context.Context, mns *vfs.MountNamespace, gidString string) auth.KGID {
// gid is a number, we don't need to read the file.
gidInt, err := strconv.Atoi(gidString)
if err == nil {
return auth.KGID(gidInt)
}
fd, err := openFile(ctx, mns, "/etc/group")
if err != nil {
return defaultGID
}
defer fd.DecRef(ctx)
r := &fileReader{
ctx: ctx,
fd: fd,
}
// Group file format:
// group_name:password:gid:<user1,user2,user3>
const (
grpIdx = 0
passwdIdx = 1
gidIdx = 2
)
s := bufio.NewScanner(r)
for s.Scan() {
if err := s.Err(); err != nil {
return defaultGID
}
line := strings.TrimSpace(s.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.Split(line, ":")
if parts[grpIdx] == gidString {
gidInt, err := strconv.Atoi(parts[gidIdx])
if err != nil {
return defaultGID
}
return auth.KGID(gidInt)
}
}
// Not found, return 0
return defaultGID
}
func findUIDGIDInPasswd(passwd io.Reader, user string) (auth.KUID, auth.KGID) {
uid := defaultUID
gid := defaultGID
@@ -201,7 +278,7 @@ func findUIDGIDInPasswd(passwd io.Reader, user string) (auth.KUID, auth.KGID, er
s := bufio.NewScanner(passwd)
for s.Scan() {
if err := s.Err(); err != nil {
return defaultUID, defaultGID, err
return getDefaultUIDGID(user)
}
line := strings.TrimSpace(s.Text())
@@ -211,8 +288,8 @@ func findUIDGIDInPasswd(passwd io.Reader, user string) (auth.KUID, auth.KGID, er
parts := strings.Split(line, ":")
if len(parts) != numFields {
// Return error if the format is invalid.
return defaultUID, defaultGID, fmt.Errorf("invalid line found in /etc/passwd, there should be 7 fields but found %v", len(parts))
// If format is invalid, return default values.
return getDefaultUIDGID(user)
}
for i := 0; i < numFields; i++ {
// The password, GECOS and user command interpreter fields are
@@ -221,49 +298,56 @@ func findUIDGIDInPasswd(passwd io.Reader, user string) (auth.KUID, auth.KGID, er
continue
}
if parts[i] == "" {
// Return error if the format is invalid.
return defaultUID, defaultGID, fmt.Errorf("invalid line found in /etc/passwd, field[%v] is empty", i)
// If format is invalid, return default values.
return getDefaultUIDGID(user)
}
}
if parts[idxToMatch] == uStringOrID {
parseUID, err := strconv.ParseUint(parts[uidIdx], 10, 32)
if err != nil {
return defaultUID, defaultGID, err
return getDefaultUIDGID(user)
}
parseGID, err := strconv.ParseUint(parts[gidIdx], 10, 32)
if err != nil {
return defaultUID, defaultGID, err
}
if uid != defaultUID || gid != defaultGID {
return defaultUID, defaultGID, fmt.Errorf("multiple matches for the user: %v", user)
return getDefaultUIDGID(user)
}
uid = auth.KUID(parseUID)
gid = auth.KGID(parseGID)
return uid, gid
}
}
if uid == defaultUID || gid == defaultGID {
return defaultUID, defaultGID, fmt.Errorf("couldn't retrieve UID/GID from user: %v", user)
}
return uid, gid, nil
return getDefaultUIDGID(user)
}
func getExecUIDGID(ctx context.Context, mns *vfs.MountNamespace, user string) (auth.KUID, auth.KGID, error) {
root := mns.Root(ctx)
defer root.DecRef(ctx)
func getDefaultUIDGID(user string) (auth.KUID, auth.KGID) {
usergroup := strings.SplitN(user, ":", 2)
uid := defaultUID
gid := defaultGID
creds := auth.CredentialsFromContext(ctx)
target := &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse("/etc/passwd"),
// resolving uid. If it is numeric, set uid to the int value, if not keep it to 0.
u, err := strconv.Atoi(usergroup[0])
if err == nil {
uid = auth.KUID(u)
}
fd, err := root.Mount().Filesystem().VirtualFilesystem().OpenAt(ctx, creds, target, &vfs.OpenOptions{Flags: linux.O_RDONLY})
// if we do have a group, try to get the numeric value. If numeric, set gid to the int value, if
// not keep it to 0.
if len(usergroup) == 2 {
g, err := strconv.Atoi(usergroup[1])
if err == nil {
gid = auth.KGID(g)
}
}
return uid, gid
}
func getExecUIDGID(ctx context.Context, mns *vfs.MountNamespace, user string) (auth.KUID, auth.KGID) {
fd, err := openFile(ctx, mns, "/etc/passwd")
if err != nil {
return auth.KUID(auth.OverflowUID), auth.KGID(auth.OverflowGID), fmt.Errorf("couldn't retrieve UID/GID from user: %v, err: %v", user, err)
return getDefaultUIDGID(user)
}
defer fd.DecRef(ctx)
@@ -271,17 +355,21 @@ func getExecUIDGID(ctx context.Context, mns *vfs.MountNamespace, user string) (a
ctx: ctx,
fd: fd,
}
// This return kGid from the passwd file (if we find one). We might have recieved a group id
// string or numeric from the user.
kUID, kGID := findUIDGIDInPasswd(r, user)
usergroup := strings.SplitN(user, ":", 2)
if len(usergroup) == 2 {
kGID = findGroupInGroupFile(ctx, mns, usergroup[1])
}
return kUID, kGID
return findUIDGIDInPasswd(r, user)
}
// GetExecUIDGIDFromUser retrieves the UID and GID from /etc/passwd file for
// the given user.
func GetExecUIDGIDFromUser(ctx context.Context, vmns *vfs.MountNamespace, user string) (auth.KUID, auth.KGID, error) {
func GetExecUIDGIDFromUser(ctx context.Context, vmns *vfs.MountNamespace, user string) (auth.KUID, auth.KGID) {
// Read /etc/passwd and retrieve the UID/GID based on the user string.
uid, gid, err := getExecUIDGID(ctx, vmns, user)
if err != nil {
return uid, gid, fmt.Errorf("error reading /etc/passwd: %v", err)
}
return uid, gid, nil
uid, gid := getExecUIDGID(ctx, vmns, user)
return uid, gid
}
+133 -53
View File
@@ -21,6 +21,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
@@ -29,10 +30,10 @@ import (
"gvisor.dev/gvisor/pkg/usermem"
)
// createEtcPasswd creates /etc/passwd with the given contents and mode. If
// createEtcFile creates /etc/<file> with the given contents and mode. If
// mode is empty, then no file will be created. If mode is not a regular file
// mode, then contents is ignored.
func createEtcPasswd(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, root vfs.VirtualDentry, contents string, mode linux.FileMode) error {
func createEtcFile(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, root vfs.VirtualDentry, contents string, mode linux.FileMode, filePath string) error {
pop := vfs.PathOperation{
Root: root,
Start: root,
@@ -40,14 +41,14 @@ func createEtcPasswd(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *
}
if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
Mode: 0755,
}); err != nil {
}); err != nil && err != linuxerr.EEXIST { // Ignore error if it already exists.
return fmt.Errorf("failed to create directory etc: %v", err)
}
pop = vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse("etc/passwd"),
Path: fspath.Parse(filePath),
}
switch mode.FileType() {
case 0:
@@ -128,7 +129,7 @@ func TestGetExecUserHome(t *testing.T) {
root := mns.Root(ctx)
defer root.DecRef(ctx)
if err := createEtcPasswd(ctx, &vfsObj, creds, root, tc.passwdContents, tc.passwdMode); err != nil {
if err := createEtcFile(ctx, &vfsObj, creds, root, tc.passwdContents, tc.passwdMode, "/etc/passwd"); err != nil {
t.Fatalf("createEtcPasswd failed: %v", err)
}
@@ -215,102 +216,187 @@ func TestGetExecUIDGIDFromUser(t *testing.T) {
tests := map[string]struct {
user string
passwdContents string
passwdMode linux.FileMode
groupContents string
fileMode linux.FileMode
expectedUID auth.KUID
expectedGID auth.KGID
}{
"success": {
"user0": {
user: "user0",
passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1000,
expectedGID: 1111,
},
"success_with_uid_only": {
"uid_only": {
user: "1000",
passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1000,
expectedGID: 1111,
},
"success_with_uid_and_gid": {
"uid_and_gid": {
user: "1000:1111",
passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1000,
expectedGID: 1111,
},
"success_with_uid_and_wrong_gid": {
"uid_and_wrong_gid": {
user: "1000:1112",
passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1000,
expectedGID: 1111,
expectedGID: 1112,
},
"no_user": {
"user_missing": {
user: "user1",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
expectedUID: 65534,
expectedGID: 65534,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 0,
expectedGID: 0,
},
"multiple_user_no_match": {
user: "user1",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
expectedUID: 65534,
expectedGID: 65534,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 0,
expectedGID: 0,
},
"multiple_user_many_match": {
user: "user1",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser1::1002:1112::/home/user1:/bin/sh\nuser1::1003:1113::/home/user1:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
expectedUID: 65534,
expectedGID: 65534,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1002,
expectedGID: 1112,
},
"invalid_file": {
user: "user1",
passwdContents: "user0:1000:1111::/home/user0:/bin/sh\nuser1::1001:1111::/home/user1:/bin/sh\nuser2::/home/user2:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
expectedUID: 65534,
expectedGID: 65534,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 0,
expectedGID: 0,
},
"empty_file": {
user: "user1",
passwdContents: "",
passwdMode: linux.S_IFREG | 0666,
expectedUID: 65534,
expectedGID: 65534,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 0,
expectedGID: 0,
},
"empty_user": {
user: "",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
expectedUID: 65534,
expectedGID: 65534,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 0,
expectedGID: 0,
},
"success_with_comments": {
"with_comments": {
user: "user0",
passwdContents: "#This is a comment\nuser0::1000:1111:&:/home/user0:/bin/sh\nuser2::1002:1112:&:/home/user2:/bin/sh\nuser3:&:1003:1113::/home/user3:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1000,
expectedGID: 1111,
},
"success_with_comments_mid_file": {
"with_comments_mid_file": {
user: "user0",
passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh\nuser2::1002:1112:&:/home/user2:/bin/sh\n#This is a comment\n\nuser3:&:1003:1113::/home/user3:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1000,
expectedGID: 1111,
},
"success_empty_gecos": {
"empty_gecos": {
user: "user0",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1000,
expectedGID: 1111,
},
}
"numeric_user_empty_file": {
user: "1000",
passwdContents: "",
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1000,
expectedGID: 0,
},
"numeric_user_does_not_match": {
user: "1005",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1005,
expectedGID: 0,
},
"numeric_user_numeric_group_does_not_match": {
user: "1005:1006",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1005,
expectedGID: 1006,
},
"numeric_user_string_group_does_not_match": {
user: "1005:group10",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1005,
expectedGID: 0,
},
"string_user_numeric_group_does_not_match": {
user: "user10:1006",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 0,
expectedGID: 1006,
},
"string_user_string_group_does_not_match": {
user: "user10:group10",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
groupContents: "",
fileMode: linux.S_IFREG | 0666,
expectedUID: 0,
expectedGID: 0,
},
"group_does_not_match_but_exist": {
user: "user0:group2",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
groupContents: "group1:x:1110:user0,user1,user2\ngroup2:x:1112:user0,user1,user2",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1000,
expectedGID: 1112,
},
"group_does_not_match_and_does_not_exist": {
user: "user0:group10",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
groupContents: "group1:x:1110:user0,user1,user2\ngroup2:x:1112:user0,user1,user2",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1000,
expectedGID: 0,
},
"numeric_group_does_not_match_and_does_not_exist": {
user: "user0:1113",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh\nuser2::1002:1112::/home/user2:/bin/sh\nuser3::1003:1113::/home/user3:/bin/sh",
groupContents: "group1:x:1110:user0,user1,user2\ngroup2:x:1112:user0,user1,user2",
fileMode: linux.S_IFREG | 0666,
expectedUID: 1000,
expectedGID: 1113,
}}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
@@ -333,20 +419,14 @@ func TestGetExecUIDGIDFromUser(t *testing.T) {
root := mns.Root(ctx)
defer root.DecRef(ctx)
if err := createEtcPasswd(ctx, &vfsObj, creds, root, tc.passwdContents, tc.passwdMode); err != nil {
t.Fatalf("createEtcPasswd failed: %v", err)
if err := createEtcFile(ctx, &vfsObj, creds, root, tc.passwdContents, tc.fileMode, "etc/passwd"); err != nil {
t.Fatalf("createEtcFile failed: %v", err)
}
if err := createEtcFile(ctx, &vfsObj, creds, root, tc.groupContents, tc.fileMode, "/etc/group"); err != nil {
t.Fatalf("createEtcFile failed: %v", err)
}
gotUID, gotGID, err := GetExecUIDGIDFromUser(ctx, mns, tc.user)
if strings.HasPrefix(name, "success") {
if err != nil {
t.Fatalf("failed to get UID and GID from user: %v %v", tc.user, err)
}
} else {
if err == nil {
t.Fatalf("retrieved UID and GID when user %v is not in /etc/passwd: %v", tc.user, err)
}
}
gotUID, gotGID := GetExecUIDGIDFromUser(ctx, mns, tc.user)
if gotUID != tc.expectedUID {
t.Fatalf("expectedUID %v, gotUID: %v", tc.expectedUID, gotUID)
}