Re: [PATCH] capabilities: introduce per-process capability bounding set (v10)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Sorry, any TABs are replaced by MUA.
I'll send the patch again.

> The attached patch provides several improvement for pam_cap module.
> 1. It enables pam_cap to drop capabilities from process'es capability
>    bounding set.
> 2. It enables to specify allowing inheritable capability set or dropping
>    bounding capability set for groups, not only users.
> 3. It provide pam_sm_session() method, not only pam_sm_authenticate()
>    and pam_sm_setcred(). A system administrator can select more
>    appropriate mode for his purpose.
> 4. In the auth/cred mode, it enables to cache the configuration file,
>    to avoid read and analyze it twice.
> (Therefore, most of the part in the original one got replaced....)
> 
> The default configuration file is "/etc/security/capability.conf".
> You can describe as follows:
> --------
> # kaigai get cap_net_raw and cap_kill, tak get cap_sys_pacct pI.
> # We can omit "i:" in the head of each line.
> i:cap_net_raw,cap_kill         kaigai
> cap_sys_pacct                  tak
> 
> # ymj and tak lost cap_sys_chroot from cap_bset
> b:cap_sys_chroot               ymj  tak
> 
> # Any user within webadm group get cap_net_bind_service pI.
> i:cap_net_bind_service         @webadm
> 
> # Any user within users group lost cap_sys_module from cap_bset
> b:cap_sys_module               @users
> --------
> 
> When a user or groups he belongs is on several lines, all configurations
> are simplly compounded.
> 
> In the above example, if tak belongs to webadm and users group,
> he will get cap_sys_pacct and cap_net_bind_service pI, and lost
> cap_sys_chroot and cap_sys_module from his cap_bset.
> 
> Thanks,

Signed-off-by: KaiGai Kohei <[email protected]>
--
 pam_cap/capability.conf |    6 +
 pam_cap/pam_cap.c       |  495 ++++++++++++++++++++++++++++-------------------
 2 files changed, 305 insertions(+), 196 deletions(-)

diff --git a/pam_cap/capability.conf b/pam_cap/capability.conf
index b543142..707cdc3 100644
--- a/pam_cap/capability.conf
+++ b/pam_cap/capability.conf
@@ -24,6 +24,12 @@ cap_setfcap		morgan
 ## 'everyone else' gets no inheritable capabilities
 none  *

+# user 'kaigai' lost CAP_NET_RAW capability from bounding set
+b:cap_net_raw                   kaigai
+
+# group 'acctadm' get CAP_SYS_PACCT inheritable capability
+i:cap_sys_pacct                 @acctadm
+
 ## if there is no '*' entry, all users not explicitly mentioned will
 ## get all available capabilities. This is a permissive default, and
 ## probably not what you want...
diff --git a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c
index 94c5ebc..a917d5c 100644
--- a/pam_cap/pam_cap.c
+++ b/pam_cap/pam_cap.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 1999,2007 Andrew G. Morgan <[email protected]>
+ * Copyright (c) 2007      KaiGai Kohei <[email protected]>
  *
  * The purpose of this module is to enforce inheritable capability sets
  * for a specified user.
@@ -13,298 +14,400 @@
 #include <stdarg.h>
 #include <stdlib.h>
 #include <syslog.h>
+#include <pwd.h>
+#include <grp.h>

 #include <sys/capability.h>
+#include <sys/prctl.h>

 #include <security/pam_modules.h>
 #include <security/_pam_macros.h>

+#define MODULE_NAME		"pam_cap"
 #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"
+
+#ifndef PR_CAPBSET_DROP
+#define PR_CAPBSET_DROP		24
+#endif
+
+extern char const *_cap_names[];

 struct pam_cap_s {
     int debug;
     const char *user;
     const char *conf_filename;
+    /* set in read_capabilities_for_user() */
+    cap_t result;
+    int do_set_inh : 1;
+    int do_set_bset : 1;
 };

-/* obtain the inheritable capabilities for the current user */
-
-static char *read_capabilities_for_user(const char *user, const char *source)
+/* obtain the inheritable/bounding capabilities for the current user */
+static int read_capabilities_for_user(struct pam_cap_s *pcs)
 {
-    char *cap_string = NULL;
-    char buffer[CAP_FILE_BUFFER_SIZE], *line;
+    char buffer[CAP_FILE_BUFFER_SIZE];
     FILE *cap_file;
+    struct passwd *pwd;
+    int line_num = 0;
+    int rc = -1;	/* PAM_(AUTH|CRED|SESSION)_ERR */
+
+    pwd = getpwnam(pcs->user);
+    if (!pwd) {
+	syslog(LOG_ERR, "user %s not in passwd entries", pcs->user);
+	return PAM_AUTH_ERR;
+    }

-    cap_file = fopen(source, "r");
-    if (cap_file == NULL) {
-	D(("failed to open capability file"));
-	return NULL;
+    cap_file = fopen(pcs->conf_filename, "r");
+    if (!cap_file) {
+	if (errno == ENOENT) {
+	    syslog(LOG_NOTICE, "%s is not found",
+		   pcs->conf_filename);
+	    return PAM_IGNORE;
+	} else {
+	    syslog(LOG_ERR, "unable to open '%s' (%s)",
+		   pcs->conf_filename, strerror(errno));
+	    return rc;
+	}
     }

-    while ((line = fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file))) {
-	int found_one = 0;
-	const char *cap_text;
+    pcs->result = NULL;
+    while (fgets(buffer, CAP_FILE_BUFFER_SIZE, cap_file) != NULL) {
+	char *pos, *cap_text;
+	int matched = 0;
+	int line_ops = CAP_INHERITABLE;

-	cap_text = strtok(line, CAP_FILE_DELIMITERS);
+	line_num++;

-	if (cap_text == NULL) {
-	    D(("empty line"));
-	    continue;
-	}
-	if (*cap_text == '#') {
-	    D(("comment line"));
+	/* remove comment */
+	pos = strchr(buffer, '#');
+	if (pos)
+	    *pos = '\0';
+
+	cap_text = strtok(buffer, CAP_FILE_DELIMITERS);
+	/* empty line */
+	if (!cap_text)
 	    continue;
+
+	if (!strncmp(cap_text, "b:", 2)) {
+	    /* permitted field is used to store bounding set */
+	    line_ops = CAP_PERMITTED;
+	    cap_text += 2;
+	} else if (!strncmp(cap_text, "i:", 2)) {
+	    cap_text += 2;
 	}

-	while ((line = strtok(NULL, CAP_FILE_DELIMITERS))) {
+	/* check members */
+	while ((pos = strtok(NULL, CAP_FILE_DELIMITERS)) != NULL) {
+	    /* wildcard */
+	    if (!strcmp("*", pos)) {
+		matched = 1;
+		break;
+	    }

-	    if (strcmp("*", line) == 0) {
-		D(("wildcard matched"));
-		found_one = 1;
-		cap_string = strdup(cap_text);
+	    /* It it group name? */
+	    if (*pos == '@') {
+		struct group *grp;
+		int i;
+
+		pos++;
+		grp = getgrnam(pos);
+		if (!grp) {
+		    if (pcs->debug)
+			syslog(LOG_DEBUG, "group '%s' not found at line:%d",
+			       pos, line_num);
+		    continue;
+		}
+
+		if (pwd->pw_gid == grp->gr_gid) {
+		    if (pcs->debug)
+			syslog(LOG_DEBUG, "user %s matched with group %s at line:%d",
+			       pcs->user, pos, line_num);
+		    matched = 1;
+		    break;
+		}
+
+		for (i=0; grp->gr_mem[i]; i++) {
+		    if (!strcmp(pcs->user, grp->gr_mem[i])) {
+			if (pcs->debug)
+			    syslog(LOG_DEBUG, "user %s matched with group %s at line:%d",
+				   pcs->user, pos, line_num);
+			matched = 1;
+			break;
+		    }
+		}
+		syslog(LOG_ERR, "no matching %s", pos);
+	    } else if (!strcmp(pcs->user, pos)) {
+		if (pcs->debug)
+		    syslog(LOG_DEBUG, "user '%s' matched at line:%d",
+			   pos, line_num);
+		matched = 1;
 		break;
 	    }
+	}
+
+	if (matched) {
+	    char tmpbuf[CAP_FILE_BUFFER_SIZE];
+	    cap_t tmp;
+	    cap_value_t value;
+	    cap_flag_value_t code;
+
+	    if (!pcs->result) {
+		pcs->result = cap_init();
+		if (!pcs->result) {
+		    syslog(LOG_ERR, "unable to allocate cap_t object (%s)",
+			   strerror(errno));
+		    goto out;
+		}
+	    }

-	    if (strcmp(user, line) == 0) {
-		D(("exact match for user"));
-		found_one = 1;
-		cap_string = strdup(cap_text);
+	    switch (line_ops) {
+	    case CAP_INHERITABLE:
+		pcs->do_set_inh = 1;
+		break;
+	    case CAP_PERMITTED:
+		pcs->do_set_bset = 1;
 		break;
 	    }

-	    D(("user is not [%s] - skipping", line));
+	    if (!strcmp(cap_text, "none"))
+		continue;
+
+	    snprintf(tmpbuf, sizeof(tmpbuf), "%s=p", cap_text);
+	    tmp = cap_from_text(tmpbuf);
+	    if (!tmp) {
+		syslog(LOG_ERR, "unable to convert '%s' (%s)",
+		       tmpbuf, strerror(errno));
+		cap_free(pcs->result);
+		pcs->result = NULL;
+		goto out;
+	    }
+
+	    for (value=0; ;value++) {
+		if (cap_get_flag(tmp, value, CAP_PERMITTED, &code) < 0)
+		    break;	/* If value == __CAP_BITS, we get EINVAL */
+		if (code == CAP_SET)
+		    cap_set_flag(pcs->result, line_ops, 1, &value, CAP_SET);
+	    }
+	    cap_free(tmp);
 	}
+    }

-	cap_text = NULL;
-	line = NULL;
+    if (pcs->debug) {
+	char *tmp = cap_to_text(pcs->result, NULL);

-	if (found_one) {
-	    D(("user [%s] matched - caps are [%s]", user, cap_string));
-	    break;
-	}
+	syslog(LOG_DEBUG, "configuration for user %s is %s",
+	       pcs->user, tmp);
+	cap_free(tmp);
     }
+    rc = PAM_SUCCESS;

+  out:
     fclose(cap_file);

-    memset(buffer, 0, CAP_FILE_BUFFER_SIZE);
-
-    return cap_string;
+    return rc;
 }

 /*
  * Set capabilities for current process to match the current
  * permitted+executable sets combined with the configured inheritable
- * set.
+ * and bounding set.
  */

-static int set_capabilities(struct pam_cap_s *cs)
+static int set_capabilities(struct pam_cap_s *pcs)
 {
-    cap_t cap_s;
-    ssize_t length = 0;
-    char *conf_icaps;
-    char *proc_epcaps;
-    char *combined_caps;
-    int ok = 0;
-
-    cap_s = cap_get_proc();
-    if (cap_s == NULL) {
-	D(("your kernel is capability challenged - upgrade: %s",
-	   strerror(errno)));
-	return 0;
-    }
-
-    conf_icaps =
-	read_capabilities_for_user(cs->user,
-				   cs->conf_filename
-				   ? cs->conf_filename:USER_CAP_FILE );
-    if (conf_icaps == 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;
-    }
-
-    /*
-     * 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 (!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);
-    } else {
-	sprintf(combined_caps, CAP_COMBINED_FORMAT, proc_epcaps, conf_icaps);
-    }
-    D(("combined_caps=[%s]", combined_caps));
-
-    cap_free(cap_s);
-    cap_s = cap_from_text(combined_caps);
-    _pam_overwrite(combined_caps);
-    _pam_drop(combined_caps);
-
-#ifdef DEBUG
-    {
-        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 {
-	D(("failed to set specified capabilities: %s", strerror(errno)));
-    }
-
-cleanup_epcaps:
-    cap_free(proc_epcaps);
-
-cleanup_icaps:
-    _pam_overwrite(conf_icaps);
-    _pam_drop(conf_icaps);
+    cap_value_t value;
+    cap_flag_value_t code;
+    int rc = -1;	/* PAM_(AUTH|CRED|SESSION)_ERR */
+
+    /* set inheritable capability set */
+    if (pcs->do_set_inh) {
+	cap_t cap_s = cap_get_proc();
+	if (!cap_s) {
+	    syslog(LOG_ERR, "your kernel is capability challenged - upgrade: %s",
+		   strerror(errno));
+	    goto out;
+	}
+	for (value=0; ;value++) {
+	    if (cap_get_flag(pcs->result, value, CAP_INHERITABLE, &code))
+		break;
+	    cap_set_flag(cap_s, CAP_INHERITABLE, 1, &value, code);
+	}
+	if (cap_set_proc(cap_s) < 0) {
+	    if (errno == EPERM)
+		rc = PAM_PERM_DENIED;
+	    syslog(LOG_ERR, "unable to set inheritable capabilities (%s)",
+		   strerror(errno));
+	    cap_free(cap_s);
+	    goto out;
+	}
+	if (pcs->debug) {
+	    char *tmp = cap_to_text(cap_s, NULL);

-cleanup_cap_s:
-    if (cap_s) {
+	    syslog(LOG_DEBUG, "user %s new capabilities: %s",
+		   pcs->user, tmp);
+	    cap_free(tmp);
+	}
 	cap_free(cap_s);
-	cap_s = NULL;
     }
+    /* drop capability bounding set */
+    if (pcs->do_set_bset) {
+	for (value=0; ;value++) {
+	    if (cap_get_flag(pcs->result, value, CAP_PERMITTED, &code))
+		break;
+	    if (code == CAP_SET) {
+		if (prctl(PR_CAPBSET_DROP, value) < 0) {
+		    syslog(LOG_ERR, "unable to drop capability b-set %u (%s)",
+			   value, strerror(errno));
+		    goto out;
+		}
+		if (pcs->debug)
+		    syslog(LOG_DEBUG, "%s drops capability %s from bounding set",
+			   pcs->user, _cap_names[value]);
+	    }
+	}
+    }
+    rc = PAM_SUCCESS;

-    return ok;
-}
-
-/* log errors */
-
-static void _pam_log(int err, const char *format, ...)
-{
-    va_list args;
+  out:
+    cap_free(pcs->result);

-    va_start(args, format);
-    openlog("pam_cap", LOG_CONS|LOG_PID, LOG_AUTH);
-    vsyslog(err, format, args);
-    va_end(args);
-    closelog();
+    return rc;
 }

-static void parse_args(int argc, const char **argv, struct pam_cap_s *pcs)
+static int init_pam_cap(pam_handle_t *pamh, int argc, const char **argv,
+			struct pam_cap_s *pcs)
 {
-    int ctrl=0;
+    int ctrl, rc;
+
+    /* Initialization */
+    memset(pcs, 0, sizeof(struct pam_cap_s));
+    pcs->conf_filename = USER_CAP_FILE;
+    rc = pam_get_user(pamh, &pcs->user, NULL);
+    if (rc == PAM_CONV_AGAIN) {
+	syslog(LOG_INFO, "user conversation is not available yet");
+	return PAM_INCOMPLETE;
+    }
+    if (rc != PAM_SUCCESS) {
+	syslog(LOG_INFO, "pam_get_user failed: %s", pam_strerror(pamh, rc));
+	return -1;
+    }

     /* step through arguments */
     for (ctrl=0; argc-- > 0; ++argv) {
-
 	if (!strcmp(*argv, "debug")) {
 	    pcs->debug = 1;
 	} else if (!strcmp(*argv, "config=")) {
 	    pcs->conf_filename = strlen("config=") + *argv;
 	} else {
-	    _pam_log(LOG_ERR, "unknown option; %s", *argv);
+	    syslog(LOG_ERR, "unknown option: %s", *argv);
+	    return -1;
 	}
+    }
+    return PAM_SUCCESS;
+}

+static void cleanup_pam_cap(pam_handle_t *pamh, void *data, int error_status)
+{
+    struct pam_cap_s *pcs = (struct pam_cap_s *) data;
+
+    if (pcs) {
+	if (pcs->result)
+	    cap_free(pcs->result);
+	free(pcs);
     }
 }

 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;
+    struct pam_cap_s *pcs = NULL;
+    int rc = PAM_BUF_ERR;

-    memset(&pcs, 0, sizeof(pcs));
+    openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);

-    parse_args(argc, argv, &pcs);
+    pcs = malloc(sizeof(struct pam_cap_s));
+    if (!pcs)
+	goto error;

-    retval = pam_get_user(pamh, &pcs.user, NULL);
+    rc = init_pam_cap(pamh, argc, argv, pcs);
+    if (rc != PAM_SUCCESS)
+	goto error;

-    if (retval == PAM_CONV_AGAIN) {
-	D(("user conversation is not available yet"));
-	memset(&pcs, 0, sizeof(pcs));
-	return PAM_INCOMPLETE;
-    }
+    rc = read_capabilities_for_user(pcs);
+    if (rc != PAM_SUCCESS)
+	goto error;

-    if (retval != PAM_SUCCESS) {
-	D(("pam_get_user failed: %s", pam_strerror(pamh, retval)));
-	memset(&pcs, 0, sizeof(pcs));
-	return PAM_AUTH_ERR;
+    rc = pam_set_data(pamh, MODULE_NAME, pcs, cleanup_pam_cap);
+    if (rc == PAM_SUCCESS) {
+	/* OK, pam_sm_setcred() will be called next */
+	closelog();
+	return rc;
     }

-    conf_icaps =
-	read_capabilities_for_user(pcs.user,
-				   pcs.conf_filename
-				   ? pcs.conf_filename:USER_CAP_FILE );
-
-    memset(&pcs, 0, sizeof(pcs));
-
-    if (conf_icaps) {
-	D(("it appears that there are capabilities for this user [%s]",
-	   conf_icaps));
+  error:
+    cleanup_pam_cap(pamh, pcs, rc);
+    closelog();
+    return rc < 0 ? PAM_AUTH_ERR : rc;
+}

-	/* 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.. */
+int pam_sm_setcred(pam_handle_t *pamh, int flags,
+		   int argc, const char **argv)
+{
+    struct pam_cap_s *pcs = NULL;
+    int rc = PAM_IGNORE;

-	_pam_overwrite(conf_icaps);
-	_pam_drop(conf_icaps);
+    openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);

-	return PAM_SUCCESS;
+    if (!(flags & PAM_ESTABLISH_CRED))
+	goto out;

-    } else {
+    rc = pam_get_data(pamh, MODULE_NAME, (void *)&pcs);
+    if (rc != PAM_SUCCESS)
+	return rc;

-	D(("there are no capabilities restrctions on this user"));
-	return PAM_IGNORE;
+    rc = set_capabilities(pcs);

-    }
+  out:
+    closelog();
+    return rc < 0 ? PAM_CRED_ERR : rc;
 }

-int pam_sm_setcred(pam_handle_t *pamh, int flags,
-		   int argc, const char **argv)
+int pam_sm_open_session(pam_handle_t *pamh, int flags,
+			int argc, const char **argv)
 {
-    int retval;
     struct pam_cap_s pcs;
+    int rc;

-    if (!(flags & PAM_ESTABLISH_CRED)) {
-	D(("we don't handle much in the way of credentials"));
-	return PAM_IGNORE;
-    }
+    openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTHPRIV);

-    memset(&pcs, 0, sizeof(pcs));
+    rc = init_pam_cap(pamh, argc, argv, &pcs);
+    if (rc != PAM_SUCCESS)
+	goto out;

-    parse_args(argc, argv, &pcs);
+    rc = read_capabilities_for_user(&pcs);
+    if (rc != PAM_SUCCESS)
+	goto out;

-    retval = pam_get_item(pamh, PAM_USER, (const void **)&pcs.user);
-    if ((retval != PAM_SUCCESS) || (pcs.user == NULL) || !(pcs.user[0])) {
+    rc = set_capabilities(&pcs);

-	D(("user's name is not set"));
-	return PAM_AUTH_ERR;
+    if (rc == PAM_SUCCESS) {
+	rc = set_capabilities(&pcs);
+	if (pcs.result)
+	    cap_free(pcs.result);
     }

-    retval = set_capabilities(&pcs);
+  out:
+    if (pcs.result)
+	cap_free(pcs.result);
+    closelog();

-    memset(&pcs, 0, sizeof(pcs));
+    return rc < 0 ? PAM_SESSION_ERR : rc;
+}

-    return (retval ? PAM_SUCCESS:PAM_IGNORE );
+int pam_sm_close_session(pam_handle_t *pamh, int flags,
+			 int argc, const char **argv)
+{
+    return PAM_SUCCESS; /* do nothing */
 }

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux