CVE-2018-6558 (Go, published 2018-08-23T19:29Z)
Description: 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).
Severity: MEDIUM (CVSS v3 base score: 6.5)
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/google/fscrypt
Files changed:
pam/pam.go
security/privileges.go
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.


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