Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F164485695
D58012.id181232.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
D58012.id181232.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D58012: MAC/do: add exec whitelist/blacklist support for mac_do consumers
Attached
Detach File
Event Timeline
Log In to Comment