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