]>
Commit | Line | Data |
---|---|---|
5600c405 AL |
1 | /* |
2 | * setpriv(1) - set various kernel privilege bits and run something | |
3 | * | |
4 | * Copyright (C) 2012 Andy Lutomirski <luto@amacapital.net> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; either version 2, or (at your option) any | |
9 | * later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but | |
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 | * General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License along | |
17 | * with this program; if not, write to the Free Software Foundation, Inc., | |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
19 | */ | |
20 | ||
21 | #include <cap-ng.h> | |
22 | #include <errno.h> | |
23 | #include <getopt.h> | |
24 | #include <grp.h> | |
25 | #include <linux/securebits.h> | |
637fa4c6 | 26 | #include <pwd.h> |
5600c405 AL |
27 | #include <stdarg.h> |
28 | #include <stdio.h> | |
29 | #include <stdlib.h> | |
30 | #include <sys/prctl.h> | |
637fa4c6 | 31 | #include <sys/types.h> |
5600c405 AL |
32 | #include <unistd.h> |
33 | ||
5600c405 AL |
34 | #include "c.h" |
35 | #include "closestream.h" | |
36 | #include "nls.h" | |
37 | #include "optutils.h" | |
38 | #include "strutils.h" | |
39 | #include "xalloc.h" | |
59c68b43 | 40 | #include "pathnames.h" |
5600c405 AL |
41 | |
42 | #ifndef PR_SET_NO_NEW_PRIVS | |
43 | # define PR_SET_NO_NEW_PRIVS 38 | |
44 | #endif | |
45 | #ifndef PR_GET_NO_NEW_PRIVS | |
46 | # define PR_GET_NO_NEW_PRIVS 39 | |
47 | #endif | |
48 | ||
49 | #define SETPRIV_EXIT_PRIVERR 127 /* how we exit when we fail to set privs */ | |
50 | ||
51 | /* | |
52 | * Note: We are subject to https://bugzilla.redhat.com/show_bug.cgi?id=895105 | |
53 | * and we will therefore have problems if new capabilities are added. Once | |
54 | * that bug is fixed, I'll (Andy Lutomirski) submit a corresponding fix to | |
55 | * setpriv. In the mean time, the code here tries to work reasonably well. | |
56 | */ | |
57 | ||
58 | struct privctx { | |
5600c405 AL |
59 | unsigned int |
60 | nnp:1, /* no_new_privs */ | |
61 | have_ruid:1, /* real uid */ | |
62 | have_euid:1, /* effective uid */ | |
63 | have_rgid:1, /* real gid */ | |
64 | have_egid:1, /* effective gid */ | |
65 | have_groups:1, /* add groups */ | |
66 | keep_groups:1, /* keep groups */ | |
67 | clear_groups:1, /* remove groups */ | |
68 | have_securebits:1; /* remove groups */ | |
69 | ||
70 | /* uids and gids */ | |
71 | uid_t ruid, euid; | |
72 | gid_t rgid, egid; | |
73 | ||
74 | /* supplementary groups */ | |
75 | size_t num_groups; | |
76 | gid_t *groups; | |
77 | ||
78 | /* caps */ | |
79 | const char *caps_to_inherit; | |
80 | const char *bounding_set; | |
81 | ||
82 | /* securebits */ | |
83 | int securebits; | |
84 | ||
85 | /* LSMs */ | |
86 | const char *selinux_label; | |
87 | const char *apparmor_profile; | |
88 | }; | |
89 | ||
90 | static void __attribute__((__noreturn__)) usage(FILE *out) | |
91 | { | |
92 | fputs(USAGE_HEADER, out); | |
298dc4ff BS |
93 | fprintf(out, _(" %s [options] <program> [<argument>...]\n"), |
94 | program_invocation_short_name); | |
95 | ||
451dbcfa BS |
96 | fputs(USAGE_SEPARATOR, out); |
97 | fputs(_("Run a program with different privilege settings.\n"), out); | |
98 | ||
5600c405 AL |
99 | fputs(USAGE_OPTIONS, out); |
100 | fputs(_(" -d, --dump show current state (and do not exec anything)\n"), out); | |
101 | fputs(_(" --nnp, --no-new-privs disallow granting new privileges\n"), out); | |
102 | fputs(_(" --inh-caps <caps,...> set inheritable capabilities\n"), out); | |
103 | fputs(_(" --bounding-set <caps> set capability bounding set\n"), out); | |
104 | fputs(_(" --ruid <uid> set real uid\n"), out); | |
105 | fputs(_(" --euid <uid> set effective uid\n"), out); | |
106 | fputs(_(" --rgid <gid> set real gid\n"), out); | |
107 | fputs(_(" --egid <gid> set effective gid\n"), out); | |
108 | fputs(_(" --reuid <uid> set real and effective uid\n"), out); | |
109 | fputs(_(" --regid <gid> set real and effective gid\n"), out); | |
110 | fputs(_(" --clear-groups clear supplementary groups\n"), out); | |
111 | fputs(_(" --keep-groups keep supplementary groups\n"), out); | |
112 | fputs(_(" --groups <group,...> set supplementary groups\n"), out); | |
113 | fputs(_(" --securebits <bits> set securebits\n"), out); | |
de81a77d KZ |
114 | fputs(_(" --selinux-label <label> set SELinux label\n"), out); |
115 | fputs(_(" --apparmor-profile <pr> set AppArmor profile\n"), out); | |
298dc4ff | 116 | |
5600c405 AL |
117 | fputs(USAGE_SEPARATOR, out); |
118 | fputs(USAGE_HELP, out); | |
119 | fputs(USAGE_VERSION, out); | |
120 | fputs(USAGE_SEPARATOR, out); | |
121 | fputs(_(" This tool can be dangerous. Read the manpage, and be careful.\n"), out); | |
122 | fprintf(out, USAGE_MAN_TAIL("setpriv(1)")); | |
123 | ||
124 | exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); | |
125 | } | |
126 | ||
127 | static int real_cap_last_cap(void) | |
128 | { | |
129 | /* CAP_LAST_CAP is untrustworthy. */ | |
130 | static int ret = -1; | |
131 | int matched; | |
132 | FILE *f; | |
133 | ||
134 | if (ret != -1) | |
135 | return ret; | |
136 | ||
59c68b43 | 137 | f = fopen(_PATH_PROC_CAPLASTCAP, "r"); |
5600c405 AL |
138 | if (!f) { |
139 | ret = CAP_LAST_CAP; /* guess */ | |
140 | return ret; | |
141 | } | |
142 | ||
143 | matched = fscanf(f, "%d", &ret); | |
144 | fclose(f); | |
145 | ||
146 | if (matched != 1) | |
147 | ret = CAP_LAST_CAP; /* guess */ | |
148 | ||
149 | return ret; | |
150 | } | |
151 | ||
152 | /* Returns the number of capabilities printed. */ | |
153 | static int print_caps(FILE *f, capng_type_t which) | |
154 | { | |
155 | int i, n = 0, max = real_cap_last_cap(); | |
156 | ||
157 | for (i = 0; i <= max; i++) { | |
158 | if (capng_have_capability(which, i)) { | |
159 | const char *name = capng_capability_to_name(i); | |
160 | if (n) | |
161 | fputc(',', f); | |
162 | if (name) | |
163 | fputs(name, f); | |
164 | else | |
165 | /* cap-ng has very poor handling of | |
166 | * CAP_LAST_CAP changes. This is the | |
167 | * best we can do. */ | |
168 | printf("cap_%d", i); | |
169 | n++; | |
170 | } | |
171 | } | |
172 | return n; | |
173 | } | |
174 | ||
175 | static void dump_one_secbit(int *first, int *bits, int bit, const char *name) | |
176 | { | |
177 | if (*bits & bit) { | |
7d55b2df | 178 | if (*first) |
5600c405 | 179 | *first = 0; |
7d55b2df SK |
180 | else |
181 | printf(","); | |
5600c405 AL |
182 | fputs(name, stdout); |
183 | *bits &= ~bit; | |
184 | } | |
185 | } | |
186 | ||
187 | static void dump_securebits(void) | |
188 | { | |
189 | int first = 1; | |
190 | int bits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0); | |
191 | ||
192 | if (bits < 0) { | |
193 | warnx(_("getting process secure bits failed")); | |
194 | return; | |
195 | } | |
196 | ||
197 | printf(_("Securebits: ")); | |
198 | ||
199 | dump_one_secbit(&first, &bits, SECBIT_NOROOT, "noroot"); | |
200 | dump_one_secbit(&first, &bits, SECBIT_NOROOT_LOCKED, "noroot_locked"); | |
201 | dump_one_secbit(&first, &bits, SECBIT_NO_SETUID_FIXUP, | |
202 | "no_setuid_fixup"); | |
203 | dump_one_secbit(&first, &bits, SECBIT_NO_SETUID_FIXUP_LOCKED, | |
204 | "no_setuid_fixup_locked"); | |
205 | bits &= ~SECBIT_KEEP_CAPS; | |
206 | dump_one_secbit(&first, &bits, SECBIT_KEEP_CAPS_LOCKED, | |
207 | "keep_caps_locked"); | |
208 | if (bits) { | |
7d55b2df | 209 | if (first) |
5600c405 | 210 | first = 0; |
7d55b2df SK |
211 | else |
212 | printf(","); | |
5600c405 AL |
213 | printf("0x%x", (unsigned)bits); |
214 | } | |
215 | ||
216 | if (first) | |
217 | printf(_("[none]\n")); | |
218 | else | |
219 | printf("\n"); | |
220 | } | |
221 | ||
222 | static void dump_label(const char *name) | |
223 | { | |
224 | char buf[4097]; | |
225 | ssize_t len; | |
226 | int fd, e; | |
227 | ||
59c68b43 | 228 | fd = open(_PATH_PROC_ATTR_CURRENT, O_RDONLY); |
5600c405 | 229 | if (fd == -1) { |
59c68b43 | 230 | warn(_("cannot open %s"), _PATH_PROC_ATTR_CURRENT); |
5600c405 AL |
231 | return; |
232 | } | |
233 | ||
234 | len = read(fd, buf, sizeof(buf)); | |
235 | e = errno; | |
236 | close(fd); | |
237 | if (len < 0) { | |
238 | errno = e; | |
47481cbd | 239 | warn(_("cannot read %s"), name); |
5600c405 AL |
240 | return; |
241 | } | |
242 | if (sizeof(buf) - 1 <= (size_t)len) { | |
243 | warnx(_("%s: too long"), name); | |
244 | return; | |
245 | } | |
246 | ||
247 | buf[len] = 0; | |
248 | if (0 < len && buf[len - 1] == '\n') | |
249 | buf[len - 1] = 0; | |
250 | printf("%s: %s\n", name, buf); | |
251 | } | |
252 | ||
253 | static void dump_groups(void) | |
254 | { | |
87918040 | 255 | int n = getgroups(0, NULL); |
5600c405 | 256 | gid_t *groups; |
59c68b43 | 257 | |
5600c405 AL |
258 | if (n < 0) { |
259 | warn("getgroups failed"); | |
260 | return; | |
261 | } | |
262 | ||
7370501f | 263 | groups = xmalloc(n * sizeof(gid_t)); |
5600c405 AL |
264 | n = getgroups(n, groups); |
265 | if (n < 0) { | |
7370501f | 266 | free(groups); |
5600c405 AL |
267 | warn("getgroups failed"); |
268 | return; | |
269 | } | |
270 | ||
271 | printf(_("Supplementary groups: ")); | |
272 | if (n == 0) | |
273 | printf(_("[none]")); | |
274 | else { | |
275 | int i; | |
276 | for (i = 0; i < n; i++) { | |
277 | if (0 < i) | |
278 | printf(","); | |
279 | printf("%ld", (long)groups[i]); | |
280 | } | |
281 | } | |
282 | printf("\n"); | |
7370501f | 283 | free(groups); |
5600c405 AL |
284 | } |
285 | ||
286 | static void dump(int dumplevel) | |
287 | { | |
288 | int x; | |
289 | uid_t ru, eu, su; | |
290 | gid_t rg, eg, sg; | |
291 | ||
292 | if (getresuid(&ru, &eu, &su) == 0) { | |
293 | printf(_("uid: %u\n"), ru); | |
294 | printf(_("euid: %u\n"), eu); | |
295 | /* Saved and fs uids always equal euid. */ | |
296 | if (3 <= dumplevel) | |
297 | printf(_("suid: %u\n"), su); | |
298 | } else | |
299 | warn(_("getresuid failed")); | |
300 | ||
301 | if (getresgid(&rg, &eg, &sg) == 0) { | |
302 | printf("gid: %ld\n", (long)rg); | |
303 | printf("egid: %ld\n", (long)eg); | |
304 | /* Saved and fs gids always equal egid. */ | |
305 | if (dumplevel >= 3) | |
306 | printf("sgid: %ld\n", (long)sg); | |
307 | } else | |
308 | warn(_("getresgid failed")); | |
309 | ||
310 | dump_groups(); | |
311 | ||
312 | x = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0); | |
313 | if (0 <= x) | |
314 | printf("no_new_privs: %d\n", x); | |
315 | else | |
316 | warn("setting no_new_privs failed"); | |
317 | ||
318 | if (2 <= dumplevel) { | |
319 | printf(_("Effective capabilities: ")); | |
320 | if (print_caps(stdout, CAPNG_EFFECTIVE) == 0) | |
321 | printf(_("[none]")); | |
322 | printf("\n"); | |
323 | ||
324 | printf(_("Permitted capabilities: ")); | |
325 | if (print_caps(stdout, CAPNG_PERMITTED) == 0) | |
326 | printf(_("[none]")); | |
327 | printf("\n"); | |
328 | } | |
329 | ||
330 | printf(_("Inheritable capabilities: ")); | |
331 | if (print_caps(stdout, CAPNG_INHERITABLE) == 0) | |
332 | printf(_("[none]")); | |
333 | printf("\n"); | |
334 | ||
335 | printf(_("Capability bounding set: ")); | |
336 | if (print_caps(stdout, CAPNG_BOUNDING_SET) == 0) | |
337 | printf(_("[none]")); | |
338 | printf("\n"); | |
339 | ||
340 | dump_securebits(); | |
341 | ||
59c68b43 | 342 | if (access(_PATH_SYS_SELINUX, F_OK) == 0) |
5600c405 AL |
343 | dump_label(_("SELinux label")); |
344 | ||
59c68b43 | 345 | if (access(_PATH_SYS_APPARMOR, F_OK) == 0) { |
5600c405 AL |
346 | dump_label(_("AppArmor profile")); |
347 | } | |
348 | } | |
349 | ||
350 | static void list_known_caps(void) | |
351 | { | |
352 | int i, max = real_cap_last_cap(); | |
353 | ||
354 | for (i = 0; i <= max; i++) { | |
355 | const char *name = capng_capability_to_name(i); | |
356 | if (name) | |
357 | printf("%s\n", name); | |
358 | else | |
359 | warnx(_("cap %d: libcap-ng is broken"), i); | |
360 | } | |
361 | } | |
362 | ||
363 | static void parse_groups(struct privctx *opts, const char *str) | |
364 | { | |
365 | char *groups = xstrdup(str); | |
366 | char *buf = groups; /* We'll reuse it */ | |
367 | char *c; | |
368 | size_t i = 0; | |
369 | ||
370 | opts->have_groups = 1; | |
371 | opts->num_groups = 0; | |
372 | while ((c = strsep(&groups, ","))) | |
373 | opts->num_groups++; | |
374 | ||
375 | /* Start again */ | |
376 | strcpy(buf, str); /* It's exactly the right length */ | |
377 | groups = buf; | |
378 | ||
379 | opts->groups = xcalloc(opts->num_groups, sizeof(gid_t)); | |
380 | while ((c = strsep(&groups, ","))) | |
381 | opts->groups[i++] = (gid_t) strtol_or_err(c, | |
59c68b43 | 382 | _("Invalid supplementary group id")); |
5600c405 AL |
383 | |
384 | free(groups); | |
385 | } | |
386 | ||
387 | static void do_setresuid(const struct privctx *opts) | |
388 | { | |
389 | uid_t ruid, euid, suid; | |
390 | if (getresuid(&ruid, &euid, &suid) != 0) | |
391 | err(SETPRIV_EXIT_PRIVERR, _("getresuid failed")); | |
392 | if (opts->have_ruid) | |
393 | ruid = opts->ruid; | |
394 | if (opts->have_euid) | |
395 | euid = opts->euid; | |
396 | ||
397 | /* Also copy effective to saved (for paranoia). */ | |
398 | if (setresuid(ruid, euid, euid) != 0) | |
399 | err(SETPRIV_EXIT_PRIVERR, _("setresuid failed")); | |
400 | } | |
401 | ||
402 | static void do_setresgid(const struct privctx *opts) | |
403 | { | |
404 | gid_t rgid, egid, sgid; | |
405 | if (getresgid(&rgid, &egid, &sgid) != 0) | |
406 | err(SETPRIV_EXIT_PRIVERR, _("getresgid failed")); | |
407 | if (opts->have_rgid) | |
408 | rgid = opts->rgid; | |
409 | if (opts->have_egid) | |
410 | egid = opts->egid; | |
411 | ||
412 | /* Also copy effective to saved (for paranoia). */ | |
413 | if (setresgid(rgid, egid, egid) != 0) | |
414 | err(SETPRIV_EXIT_PRIVERR, _("setresgid failed")); | |
415 | } | |
416 | ||
417 | static void bump_cap(unsigned int cap) | |
418 | { | |
419 | if (capng_have_capability(CAPNG_PERMITTED, cap)) | |
420 | capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, cap); | |
421 | } | |
422 | ||
423 | static void do_caps(capng_type_t type, const char *caps) | |
424 | { | |
425 | char *my_caps = xstrdup(caps); | |
426 | char *c; | |
427 | ||
428 | while ((c = strsep(&my_caps, ","))) { | |
429 | capng_act_t action; | |
430 | if (*c == '+') | |
431 | action = CAPNG_ADD; | |
432 | else if (*c == '-') | |
433 | action = CAPNG_DROP; | |
434 | else | |
435 | errx(EXIT_FAILURE, _("bad capability string")); | |
436 | ||
437 | if (!strcmp(c + 1, "all")) { | |
438 | int i; | |
439 | /* It would be really bad if -all didn't drop all | |
440 | * caps. It's better to just fail. */ | |
441 | if (real_cap_last_cap() > CAP_LAST_CAP) | |
442 | errx(SETPRIV_EXIT_PRIVERR, | |
443 | _("libcap-ng is too old for \"all\" caps")); | |
444 | for (i = 0; i <= CAP_LAST_CAP; i++) | |
445 | capng_update(action, type, i); | |
446 | } else { | |
447 | int cap = capng_name_to_capability(c + 1); | |
448 | if (0 <= cap) | |
449 | capng_update(action, type, cap); | |
450 | else | |
451 | errx(EXIT_FAILURE, | |
452 | _("unknown capability \"%s\""), c + 1); | |
453 | } | |
454 | } | |
455 | ||
456 | free(my_caps); | |
457 | } | |
458 | ||
459 | static void parse_securebits(struct privctx *opts, const char *arg) | |
460 | { | |
461 | char *buf = xstrdup(arg); | |
462 | char *c; | |
463 | ||
464 | opts->have_securebits = 1; | |
465 | opts->securebits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0); | |
466 | if (opts->securebits < 0) | |
467 | err(SETPRIV_EXIT_PRIVERR, _("getting process secure bits failed")); | |
468 | ||
469 | if (opts->securebits & ~(int)(SECBIT_NOROOT | | |
470 | SECBIT_NOROOT_LOCKED | | |
471 | SECBIT_NO_SETUID_FIXUP | | |
472 | SECBIT_NO_SETUID_FIXUP_LOCKED | | |
473 | SECBIT_KEEP_CAPS | | |
474 | SECBIT_KEEP_CAPS_LOCKED)) | |
475 | errx(SETPRIV_EXIT_PRIVERR, | |
476 | _("unrecognized securebit set -- refusing to adjust")); | |
477 | ||
478 | while ((c = strsep(&buf, ","))) { | |
479 | if (*c != '+' && *c != '-') | |
480 | errx(EXIT_FAILURE, _("bad securebits string")); | |
481 | ||
482 | if (!strcmp(c + 1, "all")) { | |
483 | if (*c == '-') | |
484 | opts->securebits = 0; | |
485 | else | |
486 | errx(EXIT_FAILURE, | |
487 | _("+all securebits is not allowed")); | |
488 | } else { | |
489 | int bit; | |
490 | if (!strcmp(c + 1, "noroot")) | |
491 | bit = SECBIT_NOROOT; | |
492 | else if (!strcmp(c + 1, "noroot_locked")) | |
493 | bit = SECBIT_NOROOT_LOCKED; | |
494 | else if (!strcmp(c + 1, "no_setuid_fixup")) | |
495 | bit = SECBIT_NO_SETUID_FIXUP; | |
496 | else if (!strcmp(c + 1, "no_setuid_fixup_locked")) | |
497 | bit = SECBIT_NO_SETUID_FIXUP_LOCKED; | |
498 | else if (!strcmp(c + 1, "keep_caps")) | |
499 | errx(EXIT_FAILURE, | |
500 | _("adjusting keep_caps does not make sense")); | |
501 | else if (!strcmp(c + 1, "keep_caps_locked")) | |
502 | bit = SECBIT_KEEP_CAPS_LOCKED; /* sigh */ | |
503 | else | |
504 | errx(EXIT_FAILURE, _("unrecognized securebit")); | |
505 | ||
506 | if (*c == '+') | |
507 | opts->securebits |= bit; | |
508 | else | |
509 | opts->securebits &= ~bit; | |
510 | } | |
511 | } | |
512 | ||
513 | opts->securebits |= SECBIT_KEEP_CAPS; /* We need it, and it's reset on exec */ | |
514 | ||
515 | free(buf); | |
516 | } | |
517 | ||
518 | static void do_selinux_label(const char *label) | |
519 | { | |
520 | int fd; | |
521 | size_t len; | |
522 | ||
59c68b43 | 523 | if (access(_PATH_SYS_SELINUX, F_OK) != 0) |
5600c405 AL |
524 | errx(SETPRIV_EXIT_PRIVERR, _("SELinux is not running")); |
525 | ||
59c68b43 | 526 | fd = open(_PATH_PROC_ATTR_EXEC, O_RDWR); |
5600c405 AL |
527 | if (fd == -1) |
528 | err(SETPRIV_EXIT_PRIVERR, | |
59c68b43 | 529 | _("cannot open %s"), _PATH_PROC_ATTR_EXEC); |
5600c405 AL |
530 | |
531 | len = strlen(label); | |
532 | errno = 0; | |
533 | if (write(fd, label, len) != (ssize_t) len) | |
534 | err(SETPRIV_EXIT_PRIVERR, | |
59c68b43 | 535 | _("write failed: %s"), _PATH_PROC_ATTR_EXEC); |
5600c405 | 536 | |
cc89383b | 537 | if (close(fd) != 0) |
05cef8ea | 538 | err(SETPRIV_EXIT_PRIVERR, |
cc89383b | 539 | _("close failed: %s"), _PATH_PROC_ATTR_EXEC); |
5600c405 AL |
540 | } |
541 | ||
542 | static void do_apparmor_profile(const char *label) | |
543 | { | |
544 | FILE *f; | |
545 | ||
59c68b43 | 546 | if (access(_PATH_SYS_APPARMOR, F_OK) != 0) |
5600c405 AL |
547 | errx(SETPRIV_EXIT_PRIVERR, _("AppArmor is not running")); |
548 | ||
d359c62c | 549 | f = fopen(_PATH_PROC_ATTR_EXEC, "r+"); |
5600c405 AL |
550 | if (!f) |
551 | err(SETPRIV_EXIT_PRIVERR, | |
59c68b43 | 552 | _("cannot open %s"), _PATH_PROC_ATTR_EXEC); |
5600c405 | 553 | |
d359c62c | 554 | fprintf(f, "exec %s", label); |
f99b58b3 SK |
555 | |
556 | if (close_stream(f) != 0) | |
5600c405 | 557 | err(SETPRIV_EXIT_PRIVERR, |
59c68b43 | 558 | _("write failed: %s"), _PATH_PROC_ATTR_EXEC); |
5600c405 AL |
559 | } |
560 | ||
637fa4c6 SK |
561 | static uid_t get_user(const char *s, const char *err) |
562 | { | |
563 | struct passwd *pw; | |
564 | long tmp; | |
565 | pw = getpwnam(s); | |
566 | if (pw) | |
567 | return pw->pw_uid; | |
568 | tmp = strtol_or_err(s, err); | |
569 | return tmp; | |
570 | } | |
571 | ||
572 | static gid_t get_group(const char *s, const char *err) | |
573 | { | |
574 | struct group *gr; | |
575 | long tmp; | |
576 | gr = getgrnam(s); | |
577 | if (gr) | |
578 | return gr->gr_gid; | |
579 | tmp = strtol_or_err(s, err); | |
580 | return tmp; | |
581 | } | |
582 | ||
5600c405 AL |
583 | int main(int argc, char **argv) |
584 | { | |
585 | enum { | |
586 | NNP = CHAR_MAX + 1, | |
587 | RUID, | |
588 | EUID, | |
589 | RGID, | |
590 | EGID, | |
591 | REUID, | |
592 | REGID, | |
593 | CLEAR_GROUPS, | |
594 | KEEP_GROUPS, | |
595 | GROUPS, | |
596 | INHCAPS, | |
597 | LISTCAPS, | |
598 | CAPBSET, | |
599 | SECUREBITS, | |
600 | SELINUX_LABEL, | |
601 | APPARMOR_PROFILE | |
602 | }; | |
603 | ||
604 | static const struct option longopts[] = { | |
87918040 SK |
605 | { "dump", no_argument, NULL, 'd' }, |
606 | { "nnp", no_argument, NULL, NNP }, | |
607 | { "no-new-privs", no_argument, NULL, NNP }, | |
608 | { "inh-caps", required_argument, NULL, INHCAPS }, | |
609 | { "list-caps", no_argument, NULL, LISTCAPS }, | |
610 | { "ruid", required_argument, NULL, RUID }, | |
611 | { "euid", required_argument, NULL, EUID }, | |
612 | { "rgid", required_argument, NULL, RGID }, | |
613 | { "egid", required_argument, NULL, EGID }, | |
614 | { "reuid", required_argument, NULL, REUID }, | |
615 | { "regid", required_argument, NULL, REGID }, | |
616 | { "clear-groups", no_argument, NULL, CLEAR_GROUPS }, | |
617 | { "keep-groups", no_argument, NULL, KEEP_GROUPS }, | |
618 | { "groups", required_argument, NULL, GROUPS }, | |
619 | { "bounding-set", required_argument, NULL, CAPBSET }, | |
620 | { "securebits", required_argument, NULL, SECUREBITS }, | |
621 | { "selinux-label", required_argument, NULL, SELINUX_LABEL }, | |
622 | { "apparmor-profile", required_argument, NULL, APPARMOR_PROFILE }, | |
623 | { "help", no_argument, NULL, 'h' }, | |
624 | { "version", no_argument, NULL, 'V' }, | |
625 | { NULL, 0, NULL, 0 } | |
5600c405 AL |
626 | }; |
627 | ||
628 | static const ul_excl_t excl[] = { | |
629 | /* keep in same order with enum definitions */ | |
630 | {CLEAR_GROUPS, KEEP_GROUPS, GROUPS}, | |
631 | {0} | |
632 | }; | |
633 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
634 | ||
635 | int c; | |
636 | struct privctx opts; | |
637 | int dumplevel = 0; | |
638 | int total_opts = 0; | |
639 | int list_caps = 0; | |
640 | ||
a7a5c470 | 641 | setlocale(LC_ALL, ""); |
5600c405 AL |
642 | bindtextdomain(PACKAGE, LOCALEDIR); |
643 | textdomain(PACKAGE); | |
644 | atexit(close_stdout); | |
645 | ||
646 | memset(&opts, 0, sizeof(opts)); | |
647 | ||
648 | while ((c = getopt_long(argc, argv, "+dhV", longopts, NULL)) != -1) { | |
649 | err_exclusive_options(c, longopts, excl, excl_st); | |
650 | total_opts++; | |
651 | switch (c) { | |
652 | case 'd': | |
653 | dumplevel++; | |
654 | break; | |
655 | case NNP: | |
656 | if (opts.nnp) | |
657 | errx(EXIT_FAILURE, | |
658 | _("duplicate --no-new-privs option")); | |
659 | opts.nnp = 1; | |
660 | break; | |
661 | case RUID: | |
662 | if (opts.have_ruid) | |
663 | errx(EXIT_FAILURE, _("duplicate ruid")); | |
664 | opts.have_ruid = 1; | |
637fa4c6 | 665 | opts.ruid = get_user(optarg, _("failed to parse ruid")); |
5600c405 AL |
666 | break; |
667 | case EUID: | |
668 | if (opts.have_euid) | |
669 | errx(EXIT_FAILURE, _("duplicate euid")); | |
670 | opts.have_euid = 1; | |
637fa4c6 | 671 | opts.euid = get_user(optarg, _("failed to parse euid")); |
5600c405 AL |
672 | break; |
673 | case REUID: | |
674 | if (opts.have_ruid || opts.have_euid) | |
675 | errx(EXIT_FAILURE, _("duplicate ruid or euid")); | |
676 | opts.have_ruid = opts.have_euid = 1; | |
637fa4c6 | 677 | opts.ruid = opts.euid = get_user(optarg, _("failed to parse reuid")); |
5600c405 AL |
678 | break; |
679 | case RGID: | |
680 | if (opts.have_rgid) | |
681 | errx(EXIT_FAILURE, _("duplicate rgid")); | |
682 | opts.have_rgid = 1; | |
637fa4c6 | 683 | opts.rgid = get_group(optarg, _("failed to parse rgid")); |
5600c405 AL |
684 | break; |
685 | case EGID: | |
686 | if (opts.have_egid) | |
687 | errx(EXIT_FAILURE, _("duplicate egid")); | |
688 | opts.have_egid = 1; | |
637fa4c6 | 689 | opts.egid = get_group(optarg, _("failed to parse egid")); |
5600c405 AL |
690 | break; |
691 | case REGID: | |
692 | if (opts.have_rgid || opts.have_egid) | |
693 | errx(EXIT_FAILURE, _("duplicate rgid or egid")); | |
694 | opts.have_rgid = opts.have_egid = 1; | |
637fa4c6 | 695 | opts.rgid = opts.egid = get_group(optarg, _("failed to parse regid")); |
5600c405 AL |
696 | break; |
697 | case CLEAR_GROUPS: | |
698 | if (opts.clear_groups) | |
699 | errx(EXIT_FAILURE, | |
700 | _("duplicate --clear-groups option")); | |
701 | opts.clear_groups = 1; | |
702 | break; | |
703 | case KEEP_GROUPS: | |
704 | if (opts.keep_groups) | |
705 | errx(EXIT_FAILURE, | |
706 | _("duplicate --keep-groups option")); | |
707 | opts.keep_groups = 1; | |
708 | break; | |
709 | case GROUPS: | |
710 | if (opts.have_groups) | |
711 | errx(EXIT_FAILURE, | |
712 | _("duplicate --groups option")); | |
713 | parse_groups(&opts, optarg); | |
714 | break; | |
715 | case LISTCAPS: | |
716 | list_caps = 1; | |
717 | break; | |
718 | case INHCAPS: | |
719 | if (opts.caps_to_inherit) | |
720 | errx(EXIT_FAILURE, | |
db663995 | 721 | _("duplicate --inh-caps option")); |
5600c405 AL |
722 | opts.caps_to_inherit = optarg; |
723 | break; | |
724 | case CAPBSET: | |
725 | if (opts.bounding_set) | |
726 | errx(EXIT_FAILURE, | |
727 | _("duplicate --bounding-set option")); | |
728 | opts.bounding_set = optarg; | |
729 | break; | |
730 | case SECUREBITS: | |
731 | if (opts.have_securebits) | |
732 | errx(EXIT_FAILURE, | |
733 | _("duplicate --securebits option")); | |
734 | parse_securebits(&opts, optarg); | |
735 | break; | |
736 | case SELINUX_LABEL: | |
737 | if (opts.selinux_label) | |
738 | errx(EXIT_FAILURE, | |
739 | _("duplicate --selinux-label option")); | |
740 | opts.selinux_label = optarg; | |
741 | break; | |
742 | case APPARMOR_PROFILE: | |
743 | if (opts.apparmor_profile) | |
744 | errx(EXIT_FAILURE, | |
745 | _("duplicate --apparmor-profile option")); | |
746 | opts.apparmor_profile = optarg; | |
747 | break; | |
748 | case 'h': | |
749 | usage(stdout); | |
750 | case 'V': | |
751 | printf(UTIL_LINUX_VERSION); | |
752 | return EXIT_SUCCESS; | |
5600c405 | 753 | default: |
677ec86c | 754 | errtryhelp(EXIT_FAILURE); |
5600c405 AL |
755 | } |
756 | } | |
757 | ||
758 | if (dumplevel) { | |
759 | if (total_opts != dumplevel || optind < argc) | |
760 | errx(EXIT_FAILURE, | |
761 | _("--dump is incompatible with all other options")); | |
762 | dump(dumplevel); | |
763 | return EXIT_SUCCESS; | |
764 | } | |
765 | ||
766 | if (list_caps) { | |
767 | if (total_opts != 1 || optind < argc) | |
768 | errx(EXIT_FAILURE, | |
769 | _("--list-caps must be specified alone")); | |
770 | list_known_caps(); | |
771 | return EXIT_SUCCESS; | |
772 | } | |
773 | ||
774 | if (argc <= optind) | |
775 | errx(EXIT_FAILURE, _("No program specified")); | |
776 | ||
777 | if ((opts.have_rgid || opts.have_egid) | |
778 | && !opts.keep_groups && !opts.clear_groups && !opts.have_groups) | |
779 | errx(EXIT_FAILURE, | |
780 | _("--[re]gid requires --keep-groups, --clear-groups, or --groups")); | |
781 | ||
74ce680a SK |
782 | if (opts.nnp && prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) |
783 | err(EXIT_FAILURE, _("disallow granting new privileges failed")); | |
5600c405 AL |
784 | |
785 | if (opts.selinux_label) | |
786 | do_selinux_label(opts.selinux_label); | |
787 | if (opts.apparmor_profile) | |
788 | do_apparmor_profile(opts.apparmor_profile); | |
789 | ||
790 | if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) | |
791 | err(EXIT_FAILURE, _("keep process capabilities failed")); | |
792 | ||
793 | /* We're going to want CAP_SETPCAP, CAP_SETUID, and CAP_SETGID if | |
794 | * possible. */ | |
795 | bump_cap(CAP_SETPCAP); | |
796 | bump_cap(CAP_SETUID); | |
797 | bump_cap(CAP_SETGID); | |
798 | if (capng_apply(CAPNG_SELECT_CAPS) != 0) | |
799 | err(SETPRIV_EXIT_PRIVERR, _("activate capabilities")); | |
800 | ||
801 | if (opts.have_ruid || opts.have_euid) { | |
802 | do_setresuid(&opts); | |
803 | /* KEEPCAPS doesn't work for the effective mask. */ | |
804 | if (capng_apply(CAPNG_SELECT_CAPS) != 0) | |
805 | err(SETPRIV_EXIT_PRIVERR, _("reactivate capabilities")); | |
806 | } | |
807 | ||
808 | if (opts.have_rgid || opts.have_egid) | |
809 | do_setresgid(&opts); | |
810 | ||
811 | if (opts.have_groups) { | |
812 | if (setgroups(opts.num_groups, opts.groups) != 0) | |
813 | err(SETPRIV_EXIT_PRIVERR, _("setgroups failed")); | |
814 | } else if (opts.clear_groups) { | |
815 | gid_t x = 0; | |
816 | if (setgroups(0, &x) != 0) | |
817 | err(SETPRIV_EXIT_PRIVERR, _("setgroups failed")); | |
818 | } | |
819 | ||
74ce680a SK |
820 | if (opts.have_securebits && prctl(PR_SET_SECUREBITS, opts.securebits, 0, 0, 0) != 0) |
821 | err(SETPRIV_EXIT_PRIVERR, _("set process securebits failed")); | |
5600c405 AL |
822 | |
823 | if (opts.bounding_set) { | |
824 | do_caps(CAPNG_BOUNDING_SET, opts.bounding_set); | |
825 | errno = EPERM; /* capng doesn't set errno if we're missing CAP_SETPCAP */ | |
826 | if (capng_apply(CAPNG_SELECT_BOUNDS) != 0) | |
827 | err(SETPRIV_EXIT_PRIVERR, _("apply bounding set")); | |
828 | } | |
829 | ||
830 | if (opts.caps_to_inherit) { | |
831 | do_caps(CAPNG_INHERITABLE, opts.caps_to_inherit); | |
832 | if (capng_apply(CAPNG_SELECT_CAPS) != 0) | |
833 | err(SETPRIV_EXIT_PRIVERR, _("apply capabilities")); | |
834 | } | |
835 | ||
836 | execvp(argv[optind], argv + optind); | |
837 | ||
838 | err(EXIT_FAILURE, _("cannot execute: %s"), argv[optind]); | |
839 | } |