CVE-2018-6558: Insufficient Information (NVD-CWE-noinfo)
value: The pam_fscrypt module in fscrypt before 0.2.4 may incorrectly restore primary and supplementary group IDs to the values associated with the root user, which allows attackers to gain privileges via a successful login through certain applications that use Linux-PAM (aka pam).
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 6.5
Published: 2018-08-23T19:29Z
Repository: https://github.com/google/fscrypt
Language: Go
Files changed: pam/pam.go, security/privileges.go
Commit date: 2018-08-23 11:00:34 -0700
Commit message: Ensure setting user privileges is reversible

This change makes sure after dropping then elevating privileges for a
process, the euid, guid, and groups are all the same as they were
originally. This significantly simplifies the privilege logic.

This fixes CVE-2018-6558, which allowed an unprivleged user to gain
membership in the root group (gid 0) due to the groups not being
properly reset in the process.

Diff: diff --git a/pam/pam.go b/pam/pam.go
index ba254c8..c48dd13 100644
--- a/pam/pam.go
+++ b/pam/pam.go
@@ -33,20 +33,18 @@ import (
 	"log"
 	"os/user"
 	"unsafe"
 
 	"github.com/google/fscrypt/security"
-	"github.com/google/fscrypt/util"
 )
 
 // Handle wraps the C pam_handle_t type. This is used from within modules.
 type Handle struct {
-	handle *C.pam_handle_t
-	status C.int
-	// OrigUser is the user who invoked the PAM module (usually root)
-	OrigUser *user.User
-	// PamUser is the user who the PAM module is for
+	handle    *C.pam_handle_t
+	status    C.int
+	origPrivs *security.Privileges
+	// PamUser is the user for whom the PAM module is running.
 	PamUser *user.User
 }
 
 // NewHandle creates a Handle from a raw pointer.
 func NewHandle(pamh unsafe.Pointer) (*Handle, error) {
@@ -60,17 +58,12 @@ func NewHandle(pamh unsafe.Pointer) (*Handle, error) {
 	h.status = C.pam_get_user(h.handle, &pamUsername, nil)
 	if err = h.err(); err != nil {
 		return nil, err
 	}
 
-	if h.PamUser, err = user.Lookup(C.GoString(pamUsername)); err != nil {
-		return nil, err
-	}
-	if h.OrigUser, err = util.EffectiveUser(); err != nil {
-		return nil, err
-	}
-	return h, nil
+	h.PamUser, err = user.Lookup(C.GoString(pamUsername))
+	return h, err
 }
 
 func (h *Handle) setData(name string, data unsafe.Pointer, cleanup C.CleanupFunc) error {
 	cName := C.CString(name)
 	defer C.free(unsafe.Pointer(cName))
@@ -138,18 +131,24 @@ func (h *Handle) GetItem(i Item) (unsafe.Pointer, error) {
 // configures the PAM user's keyrings to be properly linked.
 func (h *Handle) StartAsPamUser() error {
 	if _, err := security.UserKeyringID(h.PamUser, true); err != nil {
 		log.Printf("Setting up keyrings in PAM: %v", err)
 	}
-	return security.SetProcessPrivileges(h.PamUser)
+	userPrivs, err := security.UserPrivileges(h.PamUser)
+	if err != nil {
+		return err
+	}
+	if h.origPrivs, err = security.ProcessPrivileges(); err != nil {
+		return err
+	}
+	return security.SetProcessPrivileges(userPrivs)
 }
 
 // StopAsPamUser restores the original privileges that were running the
-// PAM module (this is usually root). As this error is often ignored in a defer
-// statement, any error is also logged.
+// PAM module (this is usually root).
 func (h *Handle) StopAsPamUser() error {
-	err := security.SetProcessPrivileges(h.OrigUser)
+	err := security.SetProcessPrivileges(h.origPrivs)
 	if err != nil {
 		log.Print(err)
 	}
 	return err
 }
diff --git a/security/privileges.go b/security/privileges.go
index 24cd345..eaee808 100644
--- a/security/privileges.go
+++ b/security/privileges.go
@@ -41,106 +41,112 @@ package security
 // 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.
 
 /*
+#define _GNU_SOURCE    // for getresuid and setresuid
 #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);
-}
+#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"
 
 	"github.com/google/fscrypt/util"
 )
 
-// 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 {
-		log.Printf("Privileges already set to %q", target.Username)
-		return nil
-	}
-	log.Printf("Setting privileges to %q", target.Username)
+// Privileges encapulate the effective uid/gid and groups of a process.
+type Privileges struct {
+	euid   C.uid_t
+	egid   C.gid_t
+	groups []C.gid_t
+}
 
-	// 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
-		}
-	}
-	if err := setGids(-1, egid); err != nil {
-		return err
-	}
-	if err := setGroups(target); err != nil {
-		return err
+// ProcessPrivileges returns the process's current effective privileges.
+func ProcessPrivileges() (*Privileges, error) {
+	ruid := C.getuid()
+	euid := C.geteuid()
+	rgid := C.getgid()
+	egid := C.getegid()
+
+	var groups []C.gid_t
+	n, err := C.getgroups(0, nil)
+	if n < 0 {
+		return nil, err
 	}
-	// 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
+	// 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]
 	}
-	return nil
+	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
 }
 
-func setUids(ruid, euid int) error {
-	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
+// UserPrivileges returns the defualt 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)),
 	}
-	return errors.Wrapf(err.(syscall.Errno), "setting uids")
+	userGroups, err := user.GroupIds()
+	if err != nil {
+		return nil, util.SystemError(err.Error())
+	}
+	privs.groups = make([]C.gid_t, len(userGroups))
+	for i, group := range userGroups {
+		privs.groups[i] = C.gid_t(util.AtoiOrPanic(group))
+	}
+	return privs, nil
 }
 
-func setGids(rgid, egid int) error {
-	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
+// 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)
+
+	// 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)
+
+	// Seperately 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]
 	}
-	return errors.Wrapf(err.(syscall.Errno), "setting gids")
-}
 
-func setGroups(target *user.User) error {
-	groupStrings, err := target.GroupIds()
-	if err != nil {
-		return util.SystemError(err.Error())
+	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")
 	}
-	gids := make([]C.gid_t, len(groupStrings))
-	for i, groupString := range groupStrings {
-		gids[i] = C.gid_t(util.AtoiOrPanic(groupString))
+	if res, err := C.seteuid(privs.euid); res < 0 {
+		return errors.Wrapf(err.(syscall.Errno), "setting euid")
 	}
-	res, err := C.my_setgroups(C.size_t(len(groupStrings)), &gids[0])
-	log.Printf("setgroups(%v) = %d (errno %v)", gids, res, err)
+	ProcessPrivileges()
+	return nil
+}
+
+func setUids(ruid, euid int) error {
+	res, err := C.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 groups")
+	return errors.Wrapf(err.(syscall.Errno), "setting uids")
 }

Vulnerable code: 	"github.com/google/fscrypt/util"
	handle *C.pam_handle_t
	status C.int
	// OrigUser is the user who invoked the PAM module (usually root)
	OrigUser *user.User
	// PamUser is the user who the PAM module is for
	if h.PamUser, err = user.Lookup(C.GoString(pamUsername)); err != nil {
		return nil, err
	}
	if h.OrigUser, err = util.EffectiveUser(); err != nil {
		return nil, err
	}
	return h, nil
	return security.SetProcessPrivileges(h.PamUser)
// PAM module (this is usually root). As this error is often ignored in a defer
// statement, any error is also logged.
	err := security.SetProcessPrivileges(h.OrigUser)
#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);
}
	"os"
// 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 {
		log.Printf("Privileges already set to %q", target.Username)
		return nil
	}
	log.Printf("Setting privileges to %q", target.Username)
	// 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
		}
	}
	if err := setGids(-1, egid); err != nil {
		return err
	}
	if err := setGroups(target); err != nil {
		return err
	// 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
	return nil
func setUids(ruid, euid int) error {
	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 {
	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 {
	groupStrings, err := target.GroupIds()
	if err != nil {
		return util.SystemError(err.Error())
	gids := make([]C.gid_t, len(groupStrings))
	for i, groupString := range groupStrings {
		gids[i] = C.gid_t(util.AtoiOrPanic(groupString))
	res, err := C.my_setgroups(C.size_t(len(groupStrings)), &gids[0])
	log.Printf("setgroups(%v) = %d (errno %v)", gids, res, err)
	return errors.Wrapf(err.(syscall.Errno), "setting groups")
Fixed code: 	handle    *C.pam_handle_t
	status    C.int
	origPrivs *security.Privileges
	// PamUser is the user for whom the PAM module is running.
	h.PamUser, err = user.Lookup(C.GoString(pamUsername))
	return h, err
	userPrivs, err := security.UserPrivileges(h.PamUser)
	if err != nil {
		return err
	}
	if h.origPrivs, err = security.ProcessPrivileges(); err != nil {
		return err
	}
	return security.SetProcessPrivileges(userPrivs)
// PAM module (this is usually root).
	err := security.SetProcessPrivileges(h.origPrivs)
#define _GNU_SOURCE    // for getresuid and setresuid
#include <unistd.h>    // getting and setting uids and gids
#include <grp.h>       // setgroups
// Privileges encapulate 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()

	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]
	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 defualt 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)),
	userGroups, err := user.GroupIds()
	if err != nil {
		return nil, util.SystemError(err.Error())
	}
	privs.groups = make([]C.gid_t, len(userGroups))
	for i, group := range userGroups {
		privs.groups[i] = C.gid_t(util.AtoiOrPanic(group))
	}
	return privs, nil
// 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)

	// 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)

	// Seperately 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
}

func setUids(ruid, euid int) error {
	res, err := C.setreuid(C.uid_t(ruid), C.uid_t(euid))
	log.Printf("setreuid(%d, %d) = %d (errno %v)", ruid, euid, res, err)
	return errors.Wrapf(err.(syscall.Errno), "setting uids")