Retrieve UID/GID from the user string.

Add a method to retrieve the UID and GID for a user and the tests to verify.

PiperOrigin-RevId: 626187254
This commit is contained in:
Nayana Bidari
2024-04-18 16:46:35 -07:00
committed by gVisor bot
parent fe9d22f06b
commit 93bbcbf35b
3 changed files with 244 additions and 9 deletions
+26 -9
View File
@@ -111,6 +111,9 @@ type StartContainerArgs struct {
// the root group if not set explicitly.
KGID auth.KGID `json:"KGID"`
// User is the user string used to retrieve UID/GID.
User string `json:"user"`
// ContainerID is the container for the process being executed.
ContainerID string `json:"container_id"`
@@ -198,9 +201,30 @@ func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error {
return fmt.Errorf("FilePayload.Files and DonatedFDs must have same number of elements (%d != %d)", len(args.Files), len(args.DonatedFDs))
}
l.mu.RLock()
mntns, ok := l.MountNamespacesMap[args.ContainerID]
if !ok {
l.mu.RUnlock()
return fmt.Errorf("mount namespace is nil for %s", args.ContainerID)
}
l.mu.RUnlock()
uid := args.KUID
gid := args.KGID
if args.User != "" {
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)
}
}
creds := auth.NewUserCredentials(
args.KUID,
args.KGID,
uid,
gid,
nil, /* extraKGIDs */
nil, /* capabilities */
l.Kernel.RootUserNamespace())
@@ -261,14 +285,7 @@ func (l *Lifecycle) StartContainer(args *StartContainerArgs, _ *uint32) error {
}
initArgs.FDTable = fdTable
l.mu.RLock()
mntns, ok := l.MountNamespacesMap[initArgs.ContainerID]
if !ok {
l.mu.RUnlock()
return fmt.Errorf("mount namespace is nil for %s", initArgs.ContainerID)
}
initArgs.MountNamespace = mntns
l.mu.RUnlock()
initArgs.MountNamespace.IncRef()
if args.ResolveBinaryPath {
+113
View File
@@ -161,3 +161,116 @@ 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)
uid := defaultUID
gid := defaultGID
s := bufio.NewScanner(passwd)
for s.Scan() {
if err := s.Err(); err != nil {
return defaultUID, defaultGID, err
}
line := strings.TrimSpace(s.Text())
if line == "" {
continue
}
// Per 'man 5 passwd'
// /etc/passwd contains one line for each user account, with seven
// fields delimited by colons (“:”). These fields are:
//
// - login name
// - optional encrypted password
// - numerical user ID
// - numerical group ID
// - user name or comment field
// - user home directory
// - optional user command interpreter
const (
numFields = 7
userIdx = 0
passwdIdx = 1
uidIdx = 2
gidIdx = 3
shellIdx = 6
)
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")
}
for i := 0; i < numFields; i++ {
// The password and user command interpreter fields are
// optional, no need to check if they are empty.
if i == passwdIdx || i == shellIdx {
continue
}
if parts[i] == "" {
// Return error if the format is invalid.
return defaultUID, defaultGID, fmt.Errorf("invalid line found in /etc/passwd")
}
}
if parts[userIdx] == user {
parseUID, err := strconv.ParseUint(parts[uidIdx], 10, 32)
if err != nil {
return defaultUID, defaultGID, err
}
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)
}
uid = auth.KUID(parseUID)
gid = auth.KGID(parseGID)
}
}
if uid == defaultUID || gid == defaultGID {
return defaultUID, defaultGID, fmt.Errorf("couldn't retrieve UID/GID from user: %v", user)
}
return uid, gid, nil
}
func getExecUIDGID(ctx context.Context, mns *vfs.MountNamespace, user string) (auth.KUID, auth.KGID, error) {
root := mns.Root(ctx)
defer root.DecRef(ctx)
creds := auth.CredentialsFromContext(ctx)
target := &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse("/etc/passwd"),
}
fd, err := root.Mount().Filesystem().VirtualFilesystem().OpenAt(ctx, creds, target, &vfs.OpenOptions{Flags: linux.O_RDONLY})
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)
}
defer fd.DecRef(ctx)
r := &fileReader{
ctx: ctx,
fd: fd,
}
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) {
// 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
}
+105
View File
@@ -209,3 +209,108 @@ func TestFindHomeInPasswd(t *testing.T) {
})
}
}
// TestGetExecUIDGIDFromUser tests the GetExecUIDGIDFromUser function.
func TestGetExecUIDGIDFromUser(t *testing.T) {
tests := map[string]struct {
user string
passwdContents string
passwdMode linux.FileMode
expectedUID auth.KUID
expectedGID auth.KGID
}{
"success": {
user: "user0",
passwdContents: "user0::1000:1111:&:/home/user0:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
expectedUID: 1000,
expectedGID: 1111,
},
"no_user": {
user: "user1",
passwdContents: "user0::1000:1111::/home/user0:/bin/sh",
passwdMode: linux.S_IFREG | 0666,
expectedUID: 65534,
expectedGID: 65534,
},
"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,
},
"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,
},
"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,
},
"empty_file": {
user: "user1",
passwdContents: "",
passwdMode: linux.S_IFREG | 0666,
expectedUID: 65534,
expectedGID: 65534,
},
"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,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
ctx := contexttest.Context(t)
creds := auth.CredentialsFromContext(ctx)
// Create VFS.
vfsObj := vfs.VirtualFilesystem{}
if err := vfsObj.Init(ctx); err != nil {
t.Fatalf("VFS init: %v", err)
}
vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
})
mns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil)
if err != nil {
t.Fatalf("failed to create tmpfs root mount: %v", err)
}
defer mns.DecRef(ctx)
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)
}
gotUID, gotGID, err := GetExecUIDGIDFromUser(ctx, mns, tc.user)
if 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)
}
}
if gotUID != tc.expectedUID {
t.Fatalf("expectedUID %v, gotUID: %v", tc.expectedUID, gotUID)
}
if gotGID != tc.expectedGID {
t.Fatalf("expectedGID %v, gotGID: %v", tc.expectedGID, gotGID)
}
})
}
}