Skip to content

Commit 3022c16

Browse files
committed
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.
1 parent d4d88e1 commit 3022c16

2 files changed

Lines changed: 92 additions & 87 deletions

File tree

pam/pam.go

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,14 @@ import (
3535
"unsafe"
3636

3737
"github.com/google/fscrypt/security"
38-
"github.com/google/fscrypt/util"
3938
)
4039

4140
// Handle wraps the C pam_handle_t type. This is used from within modules.
4241
type Handle struct {
43-
handle *C.pam_handle_t
44-
status C.int
45-
// OrigUser is the user who invoked the PAM module (usually root)
46-
OrigUser *user.User
47-
// PamUser is the user who the PAM module is for
42+
handle *C.pam_handle_t
43+
status C.int
44+
origPrivs *security.Privileges
45+
// PamUser is the user for whom the PAM module is running.
4846
PamUser *user.User
4947
}
5048

@@ -62,13 +60,8 @@ func NewHandle(pamh unsafe.Pointer) (*Handle, error) {
6260
return nil, err
6361
}
6462

65-
if h.PamUser, err = user.Lookup(C.GoString(pamUsername)); err != nil {
66-
return nil, err
67-
}
68-
if h.OrigUser, err = util.EffectiveUser(); err != nil {
69-
return nil, err
70-
}
71-
return h, nil
63+
h.PamUser, err = user.Lookup(C.GoString(pamUsername))
64+
return h, err
7265
}
7366

7467
func (h *Handle) setData(name string, data unsafe.Pointer, cleanup C.CleanupFunc) error {
@@ -140,14 +133,20 @@ func (h *Handle) StartAsPamUser() error {
140133
if _, err := security.UserKeyringID(h.PamUser, true); err != nil {
141134
log.Printf("Setting up keyrings in PAM: %v", err)
142135
}
143-
return security.SetProcessPrivileges(h.PamUser)
136+
userPrivs, err := security.UserPrivileges(h.PamUser)
137+
if err != nil {
138+
return err
139+
}
140+
if h.origPrivs, err = security.ProcessPrivileges(); err != nil {
141+
return err
142+
}
143+
return security.SetProcessPrivileges(userPrivs)
144144
}
145145

146146
// StopAsPamUser restores the original privileges that were running the
147-
// PAM module (this is usually root). As this error is often ignored in a defer
148-
// statement, any error is also logged.
147+
// PAM module (this is usually root).
149148
func (h *Handle) StopAsPamUser() error {
150-
err := security.SetProcessPrivileges(h.OrigUser)
149+
err := security.SetProcessPrivileges(h.origPrivs)
151150
if err != nil {
152151
log.Print(err)
153152
}

security/privileges.go

Lines changed: 76 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -43,30 +43,15 @@ package security
4343
// cgo maps them to different Go types.
4444

4545
/*
46+
#define _GNU_SOURCE // for getresuid and setresuid
4647
#include <sys/types.h>
47-
#include <unistd.h> // setreuid, setregid
48-
#include <grp.h> // setgroups
49-
50-
static int my_setreuid(uid_t ruid, uid_t euid)
51-
{
52-
return setreuid(ruid, euid);
53-
}
54-
55-
static int my_setregid(gid_t rgid, gid_t egid)
56-
{
57-
return setregid(rgid, egid);
58-
}
59-
60-
static int my_setgroups(size_t size, const gid_t *list)
61-
{
62-
return setgroups(size, list);
63-
}
48+
#include <unistd.h> // getting and setting uids and gids
49+
#include <grp.h> // setgroups
6450
*/
6551
import "C"
6652

6753
import (
6854
"log"
69-
"os"
7055
"os/user"
7156
"syscall"
7257

@@ -75,72 +60,93 @@ import (
7560
"github.com/google/fscrypt/util"
7661
)
7762

78-
// SetProcessPrivileges temporarily drops the privileges of the current process
79-
// to have the effective uid/gid of the target user. The privileges can be
80-
// changed again with another call to SetProcessPrivileges.
81-
func SetProcessPrivileges(target *user.User) error {
82-
euid := util.AtoiOrPanic(target.Uid)
83-
egid := util.AtoiOrPanic(target.Gid)
84-
if os.Geteuid() == euid {
85-
log.Printf("Privileges already set to %q", target.Username)
86-
return nil
87-
}
88-
log.Printf("Setting privileges to %q", target.Username)
63+
// Privileges encapulate the effective uid/gid and groups of a process.
64+
type Privileges struct {
65+
euid C.uid_t
66+
egid C.gid_t
67+
groups []C.gid_t
68+
}
8969

90-
// If setting privs to root, we want to set the uid first, so we will
91-
// then have the necessary permissions to perform the other actions.
92-
if euid == 0 {
93-
if err := setUids(-1, euid); err != nil {
94-
return err
95-
}
96-
}
97-
if err := setGids(-1, egid); err != nil {
98-
return err
99-
}
100-
if err := setGroups(target); err != nil {
101-
return err
70+
// ProcessPrivileges returns the process's current effective privileges.
71+
func ProcessPrivileges() (*Privileges, error) {
72+
ruid := C.getuid()
73+
euid := C.geteuid()
74+
rgid := C.getgid()
75+
egid := C.getegid()
76+
77+
var groups []C.gid_t
78+
n, err := C.getgroups(0, nil)
79+
if n < 0 {
80+
return nil, err
10281
}
103-
// If not setting privs to root, we want to avoid dropping the uid
104-
// util the very end.
105-
if euid != 0 {
106-
if err := setUids(-1, euid); err != nil {
107-
return err
82+
// If n == 0, the user isn't in any groups, so groups == nil is fine.
83+
if n > 0 {
84+
groups = make([]C.gid_t, n)
85+
n, err = C.getgroups(n, &groups[0])
86+
if n < 0 {
87+
return nil, err
10888
}
89+
groups = groups[:n]
10990
}
110-
return nil
91+
log.Printf("Current privs (real, effective): uid=(%d,%d) gid=(%d,%d) groups=%v",
92+
ruid, euid, rgid, egid, groups)
93+
return &Privileges{euid, egid, groups}, nil
11194
}
11295

113-
func setUids(ruid, euid int) error {
114-
res, err := C.my_setreuid(C.uid_t(ruid), C.uid_t(euid))
115-
log.Printf("setreuid(%d, %d) = %d (errno %v)", ruid, euid, res, err)
116-
if res == 0 {
117-
return nil
96+
// UserPrivileges returns the defualt privileges for the specified user.
97+
func UserPrivileges(user *user.User) (*Privileges, error) {
98+
privs := &Privileges{
99+
euid: C.uid_t(util.AtoiOrPanic(user.Uid)),
100+
egid: C.gid_t(util.AtoiOrPanic(user.Gid)),
118101
}
119-
return errors.Wrapf(err.(syscall.Errno), "setting uids")
102+
userGroups, err := user.GroupIds()
103+
if err != nil {
104+
return nil, util.SystemError(err.Error())
105+
}
106+
privs.groups = make([]C.gid_t, len(userGroups))
107+
for i, group := range userGroups {
108+
privs.groups[i] = C.gid_t(util.AtoiOrPanic(group))
109+
}
110+
return privs, nil
120111
}
121112

122-
func setGids(rgid, egid int) error {
123-
res, err := C.my_setregid(C.gid_t(rgid), C.gid_t(egid))
124-
log.Printf("setregid(%d, %d) = %d (errno %v)", rgid, egid, res, err)
125-
if res == 0 {
126-
return nil
113+
// SetProcessPrivileges sets the privileges of the current process to have those
114+
// specified by privs. The original privileges can be obtained by first saving
115+
// the output of ProcessPrivileges, calling SetProcessPrivileges with the
116+
// desired privs, then calling SetProcessPrivileges with the saved privs.
117+
func SetProcessPrivileges(privs *Privileges) error {
118+
log.Printf("Setting euid=%d egid=%d groups=%v", privs.euid, privs.egid, privs.groups)
119+
120+
// If setting privs as root, we need to set the euid to 0 first, so that
121+
// we will have the necessary permissions to make the other changes to
122+
// the groups/egid/euid, regardless of our original euid.
123+
C.seteuid(0)
124+
125+
// Seperately handle the case where the user is in no groups.
126+
numGroups := C.size_t(len(privs.groups))
127+
groupsPtr := (*C.gid_t)(nil)
128+
if numGroups > 0 {
129+
groupsPtr = &privs.groups[0]
127130
}
128-
return errors.Wrapf(err.(syscall.Errno), "setting gids")
129-
}
130131

131-
func setGroups(target *user.User) error {
132-
groupStrings, err := target.GroupIds()
133-
if err != nil {
134-
return util.SystemError(err.Error())
132+
if res, err := C.setgroups(numGroups, groupsPtr); res < 0 {
133+
return errors.Wrapf(err.(syscall.Errno), "setting groups")
134+
}
135+
if res, err := C.setegid(privs.egid); res < 0 {
136+
return errors.Wrapf(err.(syscall.Errno), "setting egid")
135137
}
136-
gids := make([]C.gid_t, len(groupStrings))
137-
for i, groupString := range groupStrings {
138-
gids[i] = C.gid_t(util.AtoiOrPanic(groupString))
138+
if res, err := C.seteuid(privs.euid); res < 0 {
139+
return errors.Wrapf(err.(syscall.Errno), "setting euid")
139140
}
140-
res, err := C.my_setgroups(C.size_t(len(groupStrings)), &gids[0])
141-
log.Printf("setgroups(%v) = %d (errno %v)", gids, res, err)
141+
ProcessPrivileges()
142+
return nil
143+
}
144+
145+
func setUids(ruid, euid int) error {
146+
res, err := C.setreuid(C.uid_t(ruid), C.uid_t(euid))
147+
log.Printf("setreuid(%d, %d) = %d (errno %v)", ruid, euid, res, err)
142148
if res == 0 {
143149
return nil
144150
}
145-
return errors.Wrapf(err.(syscall.Errno), "setting groups")
151+
return errors.Wrapf(err.(syscall.Errno), "setting uids")
146152
}

0 commit comments

Comments
 (0)