Page MenuHomeFreeBSD

D58012.id181232.diff
No OneTemporary

D58012.id181232.diff

diff --git a/sys/security/mac_do/mac_do.c b/sys/security/mac_do/mac_do.c
--- a/sys/security/mac_do/mac_do.c
+++ b/sys/security/mac_do/mac_do.c
@@ -31,6 +31,7 @@
#include <sys/sysctl.h>
#include <sys/ucred.h>
#include <sys/vnode.h>
+#include <sys/imgact.h>
#include <security/mac/mac_policy.h>
@@ -200,6 +201,9 @@
id_nb_t gids_nb;
struct id_spec *uids;
struct id_spec *gids;
+ u_int exec_paths_nb;
+ char **exec_paths;
+ bool exec_blacklist;
};
STAILQ_HEAD(rulehead, rule);
@@ -933,6 +937,95 @@
return (EINVAL);
}
+static int
+parse_rule_exec(char *exec_str, struct rule *const rule,
+ const char *const start, struct parse_error **const parse_error)
+{
+ char **paths, *p, *tok, *key;
+ u_int nb, capacity;
+
+ nb = 0;
+ capacity = 4;
+
+ key = strsep_noblanks(&exec_str, "=");
+ if (key == NULL || strcmp(key, "exec") != 0) {
+ make_parse_error(parse_error, 0,
+ "Unknown option '%.10s' (only 'exec' is supported).",
+ key != NULL ? key : "");
+ return (EINVAL);
+ }
+ if (exec_str == NULL) {
+ make_parse_error(parse_error, 0,
+ "Missing '=' after 'exec'.");
+ return (EINVAL);
+ }
+
+ exec_str += strspn(exec_str, " \t");
+
+ if (strcmp(exec_str, "*") == 0) {
+ rule->exec_paths_nb = 0;
+ rule->exec_paths = NULL;
+ return (0);
+ }
+
+ if (*exec_str == '!') {
+ rule->exec_blacklist = true;
+ exec_str++;
+ if (*exec_str == '\0') {
+ make_parse_error(parse_error, 0,
+ "Empty exec blacklist.");
+ return (EINVAL);
+ }
+ } else {
+ rule->exec_blacklist = false;
+ }
+
+ /*
+ * Single pass with a dynamically growing array.
+ *
+ * We cannot do a two-pass approach (count then copy) because
+ * strsep() modifies the input string by replacing ':' with '\0'.
+ * After the first pass, the string is destroyed and cannot be
+ * iterated again. Instead, we start with a small capacity and
+ * double as needed, then shrink to the actual size at the end.
+ */
+ paths = malloc(capacity * sizeof(char *), M_MAC_DO, M_WAITOK);
+ p = exec_str;
+ while ((tok = strsep_noblanks(&p, ":")) != NULL) {
+ if (*tok == '\0')
+ continue;
+ if (tok[0] != '/') {
+ make_parse_error(parse_error, 0,
+ "Exec path '%s' is not absolute.", tok);
+ goto einval;
+ }
+ if (nb >= capacity) {
+ capacity *= 2;
+ paths = reallocf(paths,
+ capacity * sizeof(char *), M_MAC_DO, M_WAITOK);
+ }
+ paths[nb] = strdup(tok, M_MAC_DO);
+ nb++;
+ }
+
+ if (nb == 0) {
+ make_parse_error(parse_error, 0, "Empty exec path list.");
+ goto einval;
+ }
+
+ /* Shrink allocation to actual size. */
+ rule->exec_paths = reallocf(paths, nb * sizeof(char *),
+ M_MAC_DO, M_WAITOK);
+ rule->exec_paths_nb = nb;
+ return (0);
+
+einval:
+ for (u_int j = 0; j < nb; j++)
+ free(paths[j], M_MAC_DO);
+ free(paths, M_MAC_DO);
+ return (EINVAL);
+}
+
/*
* See also the herald comment for parse_rules() below.
*
@@ -953,7 +1046,9 @@
{
const char *const start = rule;
const char *from_type, *from_id, *p;
- char *to_list;
+ char *to_list, *to_p;
+ char *exec_block;
+ size_t len;
struct id_list uid_list, gid_list;
struct id_elem *ie, *ie_next;
struct rule *new;
@@ -994,6 +1089,33 @@
goto einval;
}
+ exec_block = NULL;
+
+ /*
+ * Extract optional (exec=...) block from end of rule.
+ * Must happen after <from> parsing (which advances 'rule' past
+ * the from part) and before <to> comma parsing (which would
+ * consume the rule string).
+ */
+ to_list = rule != NULL ? strsep_noblanks(&rule, "(") : NULL;
+ if (rule != NULL) {
+ exec_block = rule;
+ len = strlen(exec_block);
+ if (len < 1 || exec_block[len - 1] != ')') {
+ make_parse_error(parse_error, exec_block - start,
+ "Unclosed or malformed exec block.");
+ goto einval;
+ }
+ exec_block[len - 1] = '\0'; /* Strip closing ')'. */
+ }
+
+ if (exec_block != NULL) {
+ error = parse_rule_exec(exec_block, new, start,
+ parse_error);
+ if (error != 0)
+ goto einval;
+ }
+
/*
* We will now parse the "to" list.
*
@@ -1006,11 +1128,12 @@
* allows to minimize memory allocations and enables searching IDs in
* O(log(n)) instead of linearly.
*/
- to_list = strsep_noblanks(&rule, ",");
if (to_list == NULL) {
make_parse_error(parse_error, 0, "No target list.");
goto einval;
}
+ to_p = to_list;
+ to_list = strsep_noblanks(&to_p, ",");
do {
error = parse_target_clause(to_list, new, &uid_list, &gid_list,
parse_error);
@@ -1019,7 +1142,7 @@
goto einval;
}
- to_list = strsep_noblanks(&rule, ",");
+ to_list = strsep_noblanks(&to_p, ",");
} while (to_list != NULL);
if (new->uids_nb != 0) {
@@ -2114,6 +2237,17 @@
return (hdr);
}
+struct mac_do_setcred_data {
+ struct mac_do_data_header hdr;
+ const struct ucred *new_cred;
+ u_int setcred_flags;
+ bool has_exec_constraint;
+ u_int exec_paths_nb;
+ char **exec_paths;
+ struct conf *exec_conf;
+ bool exec_blacklist;
+};
+
/* Destructor for 'osd_thread_slot'. */
static void
dealloc_thread_osd(void *const value)
@@ -2438,11 +2572,6 @@
/*
* To pass data between check_setcred() and priv_grant() (on PRIV_CRED_SETCRED).
*/
-struct mac_do_setcred_data {
- struct mac_do_data_header hdr;
- const struct ucred *new_cred;
- u_int setcred_flags;
-};
static int
mac_do_priv_grant(struct ucred *cred, int priv)
@@ -2492,8 +2621,27 @@
STAILQ_FOREACH(rule, &rules->head, r_entries)
if (rule_applies(rule, cred)) {
error = rule_grant_setcred(rule, cred, new_cred);
- if (error != EPERM)
- break;
+ if (error != EPERM) {
+ /* This rule carries an exec constraint
+ * (exec=paths). Stash it in per-thread OSD
+ * so mac_do_vnode_check_exec can enforce it
+ * on the next execve().
+ *
+ * The conf's hdr.conf reference is dropped by
+ * clear_data() in setcred_exit, before the
+ * process execs. Take a second reference on
+ * this conf via hold_conf() so it stays alive
+ * until vnode_check_exec drops it.
+ */
+ data->has_exec_constraint = true;
+ data->exec_blacklist = rule->exec_blacklist;
+ data->exec_paths_nb =
+ rule->exec_paths_nb;
+ data->exec_paths = rule->exec_paths;
+ data->exec_conf = data->hdr.conf;
+ hold_conf(data->exec_conf);
+ break;
+ }
}
return (error);
@@ -2577,12 +2725,16 @@
* Setup thread data to be used by other hooks.
*/
data = fetch_data();
- if (!is_data_reusable(data, sizeof(*data)))
+ if (!is_data_reusable(data, sizeof(*data))) {
data = alloc_data(data, sizeof(*data));
set_data_header(data, sizeof(*data), PRIV_CRED_SETCRED, conf);
/* Not really necessary, but helps to catch programming errors. */
data->new_cred = NULL;
data->setcred_flags = 0;
+ data->exec_paths_nb = 0;
+ data->exec_paths = NULL;
+ data->exec_conf = NULL;
+ data->exec_blacklist = false;
}
static int
@@ -2623,6 +2775,81 @@
clear_data(data);
}
+static int
+mac_do_vnode_check_exec(struct ucred *cred __unused,
+ struct vnode *vp __unused, struct label *vplabel __unused,
+ struct image_params *imgp, struct label *execlabel __unused)
+{
+ struct mac_do_setcred_data *data;
+ const char *path;
+ char *to_free;
+ int error;
+
+ if (do_enabled == 0)
+ return (0);
+
+ data = fetch_data();
+
+ if (data == NULL || !data->has_exec_constraint)
+ return (0);
+
+ /* No exec constraint in the matching rule — allow any. */
+ if (data->exec_paths_nb == 0) {
+ drop_conf(data->exec_conf);
+ data->has_exec_constraint = false;
+ data->exec_blacklist = false;
+ return (0);
+ }
+
+ /*
+ * Determine the path of the binary being exec'd.
+ *
+ * For execve(2) (imgp->args->fname != NULL), imgp->execpath is the
+ * user-provided path for absolute paths, or resolved via
+ * vn_fullpath_hardlink() for relative paths. Using it avoids
+ * namecache-dependent issues where vn_fullpath_jail() can return a
+ * cached path from a prior lookup of the same inode by another process.
+ *
+ * For fexecve(2) (imgp->args->fname == NULL), imgp->execpath is set
+ * via vn_fullpath() which has the same namecache limitations, and
+ * may be NULL if that call failed. Fall back to vn_fullpath_jail()
+ * on imgp->vp, which is the best we can do without a user-provided
+ * path.
+ */
+ to_free = NULL;
+ if (imgp->args->fname != NULL) {
+ path = imgp->execpath;
+ } else {
+ char *resolved;
+
+ if (vn_fullpath_jail(imgp->vp, &resolved, &to_free) != 0) {
+ drop_conf(data->exec_conf);
+ data->has_exec_constraint = false;
+ data->exec_blacklist = false;
+ return (EPERM); /* Cannot resolve: deny. */
+ }
+ path = resolved;
+ }
+
+ /*
+ * Whitelist: allow only listed paths (deny by default).
+ * Blacklist: deny listed paths (allow by default).
+ */
+ error = data->exec_blacklist ? 0 : EPERM;
+ for (u_int i = 0; i < data->exec_paths_nb; i++) {
+ if (strcmp(data->exec_paths[i], path) == 0) {
+ error = data->exec_blacklist ? EPERM : 0;
+ break;
+ }
+ }
+
+ free(to_free, M_TEMP);
+ drop_conf(data->exec_conf); /* Release our reference. */
+ data->has_exec_constraint = false; /* Enforce once, then clear. */
+ data->exec_blacklist = false;
+ return (error);
+}
+
static void
mac_do_init(struct mac_policy_conf *mpc)
{
@@ -2661,6 +2888,7 @@
.mpo_cred_check_setcred = mac_do_check_setcred,
.mpo_cred_setcred_exit = mac_do_setcred_exit,
.mpo_priv_grant = mac_do_priv_grant,
+ .mpo_vnode_check_exec = mac_do_vnode_check_exec,
};
MAC_POLICY_SET(&do_ops, mac_do, "MAC/do", MPC_LOADTIME_FLAG_UNLOADOK, NULL);

File Metadata

Mime Type
text/plain
Expires
Sun, Aug 2, 8:13 AM (2 h, 58 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
34637240
Default Alt Text
D58012.id181232.diff (9 KB)

Event Timeline