diff --git a/.gitignore b/.gitignore index 6d1b2bb..dbf7071 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/libcap-2.26.tar.gz +SOURCES/libcap-2.48.tar.gz diff --git a/.libcap.metadata b/.libcap.metadata index 8f2e041..eb616eb 100644 --- a/.libcap.metadata +++ b/.libcap.metadata @@ -1 +1 @@ -e667d815755f3f6a5819eb383827dd358372dda1 SOURCES/libcap-2.26.tar.gz +c81102815c481257e53168e83b8849bc9f154d54 SOURCES/libcap-2.48.tar.gz diff --git a/SOURCES/getpcaps.8 b/SOURCES/getpcaps.8 deleted file mode 100644 index 6bbf46a..0000000 --- a/SOURCES/getpcaps.8 +++ /dev/null @@ -1,23 +0,0 @@ -.\" Hey, EMACS: -*- nroff -*- -.TH GETPCAPS 8 "2001-05-29" -.\" Please adjust this date whenever revising the manpage. -.SH NAME -getpcaps \- display process capabilities -.SH SYNOPSIS -.B getpcaps -.IR pid ... -.SH DESCRIPTION -.B getpcaps -displays the capabilities on the processes indicated by the -.I pid -value(s) given on the commandline. The capabilities -are displayed in the -.BR cap_from_text (3) -format. -.SH SEE ALSO -.BR execcap (8). -.br -.SH AUTHOR -This manual page was written by Robert Bihlmeyer , -for the Debian GNU/Linux system (but may be used by others). - diff --git a/SOURCES/libcap-2.25-buildflags.patch b/SOURCES/libcap-2.25-buildflags.patch deleted file mode 100644 index 48745fd..0000000 --- a/SOURCES/libcap-2.25-buildflags.patch +++ /dev/null @@ -1,22 +0,0 @@ -diff -up libcap-2.25/Make.Rules.rh libcap-2.25/Make.Rules ---- libcap-2.25/Make.Rules.rh 2016-04-11 18:52:01.418065682 +0200 -+++ libcap-2.25/Make.Rules 2016-04-11 18:52:10.790113866 +0200 -@@ -49,7 +49,8 @@ KERNEL_HEADERS := $(topdir)/libcap/inclu - IPATH += -fPIC -I$(KERNEL_HEADERS) -I$(topdir)/libcap/include - - CC := gcc --CFLAGS := -O2 -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -+CFLAGS := $(RPM_OPT_FLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -+ - BUILD_CC := $(CC) - BUILD_CFLAGS := $(CFLAGS) $(IPATH) - AR := ar -@@ -60,7 +61,7 @@ WARNINGS=-Wall -Wwrite-strings \ - -Wstrict-prototypes -Wmissing-prototypes \ - -Wnested-externs -Winline -Wshadow - LD=$(CC) -Wl,-x -shared --LDFLAGS := #-g -+LDFLAGS := $(RPM_LD_FLAGS) #-g - BUILD_GPERF := $(shell which gperf >/dev/null 2>/dev/null && echo yes) - - SYSTEM_HEADERS = /usr/include diff --git a/SOURCES/libcap-2.26-ambient-caps.patch b/SOURCES/libcap-2.26-ambient-caps.patch deleted file mode 100644 index b8eeedd..0000000 --- a/SOURCES/libcap-2.26-ambient-caps.patch +++ /dev/null @@ -1,952 +0,0 @@ -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/SOURCES/libcap-2.48-buildflags.patch b/SOURCES/libcap-2.48-buildflags.patch new file mode 100644 index 0000000..7ebd16f --- /dev/null +++ b/SOURCES/libcap-2.48-buildflags.patch @@ -0,0 +1,34 @@ +diff --color -ru a/Make.Rules b/Make.Rules +--- a/Make.Rules 2021-02-05 06:52:17.000000000 +0100 ++++ b/Make.Rules 2021-12-13 17:09:11.225308225 +0100 +@@ -56,10 +56,10 @@ + + CC := $(CROSS_COMPILE)gcc + DEFINES := -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 +-COPTS ?= -O2 ++COPTS ?= $(RPM_OPT_FLAGS) + CFLAGS ?= $(COPTS) $(DEFINES) + BUILD_CC ?= $(CC) +-BUILD_COPTS ?= -O2 ++BUILD_COPTS ?= $(RPM_OPT_FLAGS) + BUILD_CFLAGS ?= $(BUILD_COPTS) $(DEFINES) $(IPATH) + AR := $(CROSS_COMPILE)ar + RANLIB := $(CROSS_COMPILE)ranlib +@@ -69,7 +69,7 @@ + -Wstrict-prototypes -Wmissing-prototypes \ + -Wnested-externs -Winline -Wshadow + LD=$(CC) -Wl,-x -shared +-LDFLAGS ?= #-g ++LDFLAGS ?= $(RPM_LD_FLAGS) + LIBCAPLIB := -L$(topdir)/libcap -lcap + PSXLINKFLAGS := -lpthread -Wl,-wrap,pthread_create + LIBPSXLIB := -L$(topdir)/libcap -lpsx $(PSXLINKFLAGS) +@@ -104,7 +104,7 @@ + + ifeq ($(PTHREADS),yes) + GO ?= go +-GOLANG ?= $(shell if [ -n "$(shell $(GO) version 2>/dev/null)" ]; then echo yes ; else echo no ; fi) ++GOLANG ?= no + ifeq ($(GOLANG),yes) + GOROOT ?= $(shell $(GO) env GOROOT) + GOCGO ?= $(shell if [ "$(shell $(GO) env CGO_ENABLED)" = 1 ]; then echo yes ; else echo no ; fi) diff --git a/SOURCES/libcap-PAM_REINITIALIZE_CRED.patch b/SOURCES/libcap-PAM_REINITIALIZE_CRED.patch deleted file mode 100644 index 3e99baf..0000000 --- a/SOURCES/libcap-PAM_REINITIALIZE_CRED.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff -urN libcap-2.25/pam_cap/pam_cap.c libcap-2.25_patched/pam_cap/pam_cap.c ---- libcap-2.25/pam_cap/pam_cap.c 2013-12-16 05:46:28.000000000 +0100 -+++ libcap-2.25_patched/pam_cap/pam_cap.c 2019-03-04 16:18:23.440525062 +0100 -@@ -286,7 +286,7 @@ - int retval; - struct pam_cap_s pcs; - -- if (!(flags & PAM_ESTABLISH_CRED)) { -+ if (!(flags & (PAM_ESTABLISH_CRED | PAM_REINITIALIZE_CRED))) { - D(("we don't handle much in the way of credentials")); - return PAM_IGNORE; - } diff --git a/SOURCES/libcap-abi-compatibility.patch b/SOURCES/libcap-abi-compatibility.patch new file mode 100644 index 0000000..88b4601 --- /dev/null +++ b/SOURCES/libcap-abi-compatibility.patch @@ -0,0 +1,37 @@ +diff --color -ru a/libcap/cap_text.c b/libcap/cap_text.c +--- a/libcap/cap_text.c 2021-02-05 06:52:17.000000000 +0100 ++++ b/libcap/cap_text.c 2021-12-15 13:03:44.993774400 +0100 +@@ -15,7 +15,7 @@ + #define LIBCAP_PLEASE_INCLUDE_ARRAY + #include "libcap.h" + +-static char const *_cap_names[__CAP_BITS] = LIBCAP_CAP_NAMES; ++extern char const *_cap_names[__CAP_BITS]; + + #include + #include +diff --color -ru a/libcap/_makenames.c b/libcap/_makenames.c +--- a/libcap/_makenames.c 2021-02-05 06:52:17.000000000 +0100 ++++ b/libcap/_makenames.c 2021-12-15 12:47:07.921408357 +0100 +@@ -66,17 +66,17 @@ + "#define __CAP_NAME_SIZE %d\n" + "\n" + "#ifdef LIBCAP_PLEASE_INCLUDE_ARRAY\n" +- "#define LIBCAP_CAP_NAMES { \\\n", maxcaps, maxlength+1); ++ " char const *_cap_names[__CAP_BITS] = {\n", maxcaps, maxlength+1); + + for (i=0; i= 0 && (x) <= CAP_LAST_CAP) - -diff --color -ruN a/Makefile b/Makefile ---- a/Makefile 2018-09-15 23:51:38.000000000 +0200 -+++ b/Makefile 2021-06-10 10:07:30.872573023 +0200 -@@ -33,7 +33,10 @@ - test: all - cd progs && sudo ./quicktest.sh - --morganrelease: distclean -+distcheck: -+ ./distcheck.sh -+ -+morganrelease: distclean distcheck - @echo "sign the tag twice: older DSA key; and newer RSA kernel.org key" - git tag -u D41A6DF2 -s libcap-$(VERSION).$(MINOR) -m "This is libcap-$(VERSION).$(MINOR)" - git tag -u E2CCF3F4 -s libcap-korg-$(VERSION).$(MINOR) -m "This is libcap-$(VERSION).$(MINOR)" diff --git a/SOURCES/libcap-fix-ambient-caps.patch b/SOURCES/libcap-fix-ambient-caps.patch new file mode 100644 index 0000000..de08d03 --- /dev/null +++ b/SOURCES/libcap-fix-ambient-caps.patch @@ -0,0 +1,147 @@ +diff --color -ru a/libcap/cap_proc.c b/libcap/cap_proc.c +--- a/libcap/cap_proc.c 2021-12-22 12:33:20.739126763 +0100 ++++ b/libcap/cap_proc.c 2021-12-22 12:33:53.195733115 +0100 +@@ -406,6 +406,29 @@ + } + + /* ++ * cap_prctl performs a prctl() 6 argument call on the current ++ * thread. Use cap_prctlw() if you want to perform a POSIX semantics ++ * prctl() system call. ++ */ ++int cap_prctl(long int pr_cmd, long int arg1, long int arg2, ++ long int arg3, long int arg4, long int arg5) ++{ ++ return prctl(pr_cmd, arg1, arg2, arg3, arg4, arg5); ++} ++ ++/* ++ * cap_prctlw performs a POSIX semantics prctl() call. That is a 6 arg ++ * prctl() call that executes on all available threads when libpsx is ++ * linked. The suffix 'w' refers to the fact one only ever needs to ++ * invoke this is if the call will write some kernel state. ++ */ ++int cap_prctlw(long int pr_cmd, long int arg1, long int arg2, ++ long int arg3, long int arg4, long int arg5) ++{ ++ return _libcap_wprctl6(&multithread, pr_cmd, arg1, arg2, arg3, arg4, arg5); ++} ++ ++/* + * Some predefined constants + */ + #define CAP_SECURED_BITS_BASIC \ +diff --color -ru a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h +--- a/libcap/include/sys/capability.h 2021-02-05 06:52:17.000000000 +0100 ++++ b/libcap/include/sys/capability.h 2021-12-22 12:33:53.196733134 +0100 +@@ -175,6 +175,11 @@ + extern unsigned cap_get_secbits(void); + extern int cap_set_secbits(unsigned bits); + ++extern int cap_prctl(long int pr_cmd, long int arg1, long int arg2, ++ long int arg3, long int arg4, long int arg5); ++extern int cap_prctlw(long int pr_cmd, long int arg1, long int arg2, ++ long int arg3, long int arg4, long int arg5); ++ + extern int cap_setuid(uid_t uid); + extern int cap_setgroups(gid_t gid, size_t ngroups, const gid_t groups[]); + +diff --color -ru a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c +--- a/pam_cap/pam_cap.c 2021-12-22 12:33:20.740126781 +0100 ++++ b/pam_cap/pam_cap.c 2021-12-22 12:33:53.196733134 +0100 +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -33,8 +34,11 @@ + + struct pam_cap_s { + int debug; ++ int keepcaps; ++ int defer; + const char *user; + const char *conf_filename; ++ pam_handle_t *pamh; + }; + + /* +@@ -178,6 +182,33 @@ + } + + /* ++ * This is the "defer" cleanup function that actually applies the IAB ++ * tuple. This happens really late in the PAM session, hopefully after ++ * the application has performed its setuid() function. ++ */ ++static void iab_apply(pam_handle_t *pamh, void *data, int error_status) ++{ ++ cap_iab_t iab = data; ++ int retval = error_status & ~(PAM_DATA_REPLACE|PAM_DATA_SILENT); ++ ++ data = NULL; ++ if (error_status & PAM_DATA_REPLACE) { ++ goto done; ++ } ++ ++ if (retval != PAM_SUCCESS || !(error_status & PAM_DATA_SILENT)) { ++ goto done; ++ } ++ ++ if (cap_iab_set_proc(iab) != 0) { ++ D(("IAB setting failed")); ++ } ++ ++done: ++ cap_free(iab); ++} ++ ++/* + * Set capabilities for current process to match the current + * permitted+executable sets combined with the configured inheritable + * set. +@@ -230,12 +261,21 @@ + goto cleanup_conf; + } + +- if (!cap_iab_set_proc(iab)) { ++ if (cs->defer) { ++ D(("configured to delay applying IAB")); ++ pam_set_data(cs->pamh, "pam_cap_iab", iab, iab_apply); ++ iab = NULL; ++ } else if (!cap_iab_set_proc(iab)) { + D(("able to set the IAB [%s] value", conf_caps)); + ok = 1; + } + cap_free(iab); + ++ if (cs->keepcaps) { ++ D(("setting keepcaps")); ++ (void) cap_prctlw(PR_SET_KEEPCAPS, 1, 0, 0, 0, 0); ++ } ++ + cleanup_conf: + memset(conf_caps, 0, conf_caps_length); + _pam_drop(conf_caps); +@@ -268,6 +308,10 @@ + pcs->debug = 1; + } else if (!strncmp(*argv, "config=", 7)) { + pcs->conf_filename = 7 + *argv; ++ } else if (!strcmp(*argv, "keepcaps")) { ++ pcs->keepcaps = 1; ++ } else if (!strcmp(*argv, "defer")) { ++ pcs->defer = 1; + } else { + _pam_log(LOG_ERR, "unknown option; %s", *argv); + } +@@ -353,6 +397,7 @@ + return PAM_AUTH_ERR; + } + ++ pcs.pamh = pamh; + retval = set_capabilities(&pcs); + memset(&pcs, 0, sizeof(pcs)); + diff --git a/SOURCES/libcap-static-analysis.patch b/SOURCES/libcap-static-analysis.patch new file mode 100644 index 0000000..c72ca73 --- /dev/null +++ b/SOURCES/libcap-static-analysis.patch @@ -0,0 +1,494 @@ +diff --color -ru a/libcap/cap_proc.c b/libcap/cap_proc.c +--- a/libcap/cap_proc.c 2022-01-28 12:42:39.726331628 +0100 ++++ b/libcap/cap_proc.c 2022-01-28 12:44:05.007936110 +0100 +@@ -712,6 +712,10 @@ + cap_value_t c; + int raising = 0; + ++ if (temp == NULL) { ++ return -1; ++ } ++ + for (i = 0; i < _LIBCAP_CAPABILITY_U32S; i++) { + __u32 newI = iab->i[i]; + __u32 oldIP = temp->u[i].flat[CAP_INHERITABLE] | +diff --color -ru a/libcap/cap_text.c b/libcap/cap_text.c +--- a/libcap/cap_text.c 2022-01-28 12:42:39.725331609 +0100 ++++ b/libcap/cap_text.c 2022-01-28 12:44:05.008936129 +0100 +@@ -160,6 +160,7 @@ + cap_blks = _LINUX_CAPABILITY_U32S_3; + break; + default: ++ cap_free(res); + errno = EINVAL; + return NULL; + } +@@ -398,6 +399,9 @@ + for (n = 0; n < cmb; n++) { + if (getstateflags(caps, n) == t) { + char *this_cap_name = cap_to_name(n); ++ if (this_cap_name == NULL) { ++ return NULL; ++ } + if ((strlen(this_cap_name) + (p - buf)) > CAP_TEXT_SIZE) { + cap_free(this_cap_name); + errno = ERANGE; +@@ -450,6 +454,9 @@ + for (n = cmb; n < __CAP_MAXBITS; n++) { + if (getstateflags(caps, n) == t) { + char *this_cap_name = cap_to_name(n); ++ if (this_cap_name == NULL) { ++ return NULL; ++ } + if ((strlen(this_cap_name) + (p - buf)) > CAP_TEXT_SIZE) { + cap_free(this_cap_name); + errno = ERANGE; +@@ -549,6 +556,9 @@ + cap_iab_t cap_iab_from_text(const char *text) + { + cap_iab_t iab = cap_iab_init(); ++ if (iab == NULL) { ++ return iab; ++ } + if (text != NULL) { + unsigned flags; + for (flags = 0; *text; text++) { +diff --color -ru a/libcap/_makenames.c b/libcap/_makenames.c +--- a/libcap/_makenames.c 2022-01-28 12:42:39.725331609 +0100 ++++ b/libcap/_makenames.c 2022-01-28 13:07:28.700817691 +0100 +@@ -45,10 +45,14 @@ + if (maxcaps <= list[i].index) { + maxcaps = list[i].index + 1; + } +- if (list[i].index >= pointers_avail) { ++ if (pointers == NULL || list[i].index >= pointers_avail) { + int was = pointers_avail * sizeof(char *); + pointers_avail = 2 * list[i].index + 1; + pointers = recalloc(pointers, was, pointers_avail * sizeof(char *)); ++ if (pointers == NULL) { ++ perror("unable to continue"); ++ exit(1); ++ } + } + pointers[list[i].index] = list[i].name; + int n = strlen(list[i].name); +diff --color -ru a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c +--- a/pam_cap/pam_cap.c 2022-01-28 12:42:39.726331628 +0100 ++++ b/pam_cap/pam_cap.c 2022-01-28 12:44:05.009936148 +0100 +@@ -64,6 +64,9 @@ + } + + *groups = calloc(ngrps, sizeof(char *)); ++ if (*groups == NULL) { ++ return -1; ++ } + int g_n = 0, i; + for (i = 0; i < ngrps; i++) { + const struct group *g = getgrgid(grps[i]); +@@ -249,7 +252,7 @@ + if (!cap_set_proc(cap_s)) { + ok = 1; + } +- goto cleanup_cap_s; ++ goto cleanup_conf; + } + + iab = cap_iab_from_text(conf_caps); +@@ -278,10 +281,9 @@ + _pam_drop(conf_caps); + + cleanup_cap_s: +- if (cap_s) { +- cap_free(cap_s); +- cap_s = NULL; +- } ++ cap_free(cap_s); ++ cap_s = NULL; ++ + return ok; + } + +diff --color -ru a/progs/capsh.c b/progs/capsh.c +--- a/progs/capsh.c 2021-02-05 06:52:17.000000000 +0100 ++++ b/progs/capsh.c 2022-01-28 13:06:15.803465885 +0100 +@@ -34,6 +34,35 @@ + + #define MAX_GROUPS 100 /* max number of supplementary groups for user */ + ++/* parse a non-negative integer with some error handling */ ++static unsigned long nonneg_uint(const char *text, const char *prefix, int *ok) ++{ ++ char *remains; ++ unsigned long value; ++ ssize_t len = strlen(text); ++ ++ if (len == 0 || *text == '-') { ++ goto fail; ++ } ++ value = strtoul(text, &remains, 0); ++ if (*remains) { ++ goto fail; ++ } ++ if (ok != NULL) { ++ *ok = 1; ++ } ++ return value; ++ ++fail: ++ if (ok == NULL) { ++ fprintf(stderr, "%s: want non-negative integer, got \"%s\"\n", ++ prefix, text); ++ exit(1); ++ } ++ *ok = 0; ++ return 0; ++} ++ + static char *binary(unsigned long value) + { + static char string[8*sizeof(unsigned long) + 1]; +@@ -100,7 +129,16 @@ + display_prctl_set("Bounding", cap_get_bound); + display_prctl_set("Ambient", cap_get_ambient); + iab = cap_iab_get_proc(); ++ if (iab == NULL) { ++ perror("failed to get IAB for process"); ++ exit(1); ++ } + text = cap_iab_to_text(iab); ++ if (text == NULL) { ++ perror("failed to obtain text for IAB"); ++ cap_free(iab); ++ exit(1); ++ } + printf("Current IAB: %s\n", text); + cap_free(text); + cap_free(iab); +@@ -336,8 +374,8 @@ + */ + static char *find_self(const char *arg0) + { +- int i; +- char *parts, *dir, *scratch; ++ int i, status=1; ++ char *p = NULL, *parts, *dir, *scratch; + const char *path; + + for (i = strlen(arg0)-1; i >= 0 && arg0[i] != '/'; i--); +@@ -352,21 +390,45 @@ + } + + parts = strdup(path); ++ if (parts == NULL) { ++ fprintf(stderr, "insufficient memory for parts of path\n"); ++ exit(1); ++ } ++ + scratch = malloc(2+strlen(path)+strlen(arg0)); +- if (parts == NULL || scratch == NULL) { ++ if (scratch == NULL) { + fprintf(stderr, "insufficient memory for path building\n"); +- exit(1); ++ goto free_parts; + } + +- for (i=0; (dir = strtok(parts, ":")); parts = NULL) { ++ for (p = parts; (dir = strtok(p, ":")); p = NULL) { + sprintf(scratch, "%s/%s", dir, arg0); + if (access(scratch, X_OK) == 0) { +- return scratch; ++ status = 0; ++ break; + } + } ++ if (status) { ++ fprintf(stderr, "unable to find executable '%s' in PATH\n", arg0); ++ free(scratch); ++ } + +- fprintf(stderr, "unable to find executable '%s' in PATH\n", arg0); +- exit(1); ++free_parts: ++ free(parts); ++ if (status) { ++ exit(status); ++ } ++ return scratch; ++} ++ ++static long safe_sysconf(int name) ++{ ++ long ans = sysconf(name); ++ if (ans <= 0) { ++ fprintf(stderr, "sysconf(%d) returned a non-positive number: %ld\n", name, ans); ++ exit(1); ++ } ++ return ans; + } + + int main(int argc, char *argv[], char *envp[]) +@@ -378,6 +440,10 @@ + child = 0; + + char *temp_name = cap_to_name(cap_max_bits() - 1); ++ if (temp_name == NULL) { ++ perror("obtaining highest capability name"); ++ exit(1); ++ } + if (temp_name[0] != 'c') { + printf("WARNING: libcap needs an update (cap=%d should have a name).\n", + cap_max_bits() - 1); +@@ -573,7 +639,7 @@ + unsigned value; + int set; + +- value = strtoul(argv[i]+7, NULL, 0); ++ value = nonneg_uint(argv[i]+7, "invalid --keep value", NULL); + set = prctl(PR_SET_KEEPCAPS, value); + if (set < 0) { + fprintf(stderr, "prctl(PR_SET_KEEPCAPS, %u) failed: %s\n", +@@ -617,7 +683,9 @@ + * Given we are now in a new directory tree, its good practice + * to start off in a sane location + */ +- status = chdir("/"); ++ if (status == 0) { ++ status = chdir("/"); ++ } + + cap_free(orig); + +@@ -628,7 +696,7 @@ + } else if (!strncmp("--secbits=", argv[i], 10)) { + unsigned value; + int status; +- value = strtoul(argv[i]+10, NULL, 0); ++ value = nonneg_uint(argv[i]+10, "invalid --secbits value", NULL); + status = cap_set_secbits(value); + if (status < 0) { + fprintf(stderr, "failed to set securebits to 0%o/0x%x\n", +@@ -641,7 +709,7 @@ + fprintf(stderr, "already forked\n"); + exit(1); + } +- value = strtoul(argv[i]+10, NULL, 0); ++ value = nonneg_uint(argv[i]+10, "invalid --forkfor value", NULL); + if (value == 0) { + goto usage; + } +@@ -657,7 +725,8 @@ + pid_t result; + unsigned value; + +- value = strtoul(argv[i]+9, NULL, 0); ++ value = nonneg_uint(argv[i]+9, "invalid --killit signo value", ++ NULL); + if (!child) { + fprintf(stderr, "no forked process to kill\n"); + exit(1); +@@ -683,7 +752,7 @@ + unsigned value; + int status; + +- value = strtoul(argv[i]+6, NULL, 0); ++ value = nonneg_uint(argv[i]+6, "invalid --uid value", NULL); + status = setuid(value); + if (status < 0) { + fprintf(stderr, "Failed to set uid=%u: %s\n", +@@ -694,7 +763,7 @@ + unsigned value; + int status; + +- value = strtoul(argv[i]+10, NULL, 0); ++ value = nonneg_uint(argv[i]+10, "invalid --cap-uid value", NULL); + status = cap_setuid(value); + if (status < 0) { + fprintf(stderr, "Failed to cap_setuid(%u): %s\n", +@@ -705,7 +774,7 @@ + unsigned value; + int status; + +- value = strtoul(argv[i]+6, NULL, 0); ++ value = nonneg_uint(argv[i]+6, "invalid --gid value", NULL); + status = setgid(value); + if (status < 0) { + fprintf(stderr, "Failed to set gid=%u: %s\n", +@@ -718,14 +787,14 @@ + gid_t *group_list; + int g_count; + +- length = sysconf(_SC_GETGR_R_SIZE_MAX); ++ length = safe_sysconf(_SC_GETGR_R_SIZE_MAX); + buf = calloc(1, length); + if (NULL == buf) { + fprintf(stderr, "No memory for [%s] operation\n", argv[i]); + exit(1); + } + +- max_groups = sysconf(_SC_NGROUPS_MAX); ++ max_groups = safe_sysconf(_SC_NGROUPS_MAX); + group_list = calloc(max_groups, sizeof(gid_t)); + if (NULL == group_list) { + fprintf(stderr, "No memory for gid list\n"); +@@ -741,8 +810,7 @@ + } + if (!isdigit(*ptr)) { + struct group *g, grp; +- getgrnam_r(ptr, &grp, buf, length, &g); +- if (NULL == g) { ++ if (getgrnam_r(ptr, &grp, buf, length, &g) || NULL == g) { + fprintf(stderr, "Failed to identify gid for group [%s]\n", ptr); + exit(1); + } +@@ -835,6 +903,7 @@ + argv[argc] = NULL; + execve(argv[i], argv+i, envp); + fprintf(stderr, "execve '%s' failed!\n", argv[i]); ++ free(argv[i]); + exit(1); + } else if (!strncmp("--shell=", argv[i], 8)) { + shell = argv[i]+8; +@@ -885,7 +954,7 @@ + } else if (!strncmp("--is-uid=", argv[i], 9)) { + unsigned value; + uid_t uid; +- value = strtoul(argv[i]+9, NULL, 0); ++ value = nonneg_uint(argv[i]+9, "invalid --is-uid value", NULL); + uid = getuid(); + if (uid != value) { + fprintf(stderr, "uid: got=%d, want=%d\n", uid, value); +@@ -894,7 +963,7 @@ + } else if (!strncmp("--is-gid=", argv[i], 9)) { + unsigned value; + gid_t gid; +- value = strtoul(argv[i]+9, NULL, 0); ++ value = nonneg_uint(argv[i]+9, "invalid --is-gid value", NULL); + gid = getgid(); + if (gid != value) { + fprintf(stderr, "gid: got=%d, want=%d\n", gid, value); +diff --color -ru a/progs/getcap.c b/progs/getcap.c +--- a/progs/getcap.c 2021-02-05 06:52:17.000000000 +0100 ++++ b/progs/getcap.c 2022-01-28 12:44:05.009936148 +0100 +@@ -110,11 +110,11 @@ + + for (i=optind; argv[i] != NULL; i++) { + struct stat stbuf; +- +- if (lstat(argv[i], &stbuf) != 0) { +- fprintf(stderr, "%s (%s)\n", argv[i], strerror(errno)); ++ char *arg = argv[i]; ++ if (lstat(arg, &stbuf) != 0) { ++ fprintf(stderr, "%s (%s)\n", arg, strerror(errno)); + } else if (recursive) { +- nftw(argv[i], do_getcap, 20, FTW_PHYS); ++ nftw(arg, do_getcap, 20, FTW_PHYS); + } else { + int tflag = S_ISREG(stbuf.st_mode) ? FTW_F : + (S_ISLNK(stbuf.st_mode) ? FTW_SL : FTW_NS); +diff --color -ru a/progs/setcap.c b/progs/setcap.c +--- a/progs/setcap.c 2021-02-05 06:52:17.000000000 +0100 ++++ b/progs/setcap.c 2022-01-28 12:44:05.009936148 +0100 +@@ -166,9 +166,12 @@ + } + + cap_on_file = cap_get_file(*++argv); +- + if (cap_on_file == NULL) { + cap_on_file = cap_from_text("="); ++ if (cap_on_file == NULL) { ++ perror("unable to use missing capability"); ++ exit(1); ++ } + } + + cmp = cap_compare(cap_on_file, cap_d); +diff --color -ru a/psx/psx.c b/psx/psx.c +--- a/psx/psx.c 2021-02-05 06:52:17.000000000 +0100 ++++ b/psx/psx.c 2022-01-28 12:44:05.009936148 +0100 +@@ -107,6 +107,10 @@ + */ + static void *psx_do_registration(void) { + registered_thread_t *node = calloc(1, sizeof(registered_thread_t)); ++ if (node == NULL) { ++ perror("unable to register psx handler"); ++ exit(1); ++ } + pthread_mutex_init(&node->mu, NULL); + node->thread = pthread_self(); + pthread_setspecific(psx_action_key, node); +@@ -454,6 +458,10 @@ + int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *arg) { + psx_starter_t *starter = calloc(1, sizeof(psx_starter_t)); ++ if (starter == NULL) { ++ perror("failed at thread creation"); ++ exit(1); ++ } + starter->fn = start_routine; + starter->arg = arg; + /* +diff --color -ru a/tests/libcap_launch_test.c b/tests/libcap_launch_test.c +--- a/tests/libcap_launch_test.c 2021-02-05 06:52:17.000000000 +0100 ++++ b/tests/libcap_launch_test.c 2022-01-28 12:44:05.010936167 +0100 +@@ -93,6 +93,10 @@ + printf("[%d] test should %s\n", i, + v->result ? "generate error" : "work"); + cap_launch_t attr = cap_new_launcher(v->args[0], v->args, v->envp); ++ if (attr == NULL) { ++ perror("failed to obtain launcher"); ++ exit(1); ++ } + if (v->chroot) { + cap_launcher_set_chroot(attr, v->chroot); + } +diff --color -ru a/tests/libcap_psx_test.c b/tests/libcap_psx_test.c +--- a/tests/libcap_psx_test.c 2021-02-05 06:52:17.000000000 +0100 ++++ b/tests/libcap_psx_test.c 2022-01-28 12:55:55.887807887 +0100 +@@ -16,8 +16,15 @@ + usleep(1234); + pid_t pid = fork(); + cap_t start = cap_get_proc(); ++ if (start == NULL) { ++ perror("FAILED: unable to start"); ++ exit(1); ++ } + if (pid == 0) { +- cap_set_proc(start); ++ if (cap_set_proc(start)) { ++ perror("setting empty caps failed"); ++ exit(1); ++ } + exit(0); + } + int res; +@@ -27,6 +34,7 @@ + exit(1); + } + cap_set_proc(start); ++ cap_free(start); + return NULL; + } + +@@ -35,6 +43,10 @@ + printf("hello libcap and libpsx "); + fflush(stdout); + cap_t start = cap_get_proc(); ++ if (start == NULL) { ++ perror("FAILED: to actually start"); ++ exit(1); ++ } + pthread_t ignored[10]; + for (i = 0; i < 10; i++) { + pthread_create(&ignored[i], NULL, thread_fork_exit, NULL); +@@ -42,7 +54,10 @@ + for (i = 0; i < 10; i++) { + printf("."); /* because of fork, this may print double */ + fflush(stdout); /* try to limit the above effect */ +- cap_set_proc(start); ++ if (cap_set_proc(start)) { ++ perror("failed to set proc"); ++ exit(1); ++ } + usleep(1000); + } + printf(" PASSED\n"); diff --git a/SPECS/libcap.spec b/SPECS/libcap.spec index b869d05..0b9e464 100644 --- a/SPECS/libcap.spec +++ b/SPECS/libcap.spec @@ -1,20 +1,19 @@ Name: libcap -Version: 2.26 -Release: 5%{?dist} +Version: 2.48 +Release: 2%{?dist} Summary: Library for getting and setting POSIX.1e capabilities URL: https://sites.google.com/site/fullycapable/ -License: GPLv2 +License: BSD or GPLv2 Group: System Environment/Libraries Source: https://git.kernel.org/pub/scm/libs/libcap/libcap.git/snapshot/%{name}-%{version}.tar.gz -# http://manned.org/getpcaps/299a4949/src: -Source1: getpcaps.8 -Patch0: %{name}-2.25-buildflags.patch -Patch1: %{name}-PAM_REINITIALIZE_CRED.patch -Patch2: %{name}-2.26-ambient-caps.patch -Patch3: %{name}-add-new-caps.patch +Patch0: %{name}-2.48-buildflags.patch +Patch1: %{name}-abi-compatibility.patch +Patch2: %{name}-static-analysis.patch +Patch3: %{name}-fix-ambient-caps.patch BuildRequires: libattr-devel pam-devel perl-interpreter +BuildRequires: make %description libcap is a library for getting and setting POSIX.1e (formerly POSIX 6) @@ -47,11 +46,7 @@ Install libcap-devel if you want to develop or compile applications using libcap. %prep -%setup -q -%patch0 -p1 -%patch1 -p1 -%patch2 -p1 -%patch3 -p1 +%autosetup -p1 %build # libcap can not be build with _smp_mflags: @@ -67,7 +62,6 @@ make install RAISE_SETFCAP=no \ mkdir -p %{buildroot}/%{_mandir}/man{2,3,8} mv -f doc/*.3 %{buildroot}/%{_mandir}/man3/ -cp -f %{SOURCE1} %{buildroot}/%{_mandir}/man8/ chmod +x %{buildroot}/%{_libdir}/*.so.* @@ -85,14 +79,22 @@ chmod +x %{buildroot}/%{_libdir}/*.so.* %files static %{_libdir}/libcap.a +%{_libdir}/libpsx.a %files devel %{_includedir}/* %{_libdir}/*.so %{_mandir}/man3/* %{_libdir}/pkgconfig/libcap.pc +%{_libdir}/pkgconfig/libpsx.pc %changelog +* Fri Jan 28 2022 Zoltan Fridrich - 2.48-2 +- rebase to 2.48 + resolves: rhbz#2032813 +- fix ambient capabilities for non-root users + resolves: rhbz#1950187 + * Thu Jun 10 2021 Zoltan Fridrich - 2.26-5 - added CAP_PERFMON, CAP_BPF and CAP_CHECKPOINT_RESTORE capabilities resolves: rhbz#1946982 rhbz#1921576