Add pam_cap.so "default=<IAB>" module argument support
Add a new optional argument to pam_cap.so. This argument substitutes
for a line like this in the capability.conf file:
<IAB> *
That is, it supplies the default <IAB> 3-tuple of capability vectors.
Any * value in the prevailing capability.conf file overrides this default.
However, the admin can supply arguments like this:
auth pam_cap.so autoauth config=/dev/null default=^cap_wake_alarm
to grant everyone who executes it the ambient capability cap_wake_alarm.
This addresses:
https://bugzilla.kernel.org/show_bug.cgi?id=213611
However, see:
https://bugzilla.kernel.org/show_bug.cgi?id=212945
for issues limiting PAM application support for ambient capabilities in
general at present.
Signed-off-by: Andrew G. Morgan <[email protected]>
diff --git a/pam_cap/Makefile b/pam_cap/Makefile
index 56604fd..ce63f16 100644
--- a/pam_cap/Makefile
+++ b/pam_cap/Makefile
@@ -27,8 +27,9 @@
testlink: test.c pam_cap.o
$(CC) $(CFLAGS) -o $@ $+ -lpam -ldl $(LIBCAPLIB) $(LDFLAGS)
-test: pam_cap.so
- make testlink
+test: pam_cap.so test_pam_cap
+ $(MAKE) testlink
+ ./test_pam_cap
sudotest: test test_pam_cap
sudo ./test_pam_cap root 0x0 0x0 0x0 config=./capability.conf
diff --git a/pam_cap/capability.conf b/pam_cap/capability.conf
index 5b523f5..fb93ed9 100644
--- a/pam_cap/capability.conf
+++ b/pam_cap/capability.conf
@@ -25,6 +25,7 @@
# config=<file> - override the default config for the module with file
# keepcaps - workaround for applications that setuid without this
# autoauth - if you want pam_cap.so to always succeed for the auth phase
+# default - provide a failback IAB value if there is no '*' rule
## user 'morgan' gets the CAP_SETFCAP inheritable capability (commented out!)
#cap_setfcap morgan
@@ -35,14 +36,15 @@
## 'everyone else' gets no inheritable capabilities (restrictive config)
none *
-## if there is no '*' entry, all users not explicitly mentioned will
-## get all available inheritable capabilities. This is a permissive
-## default, and possibly not what you want... On first reading, you
-## might think this is a security problem waiting to happen, but it
-## defaults to not being so in this sample file! Further, by 'get', we
-## mean 'get in their IAB sets'. That is, if you look at a random
-## process, even one run by root, you will see it has no IAB
-## capabilities (by default):
+## if there is no '*' entry, and no "default=<iab>" pam_cap.so module
+## argument to fallback on, all users not explicitly mentioned will
+## get all currently available inheritable capabilities. This is a
+## permissive default, and possibly not what you want... On first
+## reading, you might think this is a security problem waiting to
+## happen, but it defaults to not being so in this sample file!
+## Further, by 'get', we mean 'get in their IAB sets'. That is, if you
+## look at a random process, even one run by root, you will see it has
+## no IAB capabilities (by default):
##
## $ /sbin/capsh --decode=$(grep CapInh /proc/1/status|awk '{print $2}')
## 0000000000000000=
diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c
index 2eeab87..162e1f5 100644
--- a/pam_cap/pam_cap.c
+++ b/pam_cap/pam_cap.c
@@ -32,12 +32,16 @@
#define CAP_FILE_BUFFER_SIZE 4096
#define CAP_FILE_DELIMITERS " \t\n"
+/*
+ * pam_cap_s is used to summarize argument values in a parsed form.
+ */
struct pam_cap_s {
int debug;
int keepcaps;
int autoauth;
const char *user;
const char *conf_filename;
+ const char *fallback;
};
/*
@@ -77,7 +81,7 @@
return 0;
}
-/* obtain the inheritable capabilities for the current user */
+/* obtain the desired IAB capabilities for the current user */
static char *read_capabilities_for_user(const char *user, const char *source)
{
@@ -201,7 +205,11 @@
? cs->conf_filename:USER_CAP_FILE );
if (conf_caps == NULL) {
D(("no capabilities found for user [%s]", cs->user));
- goto cleanup_cap_s;
+ if (cs->fallback == NULL) {
+ goto cleanup_cap_s;
+ }
+ conf_caps = strdup(cs->fallback);
+ D(("user [%s] received fallback caps [%s]", cs->user, conf_caps));
}
ssize_t conf_caps_length = strlen(conf_caps);
@@ -286,6 +294,8 @@
pcs->keepcaps = 1;
} else if (!strcmp(*argv, "autoauth")) {
pcs->autoauth = 1;
+ } else if (!strncmp(*argv, "default=", 8)) {
+ pcs->fallback = 8 + *argv;
} else {
_pam_log(LOG_ERR, "unknown option; %s", *argv);
}
diff --git a/pam_cap/test_pam_cap.c b/pam_cap/test_pam_cap.c
index 452a27f..4c09a5d 100644
--- a/pam_cap/test_pam_cap.c
+++ b/pam_cap/test_pam_cap.c
@@ -5,6 +5,11 @@
* it.
*/
+#define _DEFAULT_SOURCE
+
+#include <unistd.h>
+#include <sys/types.h>
+
#include "./pam_cap.c"
const char *test_groups[] = {
@@ -121,12 +126,106 @@
cap_free(prev);
}
+struct vargs {
+ struct pam_cap_s cs;
+ const char *args[5];
+};
+
+static int test_arg_parsing(void) {
+ static struct vargs vs[] = {
+ {
+ { 1, 0, 0, NULL, NULL, NULL },
+ { "debug", NULL }
+ },
+ {
+ { 0, 1, 0, NULL, NULL, NULL },
+ { "keepcaps", NULL }
+ },
+ {
+ { 0, 0, 1, NULL, NULL, NULL },
+ { "autoauth", NULL }
+ },
+ {
+ { 1, 0, 1, NULL, NULL, NULL },
+ { "autoauth", "debug", NULL }
+ },
+ {
+ { 0, 0, 0, NULL, "/over/there", NULL },
+ { "config=/over/there", NULL }
+ },
+ {
+ { 0, 0, 0, NULL, NULL, "^cap_setfcap" },
+ { "default=^cap_setfcap", NULL }
+ },
+ {
+ { 0, 0, 0, NULL, NULL, NULL },
+ { NULL }
+ }
+ };
+ int i;
+
+ for (i=0; ; i++) {
+ int argc;
+ const char **argv;
+ struct vargs *v;
+
+ v = &vs[i];
+ argv = v->args;
+
+ for (argc = 0; argv[argc] != NULL; argc++);
+
+ struct pam_cap_s cs;
+ parse_args(argc, argv, &cs);
+
+ if (cs.debug != v->cs.debug) {
+ printf("test_arg_parsing[%d]: debug=%d, wanted debug=%d\n",
+ i, cs.debug, v->cs.debug);
+ return 1;
+ }
+ if (cs.keepcaps != v->cs.keepcaps) {
+ printf("test_arg_parsing[%d]: keepcaps=%d, wanted keepcaps=%d\n",
+ i, cs.keepcaps, v->cs.keepcaps);
+ return 1;
+ }
+ if (cs.autoauth != v->cs.autoauth) {
+ printf("test_arg_parsing[%d]: autoauth=%d, wanted autoauth=%d\n",
+ i, cs.autoauth, v->cs.autoauth);
+ return 1;
+ }
+ if (cs.conf_filename != v->cs.conf_filename &&
+ strcmp(cs.conf_filename, v->cs.conf_filename)) {
+ printf("test_arg_parsing[%d]: conf_filename=[%s], wanted=[%s]\n",
+ i, cs.conf_filename, v->cs.conf_filename);
+ return 1;
+ }
+ if (cs.fallback != v->cs.fallback &&
+ strcmp(cs.fallback, v->cs.fallback)) {
+ printf("test_arg_parsing[%d]: fallback=[%s], wanted=[%s]\n",
+ i, cs.fallback, v->cs.fallback);
+ return 1;
+ }
+
+ if (argc == 0) {
+ break;
+ }
+ }
+ return 0;
+}
+
/*
* args: user a b i config-args...
*/
int main(int argc, char *argv[]) {
unsigned long int before[3], change[3], after[3];
+ if (test_arg_parsing()) {
+ printf("failed to parse arguments\n");
+ exit(1);
+ }
+ if (read_capabilities_for_user("morgan", "/dev/null") != NULL) {
+ printf("/dev/null is not a valid config file\n");
+ }
+
/*
* Start out with a cleared inheritable set.
*/
@@ -134,6 +233,12 @@
cap_clear_flag(orig, CAP_INHERITABLE);
cap_set_proc(orig);
+ if (getuid() != 0) {
+ cap_free(orig);
+ printf("test_pam_cap: OK! (Skipping privileged tests (uid!=0))\n");
+ exit(0);
+ }
+
change[A] = strtoul(argv[2], NULL, 0);
change[B] = strtoul(argv[3], NULL, 0);
change[I] = strtoul(argv[4], NULL, 0);