diff --git a/pkg/sentry/kernel/BUILD b/pkg/sentry/kernel/BUILD index 75272d6ad..45d015001 100644 --- a/pkg/sentry/kernel/BUILD +++ b/pkg/sentry/kernel/BUILD @@ -280,6 +280,7 @@ go_library( "task_futex.go", "task_identity.go", "task_image.go", + "task_key.go", "task_list.go", "task_log.go", "task_mutex.go", diff --git a/pkg/sentry/kernel/auth/BUILD b/pkg/sentry/kernel/auth/BUILD index 28f75d703..2d5fc988f 100644 --- a/pkg/sentry/kernel/auth/BUILD +++ b/pkg/sentry/kernel/auth/BUILD @@ -1,6 +1,6 @@ +load("//pkg/sync/locking:locking.bzl", "declare_mutex", "declare_rwmutex") load("//tools:defs.bzl", "go_library") load("//tools/go_generics:defs.bzl", "go_template_instance") -load("//pkg/sync/locking:locking.bzl", "declare_mutex") package( default_applicable_licenses = ["//:license"], @@ -54,6 +54,20 @@ declare_mutex( prefix = "userNamespace", ) +declare_rwmutex( + name = "keyset_mutex", + out = "keyset_mutex.go", + package = "auth", + prefix = "keyset", +) + +declare_mutex( + name = "keyset_transaction_mutex", + out = "keyset_transaction_mutex.go", + package = "auth", + prefix = "keysetTransaction", +) + go_library( name = "auth", srcs = [ @@ -67,6 +81,9 @@ go_library( "id_map_functions.go", "id_map_range.go", "id_map_set.go", + "key.go", + "keyset_mutex.go", + "keyset_transaction_mutex.go", "user_namespace.go", "user_namespace_mutex.go", ], @@ -78,6 +95,7 @@ go_library( "//pkg/context", "//pkg/errors/linuxerr", "//pkg/log", + "//pkg/rand", "//pkg/sentry/seccheck", "//pkg/sentry/seccheck/points:points_go_proto", "//pkg/sync", diff --git a/pkg/sentry/kernel/auth/key.go b/pkg/sentry/kernel/auth/key.go new file mode 100644 index 000000000..d0f105759 --- /dev/null +++ b/pkg/sentry/kernel/auth/key.go @@ -0,0 +1,398 @@ +// Copyright 2020 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth + +import ( + "encoding/binary" + "fmt" + "strings" + + "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/rand" +) + +// KeySerial is a key ID type. +// Only strictly positive IDs are valid key IDs. +// The zero ID is meaningless but is specified when creating new keyrings. +// Strictly negative IDs are used for special key IDs which are internally +// translated to real key IDs (e.g. KEY_SPEC_SESSION_KEYRING is translated +// to the caller process's session keyring). +type KeySerial int32 + +// KeyType is the type of a key. +// This is an enum, but is also exposed to userspace in KEYCTL_DESCRIBE. +// For this reason, it must match Linux. +type KeyType string + +// List of known key types. +const ( + KeyTypeKeyring KeyType = "keyring" + // Other types are not yet supported. +) + +// KeyPermission represents a permission on a key. +type KeyPermission int + +// List of known key permissions. +const ( + KeyView KeyPermission = iota + KeyRead + KeyWrite + KeySearch + KeyLink + KeySetAttr +) + +// KeyPermissions is the full set of permissions on a single Key. +type KeyPermissions uint64 + +const ( + // MaxKeyDescSize is the maximum size of the "Description" field of keys. + // Corresponds to `KEY_MAX_DESC_SIZE` in Linux. + MaxKeyDescSize = 4096 + + // maxSetSize is the maximum number of a keys in a `Set`. + // By default, Linux limits this number to 200 per non-root user. + // Here, we limit it to 200 per Set, which is stricter. + maxSetSize = 200 +) + +// Key represents a key in the keyrings subsystem. +// +// +stateify savable +type Key struct { + // ID is the ID of the key, also often referred to as "serial number". + // Note that key IDs passed in syscalls may be negative when they refer to + // "special keys", sometimes also referred to as "shortcut IDs". + // Key IDs of real instantiated keys are always > 0. + // The key ID never changes and is unique within a KeySet (i.e. a user + // namespace). + // It must be chosen with cryptographic randomness to make enumeration + // attacks harder. + ID KeySerial + + // Description is a description of the key. It is also often referred to the + // "name" of the key. Keys are canonically identified by their ID, but the + // syscall ABI also allows look up keys by their description. + // It may not be larger than `KeyMaxDescSize`. + // Confusingly, the information returned by the KEYCTL_DESCRIBE operation, + // which you'd think means "get the key description", actually returns a + // superset of this `Description`. + Description string + + // kuid is the owner of the key in the root namespace. + // kuid is only mutable in KeySet transactions. + kuid KUID + + // kgid is the group of the key in the root namespace. + // kgid is only mutable in KeySet transactions. + kgid KGID + + // perms is a bitfield of key permissions. + // perms is only mutable in KeySet transactions. + perms KeyPermissions +} + +// Type returns the type of this key. +func (*Key) Type() KeyType { + return KeyTypeKeyring +} + +// KUID returns the KUID (owner ID) of the key. +func (k *Key) KUID() KUID { return k.kuid } + +// KGID returns the KGID (group ID) of the key. +func (k *Key) KGID() KGID { return k.kgid } + +// Permissions returns the permission bits of the key. +func (k *Key) Permissions() KeyPermissions { return k.perms } + +// String is a human-friendly representation of the key. +// Notably, this is *not* the string returned to userspace when requested +// using `KEYCTL_DESCRIBE`. +func (k *Key) String() string { + return fmt.Sprintf("id=%d,perms=0x%x,desc=%q", k.ID, k.perms, k.Description) +} + +// Bitmasks for permission checks. +const ( + keyPossessorPermissionsMask = 0x3f000000 + keyPossessorPermissionsShift = 24 + keyOwnerPermissionsMask = 0x003f0000 + keyOwnerPermissionsShift = 16 + keyGroupPermissionsMask = 0x00003f00 + keyGroupPermissionsShift = 8 + keyOtherPermissionsMask = 0x0000003f + keyOtherPermissionsShift = 0 + + keyPermissionView = 0x00000001 + keyPermissionRead = 0x00000002 + keyPermissionWrite = 0x00000004 + keyPermissionSearch = 0x00000008 + keyPermissionLink = 0x00000010 + keyPermissionSetAttr = 0x00000020 + keyPermissionAll = (keyPermissionView | + keyPermissionRead | + keyPermissionWrite | + keyPermissionSearch | + keyPermissionLink | + keyPermissionSetAttr) +) + +// String returns a human-readable version of the permission bits. +func (p KeyPermissions) String() string { + var perms strings.Builder + for i, s := range [4]struct { + kind string + shift int + }{ + {kind: "possessor", shift: keyPossessorPermissionsShift}, + {kind: "owner", shift: keyOwnerPermissionsShift}, + {kind: "group", shift: keyGroupPermissionsShift}, + {kind: "other", shift: keyOtherPermissionsShift}, + } { + if i != 0 { + perms.WriteRune(',') + } + perms.WriteString(s.kind) + perms.WriteRune('=') + kindPerms := p >> s.shift + for _, b := range [6]struct { + mask int + r rune + }{ + {mask: keyPermissionView, r: 'v'}, + {mask: keyPermissionRead, r: 'r'}, + {mask: keyPermissionWrite, r: 'w'}, + {mask: keyPermissionSearch, r: 's'}, + {mask: keyPermissionLink, r: 'l'}, + {mask: keyPermissionSetAttr, r: 'a'}, + } { + if uint64(kindPerms)&uint64(b.mask) != 0 { + perms.WriteRune(b.r) + } else { + perms.WriteRune('-') + } + } + } + return fmt.Sprintf("%08x[%s]", uint64(p), perms.String()) +} + +// Default key settings. +const ( + // Default session keyring name. + DefaultSessionKeyringName = "_ses" + + // Default permissions for unnamed session keyrings: + // Possessors have full permissions. + // Owners have view and read permissions. + DefaultUnnamedSessionKeyringPermissions KeyPermissions = ((keyPermissionAll << keyPossessorPermissionsShift) | + ((keyPermissionView | keyPermissionRead) << keyOwnerPermissionsShift)) + + // Default permissions for named session keyrings: + // Possessors have full permissions. + // Owners have view, read, and link permissions. + DefaultNamedSessionKeyringPermissions KeyPermissions = ((keyPermissionAll << keyPossessorPermissionsShift) | + ((keyPermissionView | keyPermissionRead | keyPermissionLink) << keyOwnerPermissionsShift)) +) + +// PossessedKeys is an opaque type used during key permission check. +// When iterating over all keys, the possessed set of keys should only be +// built once. Since key possession is a recursive property, it can be +// expensive to determine. PossessedKeys holds all possessed keys at +// the time it is computed. +// PossessedKeys is short-lived; it should only live for so long as there +// are no changes to the KeySet or to any key permissions. +type PossessedKeys struct { + // possessed is a list of possessed key IDs. + possessed map[KeySerial]struct{} +} + +// PossessedKeys returns a new fully-expanded set of PossessedKeys. +// The keys passed in are the set of keys that a task directly possesses: +// session keyring, process keyring, thread keyring. Each key may be nil. +// PossessedKeys is short-lived; it should only live for so long as there +// are no changes to the KeySet or to any key permissions. +func (c *Credentials) PossessedKeys(sessionKeyring, processKeyring, threadKeyring *Key) *PossessedKeys { + possessed := &PossessedKeys{possessed: make(map[KeySerial]struct{})} + for _, k := range [3]*Key{sessionKeyring, processKeyring, threadKeyring} { + if k == nil { + continue + } + // The possessor still needs "search" permission in order to actually possess anything. + if ((k.perms&keyPossessorPermissionsMask)>>keyPossessorPermissionsShift)&keyPermissionSearch != 0 { + possessed.possessed[k.ID] = struct{}{} + } + } + + // If we implement keyrings that contain other keys, this is where the + // recursion would happen. + + return possessed +} + +// HasKeyPermission returns whether the credentials grant `permission` on `k`. +// +//go:nosplit +func (c *Credentials) HasKeyPermission(k *Key, possessed *PossessedKeys, permission KeyPermission) bool { + perms := k.perms & keyOtherPermissionsMask + if _, ok := possessed.possessed[k.ID]; ok { + perms |= (k.perms & keyPossessorPermissionsMask) >> keyPossessorPermissionsShift + } + if c.EffectiveKUID == k.kuid { + perms |= (k.perms & keyOwnerPermissionsMask) >> keyOwnerPermissionsShift + } + if c.EffectiveKGID == k.kgid { + perms |= (k.perms & keyGroupPermissionsMask) >> keyGroupPermissionsShift + } + switch permission { + case KeyView: + return perms&keyPermissionView != 0 + case KeyRead: + return perms&keyPermissionRead != 0 + case KeyWrite: + return perms&keyPermissionWrite != 0 + case KeySearch: + return perms&keyPermissionSearch != 0 + case KeyLink: + return perms&keyPermissionLink != 0 + case KeySetAttr: + return perms&keyPermissionSetAttr != 0 + default: + panic("unknown key permission") + } +} + +// KeySet is a set of keys. +// +// +stateify savable +type KeySet struct { + // txnMu is used for transactionality of key changes. + // This blocks multiple tasks for concurrently changing the keyset or the + // permissions of any keys. + txnMu keysetTransactionMutex `state:"nosave"` + + // mu protects the fields below. + // Within functions on `KeySet`, `mu` may only be locked for reading. + // Locking `mu` for writing may only be done in `LockedKeySet` functions. + mu keysetRWMutex `state:"nosave"` + + // keys maps key IDs to the underlying Key struct. + // It is initially nil to save on heap space. + // It is only initialized when doing mutable transactions on it using `Do`. + keys map[KeySerial]*Key +} + +// LockedKeySet is a KeySet in a transaction. +// It exposes functions that can mutate the KeySet or its keys. +type LockedKeySet struct { + *KeySet +} + +// Do executes the given function as a transaction on the KeySet. +// It returns the error that `fn` returns. +// This is the only function where functions that lock the KeySet.mu for +// writing may be called. +func (s *KeySet) Do(fn func(*LockedKeySet) error) error { + s.txnMu.Lock() + defer s.txnMu.Unlock() + ls := &LockedKeySet{s} + ls.mu.Lock() + if s.keys == nil { + // Initialize the map from its zero value, if it hasn't been done yet. + s.keys = make(map[KeySerial]*Key) + } + ls.mu.Unlock() + return fn(ls) +} + +// Lookup looks up a key by ID. +// Callers must exercise care to verify that the key can be accessed with +// proper credentials. +func (s *KeySet) Lookup(keyID KeySerial) (*Key, error) { + s.mu.RLock() + defer s.mu.RUnlock() + key, found := s.keys[keyID] + if !found { + return nil, linuxerr.ENOKEY + } + return key, nil +} + +// ForEach iterates over all keys. +// If `fn` returns true, iteration stops immediately. +// Callers must exercise care to only process keys to which they have access. +func (s *KeySet) ForEach(fn func(*Key) bool) { + s.mu.RLock() + defer s.mu.RUnlock() + for _, key := range s.keys { + if fn(key) { + return + } + } +} + +// getNewID returns a new random key ID strictly larger than zero. +// It uses cryptographic randomness in order to make enumeration attacks +// harder. +func getNewID() (KeySerial, error) { + var newID int32 + for newID == 0 { + if err := binary.Read(rand.Reader, binary.LittleEndian, &newID); err != nil { + return 0, err + } + } + if newID < 0 { + newID = -newID + } + return KeySerial(newID), nil +} + +// Add adds a new Key to the KeySet. +func (s *LockedKeySet) Add(description string, creds *Credentials, perms KeyPermissions) (*Key, error) { + if len(description) >= MaxKeyDescSize { + return nil, linuxerr.EINVAL + } + s.mu.Lock() + defer s.mu.Unlock() + if len(s.keys) >= maxSetSize { + return nil, linuxerr.EDQUOT + } + newID, err := getNewID() + if err != nil { + return nil, err + } + for s.keys[newID] != nil { + newID, err = getNewID() + if err != nil { + return nil, err + } + } + k := &Key{ + ID: newID, + Description: description, + kuid: creds.EffectiveKUID, + kgid: creds.EffectiveKGID, + perms: perms, + } + s.keys[newID] = k + return k, nil +} + +// SetPerms sets the permissions on a given key. +// The caller must have SetAttr permission on the key. +func (s *LockedKeySet) SetPerms(key *Key, newPerms KeyPermissions) { + key.perms = newPerms +} diff --git a/pkg/sentry/kernel/auth/user_namespace.go b/pkg/sentry/kernel/auth/user_namespace.go index 3f2557606..52b0cdf73 100644 --- a/pkg/sentry/kernel/auth/user_namespace.go +++ b/pkg/sentry/kernel/auth/user_namespace.go @@ -33,6 +33,9 @@ type UserNamespace struct { // namespace. owner is immutable. owner KUID + // Keys is the set of keys in this namespace. + Keys KeySet + // mu protects the following fields. // // If mu will be locked in multiple UserNamespaces, it must be locked in diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go index c7fc5ad94..261425b0b 100644 --- a/pkg/sentry/kernel/kernel.go +++ b/pkg/sentry/kernel/kernel.go @@ -952,6 +952,8 @@ func (k *Kernel) CreateProcess(args CreateProcessArgs) (*ThreadGroup, ThreadID, ContainerID: args.ContainerID, InitialCgroups: args.InitialCgroups, UserCounters: k.GetUserCounters(args.Credentials.RealKUID), + // A task with no parent starts out with no session keyring. + SessionKeyring: nil, } config.NetworkNamespace.IncRef() t, err := k.tasks.NewTask(ctx, config) diff --git a/pkg/sentry/kernel/task.go b/pkg/sentry/kernel/task.go index 5fd3644aa..b26e2496e 100644 --- a/pkg/sentry/kernel/task.go +++ b/pkg/sentry/kernel/task.go @@ -598,6 +598,12 @@ type Task struct { // The userCounters pointer is exclusive to the task goroutine, but the // userCounters instance must be atomically accessed. userCounters *userCounters + + // sessionKeyring is a pointer to the task's session keyring, if set. + // It is guaranteed to be of type "keyring". + // + // +checklocks:mu + sessionKeyring *auth.Key } // Task related metrics diff --git a/pkg/sentry/kernel/task_clone.go b/pkg/sentry/kernel/task_clone.go index 017024250..23066317a 100644 --- a/pkg/sentry/kernel/task_clone.go +++ b/pkg/sentry/kernel/task_clone.go @@ -165,6 +165,7 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { // above Task.mu. So we copy t.image with t.mu held and call Fork() on the copy. t.mu.Lock() curImage := t.image + sessionKeyring := t.sessionKeyring t.mu.Unlock() image, err := curImage.Fork(t, t.k, args.Flags&linux.CLONE_VM != 0) if err != nil { @@ -173,6 +174,12 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { cu.Add(func() { image.release(t) }) + + if args.Flags&linux.CLONE_NEWUSER != 0 { + // If the task is in a new user namespace, it cannot share keys. + sessionKeyring = nil + } + // clone() returns 0 in the child. image.Arch.SetReturn(0) if args.Stack != 0 { @@ -259,6 +266,7 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { RSeqSignature: rseqSignature, ContainerID: t.ContainerID(), UserCounters: uc, + SessionKeyring: sessionKeyring, } if args.Flags&linux.CLONE_THREAD == 0 { cfg.Parent = t diff --git a/pkg/sentry/kernel/task_key.go b/pkg/sentry/kernel/task_key.go new file mode 100644 index 000000000..db1d9714d --- /dev/null +++ b/pkg/sentry/kernel/task_key.go @@ -0,0 +1,122 @@ +// Copyright 2023 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kernel + +import ( + "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" +) + +// SessionKeyring returns this Task's session keyring. +// Session keyrings are inherited from the parent when a task is started. +// If the session keyring is unset, it is implicitly initialized. +// As such, this function should never return ENOKEY. +func (t *Task) SessionKeyring() (*auth.Key, error) { + t.mu.Lock() + defer t.mu.Unlock() + if t.sessionKeyring != nil { + // Verify that we still have access to this keyring. + creds := t.Credentials() + if !creds.HasKeyPermission(t.sessionKeyring, creds.PossessedKeys(t.sessionKeyring, nil, nil), auth.KeySearch) { + return nil, linuxerr.EACCES + } + return t.sessionKeyring, nil + } + // If we don't have a session keyring, implicitly create one. + return t.joinNewSessionKeyringLocked(auth.DefaultSessionKeyringName, auth.DefaultUnnamedSessionKeyringPermissions) +} + +// joinNewSessionKeyringLocked creates a new session keyring with the given +// description, and joins it immediately. +// Preconditions: t.mu is held. +// +// +checklocks:t.mu +func (t *Task) joinNewSessionKeyringLocked(newKeyDesc string, newKeyPerms auth.KeyPermissions) (*auth.Key, error) { + var sessionKeyring *auth.Key + err := t.UserNamespace().Keys.Do(func(keySet *auth.LockedKeySet) error { + creds := t.Credentials() + var err error + sessionKeyring, err = keySet.Add(newKeyDesc, creds, newKeyPerms) + return err + }) + if err != nil { + return nil, err + } + t.Debugf("Joining newly-created session keyring with ID %d, permissions %v", sessionKeyring.ID, newKeyPerms) + t.sessionKeyring = sessionKeyring + return sessionKeyring, nil +} + +// JoinSessionKeyring causes the task to join a keyring with the given +// key description (not ID). +// If `keyDesc` is nil, then the task joins a newly-instantiated session +// keyring instead. +func (t *Task) JoinSessionKeyring(keyDesc *string) (*auth.Key, error) { + t.mu.Lock() + defer t.mu.Unlock() + creds := t.Credentials() + possessed := creds.PossessedKeys(t.sessionKeyring, nil, nil) + var sessionKeyring *auth.Key + newKeyPerms := auth.DefaultUnnamedSessionKeyringPermissions + newKeyDesc := auth.DefaultSessionKeyringName + if keyDesc != nil { + creds.UserNamespace.Keys.ForEach(func(k *auth.Key) bool { + if k.Description == *keyDesc && creds.HasKeyPermission(k, possessed, auth.KeySearch) { + sessionKeyring = k + return true + } + return false + }) + if sessionKeyring != nil { + t.Debugf("Joining existing session keyring with ID %d", sessionKeyring.ID) + t.sessionKeyring = sessionKeyring + return sessionKeyring, nil + } + newKeyDesc = *keyDesc + newKeyPerms = auth.DefaultNamedSessionKeyringPermissions + } + return t.joinNewSessionKeyringLocked(newKeyDesc, newKeyPerms) +} + +// LookupKey looks up a key by ID using this task's credentials. +func (t *Task) LookupKey(keyID auth.KeySerial) (*auth.Key, error) { + t.mu.Lock() + defer t.mu.Unlock() + creds := t.Credentials() + key, err := creds.UserNamespace.Keys.Lookup(keyID) + if err != nil { + return nil, err + } + if !creds.HasKeyPermission(key, creds.PossessedKeys(t.sessionKeyring, nil, nil), auth.KeySearch) { + return nil, linuxerr.EACCES + } + return key, nil +} + +// SetPermsOnKey sets the permission bits on the given key using the task's +// credentials. +func (t *Task) SetPermsOnKey(key *auth.Key, perms auth.KeyPermissions) error { + t.mu.Lock() + defer t.mu.Unlock() + creds := t.Credentials() + possessed := creds.PossessedKeys(t.sessionKeyring, nil, nil) + return creds.UserNamespace.Keys.Do(func(keySet *auth.LockedKeySet) error { + if !creds.HasKeyPermission(key, possessed, auth.KeySetAttr) { + return linuxerr.EACCES + } + keySet.SetPerms(key, perms) + return nil + }) +} diff --git a/pkg/sentry/kernel/task_start.go b/pkg/sentry/kernel/task_start.go index cd76149ba..fb7cbefb0 100644 --- a/pkg/sentry/kernel/task_start.go +++ b/pkg/sentry/kernel/task_start.go @@ -102,6 +102,10 @@ type TaskConfig struct { // UserCounters is user resource counters. UserCounters *userCounters + + // SessionKeyring is the session keyring associated with the parent task. + // It may be nil. + SessionKeyring *auth.Key } // NewTask creates a new task defined by cfg. @@ -171,6 +175,7 @@ func (ts *TaskSet) newTask(ctx context.Context, cfg *TaskConfig) (*Task, error) containerID: cfg.ContainerID, cgroups: make(map[Cgroup]struct{}), userCounters: cfg.UserCounters, + sessionKeyring: cfg.SessionKeyring, } t.netns = cfg.NetworkNamespace t.creds.Store(cfg.Credentials) diff --git a/pkg/sentry/syscalls/linux/BUILD b/pkg/sentry/syscalls/linux/BUILD index d8bcb685f..86117d91f 100644 --- a/pkg/sentry/syscalls/linux/BUILD +++ b/pkg/sentry/syscalls/linux/BUILD @@ -26,6 +26,7 @@ go_library( "sys_identity.go", "sys_inotify.go", "sys_iouring.go", + "sys_key.go", "sys_membarrier.go", "sys_mempolicy.go", "sys_mmap.go", diff --git a/pkg/sentry/syscalls/linux/linux64.go b/pkg/sentry/syscalls/linux/linux64.go index 2232c4799..327a4d5e3 100644 --- a/pkg/sentry/syscalls/linux/linux64.go +++ b/pkg/sentry/syscalls/linux/linux64.go @@ -302,7 +302,7 @@ var AMD64 = &kernel.SyscallTable{ 247: syscalls.Supported("waitid", Waitid), 248: syscalls.Error("add_key", linuxerr.EACCES, "Not available to user.", nil), 249: syscalls.Error("request_key", linuxerr.EACCES, "Not available to user.", nil), - 250: syscalls.Error("keyctl", linuxerr.EACCES, "Not available to user.", nil), + 250: syscalls.PartiallySupported("keyctl", Keyctl, "Only supports session keyrings with zero keys in them.", nil), 251: syscalls.CapError("ioprio_set", linux.CAP_SYS_ADMIN, "", nil), // requires cap_sys_nice or cap_sys_admin (depending) 252: syscalls.CapError("ioprio_get", linux.CAP_SYS_ADMIN, "", nil), // requires cap_sys_nice or cap_sys_admin (depending) 253: syscalls.PartiallySupportedPoint("inotify_init", InotifyInit, PointInotifyInit, "inotify events are only available inside the sandbox.", nil), @@ -650,7 +650,7 @@ var ARM64 = &kernel.SyscallTable{ 216: syscalls.Supported("mremap", Mremap), 217: syscalls.Error("add_key", linuxerr.EACCES, "Not available to user.", nil), 218: syscalls.Error("request_key", linuxerr.EACCES, "Not available to user.", nil), - 219: syscalls.Error("keyctl", linuxerr.EACCES, "Not available to user.", nil), + 219: syscalls.PartiallySupported("keyctl", Keyctl, "Only supports session keyrings with zero keys in them.", nil), 220: syscalls.PartiallySupportedPoint("clone", Clone, PointClone, "Options CLONE_PIDFD, CLONE_NEWCGROUP, CLONE_PARENT, CLONE_NEWTIME, CLONE_CLEAR_SIGHAND, and CLONE_SYSVSEM not supported.", nil), 221: syscalls.SupportedPoint("execve", Execve, PointExecve), 222: syscalls.Supported("mmap", Mmap), diff --git a/pkg/sentry/syscalls/linux/sys_key.go b/pkg/sentry/syscalls/linux/sys_key.go new file mode 100644 index 000000000..3b75aec68 --- /dev/null +++ b/pkg/sentry/syscalls/linux/sys_key.go @@ -0,0 +1,152 @@ +// Copyright 2023 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package linux + +import ( + "fmt" + "math" + + "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/errors/linuxerr" + "gvisor.dev/gvisor/pkg/log" + "gvisor.dev/gvisor/pkg/sentry/arch" + "gvisor.dev/gvisor/pkg/sentry/kernel" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" +) + +// Keyctl implements Linux syscall keyctl(2). +func Keyctl(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + switch args[0].Int() { + case linux.KEYCTL_GET_KEYRING_ID: + return keyCtlGetKeyringID(t, args) + case linux.KEYCTL_DESCRIBE: + return keyctlDescribe(t, args) + case linux.KEYCTL_JOIN_SESSION_KEYRING: + return keyctlJoinSessionKeyring(t, args) + case linux.KEYCTL_SETPERM: + return keyctlSetPerm(t, args) + } + log.Debugf("Unimplemented keyctl operation: %d", args[0].Int()) + kernel.IncrementUnimplementedSyscallCounter(sysno) + return 0, nil, linuxerr.ENOSYS +} + +// keyCtlGetKeyringID implements keyctl(2) with operation +// KEYCTL_GET_KEYRING_ID. +func keyCtlGetKeyringID(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + keyID := auth.KeySerial(args[1].Int()) + var key *auth.Key + var err error + if keyID > 0 { + // Not a special key ID, so return as-is. + return uintptr(keyID), nil, nil + } + switch keyID { + case linux.KEY_SPEC_SESSION_KEYRING: + key, err = t.SessionKeyring() + default: + if keyID <= 0 { + // Other special key IDs are not implemented. + return 0, nil, linuxerr.ENOSYS + } + // For positive key IDs, KEYCTL_GET_KEYRING_ID can be used as an existence + // and permissions check. + key, err = t.LookupKey(keyID) + } + if err != nil { + return 0, nil, err + } + return uintptr(key.ID), nil, nil +} + +// keyctlDescribe implements keyctl(2) with operation KEYCTL_DESCRIBE. +func keyctlDescribe(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + keyID := auth.KeySerial(args[1].Int()) + bufPtr := args[2].Pointer() + bufSize := args[3].SizeT() + + // Get address range to write to. + if bufSize > math.MaxInt32 { + bufSize = math.MaxInt32 + } + + var key *auth.Key + var err error + switch keyID { + case linux.KEY_SPEC_SESSION_KEYRING: + key, err = t.SessionKeyring() + default: + key, err = t.LookupKey(keyID) + } + if err != nil { + return 0, nil, err + } + uid := t.UserNamespace().MapFromKUID(key.KUID()) + gid := t.UserNamespace().MapFromKGID(key.KGID()) + keyDesc := fmt.Sprintf("%s;%d;%d;%08x;%s\x00", key.Type(), uid, gid, uint64(key.Permissions()), key.Description) + if bufSize > 0 { + toWrite := uint(len(keyDesc)) + if toWrite > bufSize { + toWrite = bufSize + } + _, err = t.CopyOutBytes(bufPtr, []byte(keyDesc)[:toWrite]) + } + // The KEYCTL_DESCRIBE operation returns the length of the full string, + // regardless of whether or not it was fully written out to userspace. + // It includes the zero byte at the end in the returned length. + return uintptr(len(keyDesc)), nil, err +} + +// keyctlJoinSessionKeyring implements keyctl(2) with operation +// KEYCTL_JOIN_SESSION_KEYRING. +func keyctlJoinSessionKeyring(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + keyDescPtr := args[1].Pointer() + var key *auth.Key + var err error + if keyDescPtr == 0 { + // Creating an anonymous keyring. + key, err = t.JoinSessionKeyring(nil) + } else { + // Joining a named keyring. Read in its description. + var keyringDesc string + keyringDesc, err = t.CopyInString(keyDescPtr, auth.MaxKeyDescSize) + if err != nil { + return 0, nil, err + } + key, err = t.JoinSessionKeyring(&keyringDesc) + } + if err != nil { + return 0, nil, err + } + return uintptr(key.ID), nil, nil +} + +// keyctlSetPerm implements keyctl(2) with operation KEYCTL_SETPERM. +func keyctlSetPerm(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + keyID := auth.KeySerial(args[1].Int()) + newPerms := auth.KeyPermissions(args[2].Uint64()) + var key *auth.Key + var err error + switch keyID { + case linux.KEY_SPEC_SESSION_KEYRING: + key, err = t.SessionKeyring() + default: + key, err = t.UserNamespace().Keys.Lookup(keyID) + } + if err != nil { + return 0, nil, err + } + return 0, nil, t.SetPermsOnKey(key, newPerms) +} diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 7231c4091..64fcb2450 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -316,6 +316,10 @@ syscall_test( test = "//test/syscalls/linux:kcov_test", ) +syscall_test( + test = "//test/syscalls/linux:keys_test", +) + syscall_test( test = "//test/syscalls/linux:kill_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 1997c455b..eeb357302 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1176,6 +1176,23 @@ cc_binary( ], ) +cc_binary( + name = "keys_test", + testonly = 1, + srcs = ["keys.cc"], + linkstatic = 1, + deps = [ + gtest, + "//test/util:posix_error", + "//test/util:test_main", + "//test/util:thread_util", + "@com_google_absl//absl/random", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/synchronization", + ], +) + cc_binary( name = "kill_test", testonly = 1, diff --git a/test/syscalls/linux/keys.cc b/test/syscalls/linux/keys.cc new file mode 100644 index 000000000..2579ae8f0 --- /dev/null +++ b/test/syscalls/linux/keys.cc @@ -0,0 +1,794 @@ +// Copyright 2018 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/random/random.h" +#include "absl/strings/match.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "test/util/posix_error.h" +#include "test/util/thread_util.h" + +#define KEY_POS_VIEW 0x01000000 +#define KEY_POS_READ 0x02000000 +#define KEY_POS_WRITE 0x04000000 +#define KEY_POS_SEARCH 0x08000000 +#define KEY_POS_LINK 0x10000000 +#define KEY_POS_SETATTR 0x20000000 + +#define KEY_USR_VIEW 0x00010000 +#define KEY_USR_READ 0x00020000 +#define KEY_USR_WRITE 0x00040000 +#define KEY_USR_SEARCH 0x00080000 +#define KEY_USR_LINK 0x00100000 +#define KEY_USR_SETATTR 0x00200000 + +#define KEY_GRP_VIEW 0x00000100 +#define KEY_GRP_READ 0x00000200 +#define KEY_GRP_WRITE 0x00000400 +#define KEY_GRP_SEARCH 0x00000800 +#define KEY_GRP_LINK 0x00001000 +#define KEY_GRP_SETATTR 0x00002000 + +#define KEY_OTH_VIEW 0x00000001 +#define KEY_OTH_READ 0x00000002 +#define KEY_OTH_WRITE 0x00000004 +#define KEY_OTH_SEARCH 0x00000008 +#define KEY_OTH_LINK 0x00000010 +#define KEY_OTH_SETATTR 0x00000020 + +namespace gvisor { +namespace testing { +namespace { + +// keyctl is a cosmetic wrapper for the keyctl(2) system call. +static inline PosixErrorOr keyctl(int operation, uint64_t arg2, + uint64_t arg3, uint64_t arg4, + uint64_t arg5) { + int64_t ret = syscall(__NR_keyctl, operation, arg2, arg3, arg4, arg5); + if (ret == -1) { + return PosixError( + errno, absl::StrFormat("keyctl(%d, %d, %d, %d, %d) failed", operation, + arg2, arg3, arg4, arg5)); + } + return ret; +} + +static inline PosixErrorOr keyctl(int operation) { + return keyctl(operation, 0, 0, 0, 0); +} + +static inline PosixErrorOr keyctl(int operation, uint64_t arg2) { + return keyctl(operation, arg2, 0, 0, 0); +} + +static inline PosixErrorOr keyctl(int operation, uint64_t arg2, + uint64_t arg3) { + return keyctl(operation, arg2, arg3, 0, 0); +} + +// DescribedKey is the description of a key. +struct DescribedKey { + int64_t key_id; + std::string full_desc; + std::string type; + uint64_t uid; + uint64_t gid; + uint64_t perm; + std::string description; +}; + +std::string DescribedKeyString(const DescribedKey& described_key) { + std::string process_perms = "??????"; + uint64_t perms = described_key.perm; + process_perms[0] = (perms & KEY_POS_VIEW) == 0 ? '-' : 'v'; // view + process_perms[1] = (perms & KEY_POS_READ) == 0 ? '-' : 'r'; // read + process_perms[2] = (perms & KEY_POS_WRITE) == 0 ? '-' : 'w'; // write + process_perms[3] = (perms & KEY_POS_SEARCH) == 0 ? '-' : 's'; // search + process_perms[4] = (perms & KEY_POS_LINK) == 0 ? '-' : 'l'; // link + process_perms[5] = (perms & KEY_POS_SETATTR) == 0 ? '-' : 'a'; // setattr + std::string user_perms = "??????"; + user_perms[0] = (perms & KEY_USR_VIEW) == 0 ? '-' : 'v'; // view + user_perms[1] = (perms & KEY_USR_READ) == 0 ? '-' : 'r'; // read + user_perms[2] = (perms & KEY_USR_WRITE) == 0 ? '-' : 'w'; // write + user_perms[3] = (perms & KEY_USR_SEARCH) == 0 ? '-' : 's'; // search + user_perms[4] = (perms & KEY_USR_LINK) == 0 ? '-' : 'l'; // link + user_perms[5] = (perms & KEY_USR_SETATTR) == 0 ? '-' : 'a'; // setattr + std::string group_perms = "??????"; + group_perms[0] = (perms & KEY_GRP_VIEW) == 0 ? '-' : 'v'; // view + group_perms[1] = (perms & KEY_GRP_READ) == 0 ? '-' : 'r'; // read + group_perms[2] = (perms & KEY_GRP_WRITE) == 0 ? '-' : 'w'; // write + group_perms[3] = (perms & KEY_GRP_SEARCH) == 0 ? '-' : 's'; // search + group_perms[4] = (perms & KEY_GRP_LINK) == 0 ? '-' : 'l'; // link + group_perms[5] = (perms & KEY_GRP_SETATTR) == 0 ? '-' : 'a'; // setattr + std::string other_perms = "??????"; + other_perms[0] = (perms & KEY_OTH_VIEW) == 0 ? '-' : 'v'; // view + other_perms[1] = (perms & KEY_OTH_READ) == 0 ? '-' : 'r'; // read + other_perms[2] = (perms & KEY_OTH_WRITE) == 0 ? '-' : 'w'; // write + other_perms[3] = (perms & KEY_OTH_SEARCH) == 0 ? '-' : 's'; // search + other_perms[4] = (perms & KEY_OTH_LINK) == 0 ? '-' : 'l'; // link + other_perms[5] = (perms & KEY_OTH_SETATTR) == 0 ? '-' : 'a'; // setattr + + return absl::StrFormat( + "id=%d type=%s uid=%d gid=%d perms=0x%x " + "[process=%s,user=%s,group=%s,other=%s] desc=%s", + described_key.key_id, described_key.type, described_key.uid, + described_key.gid, described_key.perm, process_perms, user_perms, + group_perms, other_perms, described_key.description); +} + +bool operator==(const DescribedKey& lhs, const DescribedKey& rhs) { + if (lhs.key_id != rhs.key_id) { + return false; + } + if (lhs.type != rhs.type) { + return false; + } + if (lhs.uid != rhs.uid) { + return false; + } + if (lhs.gid != rhs.gid) { + return false; + } + if (lhs.perm != rhs.perm) { + return false; + } + if (lhs.description != rhs.description) { + return false; + } + return true; +} + +bool operator!=(const DescribedKey& lhs, const DescribedKey& rhs) { + return !(lhs == rhs); +} + +PosixErrorOr DescribeKey(int64_t key_id) { + ASSIGN_OR_RETURN_ERRNO(int64_t resolved_id, + keyctl(KEYCTL_GET_KEYRING_ID, key_id)); + if (resolved_id <= 0) { + if (key_id == KEY_SPEC_SESSION_KEYRING) { + return PosixError( + -1, absl::StrFormat("Could not resolve session keyring (errno=%d)", + errno)); + } + return PosixError( + -1, absl::StrFormat("Could not resolve key id %d (errno=%d)", key_id, + errno)); + } + DescribedKey described_key; + described_key.key_id = resolved_id; + char described_key_buf[1024]; + ASSIGN_OR_RETURN_ERRNO( + int64_t buf_bytes, + keyctl(KEYCTL_DESCRIBE, key_id, (uint64_t)(described_key_buf), 1024, 0)); + if (buf_bytes <= 0) { + if (key_id == KEY_SPEC_SESSION_KEYRING) { + return PosixError(-1, "Could not describe session keyring"); + } + return PosixError(-1, absl::StrFormat("Could not describe key %d", key_id)); + } + // Remove one byte from the key size because the returned length + // includes the \0 at the end of the buffer. + described_key.full_desc = std::string(described_key_buf, buf_bytes - 1); + int i = 0; + for (absl::string_view element : + absl::StrSplit(described_key.full_desc, ';')) { + switch (i) { + case 0: + described_key.type = element; + break; + case 1: + if (!absl::SimpleAtoi(element, &described_key.uid)) { + return PosixError(-1, absl::StrFormat("Could not parse uid from: %s", + described_key.full_desc)); + } + break; + case 2: + if (!absl::SimpleAtoi(element, &described_key.gid)) { + return PosixError(-1, absl::StrFormat("Could not parse gid from: %s", + described_key.full_desc)); + } + break; + case 3: + described_key.perm = std::stoull(std::string(element), nullptr, 16); + break; + case 4: + described_key.description = std::string(element); + break; + default: + return PosixError( + -1, + absl::StrFormat( + "Key string had more than the expected number of elements: %s", + described_key.full_desc)); + } + i++; + } + return described_key; +} + +TEST(KeysTest, GetCurrentSessionKeyring) { + DescribedKey key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Session key: " << DescribedKeyString(key) << std::endl; + EXPECT_TRUE(absl::StartsWith(key.description, "_ses")) + << "Unexpected name for session keyring"; +} + +TEST(KeysTest, GetCurrentSessionKeyringViaID) { + DescribedKey key_via_special_id = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Session key (retrieved via KEY_SPEC_SESSION_KEYRING): " + << DescribedKeyString(key_via_special_id) << std::endl; + DescribedKey key_via_actual_id = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(key_via_special_id.key_id)); + std::cerr << "Session key (retrieved via explicit ID " + << key_via_special_id.key_id + << "): " << DescribedKeyString(key_via_special_id) << std::endl; + EXPECT_EQ(key_via_special_id, key_via_actual_id); +} + +TEST(KeysTest, GetKeyringThatDoesNotExist) { + // We don't know which keyring IDs do exist, so we just iterate until we find + // one that doesn't exist. Surely we'll find one eventually. + char described_key_buf[1024]; + uint32_t key_id; + bool found_non_existent_key = false; + for (int i = 0; i < 100; ++i) { + key_id = absl::Uniform(absl::InsecureBitGen(), 0, + std::numeric_limits::max()); + PosixErrorOr buf_bytes = + keyctl(KEYCTL_DESCRIBE, key_id, (uint64_t)(described_key_buf), 1024, 0); + if (!buf_bytes.ok() && errno == ENOKEY) { + found_non_existent_key = true; + break; + } + } + EXPECT_TRUE(found_non_existent_key) << "Did not find any non-existent key ID"; +} + +TEST(KeysTest, DescribeKeyWithNullBuffer) { + DescribedKey session_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + int64_t desc_length = ASSERT_NO_ERRNO_AND_VALUE( + keyctl(KEYCTL_DESCRIBE, KEY_SPEC_SESSION_KEYRING, 0)); + EXPECT_EQ(desc_length, session_key.full_desc.length() + 1); +} + +TEST(KeysTest, DescribeKeyWithTooSmallBuffer) { + DescribedKey session_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + char described_key_buf[4]; + ASSERT_LT(4, session_key.full_desc.length()); + int64_t desc_length = ASSERT_NO_ERRNO_AND_VALUE( + keyctl(KEYCTL_DESCRIBE, KEY_SPEC_SESSION_KEYRING, + (uint64_t)(described_key_buf), 0, 0)); + EXPECT_EQ(desc_length, session_key.full_desc.length() + 1); +} + +TEST(KeysTest, ChildThreadInheritsSessionKeyring) { + DescribedKey parent_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Parent session keyring before spawning child thread: " + << DescribedKeyString(parent_key) << std::endl; + DescribedKey child_key; + ScopedThread([&] { + child_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Child session keyring: " << DescribedKeyString(child_key) + << std::endl; + }).Join(); + EXPECT_EQ(parent_key, child_key) + << "Child session keyring did not match parent session keyring: child=" + << DescribedKeyString(child_key) + << " vs parent=" << DescribedKeyString(parent_key); +} + +TEST(KeysTest, ChildThreadInheritsSessionKeyringCreatedAfterChildIsBorn) { + DescribedKey first_parent_key; + DescribedKey second_parent_key; + DescribedKey child_key; + ScopedThread([&] { + int64_t session_keyring_id = + ASSERT_NO_ERRNO_AND_VALUE(keyctl(KEYCTL_JOIN_SESSION_KEYRING)); + ASSERT_GT(session_keyring_id, 0) + << "Failed to join session keyring: " << errno; + first_parent_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Parent session keyring before spawning child: " + << DescribedKeyString(first_parent_key) << std::endl; + ScopedThread([&] { + absl::Mutex mu; + bool child_ready = false; + bool parent_keyring_created = false; + ScopedThread child([&] { + absl::MutexLock child_ml(&mu); + child_ready = true; + std::cerr << "Child is spawned and waiting for parent." << std::endl; + mu.Await(absl::Condition(&parent_keyring_created)); + child_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Child session keyring: " << DescribedKeyString(child_key) + << std::endl; + }); + [&] { + absl::MutexLock parent_ml(&mu); + mu.Await(absl::Condition(&child_ready)); + int64_t session_keyring_id = + ASSERT_NO_ERRNO_AND_VALUE(keyctl(KEYCTL_JOIN_SESSION_KEYRING)); + ASSERT_GT(session_keyring_id, 0) + << "Failed to join session keyring: " << errno; + second_parent_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Parent session keyring after spawning child: " + << DescribedKeyString(second_parent_key) << std::endl; + parent_keyring_created = true; + }(); + child.Join(); + }).Join(); + }).Join(); + ASSERT_NE(first_parent_key, second_parent_key); + ASSERT_EQ(first_parent_key, child_key); +} + +TEST(KeysTest, JoinNewNamedSessionKeyring) { + constexpr absl::string_view kKeyringName = "my_little_keyring"; + DescribedKey child_key; + ScopedThread([&] { + ASSERT_NO_ERRNO( + keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data()))); + child_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Child session keyring after joining new session keyring: " + << DescribedKeyString(child_key) << std::endl; + }).Join(); + EXPECT_EQ(child_key.description, kKeyringName); +} + +TEST(KeysTest, ChildJoinsNewSessionKeyring) { + DescribedKey parent_key_before = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Parent session keyring before spawning child thread: " + << DescribedKeyString(parent_key_before) << std::endl; + DescribedKey child_key; + ScopedThread([&] { + int64_t session_keyring_id = + ASSERT_NO_ERRNO_AND_VALUE(keyctl(KEYCTL_JOIN_SESSION_KEYRING)); + ASSERT_GT(session_keyring_id, 0) + << "Failed to join session keyring: " << errno; + child_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Child session keyring after joining new session keyring: " + << DescribedKeyString(child_key) << std::endl; + ASSERT_EQ(child_key.key_id, session_keyring_id); + }).Join(); + DescribedKey parent_key_after = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Parent session keyring after child finished: " + << DescribedKeyString(parent_key_after) << std::endl; + EXPECT_EQ(parent_key_before, parent_key_after) + << "Parent session keyring changed after child did its thing: " + << DescribedKeyString(parent_key_after) << " vs " + << DescribedKeyString(parent_key_before); + EXPECT_NE(parent_key_before, child_key) + << "Child session keyring did not change after joining new session " + "keyring: " + << DescribedKeyString(child_key); +} + +TEST(KeysTest, ExistingNamedSessionKeyringIsNew) { + constexpr absl::string_view kKeyringName = "my_little_keyring"; + DescribedKey parent_key; + DescribedKey first_child_key; + DescribedKey second_child_initial_key; + DescribedKey second_child_existing_key; + ScopedThread([&] { + ASSERT_NO_ERRNO(keyctl(KEYCTL_JOIN_SESSION_KEYRING)); + parent_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + absl::Mutex mu; + bool first_child_created_keyring = false; + ScopedThread first_child([&] { + absl::MutexLock ml(&mu); + ASSERT_NO_ERRNO( + keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data()))); + first_child_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "First child's session keyring: " + << DescribedKeyString(first_child_key) << std::endl; + first_child_created_keyring = true; + }); + ScopedThread([&] { + absl::MutexLock ml(&mu); + second_child_initial_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Session child's initial session keyring: " + << DescribedKeyString(second_child_initial_key) << std::endl; + mu.Await(absl::Condition(&first_child_created_keyring)); + ASSERT_NO_ERRNO( + keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data()))); + second_child_existing_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Session child's second session keyring: " + << DescribedKeyString(second_child_existing_key) << std::endl; + }).Join(); + first_child.Join(); + }).Join(); + EXPECT_EQ(parent_key, second_child_initial_key); + EXPECT_EQ(first_child_key.description, kKeyringName); + EXPECT_NE(parent_key, first_child_key); + EXPECT_NE(first_child_key, second_child_existing_key); + EXPECT_EQ(first_child_key.description, second_child_existing_key.description); + EXPECT_NE(first_child_key.key_id, second_child_existing_key.key_id); +} + +TEST(KeysTest, SetAndRetrieveKeyPermissions) { + DescribedKey before_key; + DescribedKey after_key; + DescribedKey deeper_key; + uint64_t new_perms = 0; + ScopedThread([&] { + ASSERT_NO_ERRNO(keyctl(KEYCTL_JOIN_SESSION_KEYRING)); + before_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Child session keyring after joining new session keyring: " + << DescribedKeyString(before_key) << std::endl; + new_perms = (before_key.perm | KEY_USR_SEARCH) & 0xffffffff; + ASSERT_NO_ERRNO( + keyctl(KEYCTL_SETPERM, KEY_SPEC_SESSION_KEYRING, new_perms)); + after_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr + << "Child session keyring after changing session keyring permissions: " + << DescribedKeyString(after_key) << std::endl; + ScopedThread([&] { + deeper_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Second-level child session keyring: " + << DescribedKeyString(deeper_key) << std::endl; + }).Join(); + }).Join(); + EXPECT_NE(new_perms, 0) << "New permissions are empty"; + EXPECT_NE(before_key.perm, new_perms) + << "Permissions were not actually requested to change, please update " + "permissions mask"; + EXPECT_EQ(after_key.perm, new_perms) + << "Permissions were not updated correctly"; + EXPECT_EQ(after_key, deeper_key) << "Permissions were not inherited"; +} + +TEST(KeysTest, JoinExistingNamedKeyringFromParent) { + constexpr absl::string_view kKeyringName = "my_little_keyring"; + constexpr absl::string_view kOtherKeyringName = "my_other_keyring"; + DescribedKey first_level_key; + DescribedKey second_level_key; + DescribedKey third_level_key; + ScopedThread([&] { + ASSERT_NO_ERRNO( + keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data()))); + DescribedKey before_perms_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + uint64_t perms = (before_perms_key.perm | 0x80008) & 0xffffffff; + ASSERT_NO_ERRNO(keyctl(KEYCTL_SETPERM, before_perms_key.key_id, perms)); + first_level_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "First-level child keyring: " + << DescribedKeyString(first_level_key) << std::endl; + ScopedThread([&] { + ASSERT_NO_ERRNO(keyctl(KEYCTL_JOIN_SESSION_KEYRING, + (uint64_t)(kOtherKeyringName.data()))); + second_level_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Second-level child keyring: " + << DescribedKeyString(second_level_key) << std::endl; + ScopedThread([&] { + ASSERT_NO_ERRNO(keyctl(KEYCTL_JOIN_SESSION_KEYRING, + (uint64_t)(kKeyringName.data()))); + third_level_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Third-level child keyring: " + << DescribedKeyString(third_level_key) << std::endl; + }).Join(); + }).Join(); + }).Join(); + // The key_id is different per process, so don't compare that. + // We use the permissions field to verify that the keyring is the same + // between the first and the third level, to show that the same keyring + // is being looked up. + // However, the second level didn't look up the same keyring, so its + // permissions should be different. + EXPECT_EQ(first_level_key.perm, third_level_key.perm); + EXPECT_NE(first_level_key.perm, second_level_key.perm); +} + +TEST(KeysTest, DefaultKeyPermissions) { + constexpr absl::string_view kKeyringName = "named_session_keyring"; + DescribedKey default_named_session_key; + DescribedKey default_unnamed_session_key; + ScopedThread([&] { + ASSERT_NO_ERRNO( + keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data()))); + default_named_session_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + }).Join(); + ScopedThread([&] { + ASSERT_NO_ERRNO(keyctl(KEYCTL_JOIN_SESSION_KEYRING)); + default_unnamed_session_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + }).Join(); + std::cerr << "Default named session keyring: " + << DescribedKeyString(default_named_session_key) << std::endl; + std::cerr << "Default unnamed session keyring: " + << DescribedKeyString(default_unnamed_session_key) << std::endl; + // Possessor permissions: + uint64_t key_pos_all = KEY_POS_VIEW | KEY_POS_READ | KEY_POS_WRITE | + KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR; + EXPECT_EQ(default_unnamed_session_key.perm & key_pos_all, key_pos_all); + EXPECT_EQ(default_named_session_key.perm & key_pos_all, key_pos_all); + + // User permissions: + // These differ depending on whether the keyring is named or not. + EXPECT_EQ(default_unnamed_session_key.perm & KEY_USR_VIEW, KEY_USR_VIEW); + EXPECT_EQ(default_unnamed_session_key.perm & KEY_USR_READ, KEY_USR_READ); + EXPECT_EQ(default_unnamed_session_key.perm & KEY_USR_WRITE, 0); + EXPECT_EQ(default_unnamed_session_key.perm & KEY_USR_SEARCH, 0); + EXPECT_EQ(default_unnamed_session_key.perm & KEY_USR_LINK, 0); + EXPECT_EQ(default_unnamed_session_key.perm & KEY_USR_SETATTR, 0); + EXPECT_EQ(default_unnamed_session_key.perm | KEY_USR_LINK, + default_named_session_key.perm); + + // Group permissions: + uint64_t key_group_all = KEY_GRP_VIEW | KEY_GRP_READ | KEY_GRP_WRITE | + KEY_GRP_SEARCH | KEY_GRP_LINK | KEY_GRP_SETATTR; + EXPECT_EQ(default_unnamed_session_key.perm & key_group_all, 0); + EXPECT_EQ(default_named_session_key.perm & key_group_all, 0); + + // Other permissions: + uint64_t key_other_all = KEY_OTH_VIEW | KEY_OTH_READ | KEY_OTH_WRITE | + KEY_OTH_SEARCH | KEY_OTH_LINK | KEY_OTH_SETATTR; + EXPECT_EQ(default_unnamed_session_key.perm & key_other_all, 0); + EXPECT_EQ(default_named_session_key.perm & key_other_all, 0); +} + +TEST(KeysTest, EnforceKeyPermissions) { + ScopedThread([&] { + constexpr absl::string_view kKeyringName = "my_little_keyring"; + ASSERT_NO_ERRNO( + keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data()))); + DescribedKey key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + ASSERT_NO_ERRNO(keyctl(KEYCTL_SETPERM, key.key_id, 0 /* No permissions */)); + EXPECT_THAT(keyctl(KEYCTL_DESCRIBE, KEY_SPEC_SESSION_KEYRING), + PosixErrorIs(EACCES)) + << "Session keyring can be described"; + EXPECT_THAT(keyctl(KEYCTL_DESCRIBE, key.key_id), PosixErrorIs(EACCES)) + << "Session keyring can be described by ID"; + ASSERT_THAT(keyctl(KEYCTL_SETPERM, key.key_id, 0), PosixErrorIs(EACCES)) + << "Session keyring perms can be changed after locking them down"; + ScopedThread([&] { + EXPECT_THAT(keyctl(KEYCTL_DESCRIBE, KEY_SPEC_SESSION_KEYRING), + PosixErrorIs(EACCES)) + << "Session keyring can be described in child"; + EXPECT_THAT(keyctl(KEYCTL_DESCRIBE, key.key_id), PosixErrorIs(EACCES)) + << "Session keyring can be described by ID in child"; + ASSERT_THAT(keyctl(KEYCTL_SETPERM, key.key_id, 0), PosixErrorIs(EACCES)) + << "Session keyring perms can be changed after locking them down in " + "parent"; + }).Join(); + }).Join(); +} + +// JoiningNonSearchableNamedKeyring verifies what happens when joining an +// existing named keyring without the search permission. +TEST(KeysTest, JoiningNonSearchableNamedKeyring) { + constexpr absl::string_view kKeyringName = "my_little_keyring"; + DescribedKey first_key; + DescribedKey second_key; + ScopedThread([&] { + ASSERT_NO_ERRNO( + keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data()))); + first_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "First child keyring: " << DescribedKeyString(first_key) + << std::endl; + uint64_t non_searchable_perms = + first_key.perm & + ~(KEY_POS_SEARCH | KEY_USR_SEARCH | KEY_GRP_SEARCH | KEY_OTH_SEARCH); + ASSERT_NO_ERRNO( + keyctl(KEYCTL_SETPERM, KEY_SPEC_SESSION_KEYRING, non_searchable_perms)); + ScopedThread([&] { + // The man page says this should fail with EACCES, but Linux actually + // creates a new keyring with the same name instead. + ASSERT_NO_ERRNO( + keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data()))); + second_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Second child keyring: " << DescribedKeyString(first_key) + << std::endl; + }).Join(); + }).Join(); + ASSERT_NE(first_key.key_id, second_key.key_id); +} + +// JoiningSearchableNamedKeyring verifies what happens when joining an +// existing named keyring with the search permission. +TEST(KeysTest, JoiningSearchableNamedKeyring) { + constexpr absl::string_view kKeyringName = "my_little_keyring"; + DescribedKey searchable_key; + DescribedKey second_key; + ScopedThread([&] { + ASSERT_NO_ERRNO( + keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data()))); + DescribedKey initial_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Initial session keyring: " << DescribedKeyString(initial_key) + << std::endl; + uint64_t searchable_perms = initial_key.perm | KEY_USR_SEARCH; + ASSERT_NO_ERRNO( + keyctl(KEYCTL_SETPERM, KEY_SPEC_SESSION_KEYRING, searchable_perms)); + searchable_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Searchable session keyring: " + << DescribedKeyString(searchable_key) << std::endl; + ScopedThread([&] { + ASSERT_NO_ERRNO( + keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data()))); + second_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Second keyring: " << DescribedKeyString(second_key) + << std::endl; + }).Join(); + }).Join(); + EXPECT_EQ(searchable_key.key_id, second_key.key_id); +} + +TEST(KeysTest, SearchableKeyringIsSharedAcrossThreads) { + constexpr absl::string_view kKeyringName = "my_little_keyring"; + DescribedKey parent_key; + DescribedKey first_child_final_key; + DescribedKey second_child_final_key; + ScopedThread([&] { + ASSERT_NO_ERRNO(keyctl(KEYCTL_JOIN_SESSION_KEYRING)); + parent_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Parent session keyring: " << DescribedKeyString(parent_key) + << std::endl; + absl::Mutex mu; + + // We're going to do a complicated dance. + // Each of the following booleans is used to gate on the steps described + // above it. + + // - Spawn two threads, have them wait on each other until they are both + // actually running code. + bool first_child_ready = false; + bool second_child_ready = false; + + // - Have the first thread create a named keyring. + // - Have the first thread change this keyring to be searchable by user. + bool first_child_created_keyring = false; + + // - Have the second thread join it by name. + // - Have the second thread flip a bit in its permission field: + // KEY_GRP_LINK + bool second_child_modified_keyring = false; + + // - Have the first thread re-read the permissions of its keyring. + // - Have the first thread flip another bit in the permission field: + // KEY_OTH_LINK + bool first_child_modified_keyring = false; + + // - Have the second thread re-read the permissions of its keyring. + // - Verify that the final keys from both threads match and have both bits + // flipped. + + ScopedThread first_child([&] { + absl::MutexLock ml(&mu); + first_child_ready = true; + mu.Await(absl::Condition(&second_child_ready)); + ASSERT_NO_ERRNO( + keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data()))); + DescribedKey initial_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "First child: initial session keyring: " + << DescribedKeyString(initial_key) << std::endl; + uint64_t searchable_perms = initial_key.perm | KEY_USR_SEARCH; + ASSERT_NO_ERRNO( + keyctl(KEYCTL_SETPERM, KEY_SPEC_SESSION_KEYRING, searchable_perms)); + DescribedKey searchable_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "First child: searchable session keyring: " + << DescribedKeyString(searchable_key) << std::endl; + first_child_created_keyring = true; + mu.Await(absl::Condition(&second_child_modified_keyring)); + DescribedKey modified_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr + << "First child: session keyring after second thread modified it: " + << DescribedKeyString(modified_key) << std::endl; + uint64_t new_perms = modified_key.perm | KEY_OTH_LINK; + ASSERT_NO_ERRNO( + keyctl(KEYCTL_SETPERM, KEY_SPEC_SESSION_KEYRING, new_perms)); + first_child_final_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "First child: final session keyring: " + << DescribedKeyString(first_child_final_key) << std::endl; + first_child_modified_keyring = true; + }); + ScopedThread second_child([&] { + absl::MutexLock ml(&mu); + second_child_ready = true; + mu.Await(absl::Condition(&first_child_ready)); + mu.Await(absl::Condition(&first_child_created_keyring)); + ASSERT_NO_ERRNO( + keyctl(KEYCTL_JOIN_SESSION_KEYRING, (uint64_t)(kKeyringName.data()))); + DescribedKey initial_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Second child: initial session keyring: " + << DescribedKeyString(initial_key) << std::endl; + uint64_t new_perms = initial_key.perm | KEY_GRP_LINK; + ASSERT_NO_ERRNO( + keyctl(KEYCTL_SETPERM, KEY_SPEC_SESSION_KEYRING, new_perms)); + DescribedKey modified_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr + << "Second child: session keyring after modifying its permissions: " + << DescribedKeyString(modified_key) << std::endl; + second_child_modified_keyring = true; + mu.Await(absl::Condition(&first_child_modified_keyring)); + second_child_final_key = + ASSERT_NO_ERRNO_AND_VALUE(DescribeKey(KEY_SPEC_SESSION_KEYRING)); + std::cerr << "Second child: final session keyring: " + << DescribedKeyString(second_child_final_key) << std::endl; + }); + first_child.Join(); + second_child.Join(); + }).Join(); + EXPECT_NE(parent_key, first_child_final_key); + EXPECT_NE(parent_key, second_child_final_key); + EXPECT_NE(parent_key.perm, first_child_final_key.perm); + EXPECT_EQ(first_child_final_key.key_id, second_child_final_key.key_id); + for (const uint64_t bit : + {KEY_USR_LINK, KEY_USR_SEARCH, KEY_GRP_LINK, KEY_OTH_LINK}) { + EXPECT_EQ(parent_key.perm & bit, 0) << "Bit " << bit << " in parent key"; + EXPECT_EQ(first_child_final_key.perm & bit, bit) + << "Bit " << bit << " in first child key"; + EXPECT_EQ(second_child_final_key.perm & bit, bit) + << "Bit " << bit << " in second child key"; + } + EXPECT_EQ(first_child_final_key.perm, second_child_final_key.perm); +} + +} // namespace +} // namespace testing +} // namespace gvisor