* [PATCH] Add no_new_privs
@ 2012-11-23 20:23 Andy Lutomirski
2012-11-23 21:14 ` Pádraig Brady
` (2 more replies)
0 siblings, 3 replies; 21+ messages in thread
From: Andy Lutomirski @ 2012-11-23 20:23 UTC (permalink / raw)
To: util-linux; +Cc: Andy Lutomirski
---
I'm not 100% sure this is appropriate for util-linux, but it seems useful.
I've never written new programs for util-linux before, and I barely understand
autotools. Feedback is welcome :)
.gitignore | 1 +
configure.ac | 9 +++++
sys-utils/Makemodule.am | 6 ++++
sys-utils/no_new_privs.1 | 37 ++++++++++++++++++++
sys-utils/no_new_privs.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 141 insertions(+)
create mode 100644 sys-utils/no_new_privs.1
create mode 100644 sys-utils/no_new_privs.c
diff --git a/.gitignore b/.gitignore
index e85eb07..dc3e993 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,6 +127,7 @@ tests/run.sh.trs
/mountpoint
/namei
/newgrp
+/no_new_privs
/partx
/pg
/pivot_root
diff --git a/configure.ac b/configure.ac
index 727113a..36dae32 100644
--- a/configure.ac
+++ b/configure.ac
@@ -866,6 +866,15 @@ if test "x$build_unshare" = xyes; then
fi
+AC_ARG_ENABLE([no_new_privs],
+ AS_HELP_STRING([--disable-no_new_privs], [do not build no_new_privs]),
+ [], enable_no_new_privs=check
+)
+UL_BUILD_INIT([no_new_privs])
+UL_REQUIRES_LINUX([no_new_privs])
+AM_CONDITIONAL(BUILD_NO_NEW_PRIVS, test "x$build_no_new_privs" = xyes)
+
+
AC_ARG_ENABLE([arch],
AS_HELP_STRING([--enable-arch], [do build arch]),
[], enable_arch=no
diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am
index c7b1eb3..0789d63 100644
--- a/sys-utils/Makemodule.am
+++ b/sys-utils/Makemodule.am
@@ -309,3 +309,9 @@ if HAVE_AUDIT
hwclock_LDADD += -laudit
endif
endif # BUILD_HWCLOCK
+
+if BUILD_NO_NEW_PRIVS
+usrbin_exec_PROGRAMS += no_new_privs
+dist_man_MANS += sys-utils/no_new_privs.1
+no_new_privs_SOURCES = sys-utils/no_new_privs.c
+endif
diff --git a/sys-utils/no_new_privs.1 b/sys-utils/no_new_privs.1
new file mode 100644
index 0000000..59dfe4b
--- /dev/null
+++ b/sys-utils/no_new_privs.1
@@ -0,0 +1,37 @@
+.\" Process this file with
+.\" groff -man -Tascii no_new_privs.1
+.\"
+.TH NO_NEW_PRIVS 1 "December 2012" "util-linux" "User Commands"
+.SH NAME
+no_new_privs \- run program with new_new_privs set
+.SH SYNOPSIS
+.B no_new_privs
+.RI [ options ]
+program
+.RI [ arguments ]
+.SH DESCRIPTION
+Sets the \fIno_new_privs\fP bit and then executes specified program. With
+this bit set,
+.BR execve (2)
+will not grant new privileges. For example, the setuid
+and setgid bits as well as file capabilities will not function. This bit
+is inherited by child processes and cannot be unset. See
+.BR prctl (2)
+and
+.IR Documentation/prctl/no_new_privs.txt
+in the Linux kernel source.
+.SH OPTIONS
+.TP
+.BR \-h , " \-\-help"
+Print a help message,
+.SH NOTES
+If setting the no_new_privs bit fails, \fIprogram\fP will not be run.
+.SH SEE ALSO
+.BR prctl (2)
+.SH BUGS
+None known so far.
+.SH AUTHOR
+Andy Lutomirski <luto@amacapital.net>
+.SH AVAILABILITY
+The no_new_privs command is part of the util-linux package and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
diff --git a/sys-utils/no_new_privs.c b/sys-utils/no_new_privs.c
new file mode 100644
index 0000000..094f5a9
--- /dev/null
+++ b/sys-utils/no_new_privs.c
@@ -0,0 +1,88 @@
+/*
+ * no_new_privs(1) - command-line interface for PR_SET_NO_NEW_PRIVS
+ *
+ * Copyright (C) 2012 Andy Lutomirski <luto@amacapital.net>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/prctl.h>
+
+#include "nls.h"
+#include "c.h"
+#include "closestream.h"
+
+#ifndef PR_SET_NO_NEW_PRIVS
+# define PR_SET_NO_NEW_PRIVS 38
+#endif
+
+static void usage(int status)
+{
+ FILE *out = status == EXIT_SUCCESS ? stdout : stderr;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s <program> [args...]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(USAGE_HELP, out);
+ fputs(USAGE_VERSION, out);
+ fprintf(out, USAGE_MAN_TAIL("no_new_privs(1)"));
+
+ exit(status);
+}
+
+int main(int argc, char *argv[])
+{
+ static const struct option longopts[] = {
+ { "help", no_argument, 0, 'h' },
+ { "version", no_argument, 0, 'V'},
+ { NULL, 0, 0, 0 }
+ };
+
+ int c;
+
+ setlocale(LC_MESSAGES, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ while((c = getopt_long(argc, argv, "+hV", longopts, NULL)) != -1) {
+ switch(c) {
+ case 'h':
+ usage(EXIT_SUCCESS);
+ case 'V':
+ printf(UTIL_LINUX_VERSION);
+ return EXIT_SUCCESS;
+ default:
+ usage(EXIT_FAILURE);
+ }
+ }
+
+ if(optind >= argc)
+ usage(EXIT_FAILURE);
+
+ if(-1 == prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0))
+ err(EXIT_FAILURE, _("PR_SET_NO_NEW_PRIVS failed"));
+
+ execvp(argv[optind], argv + optind);
+
+ err(EXIT_FAILURE, _("exec %s failed"), argv[optind]);
+}
--
1.7.11.7
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH] Add no_new_privs 2012-11-23 20:23 [PATCH] Add no_new_privs Andy Lutomirski @ 2012-11-23 21:14 ` Pádraig Brady 2012-11-26 10:08 ` Karel Zak 2012-11-23 22:52 ` Ángel González 2012-12-08 8:19 ` [PATCH] Add setpriv, a tool to set privileges and such Andy Lutomirski 2 siblings, 1 reply; 21+ messages in thread From: Pádraig Brady @ 2012-11-23 21:14 UTC (permalink / raw) To: Andy Lutomirski; +Cc: util-linux On 11/23/2012 08:23 PM, Andy Lutomirski wrote: > --- > > I'm not 100% sure this is appropriate for util-linux, but it seems useful. > > I've never written new programs for util-linux before, and I barely understand > autotools. Feedback is welcome :) > > +no_new_privs \- run program with new_new_privs set > +Sets the \fIno_new_privs\fP bit and then executes specified program. With > +this bit set, > +.BR execve (2) > +will not grant new privileges. For example, the setuid > +and setgid bits as well as file capabilities will not function. This bit > +is inherited by child processes and cannot be unset. See > +.BR prctl (2) > +and > +.IR Documentation/prctl/no_new_privs.txt > +in the Linux kernel source. Seems very useful but a bit low level for a user command. How about a prctl(1) command or equivalent, that could accept that among other options to set. I also notice the similar capsh(1) program for doing so with capabilities. Perhaps these could be merged to a setpriv(1) command or something for tweaking all these knobs before exec? cheers, Pádraig. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] Add no_new_privs 2012-11-23 21:14 ` Pádraig Brady @ 2012-11-26 10:08 ` Karel Zak 2012-11-26 12:45 ` Ángel González 2012-11-26 19:03 ` Andy Lutomirski 0 siblings, 2 replies; 21+ messages in thread From: Karel Zak @ 2012-11-26 10:08 UTC (permalink / raw) To: Pádraig Brady; +Cc: Andy Lutomirski, util-linux On Fri, Nov 23, 2012 at 09:14:19PM +0000, Pádraig Brady wrote: > On 11/23/2012 08:23 PM, Andy Lutomirski wrote: > >--- > > > >I'm not 100% sure this is appropriate for util-linux, but it seems useful. > > > >I've never written new programs for util-linux before, and I barely understand > >autotools. Feedback is welcome :) > > > >+no_new_privs \- run program with new_new_privs set > > >+Sets the \fIno_new_privs\fP bit and then executes specified program. With > >+this bit set, > >+.BR execve (2) > >+will not grant new privileges. For example, the setuid > >+and setgid bits as well as file capabilities will not function. This bit > >+is inherited by child processes and cannot be unset. See > >+.BR prctl (2) > >+and > >+.IR Documentation/prctl/no_new_privs.txt > >+in the Linux kernel source. > > Seems very useful but a bit low level for a user command. > How about a prctl(1) command or equivalent, that could > accept that among other options to set. It would be nice to have prctl(1) implemented like prlimit(1), it means to support --set as well as --get operations. prctl --set-endian=big --set-name=foo prctl --pid 123 # return all --get-* prctl --get-name --pid 123 > I also notice the similar capsh(1) program for doing > so with capabilities. Perhaps these could be merged > to a setpriv(1) command or something for tweaking all > these knobs before exec? hmm.. capsh(1) is libcap baby and it probably makes sense to maintain it on the same place like libcap. I guess that there will be never one super util to set all the possible properties (prlimit, personality, scheduler stuff, ....) and I personally don't see problem to type setarch x86_64 --addr-no-randomize \ taskset --cpu-list 1,2 \ prlimit --nofile=1024:4095 \ nice -20 \ <myprog> Karel -- Karel Zak <kzak@redhat.com> http://karelzak.blogspot.com ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] Add no_new_privs 2012-11-26 10:08 ` Karel Zak @ 2012-11-26 12:45 ` Ángel González 2012-11-26 19:03 ` Andy Lutomirski 1 sibling, 0 replies; 21+ messages in thread From: Ángel González @ 2012-11-26 12:45 UTC (permalink / raw) To: Karel Zak; +Cc: Pádraig Brady, Andy Lutomirski, util-linux On 26/11/12 11:08, Karel Zak wrote: > I guess that there will be never one super util to set all the > possible properties (prlimit, personality, scheduler stuff, ....) and > I personally don't see problem to type > > setarch x86_64 --addr-no-randomize \ > taskset --cpu-list 1,2 \ > prlimit --nofile=1024:4095 \ > nice -20 \ > <myprog> It may be a problem if the restrictions placed with one program are incompatible with chaining another one. For instance, I could want to run a static binary foo as: prlimit --nofile 1:1 /usr/local/bin/foo But I won't be able to do prlimit --nofile 1:1 nice /usr/local/bin/foo since nice wouldn't be able to open libc. In this case nice can be called with prlimit as parameter, but you will end up with some options provided by different binaries and which are incompatible. We probably can't avoid it, so go ahead with it. Make sure all these tools have their man pages properly interlinked, though. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] Add no_new_privs 2012-11-26 10:08 ` Karel Zak 2012-11-26 12:45 ` Ángel González @ 2012-11-26 19:03 ` Andy Lutomirski 2012-11-27 1:39 ` Andy Lutomirski 1 sibling, 1 reply; 21+ messages in thread From: Andy Lutomirski @ 2012-11-26 19:03 UTC (permalink / raw) To: Karel Zak; +Cc: Pádraig Brady, util-linux On Mon, Nov 26, 2012 at 2:08 AM, Karel Zak <kzak@redhat.com> wrote: > On Fri, Nov 23, 2012 at 09:14:19PM +0000, Pádraig Brady wrote: >> On 11/23/2012 08:23 PM, Andy Lutomirski wrote: >> >--- >> > >> >I'm not 100% sure this is appropriate for util-linux, but it seems useful. >> > >> >I've never written new programs for util-linux before, and I barely understand >> >autotools. Feedback is welcome :) >> > >> >+no_new_privs \- run program with new_new_privs set >> >> >+Sets the \fIno_new_privs\fP bit and then executes specified program. With >> >+this bit set, >> >+.BR execve (2) >> >+will not grant new privileges. For example, the setuid >> >+and setgid bits as well as file capabilities will not function. This bit >> >+is inherited by child processes and cannot be unset. See >> >+.BR prctl (2) >> >+and >> >+.IR Documentation/prctl/no_new_privs.txt >> >+in the Linux kernel source. >> >> Seems very useful but a bit low level for a user command. >> How about a prctl(1) command or equivalent, that could >> accept that among other options to set. > > It would be nice to have prctl(1) implemented like prlimit(1), it > means to support --set as well as --get operations. > > prctl --set-endian=big --set-name=foo > > prctl --pid 123 # return all --get-* > > prctl --get-name --pid 123 > >> I also notice the similar capsh(1) program for doing >> so with capabilities. Perhaps these could be merged >> to a setpriv(1) command or something for tweaking all >> these knobs before exec? > > hmm.. capsh(1) is libcap baby and it probably makes sense to maintain > it on the same place like libcap. Taking a quick look through prctl: PR_CAPBSET_READ/DROP: these want libcap for parsing, and maybe they should go in capsh. (Also, dropping caps requires privilege.) PR_SET/GET_DUMPABLE: IIRC these are cleared on exec. PR_SET/GET_ENDIAN: Does this survive exec? I'd assume it's an ELF header flag as well. PR_SET/GET_FPEMU: IA-64 only. Seems to be of limited use. PR_SET/GET_FPEXC: Not sure about this one. PR_SET/GET_KEEPCAPS: Useless except in combination with capsh-like controls. PR_SET/GET_NAME: Reset on exec PR_SET/GET_PDEATHSIG: This is potentially very useful. PR_SET/GET_SECCOMP: This is way too specialized for a general tool. A general-purpose seccomp mode 2 sandbox would be wonderful, but it would be rather complex is and is far beyond the scope of util-linux IMO. seccomp mode 1 is simple but entirely incompatible with exec. PR_SET/GET_SECUREBITS: Useful. Requires some parsing. Might be supported by capsh already. PR_SET_TIMING: According to the man page, it gives you a choice of one option. PR_GET/SET_TSC: Potentially useful. PR_GET/SET_UNALIGN: Potentially useful on some platforms. PR_MCE_KILL[_GET]: Potentially useful. PR_GET/SET_TIMERSLACK: Useful PR_TASK_PERF_EVENTS_ENABLE/DISABLE: No clue PR_SET_MM: I'm not sure this is useful across exec PR_SET_PTRACER: Maybe useful. I don't know if it survives exec. PR_GET/SET_CHILD_SUBREAPER: Very useful, but probably wants to be its own tool PR_GET_TID_ADDRESS: Not useful Do we want the useful flags in a prctl(1) tool or should the security-related ones possibly be in their own tool (maybe capsh; maybe something else)? --Andy ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] Add no_new_privs 2012-11-26 19:03 ` Andy Lutomirski @ 2012-11-27 1:39 ` Andy Lutomirski 0 siblings, 0 replies; 21+ messages in thread From: Andy Lutomirski @ 2012-11-27 1:39 UTC (permalink / raw) To: Karel Zak; +Cc: Pádraig Brady, util-linux On Mon, Nov 26, 2012 at 11:03 AM, Andy Lutomirski <luto@amacapital.net> wrote: > On Mon, Nov 26, 2012 at 2:08 AM, Karel Zak <kzak@redhat.com> wrote: >> >> It would be nice to have prctl(1) implemented like prlimit(1), it >> means to support --set as well as --get operations. >> >> prctl --set-endian=big --set-name=foo >> >> prctl --pid 123 # return all --get-* >> >> prctl --get-name --pid 123 >> >>> I also notice the similar capsh(1) program for doing >>> so with capabilities. Perhaps these could be merged >>> to a setpriv(1) command or something for tweaking all >>> these knobs before exec? >> >> hmm.. capsh(1) is libcap baby and it probably makes sense to maintain >> it on the same place like libcap. > > Taking a quick look through prctl: > > > PR_GET/SET_CHILD_SUBREAPER: Very useful, but probably wants to be its own tool > For example, the tool below would probably be quite handy, but I don't think it would make too much sense as part of a hypothetical prctl(1). (I'll send this as a proper patch later on, after I make its interface a little less silly.) ---wait_all_descendents.c--- #include <stdio.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/prctl.h> #include <unistd.h> #include <errno.h> static volatile sig_atomic_t exec_failed = 0; static volatile int exec_errno; static int wait_all_children(pid_t child_pid) { while (1) { siginfo_t si; if (waitid(P_ALL, 0, &si, WEXITED) != 0) { if (errno == ECHILD) { if (child_pid) fprintf(stderr, "Warning: child never exited\n"); return 0; } else { perror("waitid"); return 1; } } if (si.si_pid == child_pid) { child_pid = 0; fprintf(stderr, "Immediate child exited\n"); } } } int main(int argc, char **argv) { if (argc < 2) { printf("Usage: wait_descendents PROGRAM [ARGS]...\n"); return 1; } if (signal(SIGCHLD, SIG_DFL)) { perror("signal"); return 127; } if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) != 0) { perror("PR_SET_CHILD_SUBREAPER"); return 127; } pid_t child_pid = vfork(); if (child_pid == -1) { perror("fork"); return 1; } else if (child_pid == 0) { /* We're the child. */ execvp(argv[1], argv + 1); exec_failed = 1; exec_errno = errno; _exit(1); } else { if (exec_failed) { errno = exec_errno; perror(argv[1]); return 127; } return wait_all_children(child_pid); } } ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] Add no_new_privs 2012-11-23 20:23 [PATCH] Add no_new_privs Andy Lutomirski 2012-11-23 21:14 ` Pádraig Brady @ 2012-11-23 22:52 ` Ángel González 2012-12-08 8:19 ` [PATCH] Add setpriv, a tool to set privileges and such Andy Lutomirski 2 siblings, 0 replies; 21+ messages in thread From: Ángel González @ 2012-11-23 22:52 UTC (permalink / raw) To: Andy Lutomirski; +Cc: util-linux On 23/11/12 21:23, Andy Lutomirski wrote: > --- > > I'm not 100% sure this is appropriate for util-linux, but it seems useful. > > I've never written new programs for util-linux before, and I barely understand > autotools. Feedback is welcome :) (...) > --- /dev/null > +++ b/sys-utils/no_new_privs.1 > @@ -0,0 +1,37 @@ > +.\" Process this file with > +.\" groff -man -Tascii no_new_privs.1 > +.\" > +.TH NO_NEW_PRIVS 1 "December 2012" "util-linux" "User Commands" > +.SH NAME > +no_new_privs \- run program with new_new_privs set s/new_new_privs/no_new_privs/ > +.SH SYNOPSIS > +.B no_new_privs > +.RI [ options ] > +program > +.RI [ arguments ] > +.SH DESCRIPTION > +Sets the \fIno_new_privs\fP bit and then executes specified program. With > +this bit set, > +.BR execve (2) > +will not grant new privileges. For example, the setuid > +and setgid bits as well as file capabilities will not function. This bit "will not be granted" instead of "will not function"? It's not clear from the description if a privileged program would run without setuid or if it wouldn't run at all (although to be fair, no_new_privs.txt doesn't clarify that, either). > +is inherited by child processes and cannot be unset. See > +.BR prctl (2) > +and > +.IR Documentation/prctl/no_new_privs.txt > +in the Linux kernel source. > +.SH OPTIONS > +.TP > +.BR \-h , " \-\-help" > +Print a help message, > +.SH NOTES > +If setting the no_new_privs bit fails, \fIprogram\fP will not be run. Also document that it will return 1 ? It is possible that something like 127 would be more appropiate, though. > +.SH SEE ALSO > +.BR prctl (2) > +.SH BUGS > +None known so far. > +.SH AUTHOR > +Andy Lutomirski <luto@amacapital.net> > +.SH AVAILABILITY > +The no_new_privs command is part of the util-linux package and is available from > +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. IMHO it should mention that PR_SET_NO_NEW_PRIVS is available since Linux 3.5 (...) ^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH] Add setpriv, a tool to set privileges and such 2012-11-23 20:23 [PATCH] Add no_new_privs Andy Lutomirski 2012-11-23 21:14 ` Pádraig Brady 2012-11-23 22:52 ` Ángel González @ 2012-12-08 8:19 ` Andy Lutomirski 2012-12-08 16:23 ` Ángel González ` (3 more replies) 2 siblings, 4 replies; 21+ messages in thread From: Andy Lutomirski @ 2012-12-08 8:19 UTC (permalink / raw) To: util-linux Cc: Pádraig Brady, Ángel González, Karel Zak, Andy Lutomirski This can set no_new_privs, uid, gid, groups, securebits, inheritable caps, the cap bounding set, securebits, and selinux and apparmor labels. Signed-off-by: Andy Lutomirski <luto@amacapital.net> --- This is like my no_new_privs tool, but way more fun :) .gitignore | 1 + configure.ac | 9 + sys-utils/Makemodule.am | 7 + sys-utils/setpriv.1 | 132 +++++++++ sys-utils/setpriv.c | 751 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 900 insertions(+) create mode 100644 sys-utils/setpriv.1 create mode 100644 sys-utils/setpriv.c diff --git a/.gitignore b/.gitignore index e85eb07..b3ed077 100644 --- a/.gitignore +++ b/.gitignore @@ -146,6 +146,7 @@ tests/run.sh.trs /script /scriptreplay /setarch +/setpriv /setsid /setterm /sfdisk diff --git a/configure.ac b/configure.ac index 727113a..18a8f43 100644 --- a/configure.ac +++ b/configure.ac @@ -866,6 +866,15 @@ if test "x$build_unshare" = xyes; then fi +AC_ARG_ENABLE([setpriv], + AS_HELP_STRING([--disable-setpriv], [do not build setpriv]), + [], enable_setpriv=check +) +UL_BUILD_INIT([setpriv]) +UL_REQUIRES_LINUX([setpriv]) +AM_CONDITIONAL(BUILD_SETPRIV, test "x$build_setpriv" = xyes) + + AC_ARG_ENABLE([arch], AS_HELP_STRING([--enable-arch], [do build arch]), [], enable_arch=no diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am index c7b1eb3..329dd95 100644 --- a/sys-utils/Makemodule.am +++ b/sys-utils/Makemodule.am @@ -309,3 +309,10 @@ if HAVE_AUDIT hwclock_LDADD += -laudit endif endif # BUILD_HWCLOCK + +if BUILD_SETPRIV +usrbin_exec_PROGRAMS += setpriv +dist_man_MANS += sys-utils/setpriv.1 +setpriv_SOURCES = sys-utils/setpriv.c +setpriv_LDADD = -lcap-ng +endif diff --git a/sys-utils/setpriv.1 b/sys-utils/setpriv.1 new file mode 100644 index 0000000..b76c4f1 --- /dev/null +++ b/sys-utils/setpriv.1 @@ -0,0 +1,132 @@ +.\" Process this file with +.\" groff -man -Tascii no_new_privs.1 +.\" +.TH SETPRIV 1 "December 2012" "util-linux" "User Commands" +.SH NAME +setpriv \- run program with different Linux privilege settings +.SH SYNOPSIS +.B setpriv +.RI [ options ] +program +.RI [ arguments ] +.SH DESCRIPTION +Sets or queries various Linux privilege settings that are inherited across +.BR execve (2) +. +.SH OPTION + +.TP +.BR \-d,\ --dump +Dumps current privilege state. Specify more than once to show extra, mostly useless, +information. Incompatible with all other options. + +.TP +.BR \--nnp +Sets the \fIno_new_privs\fP bit. With this bit set, +.BR execve (2) +will not grant new privileges. For example, the setuid and setgid bits as +well as file capabilities will be disabled. (Executing binaries with these +bits set will still work, but they will not gain privilege. Certain LSMs, +especially AppArmor, may result in failures to execute certain programs.) +This bit is inherited by child processes and cannot be unset. See +.BR prctl (2) +and +.IR Documentation/prctl/no_new_privs.txt +in the Linux kernel source. + +The no_new_privs bit is supported since Linux 3.5. + +.TP +.BR \--inh-caps=(+|-)cap,...\ or\ --bounding-set=(+|-)cap,... +Sets inheritable capabilities or capability bounding set. See +.BR capabilities (7). +The argument is a comma-separated list of +cap and -cap entries, +which add or remove an entry respectively. +all and -all can be used +to add or remove all caps. The set of capabilities starts out as +the current inheritable set for --inh-caps and the current bounding set +for --bounding-set. If you drop something from the bounding set +without also dropping it from the inheritable set, you are likely +to become confused. Don't do that. + +.TP +.BR \--list-caps +Lists all known capabilities. Must be specified alone. + +.TP +.BR \--ruid,\ --euid,\ --reuid +Sets the real, effective, or both UIDs. + +Setting uid or gid does not change capabilities, although the exec +call at the end might change capabilities. This means that, if you +are root, you probably want to do something like: + +--reuid=1000 --regid=1000 --caps=-all + +.TP +.BR \--rgid,\ --egid,\ --regid +Sets the real, effective, or both GIDs. + +For safety, you must specify one of --keep-groups, --clear-groups, or --groups if you +set any primary GID. + +.TP +.BR \--clear-groups +Clears supplementary groups. + +.TP +.BR \--keep-groups +Preserves supplementary groups. Only useful in conjunction with --rgid, --egid, +or --regid. + +.TP +.BR \--groups=group,... +Sets supplementary groups. + +.TP +.BR \--securebits=(+|-)securebit,... +Sets or clears securebits. The valid securebits are \fInoroot\fP, \fInoroot_locked\fP, +\fIno_setuid_fixup\fP, \fIno_setuid_fixup_locked\fP, and \fIkeep_caps_locked\fP. +\fIkeep_caps\fP is cleared by +.BR execve (2) +and is therefore not allowed. + +.TP +.BR \--selinux-label +Requests a particular SELinux transition (using a transition on exec, not dyntrans). +This will fail and cause +.BR setpriv (1) +to abort if SELinux is not in use, and the transition may be ignored or cause +.BR execve (2) +to fail at SELinux's whim. (In particular, this is unlikely to work in conjunction +with \fIno_new_privs\fP.) + +.TP +.BR \--apparmor-profile +Requests a particular AppArmor profile (using a transition on exec). +This will fail and cause +.BR setpriv (1) +to abort if AppArmor is not in use, and the transition may be ignored or cause +.BR execve (2) +to fail at AppArmor's whim. + +.TP +.BR \-h , " \-\-help" +Print a help message, +.SH NOTES +If applying any specified option fails, \fIprogram\fP will not be run and +\fIsetpriv\fP will return with exit code 127. + +Be careful with this tool -- it may have unexpected security consequences. +For example, setting no_new_privs and then execing a program that is +SELinux-confined (as this tool would do) may prevent the SELinux +restrictions from taking effect. +.SH SEE ALSO +.BR prctl (2) +.BR capability (7) +.SH BUS +None known so far. +.SH AUTHOR +Andy Lutomirski <luto@amacapital.net> +.SH AVAILABILITY +The \fIsetpriv\fP command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c new file mode 100644 index 0000000..16d0262 --- /dev/null +++ b/sys-utils/setpriv.c @@ -0,0 +1,751 @@ +/* + * setpriv(1) - set various kernel privilege bits and run something + * + * Copyright (C) 2012 Andy Lutomirski <luto@amacapital.net> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/prctl.h> +#include <linux/securebits.h> +#include <grp.h> +#include <stdbool.h> +#include <stdarg.h> +#include <cap-ng.h> + +#include "nls.h" +#include "c.h" +#include "closestream.h" + +#ifndef PR_SET_NO_NEW_PRIVS +# define PR_SET_NO_NEW_PRIVS 38 +#endif +#ifndef PR_GET_NO_NEW_PRIVS +# define PR_GET_NO_NEW_PRIVS 39 +#endif + +static void usage(int status) +{ + FILE *out = status == EXIT_SUCCESS ? stdout : stderr; + + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] <program> [args...]\n"), program_invocation_short_name); + fputs(_(" -d, --dump show current state (and don't exec anything)\n" + " --nnp set no_new_privs\n" + " --inh-caps=caps set inheritable capabilities\n" + " --bounding-set=caps set capability bounding set\n" + " --ruid, --euid set real or effective uid respectively\n" + " --rgid, --egid set real or effective gid respectively\n" + " --reuid, --regid set real and effective uid or gid\n" + " --clear-groups clear supplementary groups\n" + " --keep-groups keep supplementary groups\n" + " --groups=group,... set supplementary groups\n" + " --securebits=bits set securebits\n" + " --selinux-label set SELinux label (requires process:transition)\n" + " --apparmor-profile set AppArmor profile (requires onexec permission)\n" + "\n" + " This tool can be dangerous. Be careful and read the manpage."), out); + + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("setpriv(1)")); + + exit(status); +} + +static void complain(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + usage(EXIT_FAILURE); +} + +// Returns the number of capabilities printed +static int print_caps(FILE *f, capng_type_t which) +{ + int n = 0; + for (int i = 0; i <= CAP_LAST_CAP; i++) { + if (capng_have_capability(which, i)) { + if (n) + fputc(',', f); + fputs(capng_capability_to_name(i), f); + n++; + } + } + return n; +} + +static void stdout_perror(const char *prefix) +{ + if (errno < 0 || errno >= sys_nerr) + printf(_("%s: error %d\n"), prefix, errno); + else + printf(_("%s: %s\n"), prefix, sys_errlist[errno]); +} + +static void dump_one_secbit(bool *first, int *bits, int bit, const char *name) +{ + if (*bits & bit) { + if (!*first) + printf(","); + else + *first = false; + fputs(name, stdout); + *bits &= ~bit; + } +} + +static void dump_securebits(void) +{ + int bits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0); + if (bits < 0) { + stdout_perror("PR_GET_SECUREBITS"); + return; + } + + printf("Securebits: "); + + bool first = true; + dump_one_secbit(&first, &bits, SECBIT_NOROOT, "noroot"); + dump_one_secbit(&first, &bits, SECBIT_NOROOT_LOCKED, "noroot_locked"); + dump_one_secbit(&first, &bits, SECBIT_NO_SETUID_FIXUP, "no_setuid_fixup"); + dump_one_secbit(&first, &bits, + SECBIT_NO_SETUID_FIXUP_LOCKED, "no_setuid_fixup_locked"); + bits &= ~SECBIT_KEEP_CAPS; + dump_one_secbit(&first, &bits, SECBIT_KEEP_CAPS_LOCKED, "keep_caps_locked"); + if (bits) { + if (!first) + printf(","); + else + first = false; + printf("0x%x", (unsigned)bits); + } + + if (first) + printf("[none]\n"); + else + printf("\n"); +} + +static void dump_label(const char *name) +{ + int fd = open("/proc/self/attr/current", O_RDONLY); + if (fd == -1) { + stdout_perror(name); + return; + } + + char buf[4097]; + ssize_t len = read(fd, buf, sizeof(buf)); + int e = errno; + close(fd); + errno = e; + if (len < 0) { + stdout_perror(name); + return; + } + if ((size_t)len >= sizeof(buf) - 1) { + printf(_("%s: too long\n"), name); + return; + } + + buf[len] = 0; + if (len > 0 && buf[len-1] == '\n') + buf[len-1] = 0; + printf(_("%s: %s\n"), name, buf); +} + +static void dump_groups(void) +{ + int n = getgroups(0, 0); + if (n < 0) { + stdout_perror("getgroups"); + return; + } + + gid_t *groups = alloca(n * sizeof(gid_t)); + n = getgroups(n, groups); + if (n < 0) { + stdout_perror("getgroups"); + return; + } + + printf("Supplementary groups: "); + if (n == 0) { + printf("[none]"); + } else { + for (int i = 0; i < n; i++) { + if (i > 0) + printf(","); + printf("%ld", (long)groups[i]); + } + } + printf("\n"); +} + +static void dump(int dumplevel) +{ + int x; + + uid_t ru, eu, su; + if (getresuid(&ru, &eu, &su) == 0) { + printf("uid: %ld\n", (long)ru); + printf("euid: %ld\n", (long)eu); + /* Saved and fs uids always equal euid. */ + if (dumplevel >= 3) + printf("suid: %ld\n", (long)su); + } else { + stdout_perror("getresuid"); + } + + gid_t rg, eg, sg; + if (getresgid(&rg, &eg, &sg) == 0) { + printf("gid: %ld\n", (long)rg); + printf("egid: %ld\n", (long)eg); + /* Saved and fs gids always equal egid. */ + if (dumplevel >= 3) + printf("sgid: %ld\n", (long)sg); + } else { + stdout_perror("getresgid"); + } + + dump_groups(); + + x = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0); + if (x >= 0) + printf("no_new_privs: %d\n", x); + else + stdout_perror("no_new_privs"); + + if (dumplevel >= 2) { + printf(_("Effective capabilities: ")); + if (print_caps(stdout, CAPNG_EFFECTIVE) == 0) + printf("[none]"); + printf("\n"); + + printf(_("Permitted capabilities: ")); + if (print_caps(stdout, CAPNG_PERMITTED) == 0) + printf("[none]"); + printf("\n"); + } + + printf(_("Inheritable capabilities: ")); + if (print_caps(stdout, CAPNG_INHERITABLE) == 0) + printf("[none]"); + printf("\n"); + + printf(_("Capability bounding set: ")); + if (print_caps(stdout, CAPNG_BOUNDING_SET) == 0) + printf("[none]"); + printf("\n"); + + dump_securebits(); + + if (access("/sys/fs/selinux", F_OK) == 0) + dump_label("SELinux label"); + + if (access("/sys/kernel/security/apparmor", F_OK) == 0) { + dump_label("AppArmor profile"); + } +} + +static void list_known_caps(void) +{ + for (int i = 0; i <= CAP_LAST_CAP; i++) + printf("%s\n", capng_capability_to_name(i)); +} + +struct options { + bool nnp; + + // real and effective (in that order) + bool have_uid[2]; + uid_t uid[2]; + bool have_gid[2]; + gid_t gid[2]; + + // supplementary groups + bool have_groups, keep_groups, clear_groups; + size_t num_groups; + gid_t *groups; + + // caps + const char *caps_to_inherit; + const char *bounding_set; + + // securebits + bool have_securebits; + int securebits; + + // LSMs + const char *selinux_label; + const char *apparmor_profile; +}; + +static void priverr(const char *str) +{ + perror(str); + exit(127); +} + +static void parse_groups(struct options *opts, const char *str) +{ + char *groups = strdup(str); + char *buf = groups; /* We'll reuse it */ + char *c; + + opts->have_groups = true; + opts->num_groups = 0; + while ((c = strsep(&groups, ",")) != 0) + opts->num_groups++; + + // Start again + strcpy(buf, str); // It's exactly the right length + groups = buf; + + opts->groups = calloc(opts->num_groups, sizeof(gid_t)); + size_t i = 0; + while ((c = strsep(&groups, ",")) != 0) { + char *end; + errno = 0; + long val = strtol(c, &end, 10); + if (!*c || *end || errno || val != (long long)(gid_t)val) + complain(_("Invalid supplementary group id")); + opts->groups[i++] = (gid_t)val; + } + + free(groups);; +} + +static void do_setresuid(const struct options *opts) +{ + uid_t id[3]; + if (getresuid(&id[0], &id[1], &id[2]) != 0) + priverr("getresuid failed"); + for (int i = 0; i < 2; i++) + if (opts->have_uid[i]) + id[i] = opts->uid[i]; + + /* Also copy effective to saved (for paranoia). */ + if (setresuid(id[0], id[1], id[1]) != 0) + priverr("setresuid failed"); +} + +static void do_setresgid(const struct options *opts) +{ + gid_t id[3]; + if (getresgid(&id[0], &id[1], &id[2]) != 0) + priverr("getresgid failed"); + for (int i = 0; i < 2; i++) + if (opts->have_gid[i]) + id[i] = opts->gid[i]; + + /* Also copy effective to saved (for paranoia). */ + if (setresgid(id[0], id[1], id[1]) != 0) + priverr("setresgid failed"); +} + +static void bump_cap(unsigned int cap) +{ + if (capng_have_capability(CAPNG_PERMITTED, cap)) + capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, cap); +} + +static void do_caps(capng_type_t type, const char *caps) +{ + char *my_caps = strdup(caps); /* Don't bother checking for error */ + + char *c; + while ((c = strsep(&my_caps, ",")) != 0) { + capng_act_t action; + if (*c == '+') + action = CAPNG_ADD; + else if (*c == '-') + action = CAPNG_DROP; + else + complain(_("Bad capability string")); + + if (!strcmp(c+1, "all")) { + for (int i = 0; i <= CAP_LAST_CAP; i++) + capng_update(action, type, i); + } else { + int cap = capng_name_to_capability(c+1); + if (cap >= 0) + capng_update(action, type, cap); + else + complain(_("Unknown capability \"%s\""), c+1); + } + } + + free(my_caps); +} + +static void parse_securebits(struct options *opts, const char *arg) +{ + opts->have_securebits = true; + opts->securebits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0); + if (opts->securebits < 0) + priverr("PR_GET_SECUREBITS"); + + if (opts->securebits & ~(int)(SECBIT_NOROOT | SECBIT_NOROOT_LOCKED | + SECBIT_NO_SETUID_FIXUP | SECBIT_NO_SETUID_FIXUP_LOCKED | + SECBIT_KEEP_CAPS | SECBIT_KEEP_CAPS_LOCKED)) + { + priverr(_("Unrecognized securebit is set -- refusing to adjust")); + } + + char *buf = strdup(arg); + + char *c; + while ((c = strsep(&buf, ",")) != 0) { + if (*c != '+' && *c != '-') + complain(_("Bad securebits string")); + + if (!strcmp(c+1, "all")) { + if (*c == '-') + opts->securebits = 0; + else + complain(_("+all securebits is not allowed")); + } else { + int bit; + if (!strcmp(c+1, "noroot")) + bit = SECBIT_NOROOT; + else if (!strcmp(c+1, "noroot_locked")) + bit = SECBIT_NOROOT_LOCKED; + else if (!strcmp(c+1, "no_setuid_fixup")) + bit = SECBIT_NO_SETUID_FIXUP; + else if (!strcmp(c+1, "no_setuid_fixup_locked")) + bit = SECBIT_NO_SETUID_FIXUP_LOCKED; + else if (!strcmp(c+1, "keep_caps")) + complain(_("Adjusting keep_caps makes no sense")); + else if (!strcmp(c+1, "keep_caps_locked")) + bit = SECBIT_KEEP_CAPS_LOCKED; /* sigh */ + else + complain(_("Unrecognized securebit")); + + if (*c == '+') + opts->securebits |= bit; + else + opts->securebits &= ~bit; + } + } + + opts->securebits |= SECBIT_KEEP_CAPS; /* We need it, and it's reset on exec */ + + free(buf); +} + +static void do_selinux_label(const char *label) +{ + if (access("/sys/fs/selinux", F_OK) != 0) { + fputs("SELinux is not running\n", stderr); + exit(127); + } + + int fd = open("/proc/self/attr/exec", O_RDWR); + if (fd == -1) + priverr("open /proc/self/attr/exec for selinux"); + + size_t len = strlen(label); + errno = 0; + if (write(fd, label, len) != (ssize_t)len) + priverr("write /proc/self/attr/exec for selinux"); + + close(fd); +} + +static void do_apparmor_profile(const char *label) +{ + if (access("/sys/kernel/security/apparmor", F_OK) != 0) { + fputs("AppArmor is not running\n", stderr); + exit(127); + } + + FILE *f = fopen("/proc/self/attr/exec", "wx"); + if (!f) + priverr("open /proc/self/attr/exec for apparmor"); + + if (fprintf(f, "changeprofile %s", label) < 0 || fflush(f) != 0 || fclose(f) != 0) + priverr("write /proc/self/attr/exec for apparmor"); +} + +int main(int argc, char *argv[]) +{ + enum { + NNP = 256, + RUID = 256, + EUID, + RGID, + EGID, + REUID, + REGID, + CLEAR_GROUPS, + KEEP_GROUPS, + GROUPS, + INHCAPS, + LISTCAPS, + CAPBSET, + SECUREBITS, + SELINUX_LABEL, + APPARMOR_PROFILE + }; + + static const struct option longopts[] = { + { "dump", no_argument, 0, 'd' }, + { "nnp", no_argument, 0, NNP }, + { "inh-caps", required_argument, 0, INHCAPS }, + { "list-caps", no_argument, 0, LISTCAPS }, + { "ruid", required_argument, 0, RUID }, + { "euid", required_argument, 0, EUID }, + { "rgid", required_argument, 0, RGID }, + { "egid", required_argument, 0, EGID }, + { "reuid", required_argument, 0, REUID }, + { "regid", required_argument, 0, REGID }, + { "clear-groups", no_argument, 0, CLEAR_GROUPS }, + { "keep-groups", no_argument, 0, KEEP_GROUPS }, + { "groups", required_argument, 0, GROUPS }, + { "bounding-set", required_argument, 0, CAPBSET }, + { "securebits", required_argument, 0, SECUREBITS }, + { "selinux-label", required_argument, 0, SELINUX_LABEL }, + { "apparmor-profile", required_argument, 0, APPARMOR_PROFILE }, + { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'V'}, + { NULL, 0, 0, 0 } + }; + + int c; + struct options opts; + int dumplevel = 0, total_opts = 0; + bool list_caps = false; + + setlocale(LC_MESSAGES, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + memset(&opts, 0, sizeof(opts)); + + while((c = getopt_long(argc, argv, "+dhV", longopts, NULL)) != -1) { + total_opts++; + + if (RUID <= c && c <= REGID) { + /* This is easier than six independent cases. */ + char *end; + errno = 0; + long val = strtol(optarg, &end, 10); + if (!*optarg || errno || *end) + complain(_("Failed to parse uid or gid")); + + if (c == REUID) { + for (int i = 0; i < 2; i++) { + if (opts.have_uid[i]) + complain(_("Duplicate uid")); + opts.have_uid[i] = true; + opts.uid[i] = val; + if (opts.uid[i] != val) + complain(_("uid out of range")); + } + } else if (c == REGID) { + for (int i = 0; i < 2; i++) { + if (opts.have_gid[i]) + complain(_("Duplicate gid")); + opts.have_gid[i] = true; + opts.gid[i] = val; + if (opts.gid[i] != val) + complain(_("gid out of range")); + } + } else { + bool *have = (c <= EUID ? &opts.have_uid[c-RUID] + : &opts.have_gid[c-RGID]); + if (*have) + complain(_("Duplicate uid or gid")); + *have = true; + if (c <= EUID) + opts.uid[c-RUID] = val; + else + opts.gid[c-RGID] = val; + if ((c <= EUID ? opts.uid[c-RUID] : opts.gid[c-RGID]) != + val) + complain(_("uid or gid out of range")); + } + + continue; + } + + switch(c) { + case 'd': + dumplevel++; + break; + case NNP: + if (opts.nnp) + complain(_("Duplicate --nnp option")); + opts.nnp = true; + break; + case CLEAR_GROUPS: + if (opts.clear_groups) + complain(_("Duplicate --clear-groups option")); + opts.clear_groups = true; + break; + case KEEP_GROUPS: + if (opts.keep_groups) + complain(_("Duplicate --keep-groups option")); + opts.keep_groups = true; + break; + case GROUPS: + if (opts.have_groups) + complain(_("Duplicate --groups option")); + parse_groups(&opts, optarg); + break; + case LISTCAPS: + list_caps = true; + break; + case INHCAPS: + if (opts.caps_to_inherit) + complain(_("Duplicate --caps option")); + opts.caps_to_inherit = optarg; + break; + case CAPBSET: + if (opts.bounding_set) + complain(_("Duplicate --bounding-set option")); + opts.bounding_set = optarg; + break; + case SECUREBITS: + if (opts.have_securebits) + complain(_("Duplicate --securebits option")); + parse_securebits(&opts, optarg); + break; + case SELINUX_LABEL: + if (opts.selinux_label) + complain(_("Duplicate --selinux-label option")); + opts.selinux_label = optarg; + break; + case APPARMOR_PROFILE: + if (opts.apparmor_profile) + complain(_("Duplicate --apparmor-profile option")); + opts.apparmor_profile = optarg; + break; + case 'h': + usage(EXIT_SUCCESS); + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + default: + complain(_("Unrecognized option '%c'\n"), c); + } + } + + if (dumplevel) { + if (total_opts != dumplevel || optind < argc) + complain(_("--dump is incompatible with all other options")); + dump(dumplevel); + return 0; + } + + if (list_caps) { + if (total_opts != 1 || optind < argc) + complain(_("--list-caps must be specified alone")); + list_known_caps(); + return 0; + } + + if (optind >= argc) + complain(_("No program specified")); + + if ((opts.have_gid[0] || opts.have_gid[1]) + && !opts.keep_groups && !opts.clear_groups && !opts.have_groups) + complain(_("--[re]gid requires --keep-groups, --clear-groups, or --groups")); + + if ((int)opts.keep_groups + (int)opts.clear_groups + (int)opts.have_groups > 1) + complain(_("Too many of --keep-groups, --clear-groups, or --groups")); + + if (opts.nnp) { + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) + err(EXIT_FAILURE, _("PR_SET_NO_NEW_PRIVS failed")); + } + + if (opts.selinux_label) + do_selinux_label(opts.selinux_label); + if (opts.apparmor_profile) + do_apparmor_profile(opts.apparmor_profile); + + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) + err(EXIT_FAILURE, _("PR_SET_KEEPCAPS failed")); + + // We're going to want CAP_SETPCAP, CAP_SETUID, and CAP_SETGID if possible. + bump_cap(CAP_SETPCAP); + bump_cap(CAP_SETUID); + bump_cap(CAP_SETGID); + if (capng_apply(CAPNG_SELECT_CAPS) != 0) + priverr("activate capabilities"); + + for (int i = 0; i < 2; i++) { + if (opts.have_uid[i]) { + do_setresuid(&opts); + + /* KEEPCAPS doesn't work for the effective mask. */ + if (capng_apply(CAPNG_SELECT_CAPS) != 0) + priverr("reactivate capabilities"); + break; + } + } + + for (int i = 0; i < 2; i++) { + if (opts.have_gid[i]) { + do_setresgid(&opts); + break; + } + } + + if (opts.have_groups) { + if (setgroups(opts.num_groups, opts.groups) != 0) + priverr("setgroups"); + } else if (opts.clear_groups) { + gid_t x = 0; + if (setgroups(0, &x) != 0) + priverr("setgroups"); + } + + if (opts.have_securebits) { + if (prctl(PR_SET_SECUREBITS, opts.securebits, 0, 0, 0) != 0) + priverr("PR_SET_SECUREBITS"); + } + + if (opts.bounding_set) { + do_caps(CAPNG_BOUNDING_SET, opts.bounding_set); + errno = EPERM; /* capng doesn't set errno if we're missing CAP_SETPCAP */ + if (capng_apply(CAPNG_SELECT_BOUNDS) != 0) + priverr("apply bounding set"); + } + + if (opts.caps_to_inherit) { + do_caps(CAPNG_INHERITABLE, opts.caps_to_inherit); + if (capng_apply(CAPNG_SELECT_CAPS) != 0) + priverr("apply capabilities"); + } + + execvp(argv[optind], argv + optind); + + err(127, _("exec %s failed"), argv[optind]); +} -- 1.7.11.7 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH] Add setpriv, a tool to set privileges and such 2012-12-08 8:19 ` [PATCH] Add setpriv, a tool to set privileges and such Andy Lutomirski @ 2012-12-08 16:23 ` Ángel González 2012-12-08 19:04 ` Andy Lutomirski 2012-12-09 22:24 ` Pádraig Brady ` (2 subsequent siblings) 3 siblings, 1 reply; 21+ messages in thread From: Ángel González @ 2012-12-08 16:23 UTC (permalink / raw) To: Andy Lutomirski; +Cc: util-linux, Pádraig Brady, Karel Zak On 08/12/12 09:19, Andy Lutomirski wrote: > This can set no_new_privs, uid, gid, groups, securebits, inheritable caps, the cap > bounding set, securebits, and selinux and apparmor labels. > > Signed-off-by: Andy Lutomirski <luto@amacapital.net> > --- The no_new_privs can only be set with --nnp, which is not a very self-documenting name. I would also add a long name --no-new-privs for that option. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] Add setpriv, a tool to set privileges and such 2012-12-08 16:23 ` Ángel González @ 2012-12-08 19:04 ` Andy Lutomirski 0 siblings, 0 replies; 21+ messages in thread From: Andy Lutomirski @ 2012-12-08 19:04 UTC (permalink / raw) To: Ángel González; +Cc: util-linux, Pádraig Brady, Karel Zak On Sat, Dec 8, 2012 at 8:23 AM, Ángel González <ingenit@zoho.com> wrote: > On 08/12/12 09:19, Andy Lutomirski wrote: >> This can set no_new_privs, uid, gid, groups, securebits, inheritable caps, the cap >> bounding set, securebits, and selinux and apparmor labels. >> >> Signed-off-by: Andy Lutomirski <luto@amacapital.net> >> --- > > The no_new_privs can only be set with --nnp, which is not a very > self-documenting name. > I would also add a long name --no-new-privs for that option. > Fixed, along with two silly bugs. --Andy > > -- Andy Lutomirski AMA Capital Management, LLC ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] Add setpriv, a tool to set privileges and such 2012-12-08 8:19 ` [PATCH] Add setpriv, a tool to set privileges and such Andy Lutomirski 2012-12-08 16:23 ` Ángel González @ 2012-12-09 22:24 ` Pádraig Brady 2012-12-09 23:12 ` Andy Lutomirski 2013-01-08 8:31 ` Karel Zak 2013-01-14 15:58 ` [PATCH v2] " Andy Lutomirski 3 siblings, 1 reply; 21+ messages in thread From: Pádraig Brady @ 2012-12-09 22:24 UTC (permalink / raw) To: Andy Lutomirski; +Cc: util-linux, Ángel González, Karel Zak On 12/08/2012 08:19 AM, Andy Lutomirski wrote: > This can set no_new_privs, uid, gid, groups, securebits, inheritable caps, the cap > bounding set, securebits, and selinux and apparmor labels. Thanks a lot for doing this. > +.BR \--securebits=(+|-)securebit,... > +Sets or clears securebits. The valid securebits are \fInoroot\fP, \fInoroot_locked\fP, > +\fIno_setuid_fixup\fP, \fIno_setuid_fixup_locked\fP, and \fIkeep_caps_locked\fP. > +\fIkeep_caps\fP is cleared by > +.BR execve (2) > +and is therefore not allowed. It might be good to at least mention this is in relation to capabilities and add a cross reference to cap_ng(3) > + > +.TP > +.BR \--selinux-label > +Requests a particular SELinux transition (using a transition on exec, not dyntrans). > +This will fail and cause > +.BR setpriv (1) > +to abort if SELinux is not in use, and the transition may be ignored or cause > +.BR execve (2) > +to fail at SELinux's whim. (In particular, this is unlikely to work in conjunction > +with \fIno_new_privs\fP.) In general it could be good to reference specific tools that can do the same thing. runcon(1) in this case. > +.TP > +.BR \-h , " \-\-help" > +Print a help message, > +.SH NOTES > +If applying any specified option fails, \fIprogram\fP will not be run and > +\fIsetpriv\fP will return with exit code 127. It seems worth standardising on error. Most commands that exec on behalf of another use something like the following, which I snarfed from timeout(1): EXIT_CANCELED 125 internal error EXIT_CANNOT_INVOKE 126 error executing job EXIT_ENOENT 127 couldn't find job to exec So I suppose you could use 125 if there was an error setting an option, so that an exec wasn't even tried. thanks again! Pádraig. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] Add setpriv, a tool to set privileges and such 2012-12-09 22:24 ` Pádraig Brady @ 2012-12-09 23:12 ` Andy Lutomirski 0 siblings, 0 replies; 21+ messages in thread From: Andy Lutomirski @ 2012-12-09 23:12 UTC (permalink / raw) To: Pádraig Brady; +Cc: util-linux, Ángel González, Karel Zak On Sun, Dec 9, 2012 at 2:24 PM, Pádraig Brady <P@draigbrady.com> wrote: > On 12/08/2012 08:19 AM, Andy Lutomirski wrote: > >> +.BR \--securebits=(+|-)securebit,... >> +Sets or clears securebits. The valid securebits are \fInoroot\fP, >> \fInoroot_locked\fP, >> +\fIno_setuid_fixup\fP, \fIno_setuid_fixup_locked\fP, and >> \fIkeep_caps_locked\fP. >> +\fIkeep_caps\fP is cleared by >> +.BR execve (2) >> +and is therefore not allowed. > > > It might be good to at least mention this is in relation to > capabilities and add a cross reference to cap_ng(3) Agreed. > > >> + >> +.TP >> +.BR \--selinux-label >> +Requests a particular SELinux transition (using a transition on exec, not >> dyntrans). >> +This will fail and cause >> +.BR setpriv (1) >> +to abort if SELinux is not in use, and the transition may be ignored or >> cause >> +.BR execve (2) >> +to fail at SELinux's whim. (In particular, this is unlikely to work in >> conjunction >> +with \fIno_new_privs\fP.) > > > In general it could be good to reference specific tools > that can do the same thing. runcon(1) in this case. > Hmm. I'll do that. Admittedly, this functionality is not really needed here given that runcon exists, but it's certainly a reasonable thing to do when adjusting privilege. (OTOH, the selinux reference policy is extremely stingy about granting transition and entrypoint privileges, so it's not terribly useful.) > >> +.TP >> +.BR \-h , " \-\-help" >> +Print a help message, >> +.SH NOTES >> +If applying any specified option fails, \fIprogram\fP will not be run and >> +\fIsetpriv\fP will return with exit code 127. > > > It seems worth standardising on error. > Most commands that exec on behalf of another use something like > the following, which I snarfed from timeout(1): > > EXIT_CANCELED 125 internal error > EXIT_CANNOT_INVOKE 126 error executing job > EXIT_ENOENT 127 couldn't find job to exec > > So I suppose you could use 125 if there was an error setting an option, > so that an exec wasn't even tried. Will do. --Andy ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] Add setpriv, a tool to set privileges and such 2012-12-08 8:19 ` [PATCH] Add setpriv, a tool to set privileges and such Andy Lutomirski 2012-12-08 16:23 ` Ángel González 2012-12-09 22:24 ` Pádraig Brady @ 2013-01-08 8:31 ` Karel Zak 2013-01-14 15:33 ` Andy Lutomirski 2013-01-14 15:58 ` [PATCH v2] " Andy Lutomirski 3 siblings, 1 reply; 21+ messages in thread From: Karel Zak @ 2013-01-08 8:31 UTC (permalink / raw) To: Andy Lutomirski; +Cc: util-linux, Pádraig Brady, Ángel González Sorry for the delay, I had 3 weeks vacation. On Sat, Dec 08, 2012 at 12:19:12AM -0800, Andy Lutomirski wrote: > +if BUILD_SETPRIV > +usrbin_exec_PROGRAMS += setpriv > +dist_man_MANS += sys-utils/setpriv.1 > +setpriv_SOURCES = sys-utils/setpriv.c > +setpriv_LDADD = -lcap-ng > +endif Maybe the caps support should be optional, anyway you need to check for the library in the configure script. > +static void complain(const char *fmt, ...) > +{ > + va_list ap; > + va_start(ap, fmt); > + vfprintf(stderr, fmt, ap); > + va_end(ap); > + fputc('\n', stderr); > + usage(EXIT_FAILURE); > +} seems like poor reimplementation of err() from err.h :-) > +// Returns the number of capabilities printed please use /* comment */ > +static void stdout_perror(const char *prefix) > +{ > + if (errno < 0 || errno >= sys_nerr) > + printf(_("%s: error %d\n"), prefix, errno); > + else > + printf(_("%s: %s\n"), prefix, sys_errlist[errno]); > +} warnx() from err.h > +static void dump_label(const char *name) > +{ > + int fd = open("/proc/self/attr/current", O_RDONLY); > + if (fd == -1) { > + stdout_perror(name); > + return; > + } > + > + char buf[4097]; > + ssize_t len = read(fd, buf, sizeof(buf)); > + int e = errno; we usually have declarations at the begin of block (function)... > +struct options { ...and structs at the begin of the file. The name "options" seems also too generic (like anything for getopt_long), what about struct privcxt { } or so. > bool nnp; don't use bool in util-linux, within structs you can use bit arrays. > // real and effective (in that order) > bool have_uid[2]; > uid_t uid[2]; > bool have_gid[2]; > gid_t gid[2]; it would be more readable to use uid_t ruid; uid_t euid; unsigned have_ruid:1, have_euid:1; (and the same for groups) rather than depend on an order. > +static void priverr(const char *str) > +{ > + perror(str); > + exit(127); > +} err(127, str); it would be also nice to have a macro (SETPRV_EX_* ?) in the code rather than the magic number. > + > +static void parse_groups(struct options *opts, const char *str) > +{ > + char *groups = strdup(str); > + char *buf = groups; /* We'll reuse it */ > + char *c; > + > + opts->have_groups = true; > + opts->num_groups = 0; > + while ((c = strsep(&groups, ",")) != 0) > + opts->num_groups++; > + > + // Start again > + strcpy(buf, str); // It's exactly the right length > + groups = buf; > + > + opts->groups = calloc(opts->num_groups, sizeof(gid_t)); > + size_t i = 0; > + while ((c = strsep(&groups, ",")) != 0) { > + char *end; > + errno = 0; > + long val = strtol(c, &end, 10); strtol_or_err() (include/strutils.h) > + if (!*c || *end || errno || val != (long long)(gid_t)val) > + complain(_("Invalid supplementary group id")); > + opts->groups[i++] = (gid_t)val; > + } > + > + free(groups);; > +} > + > +int main(int argc, char *argv[]) > +{ > + enum { > + NNP = 256, SETPRIV_OPT_NNP = CHAR_MAX + 1 > + RUID = 256, > + EUID, > + RGID, > + EGID, > + REUID, > + REGID, > + CLEAR_GROUPS, > + KEEP_GROUPS, > + GROUPS, > + INHCAPS, > + LISTCAPS, > + CAPBSET, > + SECUREBITS, > + SELINUX_LABEL, > + APPARMOR_PROFILE > + }; [...] > + if (RUID <= c && c <= REGID) { > + /* This is easier than six independent cases. */ > + char *end; > + errno = 0; > + long val = strtol(optarg, &end, 10); > + if (!*optarg || errno || *end) > + complain(_("Failed to parse uid or gid")); > + > + if (c == REUID) { if (opts.have_euid) err(EXIT_FAILURE, _("duplicate euid"); opts.have_euid = 1; opts.euid = strtol_or_err(optarg, _("failed to parse euid"); ... no loop, no arrays for uids, just readable code. [..] > + if (dumplevel) { > + if (total_opts != dumplevel || optind < argc) > + complain(_("--dump is incompatible with all other options")); > + dump(dumplevel); > + return 0; this is main(), so return EXIT_SUCCES; Karel -- Karel Zak <kzak@redhat.com> http://karelzak.blogspot.com ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] Add setpriv, a tool to set privileges and such 2013-01-08 8:31 ` Karel Zak @ 2013-01-14 15:33 ` Andy Lutomirski 0 siblings, 0 replies; 21+ messages in thread From: Andy Lutomirski @ 2013-01-14 15:33 UTC (permalink / raw) To: Karel Zak; +Cc: util-linux, Pádraig Brady, Ángel González On Tue, Jan 8, 2013 at 12:31 AM, Karel Zak <kzak@redhat.com> wrote: > > Sorry for the delay, I had 3 weeks vacation. > > On Sat, Dec 08, 2012 at 12:19:12AM -0800, Andy Lutomirski wrote: >> +if BUILD_SETPRIV >> +usrbin_exec_PROGRAMS += setpriv >> +dist_man_MANS += sys-utils/setpriv.1 >> +setpriv_SOURCES = sys-utils/setpriv.c >> +setpriv_LDADD = -lcap-ng >> +endif > > Maybe the caps support should be optional, anyway you need to check > for the library in the configure script. I'll make the whole thing depend on libcap-ng for now. Making the dependency optional would require extra (careful) thought to remove the "reactivate capabilities" part after calling setresuid. > >> +static void complain(const char *fmt, ...) >> +{ >> + va_list ap; >> + va_start(ap, fmt); >> + vfprintf(stderr, fmt, ap); >> + va_end(ap); >> + fputc('\n', stderr); >> + usage(EXIT_FAILURE); >> +} > > seems like poor reimplementation of err() from err.h :-) I didn't know about that. Neat! (Although I changed all complain callers to use errx instead of err - errno makes no sense there.) > >> +// Returns the number of capabilities printed > > please use /* comment */ Done. > >> +static void stdout_perror(const char *prefix) >> +{ >> + if (errno < 0 || errno >= sys_nerr) >> + printf(_("%s: error %d\n"), prefix, errno); >> + else >> + printf(_("%s: %s\n"), prefix, sys_errlist[errno]); >> +} > > warnx() from err.h > Done. >> +static void dump_label(const char *name) >> +{ >> + int fd = open("/proc/self/attr/current", O_RDONLY); >> + if (fd == -1) { >> + stdout_perror(name); >> + return; >> + } >> + >> + char buf[4097]; >> + ssize_t len = read(fd, buf, sizeof(buf)); >> + int e = errno; > > we usually have declarations at the begin of block (function)... Done. > >> +struct options { > > ...and structs at the begin of the file. The name "options" seems > also too generic (like anything for getopt_long), what about > > struct privcxt { > > } > Done <irrelevant>Why do some people like cxt and other like ctx?</irrelevant> > or so. > >> bool nnp; > > don't use bool in util-linux, within structs you can use bit arrays. There do seem to be other users of stdbool in util-linux (in login-utils and lib/mbsalign.c). Do you object to true and false or just to bool? > >> // real and effective (in that order) >> bool have_uid[2]; >> uid_t uid[2]; >> bool have_gid[2]; >> gid_t gid[2]; > > it would be more readable to use > > uid_t ruid; > uid_t euid; > > unsigned have_ruid:1, > have_euid:1; > > (and the same for groups) rather than depend on an order. Done. > >> +static void priverr(const char *str) >> +{ >> + perror(str); >> + exit(127); >> +} > > err(127, str); > > it would be also nice to have a macro (SETPRV_EX_* ?) in the code > rather than the magic number. Done. I called it SETPRIV_EXIT_CANT_SET. > >> + >> +static void parse_groups(struct options *opts, const char *str) >> +{ >> + char *groups = strdup(str); >> + char *buf = groups; /* We'll reuse it */ >> + char *c; >> + >> + opts->have_groups = true; >> + opts->num_groups = 0; >> + while ((c = strsep(&groups, ",")) != 0) >> + opts->num_groups++; >> + >> + // Start again >> + strcpy(buf, str); // It's exactly the right length >> + groups = buf; >> + >> + opts->groups = calloc(opts->num_groups, sizeof(gid_t)); >> + size_t i = 0; >> + while ((c = strsep(&groups, ",")) != 0) { >> + char *end; >> + errno = 0; >> + long val = strtol(c, &end, 10); > > strtol_or_err() (include/strutils.h) > >> + if (!*c || *end || errno || val != (long long)(gid_t)val) >> + complain(_("Invalid supplementary group id")); >> + opts->groups[i++] = (gid_t)val; >> + } >> + >> + free(groups);; >> +} >> + > >> +int main(int argc, char *argv[]) >> +{ >> + enum { >> + NNP = 256, > > SETPRIV_OPT_NNP = CHAR_MAX + 1 > >> + RUID = 256, >> + EUID, >> + RGID, >> + EGID, >> + REUID, >> + REGID, >> + CLEAR_GROUPS, >> + KEEP_GROUPS, >> + GROUPS, >> + INHCAPS, >> + LISTCAPS, >> + CAPBSET, >> + SECUREBITS, >> + SELINUX_LABEL, >> + APPARMOR_PROFILE >> + }; > > [...] > >> + if (RUID <= c && c <= REGID) { >> + /* This is easier than six independent cases. */ >> + char *end; >> + errno = 0; >> + long val = strtol(optarg, &end, 10); >> + if (!*optarg || errno || *end) >> + complain(_("Failed to parse uid or gid")); >> + >> + if (c == REUID) { > > if (opts.have_euid) > err(EXIT_FAILURE, _("duplicate euid"); > opts.have_euid = 1; > opts.euid = strtol_or_err(optarg, _("failed to parse euid"); > > ... no loop, no arrays for uids, just readable code. > > [..] Done. > >> + if (dumplevel) { >> + if (total_opts != dumplevel || optind < argc) >> + complain(_("--dump is incompatible with all other options")); >> + dump(dumplevel); >> + return 0; > > > this is main(), so return EXIT_SUCCES; Done. ^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v2] Add setpriv, a tool to set privileges and such 2012-12-08 8:19 ` [PATCH] Add setpriv, a tool to set privileges and such Andy Lutomirski ` (2 preceding siblings ...) 2013-01-08 8:31 ` Karel Zak @ 2013-01-14 15:58 ` Andy Lutomirski 2013-01-26 14:29 ` [PATCH] setpriv: run a program with different Linux privilege settings Sami Kerola 3 siblings, 1 reply; 21+ messages in thread From: Andy Lutomirski @ 2013-01-14 15:58 UTC (permalink / raw) To: util-linux Cc: Pádraig Brady, Ángel González, Karel Zak, Andy Lutomirski This can set no_new_privs, uid, gid, groups, securebits, inheritable caps, the cap bounding set, securebits, and selinux and apparmor labels. Signed-off-by: Andy Lutomirski <luto@amacapital.net> --- For now, this still uses stdbool. I can change that. I also removed priverr completely. Changes from v1: - Check for libcap-ng in configure - Use err, errx, etc. as appropriate - Use strtol_or_err - Rename struct options to struct privctx and move it to the top - Improve behavior if new capabilities are added to the kernel - Fix some option parsing breakage (I used 256 twice) - Use CHAR_MAX+1 instead of 256 - Remove silly arrays for reuid and regid - Exit with code 1 instead of 127 if execve fails - Accept --no-new-privs - Improve man page Known issues: - There's no period after runcon(1) in setpriv.1. Anyone know how to add one? - The return codes are still 1 for general errors (including execve failure) and 127 for failures to set privileges. Is this okay? .gitignore | 1 + configure.ac | 16 + sys-utils/Makemodule.am | 7 + sys-utils/setpriv.1 | 135 +++++++++ sys-utils/setpriv.c | 778 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 937 insertions(+) create mode 100644 sys-utils/setpriv.1 create mode 100644 sys-utils/setpriv.c diff --git a/.gitignore b/.gitignore index e85eb07..b3ed077 100644 --- a/.gitignore +++ b/.gitignore @@ -146,6 +146,7 @@ tests/run.sh.trs /script /scriptreplay /setarch +/setpriv /setsid /setterm /sfdisk diff --git a/configure.ac b/configure.ac index 727113a..17f4f0c 100644 --- a/configure.ac +++ b/configure.ac @@ -866,6 +866,22 @@ if test "x$build_unshare" = xyes; then fi +dnl +dnl setpriv depends on libcap-ng. It would be possible to build +dnl a version of setpriv with limited functionality without libcap-ng, +dnl but this isn't currently supported. +dnl +UL_CHECK_LIB([cap-ng], [capng_apply], [cap_ng]) +AC_ARG_ENABLE([setpriv], + AS_HELP_STRING([--disable-setpriv], [do not build setpriv]), + [], enable_setpriv=check +) +UL_BUILD_INIT([setpriv]) +UL_REQUIRES_LINUX([setpriv]) +UL_REQUIRES_HAVE([setpriv], [cap_ng], [libcap-ng]) +AM_CONDITIONAL(BUILD_SETPRIV, test "x$build_setpriv" = xyes) + + AC_ARG_ENABLE([arch], AS_HELP_STRING([--enable-arch], [do build arch]), [], enable_arch=no diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am index c7b1eb3..1e8adc7 100644 --- a/sys-utils/Makemodule.am +++ b/sys-utils/Makemodule.am @@ -309,3 +309,10 @@ if HAVE_AUDIT hwclock_LDADD += -laudit endif endif # BUILD_HWCLOCK + +if BUILD_SETPRIV +usrbin_exec_PROGRAMS += setpriv +dist_man_MANS += sys-utils/setpriv.1 +setpriv_SOURCES = sys-utils/setpriv.c +setpriv_LDADD = $(LDADD) -lcap-ng libcommon.la +endif diff --git a/sys-utils/setpriv.1 b/sys-utils/setpriv.1 new file mode 100644 index 0000000..94227d3 --- /dev/null +++ b/sys-utils/setpriv.1 @@ -0,0 +1,135 @@ +.\" Process this file with +.\" groff -man -Tascii no_new_privs.1 +.\" +.TH SETPRIV 1 "December 2012" "util-linux" "User Commands" +.SH NAME +setpriv \- run program with different Linux privilege settings +.SH SYNOPSIS +.B setpriv +.RI [ options ] +program +.RI [ arguments ] +.SH DESCRIPTION +Sets or queries various Linux privilege settings that are inherited across +.BR execve (2) +. +.SH OPTION + +.TP +.BR \-d,\ --dump +Dumps current privilege state. Specify more than once to show extra, mostly useless, +information. Incompatible with all other options. + +.TP +.BR \--nnp,\ --no-new-privs +Sets the \fIno_new_privs\fP bit. With this bit set, +.BR execve (2) +will not grant new privileges. For example, the setuid and setgid bits as +well as file capabilities will be disabled. (Executing binaries with these +bits set will still work, but they will not gain privilege. Certain LSMs, +especially AppArmor, may result in failures to execute certain programs.) +This bit is inherited by child processes and cannot be unset. See +.BR prctl (2) +and +.IR Documentation/prctl/no_new_privs.txt +in the Linux kernel source. + +The no_new_privs bit is supported since Linux 3.5. + +.TP +.BR \--inh-caps=(+|-)cap,...\ or\ --bounding-set=(+|-)cap,... +Sets inheritable capabilities or capability bounding set. See +.BR capabilities (7). +The argument is a comma-separated list of +cap and -cap entries, +which add or remove an entry respectively. +all and -all can be used +to add or remove all caps. The set of capabilities starts out as +the current inheritable set for --inh-caps and the current bounding set +for --bounding-set. If you drop something from the bounding set +without also dropping it from the inheritable set, you are likely +to become confused. Don't do that. + +.TP +.BR \--list-caps +Lists all known capabilities. Must be specified alone. + +.TP +.BR \--ruid,\ --euid,\ --reuid +Sets the real, effective, or both UIDs. + +Setting uid or gid does not change capabilities, although the exec +call at the end might change capabilities. This means that, if you +are root, you probably want to do something like: + +--reuid=1000 --regid=1000 --caps=-all + +.TP +.BR \--rgid,\ --egid,\ --regid +Sets the real, effective, or both GIDs. + +For safety, you must specify one of --keep-groups, --clear-groups, or --groups if you +set any primary GID. + +.TP +.BR \--clear-groups +Clears supplementary groups. + +.TP +.BR \--keep-groups +Preserves supplementary groups. Only useful in conjunction with --rgid, --egid, +or --regid. + +.TP +.BR \--groups=group,... +Sets supplementary groups. + +.TP +.BR \--securebits=(+|-)securebit,... +Sets or clears securebits. The valid securebits are \fInoroot\fP, \fInoroot_locked\fP, +\fIno_setuid_fixup\fP, \fIno_setuid_fixup_locked\fP, and \fIkeep_caps_locked\fP. +\fIkeep_caps\fP is cleared by +.BR execve (2) +and is therefore not allowed. + +.TP +.BR \--selinux-label +Requests a particular SELinux transition (using a transition on exec, not dyntrans). +This will fail and cause +.BR setpriv (1) +to abort if SELinux is not in use, and the transition may be ignored or cause +.BR execve (2) +to fail at SELinux's whim. (In particular, this is unlikely to work in conjunction +with \fIno_new_privs\fP.) + +This is similar to +.BR runcon (1) + +.TP +.BR \--apparmor-profile +Requests a particular AppArmor profile (using a transition on exec). +This will fail and cause +.BR setpriv (1) +to abort if AppArmor is not in use, and the transition may be ignored or cause +.BR execve (2) +to fail at AppArmor's whim. + +.TP +.BR \-h , " \-\-help" +Print a help message, +.SH NOTES +If applying any specified option fails, \fIprogram\fP will not be run and +\fIsetpriv\fP will return with exit code 127. + +Be careful with this tool -- it may have unexpected security consequences. +For example, setting no_new_privs and then execing a program that is +SELinux-confined (as this tool would do) may prevent the SELinux +restrictions from taking effect. +.SH SEE ALSO +.BR prctl (2) +.BR capability (7) +.SH BUS +None known so far. +.SH AUTHOR +Andy Lutomirski <luto@amacapital.net> +.SH AVAILABILITY +The \fIsetpriv\fP command is part of the util-linux package and is available from +ftp://ftp.kernel.org/pub/linux/utils/util-linux/. diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c new file mode 100644 index 0000000..d1f04f2 --- /dev/null +++ b/sys-utils/setpriv.c @@ -0,0 +1,778 @@ +/* + * setpriv(1) - set various kernel privilege bits and run something + * + * Copyright (C) 2012 Andy Lutomirski <luto@amacapital.net> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <unistd.h> +#include <sys/prctl.h> +#include <linux/securebits.h> +#include <grp.h> +#include <stdarg.h> +#include <cap-ng.h> +#include "strutils.h" + +#include "nls.h" +#include "c.h" +#include "closestream.h" + +#ifndef PR_SET_NO_NEW_PRIVS +# define PR_SET_NO_NEW_PRIVS 38 +#endif +#ifndef PR_GET_NO_NEW_PRIVS +# define PR_GET_NO_NEW_PRIVS 39 +#endif + +#define SETPRIV_EXIT_PRIVERR 127 /* how we exit when we fail to set privs */ + +/* + * Note: we are subject to https://bugzilla.redhat.com/show_bug.cgi?id=895105 + * and we will therefore have problems if new capabilities are added. + * Once that bug is fixed, I'll submit a corresponding fix to setpriv. In + * the mean time, the code here tries to work reasonably well. + */ + +struct privctx { + bool nnp; + + /* real and effective (in that order) */ + bool have_ruid, have_euid, have_rgid, have_egid; + uid_t ruid, euid; + gid_t rgid, egid; + + /* supplementary groups */ + bool have_groups, keep_groups, clear_groups; + size_t num_groups; + gid_t *groups; + + /* caps */ + const char *caps_to_inherit; + const char *bounding_set; + + /* securebits */ + bool have_securebits; + int securebits; + + /* LSMs */ + const char *selinux_label; + const char *apparmor_profile; +}; + +static void usage(int status) +{ + FILE *out = status == EXIT_SUCCESS ? stdout : stderr; + + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %s [options] <program> [args...]\n"), program_invocation_short_name); + fputs(_(" -d, --dump show current state (and don't exec anything)\n" + " --nnp, --no-new-privs set no_new_privs\n" + " --inh-caps=caps set inheritable capabilities\n" + " --bounding-set=caps set capability bounding set\n" + " --ruid, --euid set real or effective uid respectively\n" + " --rgid, --egid set real or effective gid respectively\n" + " --reuid, --regid set real and effective uid or gid\n" + " --clear-groups clear supplementary groups\n" + " --keep-groups keep supplementary groups\n" + " --groups=group,... set supplementary groups\n" + " --securebits=bits set securebits\n" + " --selinux-label set SELinux label (requires process:transition)\n" + " --apparmor-profile set AppArmor profile (requires onexec permission)\n" + "\n" + " This tool can be dangerous. Be careful and read the manpage."), out); + + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + fprintf(out, USAGE_MAN_TAIL("setpriv(1)")); + + exit(status); +} + +static int real_cap_last_cap(void) +{ + /* CAP_LAST_CAP is untrustworthy. */ + + static int ret = -1; + int matched; + + if (ret != -1) + return ret; + + FILE *f = fopen("/proc/sys/kernel/cap_last_cap", "r"); + if (!f) { + ret = CAP_LAST_CAP; /* guess */ + return ret; + } + + matched = fscanf(f, "%d", &ret); + fclose(f); + + if (matched != 1) + ret = CAP_LAST_CAP; /* guess */ + + return ret; +} + +/* Returns the number of capabilities printed. */ +static int print_caps(FILE *f, capng_type_t which) +{ + int n = 0; + for (int i = 0; i <= real_cap_last_cap(); i++) { + if (capng_have_capability(which, i)) { + const char *name = capng_capability_to_name(i); + if (n) + fputc(',', f); + if (name) { + fputs(name, f); + } else { + /* + * cap-ng has very poor handling of + * CAP_LAST_CAP changes. This is the best + * we can do. + */ + printf("cap_%d", i); + } + n++; + } + } + return n; +} + +static void dump_one_secbit(bool *first, int *bits, int bit, const char *name) +{ + if (*bits & bit) { + if (!*first) + printf(","); + else + *first = false; + fputs(name, stdout); + *bits &= ~bit; + } +} + +static void dump_securebits(void) +{ + int bits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0); + if (bits < 0) { + warnx("PR_GET_SECUREBITS"); + return; + } + + printf("Securebits: "); + + bool first = true; + dump_one_secbit(&first, &bits, SECBIT_NOROOT, "noroot"); + dump_one_secbit(&first, &bits, SECBIT_NOROOT_LOCKED, "noroot_locked"); + dump_one_secbit(&first, &bits, SECBIT_NO_SETUID_FIXUP, + "no_setuid_fixup"); + dump_one_secbit(&first, &bits, + SECBIT_NO_SETUID_FIXUP_LOCKED, "no_setuid_fixup_locked"); + bits &= ~SECBIT_KEEP_CAPS; + dump_one_secbit(&first, &bits, SECBIT_KEEP_CAPS_LOCKED, + "keep_caps_locked"); + if (bits) { + if (!first) + printf(","); + else + first = false; + printf("0x%x", (unsigned)bits); + } + + if (first) + printf("[none]\n"); + else + printf("\n"); +} + +static void dump_label(const char *name) +{ + char buf[4097]; + ssize_t len; + int fd, e; + + fd = open("/proc/self/attr/current", O_RDONLY); + if (fd == -1) { + warnx(name); + return; + } + + len = read(fd, buf, sizeof(buf)); + e = errno; + close(fd); + errno = e; + if (len < 0) { + warnx(name); + return; + } + if ((size_t)len >= sizeof(buf) - 1) { + printf(_("%s: too long\n"), name); + return; + } + + buf[len] = 0; + if (len > 0 && buf[len-1] == '\n') + buf[len-1] = 0; + printf(_("%s: %s\n"), name, buf); +} + +static void dump_groups(void) +{ + int n = getgroups(0, 0); + if (n < 0) { + warnx("getgroups"); + return; + } + + gid_t *groups = alloca(n * sizeof(gid_t)); + n = getgroups(n, groups); + if (n < 0) { + warnx("getgroups"); + return; + } + + printf("Supplementary groups: "); + if (n == 0) { + printf("[none]"); + } else { + for (int i = 0; i < n; i++) { + if (i > 0) + printf(","); + printf("%ld", (long)groups[i]); + } + } + printf("\n"); +} + +static void dump(int dumplevel) +{ + int x; + + uid_t ru, eu, su; + if (getresuid(&ru, &eu, &su) == 0) { + printf("uid: %ld\n", (long)ru); + printf("euid: %ld\n", (long)eu); + /* Saved and fs uids always equal euid. */ + if (dumplevel >= 3) + printf("suid: %ld\n", (long)su); + } else { + warnx("getresuid"); + } + + gid_t rg, eg, sg; + if (getresgid(&rg, &eg, &sg) == 0) { + printf("gid: %ld\n", (long)rg); + printf("egid: %ld\n", (long)eg); + /* Saved and fs gids always equal egid. */ + if (dumplevel >= 3) + printf("sgid: %ld\n", (long)sg); + } else { + warnx("getresgid"); + } + + dump_groups(); + + x = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0); + if (x >= 0) + printf("no_new_privs: %d\n", x); + else + warnx("no_new_privs"); + + if (dumplevel >= 2) { + printf(_("Effective capabilities: ")); + if (print_caps(stdout, CAPNG_EFFECTIVE) == 0) + printf("[none]"); + printf("\n"); + + printf(_("Permitted capabilities: ")); + if (print_caps(stdout, CAPNG_PERMITTED) == 0) + printf("[none]"); + printf("\n"); + } + + printf(_("Inheritable capabilities: ")); + if (print_caps(stdout, CAPNG_INHERITABLE) == 0) + printf("[none]"); + printf("\n"); + + printf(_("Capability bounding set: ")); + if (print_caps(stdout, CAPNG_BOUNDING_SET) == 0) + printf("[none]"); + printf("\n"); + + dump_securebits(); + + if (access("/sys/fs/selinux", F_OK) == 0) + dump_label("SELinux label"); + + if (access("/sys/kernel/security/apparmor", F_OK) == 0) { + dump_label("AppArmor profile"); + } +} + +static void list_known_caps(void) +{ + for (int i = 0; i <= real_cap_last_cap(); i++) { + const char *name = capng_capability_to_name(i); + if (name) + printf("%s\n", name); + else + printf("cap %d [cap-ng broken]\n", i); + } +} + +static void parse_groups(struct privctx *opts, const char *str) +{ + char *groups = strdup(str); + char *buf = groups; /* We'll reuse it */ + char *c; + + opts->have_groups = true; + opts->num_groups = 0; + while ((c = strsep(&groups, ",")) != 0) + opts->num_groups++; + + /* Start again */ + strcpy(buf, str); /* It's exactly the right length */ + groups = buf; + + opts->groups = calloc(opts->num_groups, sizeof(gid_t)); + size_t i = 0; + while ((c = strsep(&groups, ",")) != 0) { + opts->groups[i++] = (gid_t)strtol_or_err(c, + _("Invalid supplementary group id")); + } + + free(groups); +} + +static void do_setresuid(const struct privctx *opts) +{ + uid_t ruid, euid, suid; + if (getresuid(&ruid, &euid, &suid) != 0) + err(SETPRIV_EXIT_PRIVERR, _("getresuid failed")); + if (opts->have_ruid) + ruid = opts->ruid; + if (opts->have_euid) + euid = opts->euid; + + /* Also copy effective to saved (for paranoia). */ + if (setresuid(ruid, euid, euid) != 0) + err(SETPRIV_EXIT_PRIVERR, _("setresuid failed")); +} + +static void do_setresgid(const struct privctx *opts) +{ + gid_t rgid, egid, sgid; + if (getresgid(&rgid, &egid, &sgid) != 0) + err(SETPRIV_EXIT_PRIVERR, _("getresgid failed")); + if (opts->have_rgid) + rgid = opts->rgid; + if (opts->have_egid) + egid = opts->egid; + + /* Also copy effective to saved (for paranoia). */ + if (setresgid(rgid, egid, egid) != 0) + err(SETPRIV_EXIT_PRIVERR, _("setresgid failed")); +} + +static void bump_cap(unsigned int cap) +{ + if (capng_have_capability(CAPNG_PERMITTED, cap)) + capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, cap); +} + +static void do_caps(capng_type_t type, const char *caps) +{ + char *my_caps = strdup(caps); /* Don't bother checking for error */ + + char *c; + while ((c = strsep(&my_caps, ",")) != 0) { + capng_act_t action; + if (*c == '+') + action = CAPNG_ADD; + else if (*c == '-') + action = CAPNG_DROP; + else + errx(EXIT_FAILURE, _("Bad capability string")); + + if (!strcmp(c+1, "all")) { + /* + * It would be really bad if -all didn't drop all + * caps. It's better to just fail. + */ + if (real_cap_last_cap() > CAP_LAST_CAP) + errx(SETPRIV_EXIT_PRIVERR, _("libcap-ng is too old for \"all\" caps")); + for (int i = 0; i <= CAP_LAST_CAP; i++) + capng_update(action, type, i); + } else { + int cap = capng_name_to_capability(c+1); + if (cap >= 0) + capng_update(action, type, cap); + else + errx(EXIT_FAILURE, _("Unknown capability \"%s\""), c+1); + } + } + + free(my_caps); +} + +static void parse_securebits(struct privctx *opts, const char *arg) +{ + opts->have_securebits = true; + opts->securebits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0); + if (opts->securebits < 0) + err(SETPRIV_EXIT_PRIVERR, _("PR_GET_SECUREBITS")); + + if (opts->securebits & ~(int)(SECBIT_NOROOT | SECBIT_NOROOT_LOCKED | + SECBIT_NO_SETUID_FIXUP | SECBIT_NO_SETUID_FIXUP_LOCKED | + SECBIT_KEEP_CAPS | SECBIT_KEEP_CAPS_LOCKED)) + { + errx(SETPRIV_EXIT_PRIVERR, _("Unrecognized securebit is set -- refusing to adjust")); + } + + char *buf = strdup(arg); + + char *c; + while ((c = strsep(&buf, ",")) != 0) { + if (*c != '+' && *c != '-') + errx(EXIT_FAILURE, _("Bad securebits string")); + + if (!strcmp(c+1, "all")) { + if (*c == '-') + opts->securebits = 0; + else + errx(EXIT_FAILURE, _("+all securebits is not allowed")); + } else { + int bit; + if (!strcmp(c+1, "noroot")) + bit = SECBIT_NOROOT; + else if (!strcmp(c+1, "noroot_locked")) + bit = SECBIT_NOROOT_LOCKED; + else if (!strcmp(c+1, "no_setuid_fixup")) + bit = SECBIT_NO_SETUID_FIXUP; + else if (!strcmp(c+1, "no_setuid_fixup_locked")) + bit = SECBIT_NO_SETUID_FIXUP_LOCKED; + else if (!strcmp(c+1, "keep_caps")) + errx(EXIT_FAILURE, _("Adjusting keep_caps makes no sense")); + else if (!strcmp(c+1, "keep_caps_locked")) + bit = SECBIT_KEEP_CAPS_LOCKED; /* sigh */ + else + errx(EXIT_FAILURE, _("Unrecognized securebit")); + + if (*c == '+') + opts->securebits |= bit; + else + opts->securebits &= ~bit; + } + } + + opts->securebits |= SECBIT_KEEP_CAPS; /* We need it, and it's reset on exec */ + + free(buf); +} + +static void do_selinux_label(const char *label) +{ + if (access("/sys/fs/selinux", F_OK) != 0) + errx(SETPRIV_EXIT_PRIVERR, _("SELinux is not running")); + + int fd = open("/proc/self/attr/exec", O_RDWR); + if (fd == -1) + err(SETPRIV_EXIT_PRIVERR, _("open /proc/self/attr/exec for selinux")); + + size_t len = strlen(label); + errno = 0; + if (write(fd, label, len) != (ssize_t)len) + err(SETPRIV_EXIT_PRIVERR, _("write /proc/self/attr/exec for selinux")); + + close(fd); +} + +static void do_apparmor_profile(const char *label) +{ + if (access("/sys/kernel/security/apparmor", F_OK) != 0) + errx(SETPRIV_EXIT_PRIVERR, _("AppArmor is not running")); + + FILE *f = fopen("/proc/self/attr/exec", "wx"); + if (!f) + err(SETPRIV_EXIT_PRIVERR, _("open /proc/self/attr/exec for apparmor")); + + if (fprintf(f, "changeprofile %s", label) < 0 || fflush(f) != 0 || fclose(f) != 0) + err(SETPRIV_EXIT_PRIVERR, _("write /proc/self/attr/exec for apparmor")); +} + +int main(int argc, char *argv[]) +{ + enum { + NNP = CHAR_MAX + 1, + RUID, + EUID, + RGID, + EGID, + REUID, + REGID, + CLEAR_GROUPS, + KEEP_GROUPS, + GROUPS, + INHCAPS, + LISTCAPS, + CAPBSET, + SECUREBITS, + SELINUX_LABEL, + APPARMOR_PROFILE + }; + + static const struct option longopts[] = { + { "dump", no_argument, 0, 'd' }, + { "nnp", no_argument, 0, NNP }, + { "no-new-privs", no_argument, 0, NNP }, + { "inh-caps", required_argument, 0, INHCAPS }, + { "list-caps", no_argument, 0, LISTCAPS }, + { "ruid", required_argument, 0, RUID }, + { "euid", required_argument, 0, EUID }, + { "rgid", required_argument, 0, RGID }, + { "egid", required_argument, 0, EGID }, + { "reuid", required_argument, 0, REUID }, + { "regid", required_argument, 0, REGID }, + { "clear-groups", no_argument, 0, CLEAR_GROUPS }, + { "keep-groups", no_argument, 0, KEEP_GROUPS }, + { "groups", required_argument, 0, GROUPS }, + { "bounding-set", required_argument, 0, CAPBSET }, + { "securebits", required_argument, 0, SECUREBITS }, + { "selinux-label", required_argument, 0, SELINUX_LABEL }, + { "apparmor-profile", required_argument, 0, APPARMOR_PROFILE }, + { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'V'}, + { NULL, 0, 0, 0 } + }; + + int c; + struct privctx opts; + int dumplevel = 0, total_opts = 0; + bool list_caps = false; + + setlocale(LC_MESSAGES, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + memset(&opts, 0, sizeof(opts)); + + while((c = getopt_long(argc, argv, "+dhV", longopts, NULL)) != -1) { + total_opts++; + switch(c) { + case 'd': + dumplevel++; + break; + case NNP: + if (opts.nnp) + errx(EXIT_FAILURE, _("Duplicate --no-new-privs option")); + opts.nnp = true; + break; + case RUID: + if (opts.have_ruid) + errx(EXIT_FAILURE, _("duplicate ruid")); + opts.have_ruid = true; + opts.ruid = strtol_or_err(optarg, + _("failed to parse ruid")); + break; + case EUID: + if (opts.have_euid) + errx(EXIT_FAILURE, _("duplicate euid")); + opts.have_euid = true; + opts.euid = strtol_or_err(optarg, + _("failed to parse euid")); + break; + case REUID: + if (opts.have_ruid || opts.have_euid) + errx(EXIT_FAILURE, _("duplicate ruid or euid")); + opts.have_ruid = opts.have_euid = true; + opts.ruid = opts.euid = strtol_or_err(optarg, + _("failed to parse reuid")); + break; + case RGID: + if (opts.have_rgid) + errx(EXIT_FAILURE, _("duplicate rgid")); + opts.have_rgid = true; + opts.rgid = strtol_or_err(optarg, + _("failed to parse rgid")); + break; + case EGID: + if (opts.have_egid) + errx(EXIT_FAILURE, _("duplicate egid")); + opts.have_egid = true; + opts.egid = strtol_or_err(optarg, + _("failed to parse egid")); + break; + case REGID: + if (opts.have_rgid || opts.have_egid) + errx(EXIT_FAILURE, _("duplicate rgid or egid")); + opts.have_rgid = opts.have_egid = true; + opts.rgid = opts.egid = strtol_or_err(optarg, + _("failed to parse regid")); + break; + case CLEAR_GROUPS: + if (opts.clear_groups) + errx(EXIT_FAILURE, _("Duplicate --clear-groups option")); + opts.clear_groups = true; + break; + case KEEP_GROUPS: + if (opts.keep_groups) + errx(EXIT_FAILURE, _("Duplicate --keep-groups option")); + opts.keep_groups = true; + break; + case GROUPS: + if (opts.have_groups) + errx(EXIT_FAILURE, _("Duplicate --groups option")); + parse_groups(&opts, optarg); + break; + case LISTCAPS: + list_caps = true; + break; + case INHCAPS: + if (opts.caps_to_inherit) + errx(EXIT_FAILURE, _("Duplicate --caps option")); + opts.caps_to_inherit = optarg; + break; + case CAPBSET: + if (opts.bounding_set) + errx(EXIT_FAILURE, _("Duplicate --bounding-set option")); + opts.bounding_set = optarg; + break; + case SECUREBITS: + if (opts.have_securebits) + errx(EXIT_FAILURE, _("Duplicate --securebits option")); + parse_securebits(&opts, optarg); + break; + case SELINUX_LABEL: + if (opts.selinux_label) + errx(EXIT_FAILURE, _("Duplicate --selinux-label option")); + opts.selinux_label = optarg; + break; + case APPARMOR_PROFILE: + if (opts.apparmor_profile) + errx(EXIT_FAILURE, _("Duplicate --apparmor-profile option")); + opts.apparmor_profile = optarg; + break; + case 'h': + usage(EXIT_SUCCESS); + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case '?': + usage(EXIT_FAILURE); + default: + errx(EXIT_FAILURE, _("Unrecognized option '%c'\n"), c); + } + } + + if (dumplevel) { + if (total_opts != dumplevel || optind < argc) + errx(EXIT_FAILURE, _("--dump is incompatible with all other options")); + dump(dumplevel); + return EXIT_SUCCESS; + } + + if (list_caps) { + if (total_opts != 1 || optind < argc) + errx(EXIT_FAILURE, _("--list-caps must be specified alone")); + list_known_caps(); + return EXIT_SUCCESS; + } + + if (optind >= argc) + errx(EXIT_FAILURE, _("No program specified")); + + if ((opts.have_rgid || opts.have_egid) + && !opts.keep_groups && !opts.clear_groups && !opts.have_groups) + errx(EXIT_FAILURE, _("--[re]gid requires --keep-groups, --clear-groups, or --groups")); + + if ((int)opts.keep_groups + (int)opts.clear_groups + + (int)opts.have_groups > 1) + errx(EXIT_FAILURE, _("Too many of --keep-groups, --clear-groups, or --groups")); + + if (opts.nnp) { + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) + err(EXIT_FAILURE, _("PR_SET_NO_NEW_PRIVS failed")); + } + + if (opts.selinux_label) + do_selinux_label(opts.selinux_label); + if (opts.apparmor_profile) + do_apparmor_profile(opts.apparmor_profile); + + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) + err(EXIT_FAILURE, _("PR_SET_KEEPCAPS failed")); + + /* + * We're going to want CAP_SETPCAP, CAP_SETUID, and CAP_SETGID + * if possible. + */ + bump_cap(CAP_SETPCAP); + bump_cap(CAP_SETUID); + bump_cap(CAP_SETGID); + if (capng_apply(CAPNG_SELECT_CAPS) != 0) + err(SETPRIV_EXIT_PRIVERR, _("activate capabilities")); + + if (opts.have_ruid || opts.have_euid) { + do_setresuid(&opts); + + /* KEEPCAPS doesn't work for the effective mask. */ + if (capng_apply(CAPNG_SELECT_CAPS) != 0) + err(SETPRIV_EXIT_PRIVERR, _("reactivate capabilities")); + } + + if (opts.have_rgid || opts.have_egid) + do_setresgid(&opts); + + if (opts.have_groups) { + if (setgroups(opts.num_groups, opts.groups) != 0) + err(SETPRIV_EXIT_PRIVERR, _("setgroups")); + } else if (opts.clear_groups) { + gid_t x = 0; + if (setgroups(0, &x) != 0) + err(SETPRIV_EXIT_PRIVERR, _("setgroups")); + } + + if (opts.have_securebits) { + if (prctl(PR_SET_SECUREBITS, opts.securebits, 0, 0, 0) != 0) + err(SETPRIV_EXIT_PRIVERR, _("PR_SET_SECUREBITS")); + } + + if (opts.bounding_set) { + do_caps(CAPNG_BOUNDING_SET, opts.bounding_set); + errno = EPERM; /* capng doesn't set errno if we're missing CAP_SETPCAP */ + if (capng_apply(CAPNG_SELECT_BOUNDS) != 0) + err(SETPRIV_EXIT_PRIVERR, _("apply bounding set")); + } + + if (opts.caps_to_inherit) { + do_caps(CAPNG_INHERITABLE, opts.caps_to_inherit); + if (capng_apply(CAPNG_SELECT_CAPS) != 0) + err(SETPRIV_EXIT_PRIVERR, _("apply capabilities")); + } + + execvp(argv[optind], argv + optind); + + err(EXIT_FAILURE, _("exec %s failed"), argv[optind]); +} -- 1.7.11.7 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH] setpriv: run a program with different Linux privilege settings 2013-01-14 15:58 ` [PATCH v2] " Andy Lutomirski @ 2013-01-26 14:29 ` Sami Kerola 2013-02-04 20:20 ` Andy Lutomirski 0 siblings, 1 reply; 21+ messages in thread From: Sami Kerola @ 2013-01-26 14:29 UTC (permalink / raw) To: util-linux; +Cc: kerolasa, Andy Lutomirski From: Andy Lutomirski <luto@amacapital.net> This new command can set no_new_privs, uid, gid, groups, securebits, inheritable caps, the cap bounding set, securebits, and selinux and apparmor labels. [kerolasa@iki.fi: a lot of small adjustment making the command to be a good fit in util-linux project] Signed-off-by: Sami Kerola <kerolasa@iki.fi> Signed-off-by: Andy Lutomirski <luto@amacapital.net> --- Changes since Andy Lutomirski's V2 at Mon, 14 Jan 2013. - Align man page with Documentation/howto-man-page.txt - Clean up usage output. - Re-use messages making translators to work less. - Make some messages easier to understand. - Drop bool usage, use bit fields instead. - Fix smatch warnings. - Remove unnecessary braces where possible. - Tidy few code style issues. .gitignore | 1 + configure.ac | 14 + sys-utils/Makemodule.am | 7 + sys-utils/setpriv.1 | 149 +++++++++ sys-utils/setpriv.c | 814 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 985 insertions(+) create mode 100644 sys-utils/setpriv.1 create mode 100644 sys-utils/setpriv.c diff --git a/.gitignore b/.gitignore index b2e9d6d..7dbb81c 100644 --- a/.gitignore +++ b/.gitignore @@ -150,6 +150,7 @@ tests/run.sh.trs /script /scriptreplay /setarch +/setpriv /setsid /setterm /sfdisk diff --git a/configure.ac b/configure.ac index 9024809..d3a8e9e 100644 --- a/configure.ac +++ b/configure.ac @@ -879,6 +879,20 @@ if test "x$build_nsenter" = xyes; then AC_CHECK_FUNCS([setns]) fi +dnl setpriv depends on libcap-ng. It would be possible to build +dnl a version of setpriv with limited functionality without libcap-ng, +dnl but this isn't currently supported. +UL_CHECK_LIB([cap-ng], [capng_apply], [cap_ng]) +AC_ARG_ENABLE([setpriv], + AS_HELP_STRING([--disable-setpriv], [do not build setpriv]), + [], enable_setpriv=check +) +UL_BUILD_INIT([setpriv]) +UL_REQUIRES_LINUX([setpriv]) +UL_REQUIRES_HAVE([setpriv], [cap_ng], [libcap-ng]) +AM_CONDITIONAL(BUILD_SETPRIV, test "x$build_setpriv" = xyes) + + AC_ARG_ENABLE([arch], AS_HELP_STRING([--enable-arch], [do build arch]), [], enable_arch=no diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am index 978d97f..86c529e 100644 --- a/sys-utils/Makemodule.am +++ b/sys-utils/Makemodule.am @@ -318,3 +318,10 @@ if HAVE_AUDIT hwclock_LDADD += -laudit endif endif # BUILD_HWCLOCK + +if BUILD_SETPRIV +usrbin_exec_PROGRAMS += setpriv +dist_man_MANS += sys-utils/setpriv.1 +setpriv_SOURCES = sys-utils/setpriv.c +setpriv_LDADD = $(LDADD) -lcap-ng libcommon.la +endif diff --git a/sys-utils/setpriv.1 b/sys-utils/setpriv.1 new file mode 100644 index 0000000..c56d89f --- /dev/null +++ b/sys-utils/setpriv.1 @@ -0,0 +1,149 @@ +.TH SETPRIV 1 "January 2013" "util-linux" "User Commands" +.SH NAME +setpriv \- run a program with different Linux privilege settings +.SH SYNOPSIS +.B setpriv +.RI [ options ] +program +.RI [ arguments ] +.SH DESCRIPTION +Sets or queries various Linux privilege settings that are inherited across +.BR execve (2). +.SH OPTION +.TP +\fB\-d\fR, \fB\-\-dump\fR +Dumps current privilege state. Specify more than once to show extra, mostly +useless, information. Incompatible with all other options. +.TP +\fB\-\-no\-new\-privs\fR +Sets the +.I no_\:new_\:privs +bit. With this bit set, +.BR execve (2) +will not grant new privileges. For example, the setuid and setgid bits as well +as file capabilities will be disabled. (Executing binaries with these bits set +will still work, but they will not gain privilege. Certain LSMs, especially +AppArmor, may result in failures to execute certain programs.) This bit is +inherited by child processes and cannot be unset. See +.BR prctl (2) +and +.IR Documentation/\:prctl/\:no_\:new_\:privs.txt +in the Linux kernel source. +.IP +The no_\:new_\:privs bit is supported since Linux 3.5. +.TP +\fB\-\-inh\-caps\fR \fI(+|\-)cap\fR,\fI...\fR or \fB\-\-bounding\-set\fR \fI(+|\-)cap\fR,\fI...\fR +Sets inheritable capabilities or capability bounding set. See +.BR capabilities (7). +The argument is a comma-separated list of +.I +cap +and +.I \-cap +entries, which add or remove an entry respectively. +.I +all +and +.I \-all +can be used to add or remove all caps. The set of capabilities starts out as +the current inheritable set for +.B \-\-\:inh\-\:caps +and the current bounding set for +.BR \-\-\:bounding\-\:set . +If you drop something from the bounding set without also dropping it from the +inheritable set, you are likely to become confused. Do not do that. +.TP +.BR \-\-list\-caps +Lists all known capabilities. Must be specified alone. +.TP +\fB\-\-ruid\fR \fIuid\fR, \fB\-\-euid\fR \fIuid\fR, \fB\-\-reuid\fR \fIuid\fR +Sets the real, effective, or both \fIuid\fRs. +.IP +Setting +.I uid +or +.I gid +does not change capabilities, although the exec call at the end might change +capabilities. This means that, if you are root, you probably want to do +something like: +.IP +\-\-reuid=1000 \-\-\:regid=1000 \-\-\:caps=\-\:all +.TP +\fB\-\-rgid\fR \fIgid\fR, \fB\-\-egid\fR \fIgid\fR, \fB\-\-regid\fR \fIgid\fR +Sets the real, effective, or both \fIgid\fRs. +.IP +For safety, you must specify one of \-\-\:keep\-\:groups, +\-\-\:clear\-\:groups, or \-\-\:groups if you set any primary +.IR gid . +.TP +.BR \-\-clear\-groups +Clears supplementary groups. +.TP +\fB\-\-keep\-groups\fR +Preserves supplementary groups. Only useful in conjunction with \-\-rgid, +\-\-egid, or \-\-regid. +.TP +\fB\-\-groups\fR \fIgroup\fR,\fI...\fR +Sets supplementary groups. +.TP +\fB\-\-securebits\fR \fI(+|\-)securebit\fR,\fI...\fR +Sets or clears securebits. The valid securebits are +.IR noroot , +.IR noroot_\:locked , +.IR no_\:setuid_\:fixup , +.IR no_\:setuid_\:fixup_\:locked , +and +.IR keep_\:caps_\:locked . +.I keep_\:caps +is cleared by +.BR execve (2) +and is therefore not allowed. +.TP +\fB\-\-selinux\-label\fR \fIlabel\fR +Requests a particular SELinux transition (using a transition on exec, not +dyntrans). This will fail and cause +.BR setpriv (1) +to abort if SELinux is not in use, and the transition may be ignored or cause +.BR execve (2) +to fail at SELinux's whim. (In particular, this is unlikely to work in +conjunction with +.IR no_\:new_\:privs .) +This is similar to +.BR runcon (1). +.TP +\fB\-\-apparmor\-profile\fR \fIprofile\fR +Requests a particular AppArmor profile (using a transition on exec). This will +fail and cause +.BR setpriv (1) +to abort if AppArmor is not in use, and the transition may be ignored or cause +.BR execve (2) +to fail at AppArmor's whim. +.TP +\fB\-V\fR, \fB\-\-version\fR +Display version information and exit. +.TP +\fB\-h\fR, \fB\-\-help\fR +Display help and exit. +.SH NOTES +If applying any specified option fails, +.I program +will not be run and +.B setpriv +will return with exit code 127. +.PP +Be careful with this tool \-\- it may have unexpected security consequences. +For example, setting no_\:new_\:privs and then execing a program that is +SELinux\-\:confined (as this tool would do) may prevent the SELinux +restrictions from taking effect. +.SH SEE ALSO +.BR prctl (2) +.BR capability (7) +.SH AUTHOR +.MT luto@amacapital.net +Andy Lutomirski +.ME +.SH AVAILABILITY +The +.B setpriv +command is part of the util-linux package and is available from +.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ +Linux Kernel Archive +.UE . diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c new file mode 100644 index 0000000..1662daf --- /dev/null +++ b/sys-utils/setpriv.c @@ -0,0 +1,814 @@ +/* + * setpriv(1) - set various kernel privilege bits and run something + * + * Copyright (C) 2012 Andy Lutomirski <luto@amacapital.net> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <cap-ng.h> +#include <errno.h> +#include <getopt.h> +#include <grp.h> +#include <linux/securebits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/prctl.h> +#include <unistd.h> + +#include "bitops.h" +#include "c.h" +#include "closestream.h" +#include "nls.h" +#include "optutils.h" +#include "strutils.h" +#include "xalloc.h" + +#ifndef PR_SET_NO_NEW_PRIVS +# define PR_SET_NO_NEW_PRIVS 38 +#endif +#ifndef PR_GET_NO_NEW_PRIVS +# define PR_GET_NO_NEW_PRIVS 39 +#endif + +#define SETPRIV_EXIT_PRIVERR 127 /* how we exit when we fail to set privs */ + +/* + * Note: We are subject to https://bugzilla.redhat.com/show_bug.cgi?id=895105 + * and we will therefore have problems if new capabilities are added. Once + * that bug is fixed, I'll (Andy Lutomirski) submit a corresponding fix to + * setpriv. In the mean time, the code here tries to work reasonably well. + */ + +struct privctx { + /* bit arrays -- see include/bitops.h */ + unsigned int + nnp:1, /* no_new_privs */ + have_ruid:1, /* real uid */ + have_euid:1, /* effective uid */ + have_rgid:1, /* real gid */ + have_egid:1, /* effective gid */ + have_groups:1, /* add groups */ + keep_groups:1, /* keep groups */ + clear_groups:1, /* remove groups */ + have_securebits:1; /* remove groups */ + + /* uids and gids */ + uid_t ruid, euid; + gid_t rgid, egid; + + /* supplementary groups */ + size_t num_groups; + gid_t *groups; + + /* caps */ + const char *caps_to_inherit; + const char *bounding_set; + + /* securebits */ + int securebits; + + /* LSMs */ + const char *selinux_label; + const char *apparmor_profile; +}; + +static void __attribute__((__noreturn__)) usage(FILE *out) +{ + fputs(USAGE_HEADER, out); + fprintf(out, _(" %s [options] <program> [args...]\n"), program_invocation_short_name); + fputs(USAGE_OPTIONS, out); + fputs(_(" -d, --dump show current state (and do not exec anything)\n"), out); + fputs(_(" --nnp, --no-new-privs disallow granting new privileges\n"), out); + fputs(_(" --inh-caps <caps,...> set inheritable capabilities\n"), out); + fputs(_(" --bounding-set <caps> set capability bounding set\n"), out); + fputs(_(" --ruid <uid> set real uid\n"), out); + fputs(_(" --euid <uid> set effective uid\n"), out); + fputs(_(" --rgid <gid> set real gid\n"), out); + fputs(_(" --egid <gid> set effective gid\n"), out); + fputs(_(" --reuid <uid> set real and effective uid\n"), out); + fputs(_(" --regid <gid> set real and effective gid\n"), out); + fputs(_(" --clear-groups clear supplementary groups\n"), out); + fputs(_(" --keep-groups keep supplementary groups\n"), out); + fputs(_(" --groups <group,...> set supplementary groups\n"), out); + fputs(_(" --securebits <bits> set securebits\n"), out); + fputs(_(" --selinux-label <label> set SELinux label (requires process:transition)\n"), out); + fputs(_(" --apparmor-profile <pr> set AppArmor profile (requires onexec permission)\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(USAGE_HELP, out); + fputs(USAGE_VERSION, out); + fputs(USAGE_SEPARATOR, out); + fputs(_(" This tool can be dangerous. Read the manpage, and be careful.\n"), out); + fprintf(out, USAGE_MAN_TAIL("setpriv(1)")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +static int real_cap_last_cap(void) +{ + /* CAP_LAST_CAP is untrustworthy. */ + static int ret = -1; + int matched; + FILE *f; + + if (ret != -1) + return ret; + + f = fopen("/proc/sys/kernel/cap_last_cap", "r"); + if (!f) { + ret = CAP_LAST_CAP; /* guess */ + return ret; + } + + matched = fscanf(f, "%d", &ret); + fclose(f); + + if (matched != 1) + ret = CAP_LAST_CAP; /* guess */ + + return ret; +} + +/* Returns the number of capabilities printed. */ +static int print_caps(FILE *f, capng_type_t which) +{ + int i, n = 0, max = real_cap_last_cap(); + + for (i = 0; i <= max; i++) { + if (capng_have_capability(which, i)) { + const char *name = capng_capability_to_name(i); + if (n) + fputc(',', f); + if (name) + fputs(name, f); + else + /* cap-ng has very poor handling of + * CAP_LAST_CAP changes. This is the + * best we can do. */ + printf("cap_%d", i); + n++; + } + } + return n; +} + +static void dump_one_secbit(int *first, int *bits, int bit, const char *name) +{ + if (*bits & bit) { + if (!*first) + printf(","); + else + *first = 0; + fputs(name, stdout); + *bits &= ~bit; + } +} + +static void dump_securebits(void) +{ + int first = 1; + int bits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0); + + if (bits < 0) { + warnx(_("getting process secure bits failed")); + return; + } + + printf(_("Securebits: ")); + + dump_one_secbit(&first, &bits, SECBIT_NOROOT, "noroot"); + dump_one_secbit(&first, &bits, SECBIT_NOROOT_LOCKED, "noroot_locked"); + dump_one_secbit(&first, &bits, SECBIT_NO_SETUID_FIXUP, + "no_setuid_fixup"); + dump_one_secbit(&first, &bits, SECBIT_NO_SETUID_FIXUP_LOCKED, + "no_setuid_fixup_locked"); + bits &= ~SECBIT_KEEP_CAPS; + dump_one_secbit(&first, &bits, SECBIT_KEEP_CAPS_LOCKED, + "keep_caps_locked"); + if (bits) { + if (!first) + printf(","); + else + first = 0; + printf("0x%x", (unsigned)bits); + } + + if (first) + printf(_("[none]\n")); + else + printf("\n"); +} + +static void dump_label(const char *name) +{ + char buf[4097]; + ssize_t len; + int fd, e; + + fd = open("/proc/self/attr/current", O_RDONLY); + if (fd == -1) { + warnx(_("cannot open %s"), "/proc/self/attr/current"); + return; + } + + len = read(fd, buf, sizeof(buf)); + e = errno; + close(fd); + if (len < 0) { + errno = e; + warnx(_("read failed: %s"), name); + return; + } + if (sizeof(buf) - 1 <= (size_t)len) { + warnx(_("%s: too long"), name); + return; + } + + buf[len] = 0; + if (0 < len && buf[len - 1] == '\n') + buf[len - 1] = 0; + printf("%s: %s\n", name, buf); +} + +static void dump_groups(void) +{ + int n = getgroups(0, 0); + gid_t *groups; + if (n < 0) { + warn("getgroups failed"); + return; + } + + groups = alloca(n * sizeof(gid_t)); + n = getgroups(n, groups); + if (n < 0) { + warn("getgroups failed"); + return; + } + + printf(_("Supplementary groups: ")); + if (n == 0) + printf(_("[none]")); + else { + int i; + for (i = 0; i < n; i++) { + if (0 < i) + printf(","); + printf("%ld", (long)groups[i]); + } + } + printf("\n"); +} + +static void dump(int dumplevel) +{ + int x; + uid_t ru, eu, su; + gid_t rg, eg, sg; + + if (getresuid(&ru, &eu, &su) == 0) { + printf(_("uid: %u\n"), ru); + printf(_("euid: %u\n"), eu); + /* Saved and fs uids always equal euid. */ + if (3 <= dumplevel) + printf(_("suid: %u\n"), su); + } else + warn(_("getresuid failed")); + + if (getresgid(&rg, &eg, &sg) == 0) { + printf("gid: %ld\n", (long)rg); + printf("egid: %ld\n", (long)eg); + /* Saved and fs gids always equal egid. */ + if (dumplevel >= 3) + printf("sgid: %ld\n", (long)sg); + } else + warn(_("getresgid failed")); + + dump_groups(); + + x = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0); + if (0 <= x) + printf("no_new_privs: %d\n", x); + else + warn("setting no_new_privs failed"); + + if (2 <= dumplevel) { + printf(_("Effective capabilities: ")); + if (print_caps(stdout, CAPNG_EFFECTIVE) == 0) + printf(_("[none]")); + printf("\n"); + + printf(_("Permitted capabilities: ")); + if (print_caps(stdout, CAPNG_PERMITTED) == 0) + printf(_("[none]")); + printf("\n"); + } + + printf(_("Inheritable capabilities: ")); + if (print_caps(stdout, CAPNG_INHERITABLE) == 0) + printf(_("[none]")); + printf("\n"); + + printf(_("Capability bounding set: ")); + if (print_caps(stdout, CAPNG_BOUNDING_SET) == 0) + printf(_("[none]")); + printf("\n"); + + dump_securebits(); + + if (access("/sys/fs/selinux", F_OK) == 0) + dump_label(_("SELinux label")); + + if (access("/sys/kernel/security/apparmor", F_OK) == 0) { + dump_label(_("AppArmor profile")); + } +} + +static void list_known_caps(void) +{ + int i, max = real_cap_last_cap(); + + for (i = 0; i <= max; i++) { + const char *name = capng_capability_to_name(i); + if (name) + printf("%s\n", name); + else + warnx(_("cap %d: libcap-ng is broken"), i); + } +} + +static void parse_groups(struct privctx *opts, const char *str) +{ + char *groups = xstrdup(str); + char *buf = groups; /* We'll reuse it */ + char *c; + size_t i = 0; + + opts->have_groups = 1; + opts->num_groups = 0; + while ((c = strsep(&groups, ","))) + opts->num_groups++; + + /* Start again */ + strcpy(buf, str); /* It's exactly the right length */ + groups = buf; + + opts->groups = xcalloc(opts->num_groups, sizeof(gid_t)); + while ((c = strsep(&groups, ","))) + opts->groups[i++] = (gid_t) strtol_or_err(c, + _("Invalid supplementary group id")); + + free(groups); +} + +static void do_setresuid(const struct privctx *opts) +{ + uid_t ruid, euid, suid; + if (getresuid(&ruid, &euid, &suid) != 0) + err(SETPRIV_EXIT_PRIVERR, _("getresuid failed")); + if (opts->have_ruid) + ruid = opts->ruid; + if (opts->have_euid) + euid = opts->euid; + + /* Also copy effective to saved (for paranoia). */ + if (setresuid(ruid, euid, euid) != 0) + err(SETPRIV_EXIT_PRIVERR, _("setresuid failed")); +} + +static void do_setresgid(const struct privctx *opts) +{ + gid_t rgid, egid, sgid; + if (getresgid(&rgid, &egid, &sgid) != 0) + err(SETPRIV_EXIT_PRIVERR, _("getresgid failed")); + if (opts->have_rgid) + rgid = opts->rgid; + if (opts->have_egid) + egid = opts->egid; + + /* Also copy effective to saved (for paranoia). */ + if (setresgid(rgid, egid, egid) != 0) + err(SETPRIV_EXIT_PRIVERR, _("setresgid failed")); +} + +static void bump_cap(unsigned int cap) +{ + if (capng_have_capability(CAPNG_PERMITTED, cap)) + capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, cap); +} + +static void do_caps(capng_type_t type, const char *caps) +{ + char *my_caps = xstrdup(caps); + char *c; + + while ((c = strsep(&my_caps, ","))) { + capng_act_t action; + if (*c == '+') + action = CAPNG_ADD; + else if (*c == '-') + action = CAPNG_DROP; + else + errx(EXIT_FAILURE, _("bad capability string")); + + if (!strcmp(c + 1, "all")) { + int i; + /* It would be really bad if -all didn't drop all + * caps. It's better to just fail. */ + if (real_cap_last_cap() > CAP_LAST_CAP) + errx(SETPRIV_EXIT_PRIVERR, + _("libcap-ng is too old for \"all\" caps")); + for (i = 0; i <= CAP_LAST_CAP; i++) + capng_update(action, type, i); + } else { + int cap = capng_name_to_capability(c + 1); + if (0 <= cap) + capng_update(action, type, cap); + else + errx(EXIT_FAILURE, + _("unknown capability \"%s\""), c + 1); + } + } + + free(my_caps); +} + +static void parse_securebits(struct privctx *opts, const char *arg) +{ + char *buf = xstrdup(arg); + char *c; + + opts->have_securebits = 1; + opts->securebits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0); + if (opts->securebits < 0) + err(SETPRIV_EXIT_PRIVERR, _("getting process secure bits failed")); + + if (opts->securebits & ~(int)(SECBIT_NOROOT | + SECBIT_NOROOT_LOCKED | + SECBIT_NO_SETUID_FIXUP | + SECBIT_NO_SETUID_FIXUP_LOCKED | + SECBIT_KEEP_CAPS | + SECBIT_KEEP_CAPS_LOCKED)) + errx(SETPRIV_EXIT_PRIVERR, + _("unrecognized securebit set -- refusing to adjust")); + + while ((c = strsep(&buf, ","))) { + if (*c != '+' && *c != '-') + errx(EXIT_FAILURE, _("bad securebits string")); + + if (!strcmp(c + 1, "all")) { + if (*c == '-') + opts->securebits = 0; + else + errx(EXIT_FAILURE, + _("+all securebits is not allowed")); + } else { + int bit; + if (!strcmp(c + 1, "noroot")) + bit = SECBIT_NOROOT; + else if (!strcmp(c + 1, "noroot_locked")) + bit = SECBIT_NOROOT_LOCKED; + else if (!strcmp(c + 1, "no_setuid_fixup")) + bit = SECBIT_NO_SETUID_FIXUP; + else if (!strcmp(c + 1, "no_setuid_fixup_locked")) + bit = SECBIT_NO_SETUID_FIXUP_LOCKED; + else if (!strcmp(c + 1, "keep_caps")) + errx(EXIT_FAILURE, + _("adjusting keep_caps does not make sense")); + else if (!strcmp(c + 1, "keep_caps_locked")) + bit = SECBIT_KEEP_CAPS_LOCKED; /* sigh */ + else + errx(EXIT_FAILURE, _("unrecognized securebit")); + + if (*c == '+') + opts->securebits |= bit; + else + opts->securebits &= ~bit; + } + } + + opts->securebits |= SECBIT_KEEP_CAPS; /* We need it, and it's reset on exec */ + + free(buf); +} + +static void do_selinux_label(const char *label) +{ + int fd; + size_t len; + + if (access("/sys/fs/selinux", F_OK) != 0) + errx(SETPRIV_EXIT_PRIVERR, _("SELinux is not running")); + + fd = open("/proc/self/attr/exec", O_RDWR); + if (fd == -1) + err(SETPRIV_EXIT_PRIVERR, + _("cannot open %s"), "/proc/self/attr/exec"); + + len = strlen(label); + errno = 0; + if (write(fd, label, len) != (ssize_t) len) + err(SETPRIV_EXIT_PRIVERR, + _("write failed: %s"), "/proc/self/attr/exec"); + + close(fd); +} + +static void do_apparmor_profile(const char *label) +{ + FILE *f; + + if (access("/sys/kernel/security/apparmor", F_OK) != 0) + errx(SETPRIV_EXIT_PRIVERR, _("AppArmor is not running")); + + f = fopen("/proc/self/attr/exec", "wx"); + if (!f) + err(SETPRIV_EXIT_PRIVERR, + _("cannot open %s"), "/proc/self/attr/exec"); + + if (fprintf(f, "changeprofile %s", label) < 0 || fflush(f) != 0 + || fclose(f) != 0) + err(SETPRIV_EXIT_PRIVERR, + _("write failed: %s"), "/proc/self/attr/exec"); +} + +int main(int argc, char **argv) +{ + enum { + NNP = CHAR_MAX + 1, + RUID, + EUID, + RGID, + EGID, + REUID, + REGID, + CLEAR_GROUPS, + KEEP_GROUPS, + GROUPS, + INHCAPS, + LISTCAPS, + CAPBSET, + SECUREBITS, + SELINUX_LABEL, + APPARMOR_PROFILE + }; + + static const struct option longopts[] = { + {"dump", no_argument, 0, 'd'}, + {"nnp", no_argument, 0, NNP}, + {"no-new-privs", no_argument, 0, NNP}, + {"inh-caps", required_argument, 0, INHCAPS}, + {"list-caps", no_argument, 0, LISTCAPS}, + {"ruid", required_argument, 0, RUID}, + {"euid", required_argument, 0, EUID}, + {"rgid", required_argument, 0, RGID}, + {"egid", required_argument, 0, EGID}, + {"reuid", required_argument, 0, REUID}, + {"regid", required_argument, 0, REGID}, + {"clear-groups", no_argument, 0, CLEAR_GROUPS}, + {"keep-groups", no_argument, 0, KEEP_GROUPS}, + {"groups", required_argument, 0, GROUPS}, + {"bounding-set", required_argument, 0, CAPBSET}, + {"securebits", required_argument, 0, SECUREBITS}, + {"selinux-label", required_argument, 0, SELINUX_LABEL}, + {"apparmor-profile", required_argument, 0, APPARMOR_PROFILE}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + {NULL, 0, 0, 0} + }; + + static const ul_excl_t excl[] = { + /* keep in same order with enum definitions */ + {CLEAR_GROUPS, KEEP_GROUPS, GROUPS}, + {0} + }; + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; + + int c; + struct privctx opts; + int dumplevel = 0; + int total_opts = 0; + int list_caps = 0; + + setlocale(LC_MESSAGES, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + memset(&opts, 0, sizeof(opts)); + + while ((c = getopt_long(argc, argv, "+dhV", longopts, NULL)) != -1) { + err_exclusive_options(c, longopts, excl, excl_st); + total_opts++; + switch (c) { + case 'd': + dumplevel++; + break; + case NNP: + if (opts.nnp) + errx(EXIT_FAILURE, + _("duplicate --no-new-privs option")); + opts.nnp = 1; + break; + case RUID: + if (opts.have_ruid) + errx(EXIT_FAILURE, _("duplicate ruid")); + opts.have_ruid = 1; + opts.ruid = strtol_or_err(optarg, + _("failed to parse ruid")); + break; + case EUID: + if (opts.have_euid) + errx(EXIT_FAILURE, _("duplicate euid")); + opts.have_euid = 1; + opts.euid = strtol_or_err(optarg, + _("failed to parse euid")); + break; + case REUID: + if (opts.have_ruid || opts.have_euid) + errx(EXIT_FAILURE, _("duplicate ruid or euid")); + opts.have_ruid = opts.have_euid = 1; + opts.ruid = opts.euid = strtol_or_err(optarg, + _("failed to parse reuid")); + break; + case RGID: + if (opts.have_rgid) + errx(EXIT_FAILURE, _("duplicate rgid")); + opts.have_rgid = 1; + opts.rgid = strtol_or_err(optarg, + _("failed to parse rgid")); + break; + case EGID: + if (opts.have_egid) + errx(EXIT_FAILURE, _("duplicate egid")); + opts.have_egid = 1; + opts.egid = strtol_or_err(optarg, + _("failed to parse egid")); + break; + case REGID: + if (opts.have_rgid || opts.have_egid) + errx(EXIT_FAILURE, _("duplicate rgid or egid")); + opts.have_rgid = opts.have_egid = 1; + opts.rgid = opts.egid = strtol_or_err(optarg, + _("failed to parse regid")); + break; + case CLEAR_GROUPS: + if (opts.clear_groups) + errx(EXIT_FAILURE, + _("duplicate --clear-groups option")); + opts.clear_groups = 1; + break; + case KEEP_GROUPS: + if (opts.keep_groups) + errx(EXIT_FAILURE, + _("duplicate --keep-groups option")); + opts.keep_groups = 1; + break; + case GROUPS: + if (opts.have_groups) + errx(EXIT_FAILURE, + _("duplicate --groups option")); + parse_groups(&opts, optarg); + break; + case LISTCAPS: + list_caps = 1; + break; + case INHCAPS: + if (opts.caps_to_inherit) + errx(EXIT_FAILURE, + _("duplicate --caps option")); + opts.caps_to_inherit = optarg; + break; + case CAPBSET: + if (opts.bounding_set) + errx(EXIT_FAILURE, + _("duplicate --bounding-set option")); + opts.bounding_set = optarg; + break; + case SECUREBITS: + if (opts.have_securebits) + errx(EXIT_FAILURE, + _("duplicate --securebits option")); + parse_securebits(&opts, optarg); + break; + case SELINUX_LABEL: + if (opts.selinux_label) + errx(EXIT_FAILURE, + _("duplicate --selinux-label option")); + opts.selinux_label = optarg; + break; + case APPARMOR_PROFILE: + if (opts.apparmor_profile) + errx(EXIT_FAILURE, + _("duplicate --apparmor-profile option")); + opts.apparmor_profile = optarg; + break; + case 'h': + usage(stdout); + case 'V': + printf(UTIL_LINUX_VERSION); + return EXIT_SUCCESS; + case '?': + usage(stderr); + default: + errx(EXIT_FAILURE, _("unrecognized option '%c'"), c); + } + } + + if (dumplevel) { + if (total_opts != dumplevel || optind < argc) + errx(EXIT_FAILURE, + _("--dump is incompatible with all other options")); + dump(dumplevel); + return EXIT_SUCCESS; + } + + if (list_caps) { + if (total_opts != 1 || optind < argc) + errx(EXIT_FAILURE, + _("--list-caps must be specified alone")); + list_known_caps(); + return EXIT_SUCCESS; + } + + if (argc <= optind) + errx(EXIT_FAILURE, _("No program specified")); + + if ((opts.have_rgid || opts.have_egid) + && !opts.keep_groups && !opts.clear_groups && !opts.have_groups) + errx(EXIT_FAILURE, + _("--[re]gid requires --keep-groups, --clear-groups, or --groups")); + + if (opts.nnp) + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) + err(EXIT_FAILURE, _("disallow granting new privileges failed")); + + if (opts.selinux_label) + do_selinux_label(opts.selinux_label); + if (opts.apparmor_profile) + do_apparmor_profile(opts.apparmor_profile); + + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) + err(EXIT_FAILURE, _("keep process capabilities failed")); + + /* We're going to want CAP_SETPCAP, CAP_SETUID, and CAP_SETGID if + * possible. */ + bump_cap(CAP_SETPCAP); + bump_cap(CAP_SETUID); + bump_cap(CAP_SETGID); + if (capng_apply(CAPNG_SELECT_CAPS) != 0) + err(SETPRIV_EXIT_PRIVERR, _("activate capabilities")); + + if (opts.have_ruid || opts.have_euid) { + do_setresuid(&opts); + /* KEEPCAPS doesn't work for the effective mask. */ + if (capng_apply(CAPNG_SELECT_CAPS) != 0) + err(SETPRIV_EXIT_PRIVERR, _("reactivate capabilities")); + } + + if (opts.have_rgid || opts.have_egid) + do_setresgid(&opts); + + if (opts.have_groups) { + if (setgroups(opts.num_groups, opts.groups) != 0) + err(SETPRIV_EXIT_PRIVERR, _("setgroups failed")); + } else if (opts.clear_groups) { + gid_t x = 0; + if (setgroups(0, &x) != 0) + err(SETPRIV_EXIT_PRIVERR, _("setgroups failed")); + } + + if (opts.have_securebits) + if (prctl(PR_SET_SECUREBITS, opts.securebits, 0, 0, 0) != 0) + err(SETPRIV_EXIT_PRIVERR, _("set procecess securebits failed")); + + if (opts.bounding_set) { + do_caps(CAPNG_BOUNDING_SET, opts.bounding_set); + errno = EPERM; /* capng doesn't set errno if we're missing CAP_SETPCAP */ + if (capng_apply(CAPNG_SELECT_BOUNDS) != 0) + err(SETPRIV_EXIT_PRIVERR, _("apply bounding set")); + } + + if (opts.caps_to_inherit) { + do_caps(CAPNG_INHERITABLE, opts.caps_to_inherit); + if (capng_apply(CAPNG_SELECT_CAPS) != 0) + err(SETPRIV_EXIT_PRIVERR, _("apply capabilities")); + } + + execvp(argv[optind], argv + optind); + + err(EXIT_FAILURE, _("cannot execute: %s"), argv[optind]); +} -- 1.8.1.1 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH] setpriv: run a program with different Linux privilege settings 2013-01-26 14:29 ` [PATCH] setpriv: run a program with different Linux privilege settings Sami Kerola @ 2013-02-04 20:20 ` Andy Lutomirski 2013-02-05 9:05 ` Karel Zak 0 siblings, 1 reply; 21+ messages in thread From: Andy Lutomirski @ 2013-02-04 20:20 UTC (permalink / raw) To: Sami Kerola; +Cc: util-linux On Sat, Jan 26, 2013 at 6:29 AM, Sami Kerola <kerolasa@iki.fi> wrote: > From: Andy Lutomirski <luto@amacapital.net> > > > This new command can set no_new_privs, uid, gid, groups, securebits, > inheritable caps, the cap bounding set, securebits, and selinux and > apparmor labels. > > [kerolasa@iki.fi: a lot of small adjustment making the command to be a > good fit in util-linux project] > > Signed-off-by: Sami Kerola <kerolasa@iki.fi> > Signed-off-by: Andy Lutomirski <luto@amacapital.net> > --- > Changes since Andy Lutomirski's V2 at Mon, 14 Jan 2013. > > - Align man page with Documentation/howto-man-page.txt > - Clean up usage output. > - Re-use messages making translators to work less. > - Make some messages easier to understand. > - Drop bool usage, use bit fields instead. > - Fix smatch warnings. > - Remove unnecessary braces where possible. > - Tidy few code style issues. What's the status of this patch? I'm not really familiar with the merge process for util-linux. FWIW, this code would benefit from s/--caps/--inhcaps/g. My bad. I can submit a new version or a followup if that would be helpful. --Andy > > .gitignore | 1 + > configure.ac | 14 + > sys-utils/Makemodule.am | 7 + > sys-utils/setpriv.1 | 149 +++++++++ > sys-utils/setpriv.c | 814 ++++++++++++++++++++++++++++++++++++++++++++++++ > 5 files changed, 985 insertions(+) > create mode 100644 sys-utils/setpriv.1 > create mode 100644 sys-utils/setpriv.c > > diff --git a/.gitignore b/.gitignore > index b2e9d6d..7dbb81c 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -150,6 +150,7 @@ tests/run.sh.trs > /script > /scriptreplay > /setarch > +/setpriv > /setsid > /setterm > /sfdisk > diff --git a/configure.ac b/configure.ac > index 9024809..d3a8e9e 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -879,6 +879,20 @@ if test "x$build_nsenter" = xyes; then > AC_CHECK_FUNCS([setns]) > fi > > +dnl setpriv depends on libcap-ng. It would be possible to build > +dnl a version of setpriv with limited functionality without libcap-ng, > +dnl but this isn't currently supported. > +UL_CHECK_LIB([cap-ng], [capng_apply], [cap_ng]) > +AC_ARG_ENABLE([setpriv], > + AS_HELP_STRING([--disable-setpriv], [do not build setpriv]), > + [], enable_setpriv=check > +) > +UL_BUILD_INIT([setpriv]) > +UL_REQUIRES_LINUX([setpriv]) > +UL_REQUIRES_HAVE([setpriv], [cap_ng], [libcap-ng]) > +AM_CONDITIONAL(BUILD_SETPRIV, test "x$build_setpriv" = xyes) > + > + > AC_ARG_ENABLE([arch], > AS_HELP_STRING([--enable-arch], [do build arch]), > [], enable_arch=no > diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am > index 978d97f..86c529e 100644 > --- a/sys-utils/Makemodule.am > +++ b/sys-utils/Makemodule.am > @@ -318,3 +318,10 @@ if HAVE_AUDIT > hwclock_LDADD += -laudit > endif > endif # BUILD_HWCLOCK > + > +if BUILD_SETPRIV > +usrbin_exec_PROGRAMS += setpriv > +dist_man_MANS += sys-utils/setpriv.1 > +setpriv_SOURCES = sys-utils/setpriv.c > +setpriv_LDADD = $(LDADD) -lcap-ng libcommon.la > +endif > diff --git a/sys-utils/setpriv.1 b/sys-utils/setpriv.1 > new file mode 100644 > index 0000000..c56d89f > --- /dev/null > +++ b/sys-utils/setpriv.1 > @@ -0,0 +1,149 @@ > +.TH SETPRIV 1 "January 2013" "util-linux" "User Commands" > +.SH NAME > +setpriv \- run a program with different Linux privilege settings > +.SH SYNOPSIS > +.B setpriv > +.RI [ options ] > +program > +.RI [ arguments ] > +.SH DESCRIPTION > +Sets or queries various Linux privilege settings that are inherited across > +.BR execve (2). > +.SH OPTION > +.TP > +\fB\-d\fR, \fB\-\-dump\fR > +Dumps current privilege state. Specify more than once to show extra, mostly > +useless, information. Incompatible with all other options. > +.TP > +\fB\-\-no\-new\-privs\fR > +Sets the > +.I no_\:new_\:privs > +bit. With this bit set, > +.BR execve (2) > +will not grant new privileges. For example, the setuid and setgid bits as well > +as file capabilities will be disabled. (Executing binaries with these bits set > +will still work, but they will not gain privilege. Certain LSMs, especially > +AppArmor, may result in failures to execute certain programs.) This bit is > +inherited by child processes and cannot be unset. See > +.BR prctl (2) > +and > +.IR Documentation/\:prctl/\:no_\:new_\:privs.txt > +in the Linux kernel source. > +.IP > +The no_\:new_\:privs bit is supported since Linux 3.5. > +.TP > +\fB\-\-inh\-caps\fR \fI(+|\-)cap\fR,\fI...\fR or \fB\-\-bounding\-set\fR \fI(+|\-)cap\fR,\fI...\fR > +Sets inheritable capabilities or capability bounding set. See > +.BR capabilities (7). > +The argument is a comma-separated list of > +.I +cap > +and > +.I \-cap > +entries, which add or remove an entry respectively. > +.I +all > +and > +.I \-all > +can be used to add or remove all caps. The set of capabilities starts out as > +the current inheritable set for > +.B \-\-\:inh\-\:caps > +and the current bounding set for > +.BR \-\-\:bounding\-\:set . > +If you drop something from the bounding set without also dropping it from the > +inheritable set, you are likely to become confused. Do not do that. > +.TP > +.BR \-\-list\-caps > +Lists all known capabilities. Must be specified alone. > +.TP > +\fB\-\-ruid\fR \fIuid\fR, \fB\-\-euid\fR \fIuid\fR, \fB\-\-reuid\fR \fIuid\fR > +Sets the real, effective, or both \fIuid\fRs. > +.IP > +Setting > +.I uid > +or > +.I gid > +does not change capabilities, although the exec call at the end might change > +capabilities. This means that, if you are root, you probably want to do > +something like: > +.IP > +\-\-reuid=1000 \-\-\:regid=1000 \-\-\:caps=\-\:all > +.TP > +\fB\-\-rgid\fR \fIgid\fR, \fB\-\-egid\fR \fIgid\fR, \fB\-\-regid\fR \fIgid\fR > +Sets the real, effective, or both \fIgid\fRs. > +.IP > +For safety, you must specify one of \-\-\:keep\-\:groups, > +\-\-\:clear\-\:groups, or \-\-\:groups if you set any primary > +.IR gid . > +.TP > +.BR \-\-clear\-groups > +Clears supplementary groups. > +.TP > +\fB\-\-keep\-groups\fR > +Preserves supplementary groups. Only useful in conjunction with \-\-rgid, > +\-\-egid, or \-\-regid. > +.TP > +\fB\-\-groups\fR \fIgroup\fR,\fI...\fR > +Sets supplementary groups. > +.TP > +\fB\-\-securebits\fR \fI(+|\-)securebit\fR,\fI...\fR > +Sets or clears securebits. The valid securebits are > +.IR noroot , > +.IR noroot_\:locked , > +.IR no_\:setuid_\:fixup , > +.IR no_\:setuid_\:fixup_\:locked , > +and > +.IR keep_\:caps_\:locked . > +.I keep_\:caps > +is cleared by > +.BR execve (2) > +and is therefore not allowed. > +.TP > +\fB\-\-selinux\-label\fR \fIlabel\fR > +Requests a particular SELinux transition (using a transition on exec, not > +dyntrans). This will fail and cause > +.BR setpriv (1) > +to abort if SELinux is not in use, and the transition may be ignored or cause > +.BR execve (2) > +to fail at SELinux's whim. (In particular, this is unlikely to work in > +conjunction with > +.IR no_\:new_\:privs .) > +This is similar to > +.BR runcon (1). > +.TP > +\fB\-\-apparmor\-profile\fR \fIprofile\fR > +Requests a particular AppArmor profile (using a transition on exec). This will > +fail and cause > +.BR setpriv (1) > +to abort if AppArmor is not in use, and the transition may be ignored or cause > +.BR execve (2) > +to fail at AppArmor's whim. > +.TP > +\fB\-V\fR, \fB\-\-version\fR > +Display version information and exit. > +.TP > +\fB\-h\fR, \fB\-\-help\fR > +Display help and exit. > +.SH NOTES > +If applying any specified option fails, > +.I program > +will not be run and > +.B setpriv > +will return with exit code 127. > +.PP > +Be careful with this tool \-\- it may have unexpected security consequences. > +For example, setting no_\:new_\:privs and then execing a program that is > +SELinux\-\:confined (as this tool would do) may prevent the SELinux > +restrictions from taking effect. > +.SH SEE ALSO > +.BR prctl (2) > +.BR capability (7) > +.SH AUTHOR > +.MT luto@amacapital.net > +Andy Lutomirski > +.ME > +.SH AVAILABILITY > +The > +.B setpriv > +command is part of the util-linux package and is available from > +.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/ > +Linux Kernel Archive > +.UE . > diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c > new file mode 100644 > index 0000000..1662daf > --- /dev/null > +++ b/sys-utils/setpriv.c > @@ -0,0 +1,814 @@ > +/* > + * setpriv(1) - set various kernel privilege bits and run something > + * > + * Copyright (C) 2012 Andy Lutomirski <luto@amacapital.net> > + * > + * This program is free software; you can redistribute it and/or modify it > + * under the terms of the GNU General Public License as published by the > + * Free Software Foundation; either version 2, or (at your option) any > + * later version. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License along > + * with this program; if not, write to the Free Software Foundation, Inc., > + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. > + */ > + > +#include <cap-ng.h> > +#include <errno.h> > +#include <getopt.h> > +#include <grp.h> > +#include <linux/securebits.h> > +#include <stdarg.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <sys/prctl.h> > +#include <unistd.h> > + > +#include "bitops.h" > +#include "c.h" > +#include "closestream.h" > +#include "nls.h" > +#include "optutils.h" > +#include "strutils.h" > +#include "xalloc.h" > + > +#ifndef PR_SET_NO_NEW_PRIVS > +# define PR_SET_NO_NEW_PRIVS 38 > +#endif > +#ifndef PR_GET_NO_NEW_PRIVS > +# define PR_GET_NO_NEW_PRIVS 39 > +#endif > + > +#define SETPRIV_EXIT_PRIVERR 127 /* how we exit when we fail to set privs */ > + > +/* > + * Note: We are subject to https://bugzilla.redhat.com/show_bug.cgi?id=895105 > + * and we will therefore have problems if new capabilities are added. Once > + * that bug is fixed, I'll (Andy Lutomirski) submit a corresponding fix to > + * setpriv. In the mean time, the code here tries to work reasonably well. > + */ > + > +struct privctx { > + /* bit arrays -- see include/bitops.h */ > + unsigned int > + nnp:1, /* no_new_privs */ > + have_ruid:1, /* real uid */ > + have_euid:1, /* effective uid */ > + have_rgid:1, /* real gid */ > + have_egid:1, /* effective gid */ > + have_groups:1, /* add groups */ > + keep_groups:1, /* keep groups */ > + clear_groups:1, /* remove groups */ > + have_securebits:1; /* remove groups */ > + > + /* uids and gids */ > + uid_t ruid, euid; > + gid_t rgid, egid; > + > + /* supplementary groups */ > + size_t num_groups; > + gid_t *groups; > + > + /* caps */ > + const char *caps_to_inherit; > + const char *bounding_set; > + > + /* securebits */ > + int securebits; > + > + /* LSMs */ > + const char *selinux_label; > + const char *apparmor_profile; > +}; > + > +static void __attribute__((__noreturn__)) usage(FILE *out) > +{ > + fputs(USAGE_HEADER, out); > + fprintf(out, _(" %s [options] <program> [args...]\n"), program_invocation_short_name); > + fputs(USAGE_OPTIONS, out); > + fputs(_(" -d, --dump show current state (and do not exec anything)\n"), out); > + fputs(_(" --nnp, --no-new-privs disallow granting new privileges\n"), out); > + fputs(_(" --inh-caps <caps,...> set inheritable capabilities\n"), out); > + fputs(_(" --bounding-set <caps> set capability bounding set\n"), out); > + fputs(_(" --ruid <uid> set real uid\n"), out); > + fputs(_(" --euid <uid> set effective uid\n"), out); > + fputs(_(" --rgid <gid> set real gid\n"), out); > + fputs(_(" --egid <gid> set effective gid\n"), out); > + fputs(_(" --reuid <uid> set real and effective uid\n"), out); > + fputs(_(" --regid <gid> set real and effective gid\n"), out); > + fputs(_(" --clear-groups clear supplementary groups\n"), out); > + fputs(_(" --keep-groups keep supplementary groups\n"), out); > + fputs(_(" --groups <group,...> set supplementary groups\n"), out); > + fputs(_(" --securebits <bits> set securebits\n"), out); > + fputs(_(" --selinux-label <label> set SELinux label (requires process:transition)\n"), out); > + fputs(_(" --apparmor-profile <pr> set AppArmor profile (requires onexec permission)\n"), out); > + fputs(USAGE_SEPARATOR, out); > + fputs(USAGE_HELP, out); > + fputs(USAGE_VERSION, out); > + fputs(USAGE_SEPARATOR, out); > + fputs(_(" This tool can be dangerous. Read the manpage, and be careful.\n"), out); > + fprintf(out, USAGE_MAN_TAIL("setpriv(1)")); > + > + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); > +} > + > +static int real_cap_last_cap(void) > +{ > + /* CAP_LAST_CAP is untrustworthy. */ > + static int ret = -1; > + int matched; > + FILE *f; > + > + if (ret != -1) > + return ret; > + > + f = fopen("/proc/sys/kernel/cap_last_cap", "r"); > + if (!f) { > + ret = CAP_LAST_CAP; /* guess */ > + return ret; > + } > + > + matched = fscanf(f, "%d", &ret); > + fclose(f); > + > + if (matched != 1) > + ret = CAP_LAST_CAP; /* guess */ > + > + return ret; > +} > + > +/* Returns the number of capabilities printed. */ > +static int print_caps(FILE *f, capng_type_t which) > +{ > + int i, n = 0, max = real_cap_last_cap(); > + > + for (i = 0; i <= max; i++) { > + if (capng_have_capability(which, i)) { > + const char *name = capng_capability_to_name(i); > + if (n) > + fputc(',', f); > + if (name) > + fputs(name, f); > + else > + /* cap-ng has very poor handling of > + * CAP_LAST_CAP changes. This is the > + * best we can do. */ > + printf("cap_%d", i); > + n++; > + } > + } > + return n; > +} > + > +static void dump_one_secbit(int *first, int *bits, int bit, const char *name) > +{ > + if (*bits & bit) { > + if (!*first) > + printf(","); > + else > + *first = 0; > + fputs(name, stdout); > + *bits &= ~bit; > + } > +} > + > +static void dump_securebits(void) > +{ > + int first = 1; > + int bits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0); > + > + if (bits < 0) { > + warnx(_("getting process secure bits failed")); > + return; > + } > + > + printf(_("Securebits: ")); > + > + dump_one_secbit(&first, &bits, SECBIT_NOROOT, "noroot"); > + dump_one_secbit(&first, &bits, SECBIT_NOROOT_LOCKED, "noroot_locked"); > + dump_one_secbit(&first, &bits, SECBIT_NO_SETUID_FIXUP, > + "no_setuid_fixup"); > + dump_one_secbit(&first, &bits, SECBIT_NO_SETUID_FIXUP_LOCKED, > + "no_setuid_fixup_locked"); > + bits &= ~SECBIT_KEEP_CAPS; > + dump_one_secbit(&first, &bits, SECBIT_KEEP_CAPS_LOCKED, > + "keep_caps_locked"); > + if (bits) { > + if (!first) > + printf(","); > + else > + first = 0; > + printf("0x%x", (unsigned)bits); > + } > + > + if (first) > + printf(_("[none]\n")); > + else > + printf("\n"); > +} > + > +static void dump_label(const char *name) > +{ > + char buf[4097]; > + ssize_t len; > + int fd, e; > + > + fd = open("/proc/self/attr/current", O_RDONLY); > + if (fd == -1) { > + warnx(_("cannot open %s"), "/proc/self/attr/current"); > + return; > + } > + > + len = read(fd, buf, sizeof(buf)); > + e = errno; > + close(fd); > + if (len < 0) { > + errno = e; > + warnx(_("read failed: %s"), name); > + return; > + } > + if (sizeof(buf) - 1 <= (size_t)len) { > + warnx(_("%s: too long"), name); > + return; > + } > + > + buf[len] = 0; > + if (0 < len && buf[len - 1] == '\n') > + buf[len - 1] = 0; > + printf("%s: %s\n", name, buf); > +} > + > +static void dump_groups(void) > +{ > + int n = getgroups(0, 0); > + gid_t *groups; > + if (n < 0) { > + warn("getgroups failed"); > + return; > + } > + > + groups = alloca(n * sizeof(gid_t)); > + n = getgroups(n, groups); > + if (n < 0) { > + warn("getgroups failed"); > + return; > + } > + > + printf(_("Supplementary groups: ")); > + if (n == 0) > + printf(_("[none]")); > + else { > + int i; > + for (i = 0; i < n; i++) { > + if (0 < i) > + printf(","); > + printf("%ld", (long)groups[i]); > + } > + } > + printf("\n"); > +} > + > +static void dump(int dumplevel) > +{ > + int x; > + uid_t ru, eu, su; > + gid_t rg, eg, sg; > + > + if (getresuid(&ru, &eu, &su) == 0) { > + printf(_("uid: %u\n"), ru); > + printf(_("euid: %u\n"), eu); > + /* Saved and fs uids always equal euid. */ > + if (3 <= dumplevel) > + printf(_("suid: %u\n"), su); > + } else > + warn(_("getresuid failed")); > + > + if (getresgid(&rg, &eg, &sg) == 0) { > + printf("gid: %ld\n", (long)rg); > + printf("egid: %ld\n", (long)eg); > + /* Saved and fs gids always equal egid. */ > + if (dumplevel >= 3) > + printf("sgid: %ld\n", (long)sg); > + } else > + warn(_("getresgid failed")); > + > + dump_groups(); > + > + x = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0); > + if (0 <= x) > + printf("no_new_privs: %d\n", x); > + else > + warn("setting no_new_privs failed"); > + > + if (2 <= dumplevel) { > + printf(_("Effective capabilities: ")); > + if (print_caps(stdout, CAPNG_EFFECTIVE) == 0) > + printf(_("[none]")); > + printf("\n"); > + > + printf(_("Permitted capabilities: ")); > + if (print_caps(stdout, CAPNG_PERMITTED) == 0) > + printf(_("[none]")); > + printf("\n"); > + } > + > + printf(_("Inheritable capabilities: ")); > + if (print_caps(stdout, CAPNG_INHERITABLE) == 0) > + printf(_("[none]")); > + printf("\n"); > + > + printf(_("Capability bounding set: ")); > + if (print_caps(stdout, CAPNG_BOUNDING_SET) == 0) > + printf(_("[none]")); > + printf("\n"); > + > + dump_securebits(); > + > + if (access("/sys/fs/selinux", F_OK) == 0) > + dump_label(_("SELinux label")); > + > + if (access("/sys/kernel/security/apparmor", F_OK) == 0) { > + dump_label(_("AppArmor profile")); > + } > +} > + > +static void list_known_caps(void) > +{ > + int i, max = real_cap_last_cap(); > + > + for (i = 0; i <= max; i++) { > + const char *name = capng_capability_to_name(i); > + if (name) > + printf("%s\n", name); > + else > + warnx(_("cap %d: libcap-ng is broken"), i); > + } > +} > + > +static void parse_groups(struct privctx *opts, const char *str) > +{ > + char *groups = xstrdup(str); > + char *buf = groups; /* We'll reuse it */ > + char *c; > + size_t i = 0; > + > + opts->have_groups = 1; > + opts->num_groups = 0; > + while ((c = strsep(&groups, ","))) > + opts->num_groups++; > + > + /* Start again */ > + strcpy(buf, str); /* It's exactly the right length */ > + groups = buf; > + > + opts->groups = xcalloc(opts->num_groups, sizeof(gid_t)); > + while ((c = strsep(&groups, ","))) > + opts->groups[i++] = (gid_t) strtol_or_err(c, > + _("Invalid supplementary group id")); > + > + free(groups); > +} > + > +static void do_setresuid(const struct privctx *opts) > +{ > + uid_t ruid, euid, suid; > + if (getresuid(&ruid, &euid, &suid) != 0) > + err(SETPRIV_EXIT_PRIVERR, _("getresuid failed")); > + if (opts->have_ruid) > + ruid = opts->ruid; > + if (opts->have_euid) > + euid = opts->euid; > + > + /* Also copy effective to saved (for paranoia). */ > + if (setresuid(ruid, euid, euid) != 0) > + err(SETPRIV_EXIT_PRIVERR, _("setresuid failed")); > +} > + > +static void do_setresgid(const struct privctx *opts) > +{ > + gid_t rgid, egid, sgid; > + if (getresgid(&rgid, &egid, &sgid) != 0) > + err(SETPRIV_EXIT_PRIVERR, _("getresgid failed")); > + if (opts->have_rgid) > + rgid = opts->rgid; > + if (opts->have_egid) > + egid = opts->egid; > + > + /* Also copy effective to saved (for paranoia). */ > + if (setresgid(rgid, egid, egid) != 0) > + err(SETPRIV_EXIT_PRIVERR, _("setresgid failed")); > +} > + > +static void bump_cap(unsigned int cap) > +{ > + if (capng_have_capability(CAPNG_PERMITTED, cap)) > + capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, cap); > +} > + > +static void do_caps(capng_type_t type, const char *caps) > +{ > + char *my_caps = xstrdup(caps); > + char *c; > + > + while ((c = strsep(&my_caps, ","))) { > + capng_act_t action; > + if (*c == '+') > + action = CAPNG_ADD; > + else if (*c == '-') > + action = CAPNG_DROP; > + else > + errx(EXIT_FAILURE, _("bad capability string")); > + > + if (!strcmp(c + 1, "all")) { > + int i; > + /* It would be really bad if -all didn't drop all > + * caps. It's better to just fail. */ > + if (real_cap_last_cap() > CAP_LAST_CAP) > + errx(SETPRIV_EXIT_PRIVERR, > + _("libcap-ng is too old for \"all\" caps")); > + for (i = 0; i <= CAP_LAST_CAP; i++) > + capng_update(action, type, i); > + } else { > + int cap = capng_name_to_capability(c + 1); > + if (0 <= cap) > + capng_update(action, type, cap); > + else > + errx(EXIT_FAILURE, > + _("unknown capability \"%s\""), c + 1); > + } > + } > + > + free(my_caps); > +} > + > +static void parse_securebits(struct privctx *opts, const char *arg) > +{ > + char *buf = xstrdup(arg); > + char *c; > + > + opts->have_securebits = 1; > + opts->securebits = prctl(PR_GET_SECUREBITS, 0, 0, 0, 0); > + if (opts->securebits < 0) > + err(SETPRIV_EXIT_PRIVERR, _("getting process secure bits failed")); > + > + if (opts->securebits & ~(int)(SECBIT_NOROOT | > + SECBIT_NOROOT_LOCKED | > + SECBIT_NO_SETUID_FIXUP | > + SECBIT_NO_SETUID_FIXUP_LOCKED | > + SECBIT_KEEP_CAPS | > + SECBIT_KEEP_CAPS_LOCKED)) > + errx(SETPRIV_EXIT_PRIVERR, > + _("unrecognized securebit set -- refusing to adjust")); > + > + while ((c = strsep(&buf, ","))) { > + if (*c != '+' && *c != '-') > + errx(EXIT_FAILURE, _("bad securebits string")); > + > + if (!strcmp(c + 1, "all")) { > + if (*c == '-') > + opts->securebits = 0; > + else > + errx(EXIT_FAILURE, > + _("+all securebits is not allowed")); > + } else { > + int bit; > + if (!strcmp(c + 1, "noroot")) > + bit = SECBIT_NOROOT; > + else if (!strcmp(c + 1, "noroot_locked")) > + bit = SECBIT_NOROOT_LOCKED; > + else if (!strcmp(c + 1, "no_setuid_fixup")) > + bit = SECBIT_NO_SETUID_FIXUP; > + else if (!strcmp(c + 1, "no_setuid_fixup_locked")) > + bit = SECBIT_NO_SETUID_FIXUP_LOCKED; > + else if (!strcmp(c + 1, "keep_caps")) > + errx(EXIT_FAILURE, > + _("adjusting keep_caps does not make sense")); > + else if (!strcmp(c + 1, "keep_caps_locked")) > + bit = SECBIT_KEEP_CAPS_LOCKED; /* sigh */ > + else > + errx(EXIT_FAILURE, _("unrecognized securebit")); > + > + if (*c == '+') > + opts->securebits |= bit; > + else > + opts->securebits &= ~bit; > + } > + } > + > + opts->securebits |= SECBIT_KEEP_CAPS; /* We need it, and it's reset on exec */ > + > + free(buf); > +} > + > +static void do_selinux_label(const char *label) > +{ > + int fd; > + size_t len; > + > + if (access("/sys/fs/selinux", F_OK) != 0) > + errx(SETPRIV_EXIT_PRIVERR, _("SELinux is not running")); > + > + fd = open("/proc/self/attr/exec", O_RDWR); > + if (fd == -1) > + err(SETPRIV_EXIT_PRIVERR, > + _("cannot open %s"), "/proc/self/attr/exec"); > + > + len = strlen(label); > + errno = 0; > + if (write(fd, label, len) != (ssize_t) len) > + err(SETPRIV_EXIT_PRIVERR, > + _("write failed: %s"), "/proc/self/attr/exec"); > + > + close(fd); > +} > + > +static void do_apparmor_profile(const char *label) > +{ > + FILE *f; > + > + if (access("/sys/kernel/security/apparmor", F_OK) != 0) > + errx(SETPRIV_EXIT_PRIVERR, _("AppArmor is not running")); > + > + f = fopen("/proc/self/attr/exec", "wx"); > + if (!f) > + err(SETPRIV_EXIT_PRIVERR, > + _("cannot open %s"), "/proc/self/attr/exec"); > + > + if (fprintf(f, "changeprofile %s", label) < 0 || fflush(f) != 0 > + || fclose(f) != 0) > + err(SETPRIV_EXIT_PRIVERR, > + _("write failed: %s"), "/proc/self/attr/exec"); > +} > + > +int main(int argc, char **argv) > +{ > + enum { > + NNP = CHAR_MAX + 1, > + RUID, > + EUID, > + RGID, > + EGID, > + REUID, > + REGID, > + CLEAR_GROUPS, > + KEEP_GROUPS, > + GROUPS, > + INHCAPS, > + LISTCAPS, > + CAPBSET, > + SECUREBITS, > + SELINUX_LABEL, > + APPARMOR_PROFILE > + }; > + > + static const struct option longopts[] = { > + {"dump", no_argument, 0, 'd'}, > + {"nnp", no_argument, 0, NNP}, > + {"no-new-privs", no_argument, 0, NNP}, > + {"inh-caps", required_argument, 0, INHCAPS}, > + {"list-caps", no_argument, 0, LISTCAPS}, > + {"ruid", required_argument, 0, RUID}, > + {"euid", required_argument, 0, EUID}, > + {"rgid", required_argument, 0, RGID}, > + {"egid", required_argument, 0, EGID}, > + {"reuid", required_argument, 0, REUID}, > + {"regid", required_argument, 0, REGID}, > + {"clear-groups", no_argument, 0, CLEAR_GROUPS}, > + {"keep-groups", no_argument, 0, KEEP_GROUPS}, > + {"groups", required_argument, 0, GROUPS}, > + {"bounding-set", required_argument, 0, CAPBSET}, > + {"securebits", required_argument, 0, SECUREBITS}, > + {"selinux-label", required_argument, 0, SELINUX_LABEL}, > + {"apparmor-profile", required_argument, 0, APPARMOR_PROFILE}, > + {"help", no_argument, 0, 'h'}, > + {"version", no_argument, 0, 'V'}, > + {NULL, 0, 0, 0} > + }; > + > + static const ul_excl_t excl[] = { > + /* keep in same order with enum definitions */ > + {CLEAR_GROUPS, KEEP_GROUPS, GROUPS}, > + {0} > + }; > + int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; > + > + int c; > + struct privctx opts; > + int dumplevel = 0; > + int total_opts = 0; > + int list_caps = 0; > + > + setlocale(LC_MESSAGES, ""); > + bindtextdomain(PACKAGE, LOCALEDIR); > + textdomain(PACKAGE); > + atexit(close_stdout); > + > + memset(&opts, 0, sizeof(opts)); > + > + while ((c = getopt_long(argc, argv, "+dhV", longopts, NULL)) != -1) { > + err_exclusive_options(c, longopts, excl, excl_st); > + total_opts++; > + switch (c) { > + case 'd': > + dumplevel++; > + break; > + case NNP: > + if (opts.nnp) > + errx(EXIT_FAILURE, > + _("duplicate --no-new-privs option")); > + opts.nnp = 1; > + break; > + case RUID: > + if (opts.have_ruid) > + errx(EXIT_FAILURE, _("duplicate ruid")); > + opts.have_ruid = 1; > + opts.ruid = strtol_or_err(optarg, > + _("failed to parse ruid")); > + break; > + case EUID: > + if (opts.have_euid) > + errx(EXIT_FAILURE, _("duplicate euid")); > + opts.have_euid = 1; > + opts.euid = strtol_or_err(optarg, > + _("failed to parse euid")); > + break; > + case REUID: > + if (opts.have_ruid || opts.have_euid) > + errx(EXIT_FAILURE, _("duplicate ruid or euid")); > + opts.have_ruid = opts.have_euid = 1; > + opts.ruid = opts.euid = strtol_or_err(optarg, > + _("failed to parse reuid")); > + break; > + case RGID: > + if (opts.have_rgid) > + errx(EXIT_FAILURE, _("duplicate rgid")); > + opts.have_rgid = 1; > + opts.rgid = strtol_or_err(optarg, > + _("failed to parse rgid")); > + break; > + case EGID: > + if (opts.have_egid) > + errx(EXIT_FAILURE, _("duplicate egid")); > + opts.have_egid = 1; > + opts.egid = strtol_or_err(optarg, > + _("failed to parse egid")); > + break; > + case REGID: > + if (opts.have_rgid || opts.have_egid) > + errx(EXIT_FAILURE, _("duplicate rgid or egid")); > + opts.have_rgid = opts.have_egid = 1; > + opts.rgid = opts.egid = strtol_or_err(optarg, > + _("failed to parse regid")); > + break; > + case CLEAR_GROUPS: > + if (opts.clear_groups) > + errx(EXIT_FAILURE, > + _("duplicate --clear-groups option")); > + opts.clear_groups = 1; > + break; > + case KEEP_GROUPS: > + if (opts.keep_groups) > + errx(EXIT_FAILURE, > + _("duplicate --keep-groups option")); > + opts.keep_groups = 1; > + break; > + case GROUPS: > + if (opts.have_groups) > + errx(EXIT_FAILURE, > + _("duplicate --groups option")); > + parse_groups(&opts, optarg); > + break; > + case LISTCAPS: > + list_caps = 1; > + break; > + case INHCAPS: > + if (opts.caps_to_inherit) > + errx(EXIT_FAILURE, > + _("duplicate --caps option")); > + opts.caps_to_inherit = optarg; > + break; > + case CAPBSET: > + if (opts.bounding_set) > + errx(EXIT_FAILURE, > + _("duplicate --bounding-set option")); > + opts.bounding_set = optarg; > + break; > + case SECUREBITS: > + if (opts.have_securebits) > + errx(EXIT_FAILURE, > + _("duplicate --securebits option")); > + parse_securebits(&opts, optarg); > + break; > + case SELINUX_LABEL: > + if (opts.selinux_label) > + errx(EXIT_FAILURE, > + _("duplicate --selinux-label option")); > + opts.selinux_label = optarg; > + break; > + case APPARMOR_PROFILE: > + if (opts.apparmor_profile) > + errx(EXIT_FAILURE, > + _("duplicate --apparmor-profile option")); > + opts.apparmor_profile = optarg; > + break; > + case 'h': > + usage(stdout); > + case 'V': > + printf(UTIL_LINUX_VERSION); > + return EXIT_SUCCESS; > + case '?': > + usage(stderr); > + default: > + errx(EXIT_FAILURE, _("unrecognized option '%c'"), c); > + } > + } > + > + if (dumplevel) { > + if (total_opts != dumplevel || optind < argc) > + errx(EXIT_FAILURE, > + _("--dump is incompatible with all other options")); > + dump(dumplevel); > + return EXIT_SUCCESS; > + } > + > + if (list_caps) { > + if (total_opts != 1 || optind < argc) > + errx(EXIT_FAILURE, > + _("--list-caps must be specified alone")); > + list_known_caps(); > + return EXIT_SUCCESS; > + } > + > + if (argc <= optind) > + errx(EXIT_FAILURE, _("No program specified")); > + > + if ((opts.have_rgid || opts.have_egid) > + && !opts.keep_groups && !opts.clear_groups && !opts.have_groups) > + errx(EXIT_FAILURE, > + _("--[re]gid requires --keep-groups, --clear-groups, or --groups")); > + > + if (opts.nnp) > + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) > + err(EXIT_FAILURE, _("disallow granting new privileges failed")); > + > + if (opts.selinux_label) > + do_selinux_label(opts.selinux_label); > + if (opts.apparmor_profile) > + do_apparmor_profile(opts.apparmor_profile); > + > + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) > + err(EXIT_FAILURE, _("keep process capabilities failed")); > + > + /* We're going to want CAP_SETPCAP, CAP_SETUID, and CAP_SETGID if > + * possible. */ > + bump_cap(CAP_SETPCAP); > + bump_cap(CAP_SETUID); > + bump_cap(CAP_SETGID); > + if (capng_apply(CAPNG_SELECT_CAPS) != 0) > + err(SETPRIV_EXIT_PRIVERR, _("activate capabilities")); > + > + if (opts.have_ruid || opts.have_euid) { > + do_setresuid(&opts); > + /* KEEPCAPS doesn't work for the effective mask. */ > + if (capng_apply(CAPNG_SELECT_CAPS) != 0) > + err(SETPRIV_EXIT_PRIVERR, _("reactivate capabilities")); > + } > + > + if (opts.have_rgid || opts.have_egid) > + do_setresgid(&opts); > + > + if (opts.have_groups) { > + if (setgroups(opts.num_groups, opts.groups) != 0) > + err(SETPRIV_EXIT_PRIVERR, _("setgroups failed")); > + } else if (opts.clear_groups) { > + gid_t x = 0; > + if (setgroups(0, &x) != 0) > + err(SETPRIV_EXIT_PRIVERR, _("setgroups failed")); > + } > + > + if (opts.have_securebits) > + if (prctl(PR_SET_SECUREBITS, opts.securebits, 0, 0, 0) != 0) > + err(SETPRIV_EXIT_PRIVERR, _("set procecess securebits failed")); > + > + if (opts.bounding_set) { > + do_caps(CAPNG_BOUNDING_SET, opts.bounding_set); > + errno = EPERM; /* capng doesn't set errno if we're missing CAP_SETPCAP */ > + if (capng_apply(CAPNG_SELECT_BOUNDS) != 0) > + err(SETPRIV_EXIT_PRIVERR, _("apply bounding set")); > + } > + > + if (opts.caps_to_inherit) { > + do_caps(CAPNG_INHERITABLE, opts.caps_to_inherit); > + if (capng_apply(CAPNG_SELECT_CAPS) != 0) > + err(SETPRIV_EXIT_PRIVERR, _("apply capabilities")); > + } > + > + execvp(argv[optind], argv + optind); > + > + err(EXIT_FAILURE, _("cannot execute: %s"), argv[optind]); > +} > -- > 1.8.1.1 > -- Andy Lutomirski AMA Capital Management, LLC ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] setpriv: run a program with different Linux privilege settings 2013-02-04 20:20 ` Andy Lutomirski @ 2013-02-05 9:05 ` Karel Zak 2013-02-05 10:51 ` Karel Zak 0 siblings, 1 reply; 21+ messages in thread From: Karel Zak @ 2013-02-05 9:05 UTC (permalink / raw) To: Andy Lutomirski; +Cc: Sami Kerola, util-linux On Mon, Feb 04, 2013 at 12:20:09PM -0800, Andy Lutomirski wrote: > What's the status of this patch? I'm not really familiar with the > merge process for util-linux. Be patient, I'm going to merge it. > FWIW, this code would benefit from s/--caps/--inhcaps/g. My bad. I > can submit a new version or a followup if that would be helpful. The latest version is https://github.com/kerolasa/lelux-utiliteetit/commits/setpriv Sami did some cosmetic changes to make it more compatible with the rest if the util-linux stuff. Please, use this version for you changes. A small patch with only bugfix or new feature will be fine. Karel -- Karel Zak <kzak@redhat.com> http://karelzak.blogspot.com ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] setpriv: run a program with different Linux privilege settings 2013-02-05 9:05 ` Karel Zak @ 2013-02-05 10:51 ` Karel Zak 2013-02-06 1:07 ` [PATCH] setpriv: Fix an error message typo Andy Lutomirski 0 siblings, 1 reply; 21+ messages in thread From: Karel Zak @ 2013-02-05 10:51 UTC (permalink / raw) To: Andy Lutomirski; +Cc: Sami Kerola, util-linux On Tue, Feb 05, 2013 at 10:05:24AM +0100, Karel Zak wrote: > On Mon, Feb 04, 2013 at 12:20:09PM -0800, Andy Lutomirski wrote: > > What's the status of this patch? I'm not really familiar with the > > merge process for util-linux. > > Be patient, I'm going to merge it. .... > https://github.com/kerolasa/lelux-utiliteetit/commits/setpriv Merged ;-) Karel -- Karel Zak <kzak@redhat.com> http://karelzak.blogspot.com ^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH] setpriv: Fix an error message typo 2013-02-05 10:51 ` Karel Zak @ 2013-02-06 1:07 ` Andy Lutomirski 2013-02-06 11:32 ` Karel Zak 0 siblings, 1 reply; 21+ messages in thread From: Andy Lutomirski @ 2013-02-06 1:07 UTC (permalink / raw) To: util-linux; +Cc: Karel Zak, Sami Kerola, Andy Lutomirski Signed-off-by: Andy Lutomirski <luto@amacapital.net> --- sys-utils/setpriv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sys-utils/setpriv.c b/sys-utils/setpriv.c index 1662daf..921ce03 100644 --- a/sys-utils/setpriv.c +++ b/sys-utils/setpriv.c @@ -689,7 +689,7 @@ int main(int argc, char **argv) case INHCAPS: if (opts.caps_to_inherit) errx(EXIT_FAILURE, - _("duplicate --caps option")); + _("duplicate --inh-caps option")); opts.caps_to_inherit = optarg; break; case CAPBSET: -- 1.8.1 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH] setpriv: Fix an error message typo 2013-02-06 1:07 ` [PATCH] setpriv: Fix an error message typo Andy Lutomirski @ 2013-02-06 11:32 ` Karel Zak 0 siblings, 0 replies; 21+ messages in thread From: Karel Zak @ 2013-02-06 11:32 UTC (permalink / raw) To: Andy Lutomirski; +Cc: util-linux, Sami Kerola On Tue, Feb 05, 2013 at 05:07:26PM -0800, Andy Lutomirski wrote: > sys-utils/setpriv.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) Applied, thanks. -- Karel Zak <kzak@redhat.com> http://karelzak.blogspot.com ^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2013-02-06 11:33 UTC | newest] Thread overview: 21+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2012-11-23 20:23 [PATCH] Add no_new_privs Andy Lutomirski 2012-11-23 21:14 ` Pádraig Brady 2012-11-26 10:08 ` Karel Zak 2012-11-26 12:45 ` Ángel González 2012-11-26 19:03 ` Andy Lutomirski 2012-11-27 1:39 ` Andy Lutomirski 2012-11-23 22:52 ` Ángel González 2012-12-08 8:19 ` [PATCH] Add setpriv, a tool to set privileges and such Andy Lutomirski 2012-12-08 16:23 ` Ángel González 2012-12-08 19:04 ` Andy Lutomirski 2012-12-09 22:24 ` Pádraig Brady 2012-12-09 23:12 ` Andy Lutomirski 2013-01-08 8:31 ` Karel Zak 2013-01-14 15:33 ` Andy Lutomirski 2013-01-14 15:58 ` [PATCH v2] " Andy Lutomirski 2013-01-26 14:29 ` [PATCH] setpriv: run a program with different Linux privilege settings Sami Kerola 2013-02-04 20:20 ` Andy Lutomirski 2013-02-05 9:05 ` Karel Zak 2013-02-05 10:51 ` Karel Zak 2013-02-06 1:07 ` [PATCH] setpriv: Fix an error message typo Andy Lutomirski 2013-02-06 11:32 ` Karel Zak
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).