diff options
| author | Eric Biggers <ebiggers@google.com> | 2018-03-25 10:13:26 -0700 |
|---|---|---|
| committer | Eric Biggers <ebiggers3@gmail.com> | 2018-03-25 10:22:39 -0700 |
| commit | aa88bf4527cced6e3e16ca3e5ae07076cc8217f0 (patch) | |
| tree | 31ef4df85e4a3507eb44d93636392687fad08aa9 /security/privileges.go | |
| parent | 3ef69aaafcfe6df03097d9ebdc8e4c7f7516999b (diff) | |
security: drop and regain privileges in all threads
After enabling pam_fscrypt for "session" and creating a directory
protected with a login protector, I was no longer able to log in as that
user. The problem is that the Go runtime is creating threads after
pam_fscrypt drops privileges, but pam_fscrypt is not re-acquiring
privileges on those threads because the Go wrappers for setreuid(),
setregid(), and setgroups() in the "sys/unix" package are using the raw
syscalls which operate on the calling thread only.
This violates glibc's assumption that all threads have the same uids and
gids, causing it to abort() the process when a later module in the PAM
stack (pam_mail in my case) tries to drop privileges using the glibc
functions.
Fix it by dropping and regaining privileges using the glibc functions
rather than the "sys/unix" functions.
This also avoids any possibility that privileges could be changed in a
thread other than the "main" one for pam_fscrypt, since the Go runtime
does not guarantee which OS-level thread runs what.
It would be nice to also exit all Go worker threads before returning
from pam_fscrypt, but the Go runtime doesn't seem to support that.
Diffstat (limited to 'security/privileges.go')
| -rw-r--r-- | security/privileges.go | 83 |
1 files changed, 65 insertions, 18 deletions
diff --git a/security/privileges.go b/security/privileges.go index 7d69da9..24cd345 100644 --- a/security/privileges.go +++ b/security/privileges.go @@ -24,21 +24,61 @@ // - Maintaining the link between the root and user keyrings. 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 +// +// Also we need to wrap the libc functions in our own C functions rather than +// calling them directly because in the glibc headers (but not necessarily in +// the headers for other C libraries that may be used on Linux) they are +// declared to take __uid_t and __gid_t arguments rather than uid_t and gid_t. +// And while these are typedef'ed to the same underlying type, before Go 1.10, +// cgo maps them to different Go types. + +/* +#include <sys/types.h> +#include <unistd.h> // setreuid, setregid +#include <grp.h> // setgroups + +static int my_setreuid(uid_t ruid, uid_t euid) +{ + return setreuid(ruid, euid); +} + +static int my_setregid(gid_t rgid, gid_t egid) +{ + return setregid(rgid, egid); +} + +static int my_setgroups(size_t size, const gid_t *list) +{ + return setgroups(size, list); +} +*/ +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 { +// SetProcessPrivileges temporarily drops the privileges of the current process +// to have the effective uid/gid of the target user. The privileges can be +// changed again with another call to SetProcessPrivileges. +func SetProcessPrivileges(target *user.User) error { euid := util.AtoiOrPanic(target.Uid) egid := util.AtoiOrPanic(target.Gid) if os.Geteuid() == euid { @@ -71,15 +111,21 @@ func SetThreadPrivileges(target *user.User) error { } 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") + res, err := C.my_setreuid(C.uid_t(ruid), C.uid_t(euid)) + log.Printf("setreuid(%d, %d) = %d (errno %v)", ruid, euid, res, err) + if res == 0 { + return nil + } + return errors.Wrapf(err.(syscall.Errno), "setting uids") } 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") + res, err := C.my_setregid(C.gid_t(rgid), C.gid_t(egid)) + log.Printf("setregid(%d, %d) = %d (errno %v)", rgid, egid, res, err) + if res == 0 { + return nil + } + return errors.Wrapf(err.(syscall.Errno), "setting gids") } func setGroups(target *user.User) error { @@ -87,13 +133,14 @@ func setGroups(target *user.User) error { if err != nil { return util.SystemError(err.Error()) } - - gids := make([]int, len(groupStrings)) + gids := make([]C.gid_t, len(groupStrings)) for i, groupString := range groupStrings { - gids[i] = util.AtoiOrPanic(groupString) + gids[i] = C.gid_t(util.AtoiOrPanic(groupString)) } - - err = unix.Setgroups(gids) - log.Printf("Setgroups(%v) = %v", gids, err) - return errors.Wrapf(err, "setting groups") + res, err := C.my_setgroups(C.size_t(len(groupStrings)), &gids[0]) + log.Printf("setgroups(%v) = %d (errno %v)", gids, res, err) + if res == 0 { + return nil + } + return errors.Wrapf(err.(syscall.Errno), "setting groups") } |