util-linux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [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 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

* 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

* [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).