1 /*
2 * $Id$
3 *
4 * This file provides two functions:
5 * pam_modutil_drop_priv:
6 * temporarily lower process fs privileges by switching to another uid/gid,
7 * pam_modutil_regain_priv:
8 * regain process fs privileges lowered by pam_modutil_drop_priv().
9 */
10
11 #include "pam_modutil_private.h"
12 #include <security/pam_ext.h>
13 #include <unistd.h>
14 #include <syslog.h>
15 #include <pwd.h>
16 #include <grp.h>
17 #include <sys/fsuid.h>
18
19 /*
20 * Two setfsuid() calls in a row are necessary to check
21 * whether setfsuid() succeeded or not.
22 */
23 static int change_uid(uid_t uid, uid_t *save)
24 {
25 uid_t tmp = setfsuid(uid);
26 if (save)
27 *save = tmp;
28 return (uid_t) setfsuid(uid) == uid ? 0 : -1;
29 }
30 static int change_gid(gid_t gid, gid_t *save)
31 {
32 gid_t tmp = setfsgid(gid);
33 if (save)
34 *save = tmp;
35 return (gid_t) setfsgid(gid) == gid ? 0 : -1;
36 }
37
38 static int cleanup(struct pam_modutil_privs *p)
39 {
40 if (p->allocated) {
41 p->allocated = 0;
42 free(p->grplist);
43 }
44 p->grplist = NULL;
45 p->number_of_groups = 0;
46 return -1;
47 }
48
49 #define PRIV_MAGIC 0x1004000a
50 #define PRIV_MAGIC_DONOTHING 0xdead000a
51
52 int pam_modutil_drop_priv(pam_handle_t *pamh,
53 struct pam_modutil_privs *p,
54 const struct passwd *pw)
55 {
56 int res;
57
58 if (p->is_dropped) {
59 pam_syslog(pamh, LOG_CRIT,
60 "pam_modutil_drop_priv: called with dropped privileges");
61 return -1;
62 }
63
64 /*
65 * If not root, we can do nothing.
66 * If switching to root, we have nothing to do.
67 * That is, in both cases, we do not care.
68 */
69 if (geteuid() != 0 || pw->pw_uid == 0) {
70 p->is_dropped = PRIV_MAGIC_DONOTHING;
71 return 0;
72 }
73
74 if (!p->grplist || p->number_of_groups <= 0) {
75 pam_syslog(pamh, LOG_CRIT,
76 "pam_modutil_drop_priv: called without room for supplementary groups");
77 return -1;
78 }
79 res = getgroups(0, NULL);
80 if (res < 0) {
81 pam_syslog(pamh, LOG_ERR,
82 "pam_modutil_drop_priv: getgroups failed: %m");
83 return -1;
84 }
85
86 p->allocated = 0;
87 if (res > p->number_of_groups) {
88 p->grplist = calloc(res, sizeof(gid_t));
89 if (!p->grplist) {
90 pam_syslog(pamh, LOG_CRIT, "out of memory");
91 return cleanup(p);
92 }
93 p->allocated = 1;
94 p->number_of_groups = res;
95 }
96
97 res = getgroups(p->number_of_groups, p->grplist);
98 if (res < 0) {
99 pam_syslog(pamh, LOG_ERR,
100 "pam_modutil_drop_priv: getgroups failed: %m");
101 return cleanup(p);
102 }
103
104 p->number_of_groups = res;
105
106 /*
107 * We should care to leave process credentials in consistent state.
108 * That is, e.g. if change_gid() succeeded but change_uid() failed,
109 * we should try to restore old gid.
110 *
111 * We try to add the supplementary groups on a best-effort
112 * basis. If it fails, it's not fatal: we fall back to using an
113 * empty list.
114 */
115 if (initgroups(pw->pw_name, pw->pw_gid)) {
116 pam_syslog(pamh, LOG_WARNING,
117 "pam_modutil_drop_priv: initgroups failed: %m");
118
119 if (setgroups(0, NULL)) {
120 pam_syslog(pamh, LOG_ERR,
121 "pam_modutil_drop_priv: setgroups failed: %m");
122 return cleanup(p);
123 }
124 }
125 if (change_gid(pw->pw_gid, &p->old_gid)) {
126 pam_syslog(pamh, LOG_ERR,
127 "pam_modutil_drop_priv: change_gid failed: %m");
128 (void) setgroups(p->number_of_groups, p->grplist);
129 return cleanup(p);
130 }
131 if (change_uid(pw->pw_uid, &p->old_uid)) {
132 pam_syslog(pamh, LOG_ERR,
133 "pam_modutil_drop_priv: change_uid failed: %m");
134 (void) change_gid(p->old_gid, NULL);
135 (void) setgroups(p->number_of_groups, p->grplist);
136 return cleanup(p);
137 }
138
139 p->is_dropped = PRIV_MAGIC;
140 return 0;
141 }
142
143 int pam_modutil_regain_priv(pam_handle_t *pamh,
144 struct pam_modutil_privs *p)
145 {
146 switch (p->is_dropped) {
147 case PRIV_MAGIC_DONOTHING:
148 p->is_dropped = 0;
149 return 0;
150
151 case PRIV_MAGIC:
152 break;
153
154 default:
155 pam_syslog(pamh, LOG_CRIT,
156 "pam_modutil_regain_priv: called with invalid state");
157 return -1;
158 }
159
160 if (change_uid(p->old_uid, NULL)) {
161 pam_syslog(pamh, LOG_ERR,
162 "pam_modutil_regain_priv: change_uid failed: %m");
163 return cleanup(p);
164 }
165 if (change_gid(p->old_gid, NULL)) {
166 pam_syslog(pamh, LOG_ERR,
167 "pam_modutil_regain_priv: change_gid failed: %m");
168 return cleanup(p);
169 }
170 if (setgroups(p->number_of_groups, p->grplist)) {
171 pam_syslog(pamh, LOG_ERR,
172 "pam_modutil_regain_priv: setgroups failed: %m");
173 return cleanup(p);
174 }
175
176 p->is_dropped = 0;
177 cleanup(p);
178 return 0;
179 }