From 462d166d5355d33a05271d24de4d52f30dd62f67 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 15 Dec 2019 19:31:39 -0800 Subject: Add keyring package In preparation for introducing support for the new filesystem-level keyrings, move the existing user keyring management code from security/keyring.go and crypto/crypto.go into a new package, 'keyring'. This package provides functions AddEncryptionKey, RemoveEncryptionKey, and GetEncryptionKeyStatus which delegate to either the filesystem keyring (added by a later patch) or to the user keyring. This provides a common interface to both types of keyrings, to the extent possible. --- security/keyring.go | 214 ------------------------------------------------- security/privileges.go | 8 +- 2 files changed, 4 insertions(+), 218 deletions(-) delete mode 100644 security/keyring.go (limited to 'security') diff --git a/security/keyring.go b/security/keyring.go deleted file mode 100644 index 3236775..0000000 --- a/security/keyring.go +++ /dev/null @@ -1,214 +0,0 @@ -/* - * keyring.go - Handles inserting/removing into user keyrings. - * - * Copyright 2017 Google Inc. - * Author: Joe Richey (joerichey@google.com) - * - * 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 security - -import ( - "fmt" - "log" - "os/user" - "runtime" - - "github.com/pkg/errors" - "golang.org/x/sys/unix" - - "github.com/google/fscrypt/util" -) - -// KeyType is always logon as required by filesystem encryption. -const KeyType = "logon" - -// Keyring related error values -var ( - ErrKeySearch = errors.New("could not find key with descriptor") - ErrKeyRemove = util.SystemError("could not remove key from the keyring") - ErrKeyInsert = util.SystemError("could not insert key into the keyring") - ErrSessionUserKeying = errors.New("user keyring not linked into session keyring") - ErrAccessUserKeyring = errors.New("could not access user keyring") - ErrLinkUserKeyring = util.SystemError("could not link user keyring into root keyring") -) - -// FindKey tries to locate a key in the kernel keyring with the provided -// description. The key ID is returned if we can find the key. An error is -// returned if the key does not exist. -func FindKey(description string, targetUser *user.User) (int, error) { - runtime.LockOSThread() // ensure target user keyring remains possessed in thread keyring - defer runtime.UnlockOSThread() - - keyringID, err := UserKeyringID(targetUser, false) - if err != nil { - return 0, err - } - - keyID, err := unix.KeyctlSearch(keyringID, KeyType, description, 0) - log.Printf("KeyctlSearch(%d, %s, %s) = %d, %v", keyringID, KeyType, description, keyID, err) - if err != nil { - return 0, errors.Wrap(ErrKeySearch, err.Error()) - } - return keyID, err -} - -// RemoveKey tries to remove a policy key from the kernel keyring with the -// provided description. An error is returned if the key does not exist. -func RemoveKey(description string, targetUser *user.User) error { - runtime.LockOSThread() // ensure target user keyring remains possessed in thread keyring - defer runtime.UnlockOSThread() - - keyID, err := FindKey(description, targetUser) - if err != nil { - return err - } - - // We use KEYCTL_INVALIDATE instead of KEYCTL_REVOKE because - // invalidating a key immediately removes it. - _, err = unix.KeyctlInt(unix.KEYCTL_INVALIDATE, keyID, 0, 0, 0) - log.Printf("KeyctlInvalidate(%d) = %v", keyID, err) - if err != nil { - return errors.Wrap(ErrKeyRemove, err.Error()) - } - return nil -} - -// InsertKey puts the provided data into the kernel keyring with the provided -// description. -func InsertKey(data []byte, description string, targetUser *user.User) error { - runtime.LockOSThread() // ensure target user keyring remains possessed in thread keyring - defer runtime.UnlockOSThread() - - keyringID, err := UserKeyringID(targetUser, true) - if err != nil { - return err - } - - keyID, err := unix.AddKey(KeyType, description, data, keyringID) - log.Printf("KeyctlAddKey(%s, %s, , %d) = %d, %v", - KeyType, description, keyringID, keyID, err) - if err != nil { - return errors.Wrap(ErrKeyInsert, err.Error()) - } - return nil -} - -// UserKeyringID returns the key id of the target user's user keyring. We also -// ensure that the keyring will be accessible by linking it into the thread -// keyring and linking it into the root user keyring (permissions allowing). If -// checkSession is true, an error is returned if a normal user requests their -// user keyring, but it is not in the current session keyring. -func UserKeyringID(targetUser *user.User, checkSession bool) (int, error) { - runtime.LockOSThread() // ensure target user keyring remains possessed in thread keyring - defer runtime.UnlockOSThread() - - uid := util.AtoiOrPanic(targetUser.Uid) - targetKeyring, err := userKeyringIDLookup(uid) - if err != nil { - return 0, errors.Wrap(ErrAccessUserKeyring, err.Error()) - } - - if !util.IsUserRoot() { - // Make sure the returned keyring will be accessible by checking - // that it is in the session keyring. - if checkSession && !isUserKeyringInSession(uid) { - return 0, ErrSessionUserKeying - } - return targetKeyring, nil - } - - // Make sure the returned keyring will be accessible by linking it into - // the root user's user keyring (which will not be garbage collected). - rootKeyring, err := userKeyringIDLookup(0) - if err != nil { - return 0, errors.Wrap(ErrLinkUserKeyring, err.Error()) - } - - if rootKeyring != targetKeyring { - if err = keyringLink(targetKeyring, rootKeyring); err != nil { - return 0, errors.Wrap(ErrLinkUserKeyring, err.Error()) - } - } - return targetKeyring, nil -} - -func userKeyringIDLookup(uid int) (keyringID int, err error) { - - // Our goals here are to: - // - Find the user keyring (for the provided uid) - // - Link it into the current thread keyring (so we can use it) - // - Make no permanent changes to the process privileges - // Complicating this are the facts that: - // - The value of KEY_SPEC_USER_KEYRING is determined by the ruid - // - Keyring linking permissions use the euid - // So we have to change both the ruid and euid to make this work, - // setting the suid to 0 so that we can later switch back. - ruid, euid, suid := getUids() - if ruid != uid || euid != uid { - if err = setUids(uid, uid, 0); err != nil { - return - } - defer func() { - resetErr := setUids(ruid, euid, suid) - if resetErr != nil { - err = resetErr - } - }() - } - - // We get the value of KEY_SPEC_USER_KEYRING. Note that this will also - // trigger the creation of the uid keyring if it does not yet exist. - keyringID, err = unix.KeyctlGetKeyringID(unix.KEY_SPEC_USER_KEYRING, true) - log.Printf("keyringID(_uid.%d) = %d, %v", uid, keyringID, err) - if err != nil { - return 0, err - } - - // We still want to use this keyring after our privileges are reset. So - // we link it into the thread keyring, preventing a loss of access. - // - // We must be under LockOSThread() for this to work reliably. Note that - // we can't just use the process keyring, since it doesn't work reliably - // in Go programs, due to the Go runtime creating threads before the - // program starts and has a chance to create the process keyring. - if err = keyringLink(keyringID, unix.KEY_SPEC_THREAD_KEYRING); err != nil { - return 0, err - } - - return keyringID, nil -} - -// isUserKeyringInSession tells us if the user's uid keyring is in the current -// session keyring. -func isUserKeyringInSession(uid int) bool { - // We cannot use unix.KEY_SPEC_SESSION_KEYRING directly as that might - // create a session keyring if one does not exist. - sessionKeyring, err := unix.KeyctlGetKeyringID(unix.KEY_SPEC_SESSION_KEYRING, false) - log.Printf("keyringID(session) = %d, %v", sessionKeyring, err) - if err != nil { - return false - } - - description := fmt.Sprintf("_uid.%d", uid) - id, err := unix.KeyctlSearch(sessionKeyring, "keyring", description, 0) - log.Printf("KeyctlSearch(%d, keyring, %s) = %d, %v", sessionKeyring, description, id, err) - return err == nil -} - -func keyringLink(keyID int, keyringID int) error { - _, err := unix.KeyctlInt(unix.KEYCTL_LINK, keyID, keyringID, 0, 0) - log.Printf("KeyctlLink(%d, %d) = %v", keyID, keyringID, err) - return err -} diff --git a/security/privileges.go b/security/privileges.go index 3a1ca81..e5751b5 100644 --- a/security/privileges.go +++ b/security/privileges.go @@ -19,9 +19,7 @@ // Package security manages: // - Cache clearing (cache.go) -// - Keyring Operations (keyring.go) // - Privilege manipulation (privileges.go) -// - Maintaining the link between the root and user keyrings. package security // Use the libc versions of setreuid, setregid, and setgroups instead of the @@ -142,7 +140,8 @@ func SetProcessPrivileges(privs *Privileges) error { return nil } -func setUids(ruid, euid, suid int) error { +// SetUids sets the process's real, effective, and saved UIDs. +func SetUids(ruid, euid, suid int) error { log.Printf("Setting ruid=%d euid=%d suid=%d", ruid, euid, suid) // We elevate all the privs before setting them. This prevents issues // with (ruid=1000,euid=1000,suid=0), where just a single call to @@ -156,7 +155,8 @@ func setUids(ruid, euid, suid int) error { return nil } -func getUids() (int, int, int) { +// GetUids gets the process's real, effective, and saved UIDs. +func GetUids() (int, int, int) { var ruid, euid, suid C.uid_t C.getresuid(&ruid, &euid, &suid) return int(ruid), int(euid), int(suid) -- cgit v1.2.3