diff options
Diffstat (limited to 'security')
| -rw-r--r-- | security/cache.go | 6 | ||||
| -rw-r--r-- | security/keyring.go | 205 | ||||
| -rw-r--r-- | security/privileges.go | 163 | ||||
| -rw-r--r-- | security/security_test.go | 24 |
4 files changed, 137 insertions, 261 deletions
diff --git a/security/cache.go b/security/cache.go index d0c60b1..f11248d 100644 --- a/security/cache.go +++ b/security/cache.go @@ -30,7 +30,7 @@ import ( // dentries. This has the effect of making encrypted directories whose keys are // not present no longer accessible. Requires root privileges. func DropFilesystemCache() error { - // Dirty reclaimible inodes must be synced so that they will be freed. + // Dirty reclaimable inodes must be synced so that they will be freed. log.Print("syncing changes to filesystem") unix.Sync() @@ -41,9 +41,9 @@ func DropFilesystemCache() error { return err } defer file.Close() - // "2" just frees the reclaimable inodes and dentries, the associated + // "2" just frees the reclaimable inodes and dentries. The associated // pages to these inodes will be freed. We do not need to free the - // entire pagecache (as this will severly impact performance). + // entire pagecache (as this will severely impact performance). _, err = file.WriteString("2") return err } diff --git a/security/keyring.go b/security/keyring.go deleted file mode 100644 index ab65631..0000000 --- a/security/keyring.go +++ /dev/null @@ -1,205 +0,0 @@ -/* - * 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, <data>, %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 -// 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(target *user.User, checkSession 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 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) (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 -} diff --git a/security/privileges.go b/security/privileges.go index 7d69da9..fe8668d 100644 --- a/security/privileges.go +++ b/security/privileges.go @@ -18,82 +18,139 @@ */ // Package security manages: -// - Cache clearing (cache.go) -// - Keyring Operations (keyring.go) -// - Privilege manipulation (privileges.go) -// - Maintaining the link between the root and user keyrings. +// - Cache clearing (cache.go) +// - Privilege manipulation (privileges.go) package security +// Use the libc versions of setreuid, setregid, and setgroups instead of the +// "sys/unix" versions. The "sys/unix" versions use the raw syscalls which +// operate on the calling thread only, whereas the libc versions operate on the +// whole process. And we need to operate on the whole process, firstly for +// pam_fscrypt to prevent the privileges of Go worker threads from diverging +// from the PAM stack's "main" thread, violating libc's assumption and causing +// an abort() later in the PAM stack; and secondly because Go code may migrate +// between OS-level threads while it's running. +// +// See also: https://github.com/golang/go/issues/1435 + +/* +#define _GNU_SOURCE // for getresuid and setresuid +#include <sys/types.h> +#include <unistd.h> // getting and setting uids and gids +#include <grp.h> // setgroups +*/ +import "C" + import ( "log" - "os" "os/user" + "syscall" "github.com/pkg/errors" - "golang.org/x/sys/unix" "github.com/google/fscrypt/util" ) -// SetThreadPrivileges temporarily drops the privileges of the current thread to -// have the effective uid/gid of the target user. The privileges can be changed -// again with another call to SetThreadPrivileges. -func SetThreadPrivileges(target *user.User) error { - euid := util.AtoiOrPanic(target.Uid) - egid := util.AtoiOrPanic(target.Gid) - if os.Geteuid() == euid { - log.Printf("Privileges already set to %q", target.Username) - return nil - } - log.Printf("Setting privileges to %q", target.Username) +// Privileges encapsulate the effective uid/gid and groups of a process. +type Privileges struct { + euid C.uid_t + egid C.gid_t + groups []C.gid_t +} + +// ProcessPrivileges returns the process's current effective privileges. +func ProcessPrivileges() (*Privileges, error) { + ruid := C.getuid() + euid := C.geteuid() + rgid := C.getgid() + egid := C.getegid() - // If setting privs to root, we want to set the uid first, so we will - // then have the necessary permissions to perform the other actions. - if euid == 0 { - if err := setUids(-1, euid); err != nil { - return err + var groups []C.gid_t + n, err := C.getgroups(0, nil) + if n < 0 { + return nil, err + } + // If n == 0, the user isn't in any groups, so groups == nil is fine. + if n > 0 { + groups = make([]C.gid_t, n) + n, err = C.getgroups(n, &groups[0]) + if n < 0 { + return nil, err } + groups = groups[:n] } - if err := setGids(-1, egid); err != nil { - return err + log.Printf("Current privs (real, effective): uid=(%d,%d) gid=(%d,%d) groups=%v", + ruid, euid, rgid, egid, groups) + return &Privileges{euid, egid, groups}, nil +} + +// UserPrivileges returns the default privileges for the specified user. +func UserPrivileges(user *user.User) (*Privileges, error) { + privs := &Privileges{ + euid: C.uid_t(util.AtoiOrPanic(user.Uid)), + egid: C.gid_t(util.AtoiOrPanic(user.Gid)), } - if err := setGroups(target); err != nil { - return err + userGroups, err := user.GroupIds() + if err != nil { + return nil, util.SystemError(err.Error()) } - // If not setting privs to root, we want to avoid dropping the uid - // util the very end. - if euid != 0 { - if err := setUids(-1, euid); err != nil { - return err - } + privs.groups = make([]C.gid_t, len(userGroups)) + for i, group := range userGroups { + privs.groups[i] = C.gid_t(util.AtoiOrPanic(group)) } - return nil + return privs, nil } -func setUids(ruid, euid int) error { - err := unix.Setreuid(ruid, euid) - log.Printf("Setreuid(%d, %d) = %v", ruid, euid, err) - return errors.Wrapf(err, "setting uids") -} +// SetProcessPrivileges sets the privileges of the current process to have those +// specified by privs. The original privileges can be obtained by first saving +// the output of ProcessPrivileges, calling SetProcessPrivileges with the +// desired privs, then calling SetProcessPrivileges with the saved privs. +func SetProcessPrivileges(privs *Privileges) error { + log.Printf("Setting euid=%d egid=%d groups=%v", privs.euid, privs.egid, privs.groups) -func setGids(rgid, egid int) error { - err := unix.Setregid(rgid, egid) - log.Printf("Setregid(%d, %d) = %v", rgid, egid, err) - return errors.Wrapf(err, "setting gids") -} + // If setting privs as root, we need to set the euid to 0 first, so that + // we will have the necessary permissions to make the other changes to + // the groups/egid/euid, regardless of our original euid. + C.seteuid(0) -func setGroups(target *user.User) error { - groupStrings, err := target.GroupIds() - if err != nil { - return util.SystemError(err.Error()) + // Separately handle the case where the user is in no groups. + numGroups := C.size_t(len(privs.groups)) + groupsPtr := (*C.gid_t)(nil) + if numGroups > 0 { + groupsPtr = &privs.groups[0] + } + + if res, err := C.setgroups(numGroups, groupsPtr); res < 0 { + return errors.Wrapf(err.(syscall.Errno), "setting groups") + } + if res, err := C.setegid(privs.egid); res < 0 { + return errors.Wrapf(err.(syscall.Errno), "setting egid") } + if res, err := C.seteuid(privs.euid); res < 0 { + return errors.Wrapf(err.(syscall.Errno), "setting euid") + } + ProcessPrivileges() + return nil +} - gids := make([]int, len(groupStrings)) - for i, groupString := range groupStrings { - gids[i] = util.AtoiOrPanic(groupString) +// 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 + // setresuid might fail with permission denied. + if res, err := C.setresuid(0, 0, 0); res < 0 { + return errors.Wrapf(err.(syscall.Errno), "setting uids") } + if res, err := C.setresuid(C.uid_t(ruid), C.uid_t(euid), C.uid_t(suid)); res < 0 { + return errors.Wrapf(err.(syscall.Errno), "setting uids") + } + return nil +} - err = unix.Setgroups(gids) - log.Printf("Setgroups(%v) = %v", gids, err) - return errors.Wrapf(err, "setting groups") +// 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) } diff --git a/security/security_test.go b/security/security_test.go new file mode 100644 index 0000000..45e4f63 --- /dev/null +++ b/security/security_test.go @@ -0,0 +1,24 @@ +/* + * security_test.go - Stub test file that has one test that always passes. + * + * Copyright 2018 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 "testing" + +func TestTrivial(t *testing.T) {} |