• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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