953 lines
25 KiB
Diff
953 lines
25 KiB
Diff
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
|
|
|