1 /*
2 * Copyright (c) 1999,2007,2019-21 Andrew G. Morgan <morgan@kernel.org>
3 *
4 * The purpose of this module is to enforce inheritable, bounding and
5 * ambient capability sets for a specified user.
6 */
7
8 /* #define PAM_DEBUG */
9
10 #ifndef _DEFAULT_SOURCE
11 #define _DEFAULT_SOURCE
12 #endif
13
14 #include <errno.h>
15 #include <grp.h>
16 #include <limits.h>
17 #include <pwd.h>
18 #include <stdarg.h>
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <syslog.h>
23 #include <sys/capability.h>
24 #include <sys/prctl.h>
25 #include <sys/types.h>
26 #include <linux/limits.h>
27
28 #include <security/pam_modules.h>
29 #include <security/_pam_macros.h>
30
31 #define USER_CAP_FILE "/etc/security/capability.conf"
32 #define CAP_FILE_BUFFER_SIZE 4096
33 #define CAP_FILE_DELIMITERS " \t\n"
34
35 /*
36 * pam_cap_s is used to summarize argument values in a parsed form.
37 */
38 struct pam_cap_s {
39 int debug;
40 int keepcaps;
41 int autoauth;
42 const char *user;
43 const char *conf_filename;
44 const char *fallback;
45 };
46
47 /*
48 * load_groups obtains the list all of the groups associated with the
49 * requested user: gid & supplemental groups.
50 */
load_groups(const char * user,char *** groups,int * groups_n)51 static int load_groups(const char *user, char ***groups, int *groups_n) {
52 struct passwd *pwd;
53 gid_t grps[NGROUPS_MAX];
54 int ngrps = NGROUPS_MAX;
55
56 *groups = NULL;
57 *groups_n = 0;
58
59 pwd = getpwnam(user);
60 if (pwd == NULL) {
61 return -1;
62 }
63
64 /* must include at least pwd->pw_gid, hence < 1 test. */
65 if (getgrouplist(user, pwd->pw_gid, grps, &ngrps) < 1) {
66 return -1;
67 }
68
69 *groups = calloc(ngrps, sizeof(char *));
70 int g_n = 0, i;
71 for (i = 0; i < ngrps; i++) {
72 const struct group *g = getgrgid(grps[i]);
73 if (g == NULL) {
74 continue;
75 }
76 D(("noting [%s] is a member of [%s]", user, g->gr_name));
77 (*groups)[g_n++] = strdup(g->gr_name);
78 }
79
80 *groups_n = g_n;
81 return 0;
82 }
83
84 /* obtain the desired IAB capabilities for the current user */
85
read_capabilities_for_user(const char * user,const char * source)86 static char *read_capabilities_for_user(const char *user, const char *source)
87 {
88 char *cap_string = NULL;
89 char buffer[CAP_FILE_BUFFER_SIZE], *line;
90 char **groups;
91 int groups_n;
92 FILE *cap_file;
93
94 if (load_groups(user, &groups, &groups_n)) {
95 D(("unknown user [%s]", user));
96 return NULL;
97 }
98
99 cap_file = fopen(source, "r");
100 if (cap_file == NULL) {
101 D(("failed to open capability file"));
102 goto defer;
103 }
104
105 int found_one = 0;
106 while (!found_one &&
107 (line = fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file))) {
108 const char *cap_text;
109
110 char *next = NULL;
111 cap_text = strtok_r(line, CAP_FILE_DELIMITERS, &next);
112
113 if (cap_text == NULL) {
114 D(("empty line"));
115 continue;
116 }
117 if (*cap_text == '#') {
118 D(("comment line"));
119 continue;
120 }
121
122 /*
123 * Explore whether any of the ids are a match for the current
124 * user.
125 */
126 while ((line = strtok_r(next, CAP_FILE_DELIMITERS, &next))) {
127 if (strcmp("*", line) == 0) {
128 D(("wildcard matched"));
129 found_one = 1;
130 break;
131 }
132
133 if (strcmp(user, line) == 0) {
134 D(("exact match for user"));
135 found_one = 1;
136 break;
137 }
138
139 if (line[0] != '@') {
140 D(("user [%s] is not [%s] - skipping", user, line));
141 }
142
143 int i;
144 for (i=0; i < groups_n; i++) {
145 if (!strcmp(groups[i], line+1)) {
146 D(("user group matched [%s]", line));
147 found_one = 1;
148 break;
149 }
150 }
151 if (found_one) {
152 break;
153 }
154 }
155
156 if (found_one) {
157 cap_string = strdup(cap_text);
158 D(("user [%s] matched - caps are [%s]", user, cap_string));
159 }
160
161 cap_text = NULL;
162 line = NULL;
163 }
164
165 fclose(cap_file);
166
167 defer:
168 memset(buffer, 0, CAP_FILE_BUFFER_SIZE);
169
170 int i;
171 for (i = 0; i < groups_n; i++) {
172 char *g = groups[i];
173 _pam_overwrite(g);
174 _pam_drop(g);
175 }
176 if (groups != NULL) {
177 memset(groups, 0, groups_n * sizeof(char *));
178 _pam_drop(groups);
179 }
180
181 return cap_string;
182 }
183
184 /*
185 * Set capabilities for current process to match the current
186 * permitted+executable sets combined with the configured inheritable
187 * set.
188 */
set_capabilities(struct pam_cap_s * cs)189 static int set_capabilities(struct pam_cap_s *cs)
190 {
191 cap_t cap_s;
192 char *conf_caps;
193 int ok = 0;
194 cap_iab_t iab;
195
196 cap_s = cap_get_proc();
197 if (cap_s == NULL) {
198 D(("your kernel is capability challenged - upgrade: %s",
199 strerror(errno)));
200 return 0;
201 }
202
203 conf_caps = read_capabilities_for_user(cs->user,
204 cs->conf_filename
205 ? cs->conf_filename:USER_CAP_FILE );
206 if (conf_caps == NULL) {
207 D(("no capabilities found for user [%s]", cs->user));
208 if (cs->fallback == NULL) {
209 goto cleanup_cap_s;
210 }
211 conf_caps = strdup(cs->fallback);
212 D(("user [%s] received fallback caps [%s]", cs->user, conf_caps));
213 }
214
215 ssize_t conf_caps_length = strlen(conf_caps);
216 if (!strcmp(conf_caps, "all")) {
217 /*
218 * all here is interpreted as no change/pass through, which is
219 * likely to be the same as none for sensible system defaults.
220 */
221 ok = 1;
222 goto cleanup_conf;
223 }
224
225 if (!strcmp(conf_caps, "none")) {
226 /* clearing CAP_INHERITABLE will also clear the ambient caps,
227 * but for legacy reasons we do not alter the bounding set. */
228 cap_clear_flag(cap_s, CAP_INHERITABLE);
229 if (!cap_set_proc(cap_s)) {
230 ok = 1;
231 }
232 goto cleanup_conf;
233 }
234
235 iab = cap_iab_from_text(conf_caps);
236 if (iab == NULL) {
237 D(("unable to parse the IAB [%s] value", conf_caps));
238 goto cleanup_conf;
239 }
240
241 if (!cap_iab_set_proc(iab)) {
242 D(("able to set the IAB [%s] value", conf_caps));
243 ok = 1;
244 }
245 cap_free(iab);
246
247 if (cs->keepcaps) {
248 /*
249 * Best effort to set keep caps - this may help work around
250 * situations where applications are using a capabilities
251 * unaware setuid() call.
252 */
253 D(("setting keepcaps"));
254 (void) cap_prctlw(PR_SET_KEEPCAPS, 1, 0, 0, 0, 0);
255 }
256
257 cleanup_conf:
258 memset(conf_caps, 0, conf_caps_length);
259 _pam_drop(conf_caps);
260
261 cleanup_cap_s:
262 cap_free(cap_s);
263 cap_s = NULL;
264
265 return ok;
266 }
267
268 /* log errors */
269
_pam_log(int err,const char * format,...)270 static void _pam_log(int err, const char *format, ...)
271 {
272 va_list args;
273
274 va_start(args, format);
275 openlog("pam_cap", LOG_CONS|LOG_PID, LOG_AUTH);
276 vsyslog(err, format, args);
277 va_end(args);
278 closelog();
279 }
280
parse_args(int argc,const char ** argv,struct pam_cap_s * pcs)281 static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
282 {
283 D(("parsing %d module arg(s)", argc));
284
285 memset(pcs, 0, sizeof(*pcs));
286
287 /* step through arguments */
288 for (; argc-- > 0; ++argv) {
289 if (!strcmp(*argv, "debug")) {
290 pcs->debug = 1;
291 } else if (!strncmp(*argv, "config=", 7)) {
292 pcs->conf_filename = 7 + *argv;
293 } else if (!strcmp(*argv, "keepcaps")) {
294 pcs->keepcaps = 1;
295 } else if (!strcmp(*argv, "autoauth")) {
296 pcs->autoauth = 1;
297 } else if (!strncmp(*argv, "default=", 8)) {
298 pcs->fallback = 8 + *argv;
299 } else {
300 _pam_log(LOG_ERR, "unknown option; %s", *argv);
301 }
302 }
303 }
304
305 /*
306 * pam_sm_authenticate parses the config file with respect to the user
307 * being authenticated and determines if they are covered by any
308 * capability inheritance rules.
309 */
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char ** argv)310 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
311 int argc, const char **argv)
312 {
313 int retval;
314 struct pam_cap_s pcs;
315 char *conf_caps;
316
317 parse_args(argc, argv, &pcs);
318
319 retval = pam_get_user(pamh, &pcs.user, NULL);
320 if (retval == PAM_CONV_AGAIN) {
321 D(("user conversation is not available yet"));
322 memset(&pcs, 0, sizeof(pcs));
323 return PAM_INCOMPLETE;
324 }
325
326 if (pcs.autoauth) {
327 D(("pam_sm_authenticate autoauth = success"));
328 memset(&pcs, 0, sizeof(pcs));
329 return PAM_SUCCESS;
330 }
331
332 if (retval != PAM_SUCCESS) {
333 D(("pam_get_user failed: %s", pam_strerror(pamh, retval)));
334 memset(&pcs, 0, sizeof(pcs));
335 return PAM_AUTH_ERR;
336 }
337
338 conf_caps = read_capabilities_for_user(pcs.user,
339 pcs.conf_filename
340 ? pcs.conf_filename:USER_CAP_FILE );
341 memset(&pcs, 0, sizeof(pcs));
342
343 if (conf_caps) {
344 D(("it appears that there are capabilities for this user [%s]",
345 conf_caps));
346
347 /* We could also store this as a pam_[gs]et_data item for use
348 by the setcred call to follow. However, this precludes
349 using pam_cap as just a cred module, and requires that the
350 'auth' component be called first. As it is, there is a
351 small race associated with a redundant read of the
352 config. */
353
354 _pam_overwrite(conf_caps);
355 _pam_drop(conf_caps);
356
357 return PAM_SUCCESS;
358
359 } else {
360
361 D(("there are no capabilities restrictions on this user"));
362 return PAM_IGNORE;
363
364 }
365 }
366
367 /*
368 * pam_sm_setcred applies inheritable capabilities loaded by the
369 * pam_sm_authenticate pass for the user.
370 */
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char ** argv)371 int pam_sm_setcred(pam_handle_t *pamh, int flags,
372 int argc, const char **argv)
373 {
374 int retval;
375 struct pam_cap_s pcs;
376
377 if (!(flags & (PAM_ESTABLISH_CRED | PAM_REINITIALIZE_CRED))) {
378 D(("we don't handle much in the way of credentials"));
379 return PAM_IGNORE;
380 }
381
382 parse_args(argc, argv, &pcs);
383
384 retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user);
385 if ((retval != PAM_SUCCESS) || (pcs.user == NULL) || !(pcs.user[0])) {
386 D(("user's name is not set"));
387 return PAM_AUTH_ERR;
388 }
389
390 retval = set_capabilities(&pcs);
391 memset(&pcs, 0, sizeof(pcs));
392
393 return (retval ? PAM_SUCCESS:PAM_IGNORE);
394 }
395