diff --git a/SOURCES/libcap-2.26-ambient-caps.patch b/SOURCES/libcap-2.26-ambient-caps.patch new file mode 100644 index 0000000..b8eeedd --- /dev/null +++ b/SOURCES/libcap-2.26-ambient-caps.patch @@ -0,0 +1,952 @@ +From 99c995b84ef2974426b0acfa584d75e9a7d82028 Mon Sep 17 00:00:00 2001 +From: "Andrew G. Morgan" +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 +--- + 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 +@@ -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 + .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 ++ * Copyright (c) 1999,2007,2019 Andrew G. Morgan + * +- * 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 +-#include + #include ++#include ++#include ++#include + #include + #include ++#include ++#include + #include +- + #include ++#include + + #include + #include +@@ -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 ++ * ++ * 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 + diff --git a/SPECS/libcap.spec b/SPECS/libcap.spec index 648f4f8..1b0361f 100644 --- a/SPECS/libcap.spec +++ b/SPECS/libcap.spec @@ -1,6 +1,6 @@ Name: libcap Version: 2.26 -Release: 3%{?dist} +Release: 4%{?dist} Summary: Library for getting and setting POSIX.1e capabilities URL: https://sites.google.com/site/fullycapable/ License: GPLv2 @@ -11,6 +11,7 @@ Source: https://git.kernel.org/pub/scm/libs/libcap/libcap.git/snapshot/%{name}-% Source1: getpcaps.8 Patch0: %{name}-2.25-buildflags.patch Patch1: %{name}-PAM_REINITIALIZE_CRED.patch +Patch2: %{name}-2.26-ambient-caps.patch BuildRequires: libattr-devel pam-devel perl-interpreter @@ -48,6 +49,7 @@ libcap. %setup -q %patch0 -p1 %patch1 -p1 +%patch2 -p1 %build # libcap can not be build with _smp_mflags: @@ -89,6 +91,10 @@ chmod +x %{buildroot}/%{_libdir}/*.so.* %{_libdir}/pkgconfig/libcap.pc %changelog +* Fri May 22 2020 Jiri Vymazal - 2.26-4 +- added patch implementing support for ambient capabilities + resolves: rhbz#1487388 + * Tue Oct 15 2019 Marek Tamaskovic - 2.26-3 - changed url