mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
fe9d22f06b
commit
93bbcbf35b
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user