Blame SOURCES/libcap-2.26-ambient-caps.patch

5e9dfe
From 99c995b84ef2974426b0acfa584d75e9a7d82028 Mon Sep 17 00:00:00 2001
5e9dfe
From: "Andrew G. Morgan" <morgan@kernel.org>
5e9dfe
Date: Sun, 22 Dec 2019 08:08:48 -0800
5e9dfe
Subject: Add group, ambient and bound setting support to pam_cap.
5e9dfe
5e9dfe
Rewrote the pam_cap config file parsing to support:
5e9dfe
5e9dfe
  - @group syntax for identifying groups of users
5e9dfe
  - ^cap_foo support for raising both inheritable and ambient caps
5e9dfe
  - !cap_bar support for dropping bounding capabilities
5e9dfe
5e9dfe
Updated documentation for pre-existing libcap's ambient support.
5e9dfe
5e9dfe
This pam_cap feature upgrade was done in collaboration with
5e9dfe
Knut Omang and Christoph Lameter.
5e9dfe
5e9dfe
Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
5e9dfe
---
5e9dfe
 doc/cap_get_ambient.3   |   1 +
5e9dfe
 doc/cap_get_proc.3      |  46 ++++++-
5e9dfe
 doc/cap_reset_ambient.3 |   1 +
5e9dfe
 doc/cap_set_ambient.3   |   1 +
5e9dfe
 pam_cap/.gitignore      |   3 +-
5e9dfe
 pam_cap/Makefile        |  21 ++-
5e9dfe
 pam_cap/pam_cap.c       | 355 +++++++++++++++++++++++++++++++++++-------------
5e9dfe
 pam_cap/sudotest.conf   |  23 ++++
5e9dfe
 pam_cap/test_pam_cap.c  | 200 +++++++++++++++++++++++++++
5e9dfe
 10 files changed, 556 insertions(+), 113 deletions(-)
5e9dfe
 create mode 100644 doc/cap_get_ambient.3
5e9dfe
 create mode 100644 doc/cap_reset_ambient.3
5e9dfe
 create mode 100644 doc/cap_set_ambient.3
5e9dfe
 create mode 100644 pam_cap/sudotest.conf
5e9dfe
 create mode 100644 pam_cap/test_pam_cap.c
5e9dfe
5e9dfe
diff --git a/doc/cap_get_ambient.3 b/doc/cap_get_ambient.3
5e9dfe
new file mode 100644
5e9dfe
index 0000000..65ea3e4
5e9dfe
--- /dev/null
5e9dfe
+++ b/doc/cap_get_ambient.3
5e9dfe
@@ -0,0 +1 @@
5e9dfe
+.so man3/cap_get_proc.3
5e9dfe
diff --git a/doc/cap_get_proc.3 b/doc/cap_get_proc.3
5e9dfe
index ed87fb7..712b3ff 100644
5e9dfe
--- a/doc/cap_get_proc.3
5e9dfe
+++ b/doc/cap_get_proc.3
5e9dfe
@@ -3,7 +3,8 @@
5e9dfe
 .\"
5e9dfe
 .TH CAP_GET_PROC 3 "2008-05-11" "" "Linux Programmer's Manual"
5e9dfe
 .SH NAME
5e9dfe
-cap_get_proc, cap_set_proc, capgetp, cap_get_bound, cap_drop_bound \-
5e9dfe
+cap_get_proc, cap_set_proc, capgetp, cap_get_bound, cap_drop_bound \
5e9dfe
+cap_get_ambient, cap_set_ambient, cap_reset_ambient, \-
5e9dfe
 capability manipulation on processes
5e9dfe
 .SH SYNOPSIS
5e9dfe
 .B #include <sys/capability.h>
5e9dfe
@@ -18,6 +19,14 @@
5e9dfe
 .sp
5e9dfe
 .BI "int cap_drop_bound(cap_value_t " cap );
5e9dfe
 .sp
5e9dfe
+.BI "int cap_get_ambient(cap_value_t " cap );
5e9dfe
+.sp
5e9dfe
+.BI "int cap_set_ambient(cap_value_t " cap ", cap_flag_value_t " value);
5e9dfe
+.sp
5e9dfe
+.B int cap_reset_ambient(void);
5e9dfe
+.sp
5e9dfe
+.BI CAP_AMBIENT_SUPPORTED();
5e9dfe
+.sp
5e9dfe
 .B #include <sys/types.h>
5e9dfe
 .sp
5e9dfe
 .BI "cap_t cap_get_pid(pid_t " pid );
5e9dfe
@@ -75,11 +84,38 @@
5e9dfe
 .PP
5e9dfe
 .BR cap_drop_bound ()
5e9dfe
 can be used to lower the specified bounding set capability,
5e9dfe
-.BR cap ,
5e9dfe
+.BR cap .
5e9dfe
 To complete successfully, the prevailing
5e9dfe
 .I effective
5e9dfe
 capability set must have a raised
5e9dfe
 .BR CAP_SETPCAP .
5e9dfe
+.BR cap_get_ambient ()
5e9dfe
+returns the prevailing value of the specified ambient capability, or
5e9dfe
+-1 if the capability is not supported by the running kernel.  A macro
5e9dfe
+.BR CAP_AMBIENT_SUPPORTED ()
5e9dfe
+uses this function to determine if ambient capabilities are supported
5e9dfe
+by the kernel.
5e9dfe
+.PP
5e9dfe
+.BR cap_set_ambient ()
5e9dfe
+sets the specified ambient capability to a specific value. To complete
5e9dfe
+successfully, the prevailing
5e9dfe
+.I effective
5e9dfe
+capability set must have a raised
5e9dfe
+.BR CAP_SETPCAP .
5e9dfe
+.PP
5e9dfe
+.BR cap_reset_ambient ()
5e9dfe
+resets all of the ambient capabilities for the current process to
5e9dfe
+their lowered value. To complete successfully, the prevailing
5e9dfe
+.I effective
5e9dfe
+capability set must have a raised
5e9dfe
+.BR CAP_SETPCAP .
5e9dfe
+Note, the ambient set is intended to operate in a legacy environment
5e9dfe
+where the application has limited awareness of capabilities in
5e9dfe
+general. Executing a file with associated filesystem capabilities, the
5e9dfe
+kernel will implicitly reset the ambient set of the process. Also,
5e9dfe
+changes to the inheritable set by the program code without explicitly
5e9dfe
+fixing up the ambient set can also drop ambient bits.
5e9dfe
+.PP
5e9dfe
 .SH "RETURN VALUE"
5e9dfe
 The functions
5e9dfe
 .BR cap_get_proc ()
5e9dfe
diff --git a/doc/cap_reset_ambient.3 b/doc/cap_reset_ambient.3
5e9dfe
new file mode 100644
5e9dfe
index 0000000..65ea3e4
5e9dfe
--- /dev/null
5e9dfe
+++ b/doc/cap_reset_ambient.3
5e9dfe
@@ -0,0 +1 @@
5e9dfe
+.so man3/cap_get_proc.3
5e9dfe
diff --git a/doc/cap_set_ambient.3 b/doc/cap_set_ambient.3
5e9dfe
new file mode 100644
5e9dfe
index 0000000..65ea3e4
5e9dfe
--- /dev/null
5e9dfe
+++ b/doc/cap_set_ambient.3
5e9dfe
@@ -0,0 +1 @@
5e9dfe
+.so man3/cap_get_proc.3
5e9dfe
diff --git a/pam_cap/.gitignore b/pam_cap/.gitignore
5e9dfe
index 11806f5..05e9bbf 100644
5e9dfe
--- a/pam_cap/.gitignore
5e9dfe
+++ b/pam_cap/.gitignore
5e9dfe
@@ -1,2 +1,3 @@
5e9dfe
 pam_cap.so
5e9dfe
-testcompile
5e9dfe
+testlink
5e9dfe
+test_pam_cap
5e9dfe
diff --git a/pam_cap/Makefile b/pam_cap/Makefile
5e9dfe
index 22f0f81..56604fd 100644
5e9dfe
--- a/pam_cap/Makefile
5e9dfe
+++ b/pam_cap/Makefile
5e9dfe
@@ -10,7 +10,7 @@
5e9dfe
 LDLIBS += -L../libcap -lcap
5e9dfe
 
5e9dfe
 all: pam_cap.so
5e9dfe
-	$(MAKE) testcompile
5e9dfe
+	$(MAKE) testlink
5e9dfe
 
5e9dfe
 install: all
5e9dfe
 	mkdir -p -m 0755 $(FAKEROOT)$(LIBDIR)/security
5e9dfe
@@ -22,8 +22,23 @@
5e9dfe
 pam_cap.o: pam_cap.c
5e9dfe
 	$(CC) $(CFLAGS) $(IPATH) -c $< -o $@
5e9dfe
 
5e9dfe
-testcompile: test.c pam_cap.o
5e9dfe
+test_pam_cap: test_pam_cap.c pam_cap.c
5e9dfe
+	$(CC) $(CFLAGS) $(IPATH) -o $@ test_pam_cap.c $(LIBCAPLIB) $(LDFLAGS) --static
5e9dfe
+
5e9dfe
+testlink: test.c pam_cap.o
5e9dfe
 	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $+ -lpam -ldl $(LDLIBS)
5e9dfe
 
5e9dfe
+test: pam_cap.so
5e9dfe
+	make testlink
5e9dfe
+
5e9dfe
+sudotest: test test_pam_cap
5e9dfe
+	sudo ./test_pam_cap root 0x0 0x0 0x0 config=./capability.conf
5e9dfe
+	sudo ./test_pam_cap root 0x0 0x0 0x0 config=./sudotest.conf
5e9dfe
+	sudo ./test_pam_cap alpha 0x0 0x0 0x0 config=./capability.conf
5e9dfe
+	sudo ./test_pam_cap alpha 0x0 0x1 0x80 config=./sudotest.conf
5e9dfe
+	sudo ./test_pam_cap beta 0x0 0x1 0x0 config=./sudotest.conf
5e9dfe
+	sudo ./test_pam_cap gamma 0x0 0x0 0x81 config=./sudotest.conf
5e9dfe
+	sudo ./test_pam_cap delta 0x41 0x80 0x41 config=./sudotest.conf
5e9dfe
+
5e9dfe
 clean:
5e9dfe
-	rm -f *.o *.so testcompile *~
5e9dfe
+	rm -f *.o *.so testlink test_pam_cap *~
5e9dfe
diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c
5e9dfe
index b1cc5cb..58ffe4a 100644
5e9dfe
--- a/pam_cap/pam_cap.c
5e9dfe
+++ b/pam_cap/pam_cap.c
5e9dfe
@@ -1,20 +1,23 @@
5e9dfe
 /*
5e9dfe
- * Copyright (c) 1999,2007 Andrew G. Morgan <morgan@kernel.org>
5e9dfe
+ * Copyright (c) 1999,2007,2019 Andrew G. Morgan <morgan@kernel.org>
5e9dfe
  *
5e9dfe
- * The purpose of this module is to enforce inheritable capability sets
5e9dfe
- * for a specified user.
5e9dfe
+ * The purpose of this module is to enforce inheritable, bounding and
5e9dfe
+ * ambient capability sets for a specified user.
5e9dfe
  */
5e9dfe
 
5e9dfe
 /* #define DEBUG */
5e9dfe
 
5e9dfe
-#include <stdio.h>
5e9dfe
-#include <string.h>
5e9dfe
 #include <errno.h>
5e9dfe
+#include <grp.h>
5e9dfe
+#include <limits.h>
5e9dfe
+#include <pwd.h>
5e9dfe
 #include <stdarg.h>
5e9dfe
 #include <stdlib.h>
5e9dfe
+#include <stdio.h>
5e9dfe
+#include <string.h>
5e9dfe
 #include <syslog.h>
5e9dfe
-
5e9dfe
 #include <sys/capability.h>
5e9dfe
+#include <sys/types.h>
5e9dfe
 
5e9dfe
 #include <security/pam_modules.h>
5e9dfe
 #include <security/_pam_macros.h>
5e9dfe
@@ -22,8 +25,6 @@
5e9dfe
 #define USER_CAP_FILE           "/etc/security/capability.conf"
5e9dfe
 #define CAP_FILE_BUFFER_SIZE    4096
5e9dfe
 #define CAP_FILE_DELIMITERS     " \t\n"
5e9dfe
-#define CAP_COMBINED_FORMAT     "%s all-i %s+i"
5e9dfe
-#define CAP_DROP_ALL            "%s all-i"
5e9dfe
 
5e9dfe
 struct pam_cap_s {
5e9dfe
     int debug;
5e9dfe
@@ -31,25 +32,71 @@ struct pam_cap_s {
5e9dfe
     const char *conf_filename;
5e9dfe
 };
5e9dfe
 
5e9dfe
+/*
5e9dfe
+ * load_groups obtains the list all of the groups associated with the
5e9dfe
+ * requested user: gid & supplemental groups.
5e9dfe
+ */
5e9dfe
+static int load_groups(const char *user, char ***groups, int *groups_n) {
5e9dfe
+    struct passwd *pwd;
5e9dfe
+    gid_t grps[NGROUPS_MAX];
5e9dfe
+    int ngrps = NGROUPS_MAX;
5e9dfe
+
5e9dfe
+    *groups = NULL;
5e9dfe
+    *groups_n = 0;
5e9dfe
+
5e9dfe
+    pwd = getpwnam(user);
5e9dfe
+    if (pwd == NULL) {
5e9dfe
+	return -1;
5e9dfe
+    }
5e9dfe
+
5e9dfe
+    /* must include at least pwd->pw_gid, hence < 1 test. */
5e9dfe
+    if (getgrouplist(user, pwd->pw_gid, grps, &ngrps) < 1) {
5e9dfe
+	return -1;
5e9dfe
+    }
5e9dfe
+
5e9dfe
+    *groups = calloc(ngrps, sizeof(char *));
5e9dfe
+    int g_n = 0;
5e9dfe
+    for (int i = 0; i < ngrps; i++) {
5e9dfe
+	const struct group *g = getgrgid(grps[i]);
5e9dfe
+	if (g == NULL) {
5e9dfe
+	    continue;
5e9dfe
+	}
5e9dfe
+	D(("noting [%s] is a member of [%s]", user, g->gr_name));
5e9dfe
+	(*groups)[g_n++] = strdup(g->gr_name);
5e9dfe
+    }
5e9dfe
+
5e9dfe
+    *groups_n = g_n;
5e9dfe
+    return 0;
5e9dfe
+}
5e9dfe
+
5e9dfe
 /* obtain the inheritable capabilities for the current user */
5e9dfe
 
5e9dfe
 static char *read_capabilities_for_user(const char *user, const char *source)
5e9dfe
 {
5e9dfe
     char *cap_string = NULL;
5e9dfe
     char buffer[CAP_FILE_BUFFER_SIZE], *line;
5e9dfe
+    char **groups;
5e9dfe
+    int groups_n;
5e9dfe
     FILE *cap_file;
5e9dfe
 
5e9dfe
+    if (load_groups(user, &groups, &groups_n)) {
5e9dfe
+	D(("unknown user [%s]", user));
5e9dfe
+	return NULL;
5e9dfe
+    }
5e9dfe
+
5e9dfe
     cap_file = fopen(source, "r");
5e9dfe
     if (cap_file == NULL) {
5e9dfe
 	D(("failed to open capability file"));
5e9dfe
-	return NULL;
5e9dfe
+	goto defer;
5e9dfe
     }
5e9dfe
 
5e9dfe
-    while ((line = fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file))) {
5e9dfe
-	int found_one = 0;
5e9dfe
+    int found_one = 0;
5e9dfe
+    while (!found_one &&
5e9dfe
+	   (line = fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file))) {
5e9dfe
 	const char *cap_text;
5e9dfe
 
5e9dfe
-	cap_text = strtok(line, CAP_FILE_DELIMITERS);
5e9dfe
+	char *next = NULL;
5e9dfe
+	cap_text = strtok_r(line, CAP_FILE_DELIMITERS, &next;;
5e9dfe
 
5e9dfe
 	if (cap_text == NULL) {
5e9dfe
 	    D(("empty line"));
5e9dfe
@@ -60,38 +107,63 @@ static char *read_capabilities_for_user(const char *user, const char *source)
5e9dfe
 	    continue;
5e9dfe
 	}
5e9dfe
 
5e9dfe
-	while ((line = strtok(NULL, CAP_FILE_DELIMITERS))) {
5e9dfe
-
5e9dfe
+	/*
5e9dfe
+	 * Explore whether any of the ids are a match for the current
5e9dfe
+	 * user.
5e9dfe
+	 */
5e9dfe
+	while ((line = strtok_r(next, CAP_FILE_DELIMITERS, &next))) {
5e9dfe
 	    if (strcmp("*", line) == 0) {
5e9dfe
 		D(("wildcard matched"));
5e9dfe
 		found_one = 1;
5e9dfe
-		cap_string = strdup(cap_text);
5e9dfe
 		break;
5e9dfe
 	    }
5e9dfe
 
5e9dfe
 	    if (strcmp(user, line) == 0) {
5e9dfe
 		D(("exact match for user"));
5e9dfe
 		found_one = 1;
5e9dfe
-		cap_string = strdup(cap_text);
5e9dfe
 		break;
5e9dfe
 	    }
5e9dfe
 
5e9dfe
-	    D(("user is not [%s] - skipping", line));
5e9dfe
-	}
5e9dfe
+	    if (line[0] != '@') {
5e9dfe
+		D(("user [%s] is not [%s] - skipping", user, line));
5e9dfe
+	    }
5e9dfe
 
5e9dfe
-	cap_text = NULL;
5e9dfe
-	line = NULL;
5e9dfe
+	    for (int i=0; i < groups_n; i++) {
5e9dfe
+		if (!strcmp(groups[i], line+1)) {
5e9dfe
+		    D(("user group matched [%s]", line));
5e9dfe
+		    found_one = 1;
5e9dfe
+		    break;
5e9dfe
+		}
5e9dfe
+	    }
5e9dfe
+	    if (found_one) {
5e9dfe
+		break;
5e9dfe
+	    }
5e9dfe
+	}
5e9dfe
 
5e9dfe
 	if (found_one) {
5e9dfe
+	    cap_string = strdup(cap_text);
5e9dfe
 	    D(("user [%s] matched - caps are [%s]", user, cap_string));
5e9dfe
-	    break;
5e9dfe
 	}
5e9dfe
+
5e9dfe
+	cap_text = NULL;
5e9dfe
+	line = NULL;
5e9dfe
     }
5e9dfe
 
5e9dfe
     fclose(cap_file);
5e9dfe
 
5e9dfe
+defer:
5e9dfe
     memset(buffer, 0, CAP_FILE_BUFFER_SIZE);
5e9dfe
 
5e9dfe
+    for (int i = 0; i < groups_n; i++) {
5e9dfe
+	char *g = groups[i];
5e9dfe
+	_pam_overwrite(g);
5e9dfe
+	_pam_drop(g);
5e9dfe
+    }
5e9dfe
+    if (groups != NULL) {
5e9dfe
+	memset(groups, 0, groups_n * sizeof(char *));
5e9dfe
+	_pam_drop(groups);
5e9dfe
+    }
5e9dfe
+
5e9dfe
     return cap_string;
5e9dfe
 }
5e9dfe
 
5e9dfe
@@ -100,15 +172,16 @@ static char *read_capabilities_for_user(const char *user, const char *source)
5e9dfe
  * permitted+executable sets combined with the configured inheritable
5e9dfe
  * set.
5e9dfe
  */
5e9dfe
-
5e9dfe
 static int set_capabilities(struct pam_cap_s *cs)
5e9dfe
 {
5e9dfe
     cap_t cap_s;
5e9dfe
-    ssize_t length = 0;
5e9dfe
-    char *conf_icaps;
5e9dfe
-    char *proc_epcaps;
5e9dfe
-    char *combined_caps;
5e9dfe
+    char *conf_caps;
5e9dfe
     int ok = 0;
5e9dfe
+    int has_ambient = 0, has_bound = 0;
5e9dfe
+    int *bound = NULL, *ambient = NULL;
5e9dfe
+    cap_flag_value_t had_setpcap = 0;
5e9dfe
+    cap_value_t max_caps = 0;
5e9dfe
+    const cap_value_t wanted_caps[] = { CAP_SETPCAP };
5e9dfe
 
5e9dfe
     cap_s = cap_get_proc();
5e9dfe
     if (cap_s == NULL) {
5e9dfe
@@ -116,82 +189,170 @@ static int set_capabilities(struct pam_cap_s *cs)
5e9dfe
 	   strerror(errno)));
5e9dfe
 	return 0;
5e9dfe
     }
5e9dfe
+    if (cap_get_flag(cap_s, CAP_SETPCAP, CAP_EFFECTIVE, &had_setpcap)) {
5e9dfe
+	D(("failed to read a e capability: %s", strerror(errno)));
5e9dfe
+	goto cleanup_cap_s;
5e9dfe
+    }
5e9dfe
+    if (cap_set_flag(cap_s, CAP_EFFECTIVE, 1, wanted_caps, CAP_SET) != 0) {
5e9dfe
+	D(("unable to raise CAP_SETPCAP: %s", strerrno(errno)));
5e9dfe
+	goto cleanup_cap_s;
5e9dfe
+    }
5e9dfe
 
5e9dfe
-    conf_icaps =
5e9dfe
-	read_capabilities_for_user(cs->user,
5e9dfe
-				   cs->conf_filename
5e9dfe
-				   ? cs->conf_filename:USER_CAP_FILE );
5e9dfe
-    if (conf_icaps == NULL) {
5e9dfe
+    conf_caps =	read_capabilities_for_user(cs->user,
5e9dfe
+					   cs->conf_filename
5e9dfe
+					   ? cs->conf_filename:USER_CAP_FILE );
5e9dfe
+    if (conf_caps == NULL) {
5e9dfe
 	D(("no capabilities found for user [%s]", cs->user));
5e9dfe
 	goto cleanup_cap_s;
5e9dfe
     }
5e9dfe
 
5e9dfe
-    proc_epcaps = cap_to_text(cap_s, &length);
5e9dfe
-    if (proc_epcaps == NULL) {
5e9dfe
-	D(("unable to convert process capabilities to text"));
5e9dfe
-	goto cleanup_icaps;
5e9dfe
+    ssize_t conf_caps_length = strlen(conf_caps);
5e9dfe
+    if (!strcmp(conf_caps, "all")) {
5e9dfe
+	/*
5e9dfe
+	 * all here is interpreted as no change/pass through, which is
5e9dfe
+	 * likely to be the same as none for sensible system defaults.
5e9dfe
+	 */
5e9dfe
+	ok = 1;
5e9dfe
+	goto cleanup_caps;
5e9dfe
     }
5e9dfe
 
5e9dfe
-    /*
5e9dfe
-     * This is a pretty inefficient way to combine
5e9dfe
-     * capabilities. However, it seems to be the most straightforward
5e9dfe
-     * one, given the limitations of the POSIX.1e draft spec. The spec
5e9dfe
-     * is optimized for applications that know the capabilities they
5e9dfe
-     * want to manipulate at compile time.
5e9dfe
-     */
5e9dfe
-
5e9dfe
-    combined_caps = malloc(1+strlen(CAP_COMBINED_FORMAT)
5e9dfe
-			   +strlen(proc_epcaps)+strlen(conf_icaps));
5e9dfe
-    if (combined_caps == NULL) {
5e9dfe
-	D(("unable to combine capabilities into one string - no memory"));
5e9dfe
-	goto cleanup_epcaps;
5e9dfe
+    if (cap_set_proc(cap_s) != 0) {
5e9dfe
+	D(("unable to use CAP_SETPCAP: %s", strerrno(errno)));
5e9dfe
+	goto cleanup_caps;
5e9dfe
+    }
5e9dfe
+    if (cap_reset_ambient() == 0) {
5e9dfe
+	// Ambient set fully declared by this config.
5e9dfe
+	has_ambient = 1;
5e9dfe
     }
5e9dfe
 
5e9dfe
-    if (!strcmp(conf_icaps, "none")) {
5e9dfe
-	sprintf(combined_caps, CAP_DROP_ALL, proc_epcaps);
5e9dfe
-    } else if (!strcmp(conf_icaps, "all")) {
5e9dfe
-	/* no change */
5e9dfe
-	sprintf(combined_caps, "%s", proc_epcaps);
5e9dfe
+    if (!strcmp(conf_caps, "none")) {
5e9dfe
+	/* clearing CAP_INHERITABLE will also clear the ambient caps. */
5e9dfe
+	cap_clear_flag(cap_s, CAP_INHERITABLE);
5e9dfe
     } else {
5e9dfe
-	sprintf(combined_caps, CAP_COMBINED_FORMAT, proc_epcaps, conf_icaps);
5e9dfe
-    }
5e9dfe
-    D(("combined_caps=[%s]", combined_caps));
5e9dfe
+	/*
5e9dfe
+	 * we know we have to perform some capability operations and
5e9dfe
+	 * we need to know how many capabilities there are to do it
5e9dfe
+	 * successfully.
5e9dfe
+	 */
5e9dfe
+	while (cap_get_bound(max_caps) >= 0) {
5e9dfe
+	    max_caps++;
5e9dfe
+	}
5e9dfe
+	has_bound = (max_caps != 0);
5e9dfe
+	if (has_bound) {
5e9dfe
+	    bound = calloc(max_caps, sizeof(int));
5e9dfe
+	    if (has_ambient) {
5e9dfe
+		// In kernel lineage, bound came first.
5e9dfe
+		ambient = calloc(max_caps, sizeof(int));
5e9dfe
+	    }
5e9dfe
+	}
5e9dfe
+
5e9dfe
+	/*
5e9dfe
+	 * Scan the configured capability string for:
5e9dfe
+	 *
5e9dfe
+	 *   cap_name: add to cap_s' inheritable vector
5e9dfe
+	 *   ^cap_name: add to cap_s' inheritable vector and ambient set
5e9dfe
+	 *   !cap_name: drop from bounding set
5e9dfe
+	 *
5e9dfe
+	 * Setting ambient capabilities requires that we first enable
5e9dfe
+	 * the corresponding inheritable capability to set them. So,
5e9dfe
+	 * there is an order we use: parse the config line, building
5e9dfe
+	 * the inheritable, ambient and bounding sets in three separate
5e9dfe
+	 * arrays. Then, set I set A set B. Finally, at the end, we
5e9dfe
+	 * restore the E value for CAP_SETPCAP.
5e9dfe
+	 */
5e9dfe
+	char *token = NULL;
5e9dfe
+	char *next = conf_caps;
5e9dfe
+	while ((token = strtok_r(next, ",", &next))) {
5e9dfe
+	    if (strlen(token) < 4) {
5e9dfe
+		D(("bogus cap: [%s] - ignored\n", token));
5e9dfe
+		goto cleanup_caps;
5e9dfe
+	    }
5e9dfe
+	    int is_a = 0, is_b = 0;
5e9dfe
+	    if (*token == '^') {
5e9dfe
+		if (!has_ambient) {
5e9dfe
+		    D(("want ambient [%s] but kernel has no support", token));
5e9dfe
+		    goto cleanup_caps;
5e9dfe
+		}
5e9dfe
+		is_a = 1;
5e9dfe
+		token++;
5e9dfe
+	    } else if (*token == '!') {
5e9dfe
+		if (!has_bound) {
5e9dfe
+		    D(("want bound [%s] dropped - no kernel support", token));
5e9dfe
+		}
5e9dfe
+		is_b = 1;
5e9dfe
+		token++;
5e9dfe
+	    }
5e9dfe
+
5e9dfe
+	    cap_value_t c;
5e9dfe
+	    if (cap_from_name(token, &c) != 0) {
5e9dfe
+		D(("unrecognized name [%s]: %s - ignored", token,
5e9dfe
+		   strerror(errno)));
5e9dfe
+		goto cleanup_caps;
5e9dfe
+	    }
5e9dfe
 
5e9dfe
-    cap_free(cap_s);
5e9dfe
-    cap_s = cap_from_text(combined_caps);
5e9dfe
-    _pam_overwrite(combined_caps);
5e9dfe
-    _pam_drop(combined_caps);
5e9dfe
+	    if (is_b) {
5e9dfe
+		bound[c] = 1;
5e9dfe
+	    } else {
5e9dfe
+		if (cap_set_flag(cap_s, CAP_INHERITABLE, 1, &c, CAP_SET)) {
5e9dfe
+		    D(("failed to raise inheritable [%s]: %s", token,
5e9dfe
+		       strerror(errno)));
5e9dfe
+		    goto cleanup_caps;
5e9dfe
+		}
5e9dfe
+		if (is_a) {
5e9dfe
+		    ambient[c] = 1;
5e9dfe
+		}
5e9dfe
+	    }
5e9dfe
+	}
5e9dfe
 
5e9dfe
 #ifdef DEBUG
5e9dfe
-    {
5e9dfe
-        char *temp = cap_to_text(cap_s, NULL);
5e9dfe
-	D(("abbreviated caps for process will be [%s]", temp));
5e9dfe
-	cap_free(temp);
5e9dfe
-    }
5e9dfe
+	{
5e9dfe
+	    char *temp = cap_to_text(cap_s, NULL);
5e9dfe
+	    D(("abbreviated caps for process will be [%s]", temp));
5e9dfe
+	    cap_free(temp);
5e9dfe
+	}
5e9dfe
 #endif /* DEBUG */
5e9dfe
+    }
5e9dfe
 
5e9dfe
-    if (cap_s == NULL) {
5e9dfe
-	D(("no capabilies to set"));
5e9dfe
-    } else if (cap_set_proc(cap_s) == 0) {
5e9dfe
-	D(("capabilities were set correctly"));
5e9dfe
-	ok = 1;
5e9dfe
-    } else {
5e9dfe
+    if (cap_set_proc(cap_s)) {
5e9dfe
 	D(("failed to set specified capabilities: %s", strerror(errno)));
5e9dfe
+    } else {
5e9dfe
+	for (cap_value_t c = 0; c < max_caps; c++) {
5e9dfe
+	    if (ambient != NULL && ambient[c]) {
5e9dfe
+		cap_set_ambient(c, CAP_SET);
5e9dfe
+	    }
5e9dfe
+	    if (bound != NULL && bound[c]) {
5e9dfe
+		cap_drop_bound(c);
5e9dfe
+	    }
5e9dfe
+	}
5e9dfe
+	ok = 1;
5e9dfe
     }
5e9dfe
 
5e9dfe
-cleanup_epcaps:
5e9dfe
-    cap_free(proc_epcaps);
5e9dfe
-
5e9dfe
-cleanup_icaps:
5e9dfe
-    _pam_overwrite(conf_icaps);
5e9dfe
-    _pam_drop(conf_icaps);
5e9dfe
+cleanup_caps:
5e9dfe
+    if (has_ambient) {
5e9dfe
+	memset(ambient, 0, max_caps * sizeof(*ambient));
5e9dfe
+	_pam_drop(ambient);
5e9dfe
+	ambient = NULL;
5e9dfe
+    }
5e9dfe
+    if (has_bound) {
5e9dfe
+	memset(bound, 0, max_caps * sizeof(*bound));
5e9dfe
+	_pam_drop(bound);
5e9dfe
+	bound = NULL;
5e9dfe
+    }
5e9dfe
+    memset(conf_caps, 0, conf_caps_length);
5e9dfe
+    _pam_drop(conf_caps);
5e9dfe
 
5e9dfe
 cleanup_cap_s:
5e9dfe
+    if (!had_setpcap) {
5e9dfe
+	/* Only need to lower if it wasn't raised by caller */
5e9dfe
+	if (!cap_set_flag(cap_s, CAP_EFFECTIVE, 1, wanted_caps,
5e9dfe
+			  CAP_CLEAR)) {
5e9dfe
+	    cap_set_proc(cap_s);
5e9dfe
+	}
5e9dfe
+    }
5e9dfe
     if (cap_s) {
5e9dfe
 	cap_free(cap_s);
5e9dfe
 	cap_s = NULL;
5e9dfe
     }
5e9dfe
-
5e9dfe
     return ok;
5e9dfe
 }
5e9dfe
 
5e9dfe
@@ -210,11 +371,8 @@ static void _pam_log(int err, const char *format, ...)
5e9dfe
 
5e9dfe
 static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
5e9dfe
 {
5e9dfe
-    int ctrl=0;
5e9dfe
-
5e9dfe
     /* step through arguments */
5e9dfe
-    for (ctrl=0; argc-- > 0; ++argv) {
5e9dfe
-
5e9dfe
+    for (; argc-- > 0; ++argv) {
5e9dfe
 	if (!strcmp(*argv, "debug")) {
5e9dfe
 	    pcs->debug = 1;
5e9dfe
 	} else if (!memcmp(*argv, "config=", 7)) {
5e9dfe
@@ -222,23 +380,25 @@ static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
5e9dfe
 	} else {
5e9dfe
 	    _pam_log(LOG_ERR, "unknown option; %s", *argv);
5e9dfe
 	}
5e9dfe
-
5e9dfe
     }
5e9dfe
 }
5e9dfe
 
5e9dfe
+/*
5e9dfe
+ * pam_sm_authenticate parses the config file with respect to the user
5e9dfe
+ * being authenticated and determines if they are covered by any
5e9dfe
+ * capability inheritance rules.
5e9dfe
+ */
5e9dfe
 int pam_sm_authenticate(pam_handle_t *pamh, int flags,
5e9dfe
 			int argc, const char **argv)
5e9dfe
 {
5e9dfe
     int retval;
5e9dfe
     struct pam_cap_s pcs;
5e9dfe
-    char *conf_icaps;
5e9dfe
+    char *conf_caps;
5e9dfe
 
5e9dfe
     memset(&pcs, 0, sizeof(pcs));
5e9dfe
-
5e9dfe
     parse_args(argc, argv, &pcs;;
5e9dfe
 
5e9dfe
     retval = pam_get_user(pamh, &pcs.user, NULL);
5e9dfe
-
5e9dfe
     if (retval == PAM_CONV_AGAIN) {
5e9dfe
 	D(("user conversation is not available yet"));
5e9dfe
 	memset(&pcs, 0, sizeof(pcs));
5e9dfe
@@ -251,24 +411,22 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags,
5e9dfe
 	return PAM_AUTH_ERR;
5e9dfe
     }
5e9dfe
 
5e9dfe
-    conf_icaps =
5e9dfe
-	read_capabilities_for_user(pcs.user,
5e9dfe
-				   pcs.conf_filename
5e9dfe
-				   ? pcs.conf_filename:USER_CAP_FILE );
5e9dfe
-
5e9dfe
+    conf_caps =	read_capabilities_for_user(pcs.user,
5e9dfe
+					   pcs.conf_filename
5e9dfe
+					   ? pcs.conf_filename:USER_CAP_FILE );
5e9dfe
     memset(&pcs, 0, sizeof(pcs));
5e9dfe
 
5e9dfe
-    if (conf_icaps) {
5e9dfe
+    if (conf_caps) {
5e9dfe
 	D(("it appears that there are capabilities for this user [%s]",
5e9dfe
-	   conf_icaps));
5e9dfe
+	   conf_caps));
5e9dfe
 
5e9dfe
 	/* We could also store this as a pam_[gs]et_data item for use
5e9dfe
 	   by the setcred call to follow. As it is, there is a small
5e9dfe
 	   race associated with a redundant read. Oh well, if you
5e9dfe
 	   care, send me a patch.. */
5e9dfe
 
5e9dfe
-	_pam_overwrite(conf_icaps);
5e9dfe
-	_pam_drop(conf_icaps);
5e9dfe
+	_pam_overwrite(conf_caps);
5e9dfe
+	_pam_drop(conf_caps);
5e9dfe
 
5e9dfe
 	return PAM_SUCCESS;
5e9dfe
 
5e9dfe
@@ -280,6 +438,10 @@ int pam_sm_authenticate(pam_handle_t *pamh, int flags,
5e9dfe
     }
5e9dfe
 }
5e9dfe
 
5e9dfe
+/*
5e9dfe
+ * pam_sm_setcred applies inheritable capabilities loaded by the
5e9dfe
+ * pam_sm_authenticate pass for the user.
5e9dfe
+ */
5e9dfe
 int pam_sm_setcred(pam_handle_t *pamh, int flags,
5e9dfe
 		   int argc, const char **argv)
5e9dfe
 {
5e9dfe
@@ -292,18 +454,15 @@ int pam_sm_setcred(pam_handle_t *pamh, int flags,
5e9dfe
     }
5e9dfe
 
5e9dfe
     memset(&pcs, 0, sizeof(pcs));
5e9dfe
-
5e9dfe
     parse_args(argc, argv, &pcs;;
5e9dfe
 
5e9dfe
     retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user);
5e9dfe
     if ((retval != PAM_SUCCESS) || (pcs.user == NULL) || !(pcs.user[0])) {
5e9dfe
-
5e9dfe
 	D(("user's name is not set"));
5e9dfe
 	return PAM_AUTH_ERR;
5e9dfe
     }
5e9dfe
 
5e9dfe
     retval = set_capabilities(&pcs;;
5e9dfe
-
5e9dfe
     memset(&pcs, 0, sizeof(pcs));
5e9dfe
 
5e9dfe
     return (retval ? PAM_SUCCESS:PAM_IGNORE );
5e9dfe
diff --git a/pam_cap/sudotest.conf b/pam_cap/sudotest.conf
5e9dfe
new file mode 100644
5e9dfe
index 0000000..ff528ce
5e9dfe
--- /dev/null
5e9dfe
+++ b/pam_cap/sudotest.conf
5e9dfe
@@ -0,0 +1,23 @@
5e9dfe
+# only root
5e9dfe
+all                                root
5e9dfe
+
5e9dfe
+# this should fire for beta only
5e9dfe
+!cap_chown                         beta
5e9dfe
+
5e9dfe
+# the next one should snag gamma since beta done
5e9dfe
+cap_setuid,cap_chown               @three
5e9dfe
+
5e9dfe
+# neither of these should fire
5e9dfe
+cap_chown                          beta gamma
5e9dfe
+
5e9dfe
+# just alpha
5e9dfe
+!cap_chown,cap_setuid              @one
5e9dfe
+
5e9dfe
+# not this one
5e9dfe
+^cap_setuid                        alpha
5e9dfe
+
5e9dfe
+# this should fire
5e9dfe
+^cap_chown,^cap_setgid,!cap_setuid delta
5e9dfe
+
5e9dfe
+# not this one
5e9dfe
+cap_setuid                         @four
5e9dfe
diff --git a/pam_cap/test_pam_cap.c b/pam_cap/test_pam_cap.c
5e9dfe
new file mode 100644
5e9dfe
index 0000000..2f519f1
5e9dfe
--- /dev/null
5e9dfe
+++ b/pam_cap/test_pam_cap.c
5e9dfe
@@ -0,0 +1,200 @@
5e9dfe
+/*
5e9dfe
+ * Copyright (c) 2019 Andrew G. Morgan <morgan@kernel.org>
5e9dfe
+ *
5e9dfe
+ * This test inlines the pam_cap module and runs test vectors against
5e9dfe
+ * it.
5e9dfe
+ */
5e9dfe
+
5e9dfe
+#include "./pam_cap.c"
5e9dfe
+
5e9dfe
+const char *test_groups[] = {
5e9dfe
+    "root", "one", "two", "three", "four", "five", "six", "seven"
5e9dfe
+};
5e9dfe
+#define n_groups sizeof(test_groups)/sizeof(*test_groups)
5e9dfe
+
5e9dfe
+const char *test_users[] = {
5e9dfe
+    "root", "alpha", "beta", "gamma", "delta"
5e9dfe
+};
5e9dfe
+#define n_users sizeof(test_users)/sizeof(*test_users)
5e9dfe
+
5e9dfe
+// Note about memberships:
5e9dfe
+//
5e9dfe
+//   user gid   suppl groups
5e9dfe
+//  root  root
5e9dfe
+//  alpha one   two
5e9dfe
+//  beta  two   three four
5e9dfe
+//  gamma three four five six
5e9dfe
+//  delta four  five six seven [eight]
5e9dfe
+//
5e9dfe
+
5e9dfe
+static char *test_user;
5e9dfe
+
5e9dfe
+int pam_get_user(pam_handle_t *pamh, const char **user, const char *prompt) {
5e9dfe
+    *user = test_user;
5e9dfe
+    if (*user == NULL) {
5e9dfe
+	return PAM_CONV_AGAIN;
5e9dfe
+    }
5e9dfe
+    return PAM_SUCCESS;
5e9dfe
+}
5e9dfe
+
5e9dfe
+int pam_get_item(const pam_handle_t *pamh, int item_type, const void **item) {
5e9dfe
+    if (item_type != PAM_USER) {
5e9dfe
+	errno = EINVAL;
5e9dfe
+	return -1;
5e9dfe
+    }
5e9dfe
+    *item = test_user;
5e9dfe
+    return 0;
5e9dfe
+}
5e9dfe
+
5e9dfe
+int getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups) {
5e9dfe
+    int i,j;
5e9dfe
+    for (i = 0; i < n_users; i++) {
5e9dfe
+	if (strcmp(user, test_users[i]) == 0) {
5e9dfe
+	    *ngroups = i+1;
5e9dfe
+	    break;
5e9dfe
+	}
5e9dfe
+    }
5e9dfe
+    if (i == n_users) {
5e9dfe
+	return -1;
5e9dfe
+    }
5e9dfe
+    groups[0] = i;
5e9dfe
+    for (j = 1; j < *ngroups; j++) {
5e9dfe
+	groups[j] = i+j;
5e9dfe
+    }
5e9dfe
+    return *ngroups;
5e9dfe
+}
5e9dfe
+
5e9dfe
+static struct group gr;
5e9dfe
+struct group *getgrgid(gid_t gid) {
5e9dfe
+    if (gid >= n_groups) {
5e9dfe
+	errno = EINVAL;
5e9dfe
+	return NULL;
5e9dfe
+    }
5e9dfe
+    gr.gr_name = strdup(test_groups[gid]);
5e9dfe
+    return &gr;
5e9dfe
+}
5e9dfe
+
5e9dfe
+static struct passwd pw;
5e9dfe
+struct passwd *getpwnam(const char *name) {
5e9dfe
+    for (int i = 0; i < n_users; i++) {
5e9dfe
+	if (strcmp(name, test_users[i]) == 0) {
5e9dfe
+	    pw.pw_gid = i;
5e9dfe
+	    return &pw;
5e9dfe
+	}
5e9dfe
+    }
5e9dfe
+    return NULL;
5e9dfe
+}
5e9dfe
+
5e9dfe
+/* we'll use these to keep track of the three vectors - only use
5e9dfe
+   lowest 64 bits */
5e9dfe
+
5e9dfe
+#define A 0
5e9dfe
+#define B 1
5e9dfe
+#define I 2
5e9dfe
+
5e9dfe
+/*
5e9dfe
+ * load_vectors caches a copy of the lowest 64 bits of the inheritable
5e9dfe
+ * cap vectors
5e9dfe
+ */
5e9dfe
+static void load_vectors(unsigned long int bits[3]) {
5e9dfe
+    memset(bits, 0, 3*sizeof(unsigned long int));
5e9dfe
+    cap_t prev = cap_get_proc();
5e9dfe
+    for (int i = 0; i < 64; i++) {
5e9dfe
+	unsigned long int mask = (1ULL << i);
5e9dfe
+	int v = cap_get_bound(i);
5e9dfe
+	if (v < 0) {
5e9dfe
+	    break;
5e9dfe
+	}
5e9dfe
+	bits[B] |= v ? mask : 0;
5e9dfe
+	cap_flag_value_t u;
5e9dfe
+	if (cap_get_flag(prev, i, CAP_INHERITABLE, &u) != 0) {
5e9dfe
+	    break;
5e9dfe
+	}
5e9dfe
+	bits[I] |= u ? mask : 0;
5e9dfe
+	v = cap_get_ambient(i);
5e9dfe
+	if (v > 0) {
5e9dfe
+	    bits[A] |= mask;
5e9dfe
+	}
5e9dfe
+    }
5e9dfe
+    cap_free(prev);
5e9dfe
+}
5e9dfe
+
5e9dfe
+/*
5e9dfe
+ * args: user a b i config-args...
5e9dfe
+ */
5e9dfe
+int main(int argc, char *argv[]) {
5e9dfe
+    unsigned long int before[3], change[3], after[3];
5e9dfe
+
5e9dfe
+    /*
5e9dfe
+     * Start out with a cleared inheritable set.
5e9dfe
+     */
5e9dfe
+    cap_t orig = cap_get_proc();
5e9dfe
+    cap_clear_flag(orig, CAP_INHERITABLE);
5e9dfe
+    cap_set_proc(orig);
5e9dfe
+
5e9dfe
+    change[A] = strtoul(argv[2], NULL, 0);
5e9dfe
+    change[B] = strtoul(argv[3], NULL, 0);
5e9dfe
+    change[I] = strtoul(argv[4], NULL, 0);
5e9dfe
+
5e9dfe
+    void* args_for_pam = argv+4;
5e9dfe
+
5e9dfe
+    int status = pam_sm_authenticate(NULL, 0, argc-4,
5e9dfe
+				     (const char **) args_for_pam);
5e9dfe
+    if (status != PAM_INCOMPLETE) {
5e9dfe
+	printf("failed to recognize no username\n");
5e9dfe
+	exit(1);
5e9dfe
+    }
5e9dfe
+
5e9dfe
+    test_user = argv[1];
5e9dfe
+
5e9dfe
+    status = pam_sm_authenticate(NULL, 0, argc-4, (const char **) args_for_pam);
5e9dfe
+    if (status == PAM_IGNORE) {
5e9dfe
+	if (strcmp(test_user, "root") == 0) {
5e9dfe
+	    exit(0);
5e9dfe
+	}
5e9dfe
+	printf("unconfigured non-root user: %s\n", test_user);
5e9dfe
+	exit(1);
5e9dfe
+    }
5e9dfe
+    if (status != PAM_SUCCESS) {
5e9dfe
+	printf("failed to recognize username\n");
5e9dfe
+	exit(1);
5e9dfe
+    }
5e9dfe
+
5e9dfe
+    // Now it is time to execute the credential setting
5e9dfe
+    load_vectors(before);
5e9dfe
+
5e9dfe
+    status = pam_sm_setcred(NULL, PAM_ESTABLISH_CRED, argc-4,
5e9dfe
+			    (const char **) args_for_pam);
5e9dfe
+
5e9dfe
+    load_vectors(after);
5e9dfe
+
5e9dfe
+    printf("before: A=0x%016lx B=0x%016lx I=0x%016lx\n",
5e9dfe
+	   before[A], before[B], before[I]);
5e9dfe
+
5e9dfe
+    long unsigned int dA = before[A] ^ after[A];
5e9dfe
+    long unsigned int dB = before[B] ^ after[B];
5e9dfe
+    long unsigned int dI = before[I] ^ after[I];
5e9dfe
+
5e9dfe
+    printf("diff  : A=0x%016lx B=0x%016lx I=0x%016lx\n", dA, dB, dI);
5e9dfe
+    printf("after : A=0x%016lx B=0x%016lx I=0x%016lx\n",
5e9dfe
+	   after[A], after[B], after[I]);
5e9dfe
+
5e9dfe
+    int failure = 0;
5e9dfe
+    if (after[A] != change[A]) {
5e9dfe
+	printf("Ambient set error: got=0x%016lx, want=0x%016lx\n",
5e9dfe
+	       after[A], change[A]);
5e9dfe
+	failure = 1;
5e9dfe
+    }
5e9dfe
+    if (dB != change[B]) {
5e9dfe
+	printf("Bounding set error: got=0x%016lx, want=0x%016lx\n",
5e9dfe
+	       after[B], before[B] ^ change[B]);
5e9dfe
+	failure = 1;
5e9dfe
+    }
5e9dfe
+    if (after[I] != change[I]) {
5e9dfe
+	printf("Inheritable set error: got=0x%016lx, want=0x%016lx\n",
5e9dfe
+	       after[I], change[I]);
5e9dfe
+	failure = 1;
5e9dfe
+    }
5e9dfe
+
5e9dfe
+    exit(failure);
5e9dfe
+}
5e9dfe
-- 
5e9dfe
cgit 1.2.3-1.el7
5e9dfe