/* * privileges.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" "os/user" "sync" "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, target *user.User) (int, error) { keyringID, err := UserKeyringID(target, 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, target *user.User) error { keyID, err := FindKey(description, target) 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, target *user.User) error { keyringID, err := UserKeyringID(target, 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 } var ( keyringIDCache = make(map[int]int) cacheLock sync.Mutex ) // 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 process // keyring and linking it into the root user keyring (permissions allowing). If // check_session 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(target *user.User, check_session bool) (int, error) { uid := util.AtoiOrPanic(target.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 check_session && !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) (int, error) { cacheLock.Lock() defer cacheLock.Unlock() if keyringID, ok := keyringIDCache[uid]; ok { return keyringID, nil } ruid, euid := os.Getuid(), os.Geteuid() // If all the ids do not agree, we will have to change them needSetUids := uid != ruid || uid != euid // The value of KEY_SPEC_USER_KEYRING is determined by the real uid, so // we must set the ruid appropriately. Note that this will also trigger // the creation of the uid keyring if it does not yet exist. if needSetUids { defer setUids(ruid, euid) // Always reset privileges if err := setUids(uid, 0); err != nil { return 0, err } } 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 key after our privileges are reset. If we // link the key into the process keyring, we will possess it and still // be able to use it. However, the permissions to link are based on the // effective uid, so we must set the euid appropriately. if needSetUids { if err := setUids(0, uid); err != nil { return 0, err } } if err := keyringLink(keyringID, unix.KEY_SPEC_PROCESS_KEYRING); err != nil { return 0, err } keyringIDCache[uid] = keyringID 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 }