Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 29 May 2026 16:01:25 +0000
From:      Olivier Certner <olce@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Cc:        Kushagra Srivastava <kushagra1403@gmail.com>
Subject:   git: 9818224174c4 - main - MAC/do: Executable paths feature (GSoC 2025's final state)
Message-ID:  <6a19b855.32eea.4f007537@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by olce:

URL: https://cgit.FreeBSD.org/src/commit/?id=9818224174c41a434028446fb6fa5516a6291755

commit 9818224174c41a434028446fb6fa5516a6291755
Author:     Kushagra Srivastava <kushagra1403@gmail.com>
AuthorDate: 2025-08-14 14:41:57 +0000
Commit:     Olivier Certner <olce@FreeBSD.org>
CommitDate: 2026-05-29 15:02:44 +0000

    MAC/do: Executable paths feature (GSoC 2025's final state)
    
    By design, mac_do(4) only authorizes credentials change requests if they
    are issued by a process spawned from '/usr/bin/mdo'.  The executable
    paths feature introduces some flexibility by allowing to change that
    path, thus allowing another executable to make requests, and to use
    multiple such paths (up to 8 in the current implementation).  Its
    purpose is to enable thin jails scenarios where mdo(1) may not be at its
    canonical path ('/usr/bin/mdo') and to allow experimenting with other
    userland programs leveraging setcred(2).
    
    Configuration of executable paths is per-jail and intentionally works
    completely similarly with rules.  It is accessible from within a jail
    through the 'security.mac.do.exec_paths' sysctl knob and from outside
    a jail through the 'mac.do.exec_paths' jail parameter.
    
    This commit groups the verbatim changes of the following commits that
    Kushagra Srivastava, our GSoC 2025 student, created in his GitHub
    repository (https://github.com/thesynthax/freebsd-src), branch
    'task/exec-paths-refactor':
    
    mac_do(4): Complete refactor of allowed executable paths feature
    mac_do(4): Fixed changing security.mac.do.* knobs in inheritance mode
    mac_do(4): Debugging rules and exec_paths leak on destroy
    mac_do(4): Deep copy rules
    mac_do(4): Fixed leak
    mac_do(4): fixed various bugs, structs inlined, leaks remain
    mac_do(4): MAC/do working in jail, leaks decreased
    mac_do(4): MAC/do fixed, works in host and jails, leaks removed
    mac_do(4): style
    
    Frozen log for these commits:
    https://github.com/OlCe2/freebsd-src/compare/main...14fdc49fb29265fac5d0daf95a13d0dce325c951.
    The corresponding pull request is at:
    https://github.com/OlCe2/freebsd-src/pull/2.
    
    The GSoC's final state of this code still has a number of problems that
    are fixed in subsequent commits.  It is however committed separately to
    clearly delineate Kushagra's work.
    
    Reviewed by:    olce (amendments to come, see above)
    MFC after:      1 month
    Relnotes:       yes
    Sponsored by:   Google LLC (GSoC 2025)
    Sponsored by:   The FreeBSD Foundation (review, commit)
    Pull Request:   https://ron-dev.freebsd.org/FreeBSD/src/pulls/38
---
 sys/security/mac_do/mac_do.c | 581 +++++++++++++++++++++++++++++++------------
 1 file changed, 422 insertions(+), 159 deletions(-)

diff --git a/sys/security/mac_do/mac_do.c b/sys/security/mac_do/mac_do.c
index ba49da22ce67..b63fc2a5bbc8 100644
--- a/sys/security/mac_do/mac_do.c
+++ b/sys/security/mac_do/mac_do.c
@@ -46,6 +46,9 @@ SYSCTL_INT(_security_mac_do, OID_AUTO, print_parse_error, CTLFLAG_RWTUN,
 
 static MALLOC_DEFINE(M_MAC_DO, "mac_do", "mac_do(4) security module");
 
+#define EXEC_PATHS_MAXLEN 2048
+#define MAX_EXEC_PATHS 8
+
 #define MAC_RULE_STRING_LEN	1024
 
 static unsigned		osd_jail_slot;
@@ -167,6 +170,17 @@ STAILQ_HEAD(rulehead, rule);
 struct rules {
 	char		string[MAC_RULE_STRING_LEN];
 	struct rulehead	head;
+};
+
+struct exec_paths {
+	char exec_paths_str[EXEC_PATHS_MAXLEN];
+	char exec_paths[MAX_EXEC_PATHS][PATH_MAX];
+	int exec_path_count;
+};
+
+struct conf {
+	struct rules rules;
+	struct exec_paths exec_paths;
 	volatile u_int	use_count __aligned(CACHE_LINE_SIZE);
 };
 
@@ -323,19 +337,34 @@ toast_rules(struct rules *const rules)
 		free(rule->gids, M_MAC_DO);
 		free(rule, M_MAC_DO);
 	}
-	free(rules, M_MAC_DO);
 }
 
-static struct rules *
-alloc_rules(void)
+/* Assuming storage is zeroed already */
+static void
+init_rules(struct rules *const rules)
 {
-	struct rules *const rules = malloc(sizeof(*rules), M_MAC_DO, M_WAITOK);
-
 	_Static_assert(MAC_RULE_STRING_LEN > 0, "MAC_RULE_STRING_LEN <= 0!");
-	rules->string[0] = 0;
 	STAILQ_INIT(&rules->head);
-	rules->use_count = 0;
-	return (rules);
+}
+
+static void
+init_exec_paths(struct exec_paths *const exec_paths)
+{
+	_Static_assert(EXEC_PATHS_MAXLEN > 0, "EXEC_PATHS_MAXLEN <= 0!");
+	bzero(exec_paths, sizeof(*exec_paths));
+	exec_paths->exec_paths_str[0] = 0;
+}
+
+static struct conf *
+alloc_conf(void)
+{
+	struct conf *const conf = malloc(sizeof(*conf), M_MAC_DO, M_WAITOK | M_ZERO);
+
+	init_rules(&conf->rules);
+	init_exec_paths(&conf->exec_paths);
+	conf->use_count = 0;
+
+	return (conf);
 }
 
 static bool
@@ -1007,12 +1036,11 @@ einval:
  * - "gid=1010>gid=1011,gid=1012,gid=1013"
  */
 static int
-parse_rules(const char *const string, struct rules **const rulesp,
+parse_rules(const char *const string, struct rules *const rules,
     struct parse_error **const parse_error)
 {
 	const size_t len = strlen(string);
 	char *copy, *p, *rule;
-	struct rules *rules;
 	int error = 0;
 
 	*parse_error = NULL;
@@ -1024,7 +1052,6 @@ parse_rules(const char *const string, struct rules **const rulesp,
 		return (ENAMETOOLONG);
 	}
 
-	rules = alloc_rules();
 	bcopy(string, rules->string, len + 1);
 	MPASS(rules->string[len] == '\0'); /* Catch some races. */
 
@@ -1044,31 +1071,90 @@ parse_rules(const char *const string, struct rules **const rulesp,
 		}
 	}
 
-	*rulesp = rules;
+out:
+	free(copy, M_MAC_DO);
+	return (error);
+}
+
+static int
+parse_exec_paths(const char *const string, struct exec_paths *const exec_paths,
+	struct parse_error **const parse_error)
+{
+	const size_t len = strlen(string);
+	char *copy, *p, *path;
+	int error = 0;
+
+	*parse_error = NULL;
+
+	if (len >= EXEC_PATHS_MAXLEN) {
+		make_parse_error(parse_error, 0,
+			"Exec path specification string is too long (%zu, max %u)",
+			len, EXEC_PATHS_MAXLEN - 1);
+		return (ENAMETOOLONG);
+	}
+
+	bcopy(string, exec_paths->exec_paths_str, len + 1);
+	MPASS(exec_paths->exec_paths_str[len] == '\0');
+
+	copy = malloc(len + 1, M_MAC_DO, M_WAITOK);
+	bcopy(string, copy, len + 1);
+	MPASS(copy[len] == '\0');
+
+	p = copy;
+	while ((path = strsep_noblanks(&p, ":")) != NULL) {
+		if (*path == '\0')
+			continue;
+
+		if (exec_paths->exec_path_count >= MAX_EXEC_PATHS) {
+			make_parse_error(parse_error, path - copy,
+				"Too many exec paths specified (max %d)", MAX_EXEC_PATHS);
+			error = EINVAL;
+			goto out;
+		}
+
+		const size_t path_len = strlen(path);
+		if (path_len >= PATH_MAX) {
+			make_parse_error(parse_error, path - copy,
+				"Exec paths too long (%zu, max %u)",
+				path_len, PATH_MAX - 1);
+			error = ENAMETOOLONG;
+			goto out;
+		}
+
+		strlcpy(exec_paths->exec_paths[exec_paths->exec_path_count], path, PATH_MAX);
+		exec_paths->exec_path_count++;
+	}
+
+	if (exec_paths->exec_path_count == 0) {
+		make_parse_error(parse_error, 0, "No valid exec paths found");
+		error = EINVAL;
+		goto out;
+	}
+
 out:
 	free(copy, M_MAC_DO);
 	return (error);
 }
 
 /*
- * Find rules applicable to the passed prison.
+ * Find conf applicable to the passed prison.
  *
- * Returns the applicable rules (and never NULL).  'pr' must be unlocked.
+ * Returns the applicable conf (and never NULL).  'pr' must be unlocked.
  * 'aprp' is set to the (ancestor) prison holding these, and it must be unlocked
- * once the caller is done accessing the rules.  '*aprp' is equal to 'pr' if and
- * only if the current jail has its own set of rules.
+ * once the caller is done accessing the conf.  '*aprp' is equal to 'pr' if and
+ * only if the current jail has its own set of conf.
  */
-static struct rules *
-find_rules(struct prison *const pr, struct prison **const aprp)
+static struct conf *
+find_conf(struct prison *const pr, struct prison **const aprp)
 {
 	struct prison *cpr, *ppr;
-	struct rules *rules;
+	struct conf *conf;
 
 	cpr = pr;
 	for (;;) {
 		prison_lock(cpr);
-		rules = osd_jail_get(cpr, osd_jail_slot);
-		if (rules != NULL)
+		conf = osd_jail_get(cpr, osd_jail_slot);
+		if (conf != NULL)
 			break;
 		prison_unlock(cpr);
 
@@ -1078,34 +1164,36 @@ find_rules(struct prison *const pr, struct prison **const aprp)
 	}
 
 	*aprp = cpr;
-	return (rules);
+	return (conf);
 }
 
 static void
-hold_rules(struct rules *const rules)
+hold_conf(struct conf *const conf)
 {
-	refcount_acquire(&rules->use_count);
+	refcount_acquire(&conf->use_count);
 }
 
 static void
-drop_rules(struct rules *const rules)
+drop_conf(struct conf *const conf)
 {
-	if (refcount_release(&rules->use_count))
-		toast_rules(rules);
+	if (refcount_release(&conf->use_count)) {
+		toast_rules(&conf->rules);
+		free(conf, M_MAC_DO);
+	}
 }
 
 #ifdef INVARIANTS
 static void
-check_rules_use_count(const struct rules *const rules, u_int expected)
+check_conf_use_count(const struct conf *const conf, u_int expected)
 {
-	const u_int use_count = refcount_load(&rules->use_count);
+	const u_int use_count = refcount_load(&conf->use_count);
 
 	if (use_count != expected)
-		panic("MAC/do: Rules at %p: Use count is %u, expected %u",
-		    rules, use_count, expected);
+		panic("MAC/do: Conf at %p: Use count is %u, expected %u",
+		    conf, use_count, expected);
 }
 #else
-#define check_rules_use_count(...)
+#define check_conf_use_count(...)
 #endif /* INVARIANTS */
 
 /*
@@ -1117,7 +1205,7 @@ check_rules_use_count(const struct rules *const rules, u_int expected)
 static void
 dealloc_jail_osd(void *const value)
 {
-	struct rules *const rules = value;
+	struct conf *const conf = value;
 
 	/*
 	 * If called because the "holding" jail goes down, no one should be
@@ -1133,8 +1221,8 @@ dealloc_jail_osd(void *const value)
 	 * we ensure that all thread's slots are freed first in mac_do_destroy()
 	 * to be able to check that only one reference remains.
 	 */
-	check_rules_use_count(rules, 1);
-	toast_rules(rules);
+	check_conf_use_count(conf, 1);
+	drop_conf(conf);
 }
 
 /*
@@ -1146,9 +1234,9 @@ dealloc_jail_osd(void *const value)
  * Destroys the 'osd_jail_slot' slot of the passed jail.
  */
 static void
-remove_rules(struct prison *const pr)
+remove_conf(struct prison *const pr)
 {
-	struct rules *old_rules;
+	struct conf *old_conf;
 	int error __unused;
 
 	prison_lock(pr);
@@ -1158,7 +1246,7 @@ remove_rules(struct prison *const pr)
 	 * decrement their use count, and possibly free them, outside of the
 	 * prison lock.
 	 */
-	old_rules = osd_jail_get(pr, osd_jail_slot);
+	old_conf = osd_jail_get(pr, osd_jail_slot);
 	error = osd_jail_set(pr, osd_jail_slot, NULL);
 	/* osd_set() never allocates memory when 'value' is NULL, nor fails. */
 	MPASS(error == 0);
@@ -1169,40 +1257,40 @@ remove_rules(struct prison *const pr)
 	osd_jail_del(pr, osd_jail_slot);
 	prison_unlock(pr);
 
-	if (old_rules != NULL)
-		drop_rules(old_rules);
+	if (old_conf != NULL)
+		drop_conf(old_conf);
 }
 
-/*
- * Assign already built rules to a jail.
- */
 static void
-set_rules(struct prison *const pr, struct rules *const rules)
+set_conf(struct prison *const pr, struct conf *const conf)
 {
-	struct rules *old_rules;
+	struct conf *old_conf;
 	void **rsv;
 
-	check_rules_use_count(rules, 0);
-	hold_rules(rules);
+	hold_conf(conf);
 	rsv = osd_reserve(osd_jail_slot);
 
 	prison_lock(pr);
-	old_rules = osd_jail_get(pr, osd_jail_slot);
-	osd_jail_set_reserved(pr, osd_jail_slot, rsv, rules);
+	old_conf = osd_jail_get(pr, osd_jail_slot);
+	osd_jail_set_reserved(pr, osd_jail_slot, rsv, conf);
 	prison_unlock(pr);
-	if (old_rules != NULL)
-		drop_rules(old_rules);
+	if (old_conf != NULL)
+		drop_conf(old_conf);
 }
 
 /*
- * Assigns empty rules to a jail.
+ * Assigns default conf to a jail.
  */
 static void
-set_empty_rules(struct prison *const pr)
+set_default_conf(struct prison *const pr)
 {
-	struct rules *const rules = alloc_rules();
+	struct conf *const conf = alloc_conf();
+
+	strlcpy(conf->exec_paths.exec_paths_str, "/usr/bin/mdo", EXEC_PATHS_MAXLEN);
+	strlcpy(conf->exec_paths.exec_paths[0], "/usr/bin/mdo", PATH_MAX);
+	conf->exec_paths.exec_path_count = 1;
 
-	set_rules(pr, rules);
+	set_conf(pr, conf);
 }
 
 /*
@@ -1210,18 +1298,99 @@ set_empty_rules(struct prison *const pr)
  *
  * Returns the same error code as parse_rules() (which see).
  */
-static int
-parse_and_set_rules(struct prison *const pr, const char *rules_string,
-    struct parse_error **const parse_error)
+
+static void
+clone_rules(struct rules *dst, struct rules *const src)
 {
-	struct rules *rules;
-	int error;
+	struct rule *src_rule, *dst_rule;
+
+	bzero(dst, sizeof(*dst));
+	strlcpy(dst->string, src->string, sizeof(dst->string));
+	STAILQ_INIT(&dst->head);
+
+	STAILQ_FOREACH(src_rule, &src->head, r_entries) {
+		dst_rule = malloc(sizeof(*dst_rule), M_MAC_DO, M_WAITOK | M_ZERO);
+		bcopy(src_rule, dst_rule, sizeof(*dst_rule));
+
+		if (src_rule->uids_nb > 0) {
+			dst_rule->uids = malloc(sizeof(*dst_rule->uids) * src_rule->uids_nb,
+			    M_MAC_DO, M_WAITOK);
+			bcopy(src_rule->uids, dst_rule->uids,
+			    sizeof(*dst_rule->uids) * src_rule->uids_nb);
+		}
+
+		if (src_rule->gids_nb > 0) {
+			dst_rule->gids = malloc(sizeof(*dst_rule->gids) * src_rule->gids_nb,
+			    M_MAC_DO, M_WAITOK);
+			bcopy(src_rule->gids, dst_rule->gids,
+			    sizeof(*dst_rule->gids) * src_rule->gids_nb);
+		}
 
-	error = parse_rules(rules_string, &rules, parse_error);
+		STAILQ_INSERT_TAIL(&dst->head, dst_rule, r_entries);
+	}
+}
+
+static void
+clone_exec_paths(struct exec_paths *dst, struct exec_paths *const src)
+{
+	bzero(dst, sizeof(*dst));
+	dst->exec_path_count = src->exec_path_count;
+	for (int i = 0; i < src->exec_path_count; i++) {
+		strlcpy(dst->exec_paths[i], src->exec_paths[i],
+				sizeof(dst->exec_paths[i]));
+	}
+
+	strlcpy(dst->exec_paths_str, src->exec_paths_str,
+			sizeof(dst->exec_paths_str));
+}
+
+static int 
+parse_and_set_conf(struct prison *pr, const char *rules_string, 
+		const char *exec_paths_string, struct parse_error **parse_error)
+{
+	struct prison *ppr = NULL;
+	struct conf *applicable_conf = NULL;
+	struct conf *conf;
+	int error = 0;
+	bool need_applicable_conf;
+
+	*parse_error = NULL;
+
+	need_applicable_conf = (rules_string == NULL || rules_string[0] == '\0' ||
+			exec_paths_string == NULL || exec_paths_string[0] == '\0');
+
+	if (need_applicable_conf) {
+		applicable_conf = find_conf(pr, &ppr);
+		hold_conf(applicable_conf);
+		prison_unlock(ppr);
+	}
+
+	conf = alloc_conf();
+
+	if (rules_string != NULL && rules_string[0] != '\0') {
+		error = parse_rules(rules_string, &conf->rules, parse_error);
+		if (error != 0)
+			goto out;
+	}
+	else if (applicable_conf != NULL)
+		clone_rules(&conf->rules, &applicable_conf->rules);
+
+	if (exec_paths_string != NULL && exec_paths_string[0] != '\0') {
+		error = parse_exec_paths(exec_paths_string, &conf->exec_paths, parse_error);
+		if (error != 0)
+			goto out;
+	} else if (applicable_conf != NULL)
+		clone_exec_paths(&conf->exec_paths, &applicable_conf->exec_paths);
+
+	set_conf(pr, conf);
+
+out:
+	if (applicable_conf != NULL)
+		drop_conf(applicable_conf);
 	if (error != 0)
-		return (error);
-	set_rules(pr, rules);
-	return (0);
+		drop_conf(conf);
+
+	return (error);
 }
 
 static int
@@ -1230,12 +1399,12 @@ mac_do_sysctl_rules(SYSCTL_HANDLER_ARGS)
 	char *const buf = malloc(MAC_RULE_STRING_LEN, M_MAC_DO, M_WAITOK);
 	struct prison *const td_pr = req->td->td_ucred->cr_prison;
 	struct prison *pr;
-	struct rules *rules;
+	struct conf *conf;
 	struct parse_error *parse_error;
 	int error;
 
-	rules = find_rules(td_pr, &pr);
-	strlcpy(buf, rules->string, MAC_RULE_STRING_LEN);
+	conf = find_conf(td_pr, &pr);
+	strlcpy(buf, conf->rules.string, MAC_RULE_STRING_LEN);
 	prison_unlock(pr);
 
 	error = sysctl_handle_string(oidp, buf, MAC_RULE_STRING_LEN, req);
@@ -1243,13 +1412,14 @@ mac_do_sysctl_rules(SYSCTL_HANDLER_ARGS)
 		goto out;
 
 	/* Set our prison's rules, not that of the jail we inherited from. */
-	error = parse_and_set_rules(td_pr, buf, &parse_error);
+	error = parse_and_set_conf(td_pr, buf, NULL, &parse_error);
 	if (error != 0) {
 		if (print_parse_error)
 			printf("MAC/do: Parse error at index %zu: %s\n",
 			    parse_error->pos, parse_error->msg);
 		free_parse_error(parse_error);
 	}
+
 out:
 	free(buf, M_MAC_DO);
 	return (error);
@@ -1265,13 +1435,52 @@ SYSCTL_JAIL_PARAM_SYS_SUBNODE(mac, do, CTLFLAG_RW, "Jail MAC/do parameters");
 SYSCTL_JAIL_PARAM_STRING(_mac_do, rules, CTLFLAG_RW, MAC_RULE_STRING_LEN,
     "Jail MAC/do rules");
 
+static int
+mac_do_sysctl_exec_paths(SYSCTL_HANDLER_ARGS)
+{
+	char *const buf = malloc(EXEC_PATHS_MAXLEN, M_MAC_DO, M_WAITOK);
+	struct prison *const td_pr = req->td->td_ucred->cr_prison;
+	struct prison *pr;
+	struct conf *conf;
+	struct parse_error *parse_error;
+	int error;
+
+	conf = find_conf(td_pr, &pr);
+	strlcpy(buf, conf->exec_paths.exec_paths_str, EXEC_PATHS_MAXLEN);
+	prison_unlock(pr);
+
+	error = sysctl_handle_string(oidp, buf, EXEC_PATHS_MAXLEN, req);
+	if (error != 0 || req->newptr == NULL)
+		goto out;
+
+	error = parse_and_set_conf(td_pr, NULL, buf, &parse_error);
+	if (error != 0) {
+		if (print_parse_error)
+			printf("MAC/do: Parse error at index %zu: %s\n",
+			    parse_error->pos, parse_error->msg);
+		free_parse_error(parse_error);
+	}
+
+out:
+	free(buf, M_MAC_DO);
+	return (error);
+}
+
+SYSCTL_PROC(_security_mac_do, OID_AUTO, exec_paths,
+		CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_PRISON | CTLFLAG_MPSAFE,
+		0, 0, mac_do_sysctl_exec_paths, "A",
+		"Colon-separated list of allowed executables");
+
+SYSCTL_JAIL_PARAM_STRING(_mac_do, exec_paths, CTLFLAG_RW, EXEC_PATHS_MAXLEN, 
+		"Jail MAC/do executable paths");
 
 static int
-mac_do_jail_create(void *obj, void *data __unused)
+mac_do_jail_create(void *obj, void *data)
 {
 	struct prison *const pr = obj;
 
-	set_empty_rules(pr);
+	set_default_conf(pr);
+
 	return (0);
 }
 
@@ -1280,10 +1489,14 @@ mac_do_jail_get(void *obj, void *data)
 {
 	struct prison *ppr, *const pr = obj;
 	struct vfsoptlist *const opts = data;
+	struct conf *conf;
 	struct rules *rules;
+	struct exec_paths *exec_paths;
 	int jsys, error;
 
-	rules = find_rules(pr, &ppr);
+	conf = find_conf(pr, &ppr);
+	rules = &conf->rules;
+	exec_paths = &conf->exec_paths;
 
 	jsys = pr == ppr ?
 	    (STAILQ_EMPTY(&rules->head) ? JAIL_SYS_DISABLE : JAIL_SYS_NEW) :
@@ -1296,6 +1509,10 @@ mac_do_jail_get(void *obj, void *data)
 	if (error != 0 && error != ENOENT)
 		goto done;
 
+	error = vfs_setopts(opts, "mac.do.exec_paths", exec_paths->exec_paths_str);
+	if (error != 0 && error != ENOENT)
+		goto done;
+
 	error = 0;
 done:
 	prison_unlock(ppr);
@@ -1313,13 +1530,16 @@ _Static_assert(-1 != JAIL_SYS_DISABLE && -1 != JAIL_SYS_NEW &&
  * We perform only cheap checks here, i.e., we do not really parse the rules
  * specification string, if any.
  */
+
 static int
 mac_do_jail_check(void *obj, void *data)
 {
 	struct vfsoptlist *opts = data;
-	char *rules_string;
-	int error, jsys, size;
+	char *rules_string, *exec_paths_string;
+	int error, jsys, rules_len = 0, exec_paths_len = 0;
+	bool has_rules, has_exec_paths;
 
+	/* Mark unspecified */
 	error = vfs_copyopt(opts, "mac.do", &jsys, sizeof(jsys));
 	if (error == ENOENT)
 		jsys = -1;
@@ -1337,60 +1557,82 @@ mac_do_jail_check(void *obj, void *data)
 	 * jail_set() calls vfs_getopts() itself later (they becoming
 	 * inconsistent wouldn't cause any security problem).
 	 */
-	error = vfs_getopt(opts, "mac.do.rules", (void**)&rules_string, &size);
-	if (error == ENOENT) {
-		/*
-		 * Default (in absence of "mac.do.rules") is to disable (and, in
-		 * particular, not inherit).
-		 */
-		if (jsys == -1)
-			jsys = JAIL_SYS_DISABLE;
-
-		if (jsys == JAIL_SYS_NEW) {
-			vfs_opterror(opts, "'mac.do.rules' must be specified "
-			    "given 'mac.do''s value");
+	error = vfs_getopt(opts, "mac.do.rules", (void **)&rules_string, &rules_len);
+	if (error == ENOENT)
+		rules_string = NULL;
+	else {
+		if (error != 0)
+			return (error);
+		if (rules_len == 0 || rules_string[rules_len - 1] != '\0') {
+			vfs_opterror(opts, "'mac.do.rules' not a proper string");
 			return (EINVAL);
 		}
+		if (rules_len > MAC_RULE_STRING_LEN) {
+			vfs_opterror(opts, "'mac.do.rules' too long");
+			return (ENAMETOOLONG);
+		}
+	}
 
-		/* Absence of "mac.do.rules" at this point is OK. */
-		error = 0;
-	} else {
+	/* Handle 'exec_paths' input */
+	error = vfs_getopt(opts, "mac.do.exec_paths", (void **)&exec_paths_string, &exec_paths_len);
+	if (error == ENOENT)
+		exec_paths_string = NULL;
+	else {
 		if (error != 0)
 			return (error);
-
-		/* Not a proper string. */
-		if (size == 0 || rules_string[size - 1] != '\0') {
-			vfs_opterror(opts, "'mac.do.rules' not a proper string");
+		if (exec_paths_len == 0 || exec_paths_string[exec_paths_len - 1] != '\0') {
+			vfs_opterror(opts, "'mac.do.exec_paths' not a proper string");
 			return (EINVAL);
 		}
-
-		if (size > MAC_RULE_STRING_LEN) {
-			vfs_opterror(opts, "'mdo.rules' too long");
+		if (exec_paths_len > EXEC_PATHS_MAXLEN) {
+			vfs_opterror(opts, "'mac.do.exec_paths' too long");
 			return (ENAMETOOLONG);
 		}
+	}
 
-		if (jsys == -1)
-			/* Default (if "mac.do.rules" is present). */
-			jsys = rules_string[0] == '\0' ? JAIL_SYS_DISABLE :
-			    JAIL_SYS_NEW;
+	/*
+	 * Be liberal, considering that an empty rule or exec paths specification 
+	 * is equivalent to no specification.
+	 * This affects the JAIL_SYS_DISABLE and JAIL_SYS_INHERIT sanity checks below.
+	 */
+	has_rules = rules_string && rules_string[0] != '\0';
+	has_exec_paths = exec_paths_string && exec_paths_string[0] != '\0';
 
-		/*
-		 * Be liberal and accept JAIL_SYS_DISABLE and JAIL_SYS_INHERIT
-		 * with an explicit empty rules specification.
-		 */
-		switch (jsys) {
-		case JAIL_SYS_DISABLE:
-		case JAIL_SYS_INHERIT:
-			if (rules_string[0] != '\0') {
-				vfs_opterror(opts, "'mac.do.rules' specified "
-				    "but should not given 'mac.do''s value");
-				return (EINVAL);
-			}
-			break;
+	/* Infer 'jsys' if needed */
+	if (jsys == -1) {
+		if (has_rules || has_exec_paths)
+			jsys = JAIL_SYS_NEW;
+		else
+			jsys = JAIL_SYS_DISABLE;
+	}
+
+	/* Final checks based on resolved 'jsys' */
+	switch (jsys) {
+	case JAIL_SYS_DISABLE:
+	case JAIL_SYS_INHERIT:
+		if (has_rules) {
+			vfs_opterror(opts, "'mac.do.rules' specified but should not be when mac.do is disabled or inherited");
+			return (EINVAL);
+		}
+		if (has_exec_paths) {
+			vfs_opterror(opts, "'mac.do.exec_paths' specified but should not be when mac.do is disabled or inherited");
+			return (EINVAL);
 		}
+		break;
+
+	case JAIL_SYS_NEW:
+		if (!has_rules && !has_exec_paths) {
+			vfs_opterror(opts, "mac.do set to 'new' but neither rules nor exec_paths specified");
+			return (EINVAL);
+		}
+		/* Allow: rules only, exec_paths only (though exec_paths only is discouraged), or both */
+		break;
+
+	default:
+		__assert_unreachable();
 	}
 
-	return (error);
+	return (0);
 }
 
 static int
@@ -1398,9 +1640,10 @@ mac_do_jail_set(void *obj, void *data)
 {
 	struct prison *pr = obj;
 	struct vfsoptlist *opts = data;
-	char *rules_string;
-	struct parse_error *parse_error;
+	char *rules_string, *exec_paths_string;
+	struct parse_error *parse_error = NULL;
 	int error, jsys;
+	bool has_rules, has_exec_paths;
 
 	/*
 	 * The invariants checks used below correspond to what has already been
@@ -1414,49 +1657,50 @@ mac_do_jail_set(void *obj, void *data)
 
 	rules_string = vfs_getopts(opts, "mac.do.rules", &error);
 	MPASS(error == 0 || error == ENOENT);
-	if (error == 0) {
-		MPASS(strlen(rules_string) < MAC_RULE_STRING_LEN);
-		if (jsys == -1)
-			/* Default (if "mac.do.rules" is present). */
-			jsys = rules_string[0] == '\0' ? JAIL_SYS_DISABLE :
-			    JAIL_SYS_NEW;
+	exec_paths_string = vfs_getopts(opts, "mac.do.exec_paths", &error);
+	MPASS(error == 0 || error == ENOENT);
+
+	has_rules = (rules_string != NULL && rules_string[0] != '\0');
+	has_exec_paths = (exec_paths_string != NULL && exec_paths_string[0] != '\0');
+
+	if (jsys == -1) {
+		if (has_rules || has_exec_paths)
+			jsys = JAIL_SYS_NEW;
 		else
-			MPASS(jsys == JAIL_SYS_NEW ||
-			    ((jsys == JAIL_SYS_DISABLE ||
-			    jsys == JAIL_SYS_INHERIT) &&
-			    rules_string[0] == '\0'));
-	} else {
-		MPASS(jsys != JAIL_SYS_NEW);
-		if (jsys == -1)
-			/*
-			 * Default (in absence of "mac.do.rules") is to disable
-			 * (and, in particular, not inherit).
-			 */
 			jsys = JAIL_SYS_DISABLE;
-		/* If disabled, we'll store an empty rule specification. */
-		if (jsys == JAIL_SYS_DISABLE)
-			rules_string = "";
 	}
 
 	switch (jsys) {
 	case JAIL_SYS_INHERIT:
-		remove_rules(pr);
-		error = 0;
-		break;
+		remove_conf(pr);
+		return (0);
+
 	case JAIL_SYS_DISABLE:
+		rules_string = "";
+		has_rules = true;
+		/* FALLTHROUGH */
+
 	case JAIL_SYS_NEW:
-		error = parse_and_set_rules(pr, rules_string, &parse_error);
+		error = parse_and_set_conf(pr,
+				has_rules ? rules_string : NULL,
+				has_exec_paths ? exec_paths_string : NULL,
+				&parse_error);
+
 		if (error != 0) {
-			vfs_opterror(opts,
-			    "MAC/do: Parse error at index %zu: %s\n",
-			    parse_error->pos, parse_error->msg);
-			free_parse_error(parse_error);
+			if (parse_error != NULL) {
+				vfs_opterror(opts, "MAC/do: Parse error at index %zu: %s\n",
+						parse_error->pos, parse_error->msg);
+				free_parse_error(parse_error);
+			}
+
+			return (error);
 		}
-		break;
+
+		return (0);
+
 	default:
 		__assert_unreachable();
 	}
-	return (error);
 }
 
 /*
@@ -1493,7 +1737,7 @@ struct mac_do_data_header {
 	 */
 	int		 priv;
 	/* Rules to apply. */
-	struct rules	*rules;
+	struct conf *conf;
 };
 
 /*
@@ -1536,7 +1780,7 @@ clear_data(void *const data)
 	struct mac_do_data_header *const hdr = data;
 
 	if (hdr != NULL) {
-		drop_rules(hdr->rules);
+		drop_conf(hdr->conf);
 		/* We don't deallocate so as to save time on next access. */
 		hdr->priv = 0;
 	}
@@ -1558,7 +1802,7 @@ is_data_reusable(const void *const data, const size_t size)
 
 static void
 set_data_header(void *const data, const size_t size, const int priv,
-    struct rules *const rules)
+    struct conf *const conf)
 {
 	struct mac_do_data_header *const hdr = data;
 
@@ -1567,7 +1811,7 @@ set_data_header(void *const data, const size_t size, const int priv,
 	MPASS(size <= hdr->allocated_size);
 	hdr->size = size;
 	hdr->priv = priv;
-	hdr->rules = rules;
+	hdr->conf = conf;
 }
 
 /* The proc lock (and any other non-sleepable lock) must not be held. */
@@ -1933,7 +2177,7 @@ static int
 mac_do_priv_grant(struct ucred *cred, int priv)
 {
 	struct mac_do_setcred_data *const data = fetch_data();
-	const struct rules *rules;
+	struct rules *rules;
 	const struct ucred *new_cred;
 	const struct rule *rule;
 	u_int setcred_flags;
@@ -1950,7 +2194,7 @@ mac_do_priv_grant(struct ucred *cred, int priv)
 		/* No. */
 		return (EPERM);
 
-	rules = data->hdr.rules;
+	rules = &data->hdr.conf->rules;
 	new_cred = data->new_cred;
 	KASSERT(new_cred != NULL,
 	    ("priv_check*() called before mac_cred_check_setcred()"));
@@ -1988,7 +2232,7 @@ static int
 check_proc(void)
 {
 	char *path, *to_free;
-	int error;
+	int error = EPERM;
 
 	/*
 	 * Only grant privileges if requested by the right executable.
@@ -2010,7 +2254,26 @@ check_proc(void)
 	 */
 	if (vn_fullpath_jail(curproc->p_textvp, &path, &to_free) != 0)
 		return (EPERM);
-	error = strcmp(path, "/usr/bin/mdo") == 0 ? 0 : EPERM;
+
+	struct conf *conf;
+	struct exec_paths *exec_paths;
+	struct prison *td_pr = curproc->p_ucred->cr_prison;
+	struct prison *pr;
+	conf = find_conf(td_pr, &pr);
+	exec_paths = &conf->exec_paths;
+
+	if (exec_paths->exec_path_count > 0) {
+		for (int i = 0; i < exec_paths->exec_path_count; i++) {
+			if (strcmp(exec_paths->exec_paths[i], path) == 0) {
+				error = 0;
+				break;
+			}
+		}
+
+	}
+
+	prison_unlock(pr);
+
 	free(to_free, M_TEMP);
 	return (error);
 }
@@ -2018,9 +2281,9 @@ check_proc(void)
 static void
 mac_do_setcred_enter(void)
 {
-	struct rules *rules;
 	struct prison *pr;
 	struct mac_do_setcred_data * data;
+	struct conf *conf;
 	int error;
 
 	/*
@@ -2042,8 +2305,8 @@ mac_do_setcred_enter(void)
 	/*
 	 * Find the currently applicable rules.
 	 */
-	rules = find_rules(curproc->p_ucred->cr_prison, &pr);
-	hold_rules(rules);
+	conf = find_conf(curproc->p_ucred->cr_prison, &pr);
+	hold_conf(conf);
 	prison_unlock(pr);
 
 	/*
@@ -2052,7 +2315,7 @@ mac_do_setcred_enter(void)
 	data = fetch_data();
 	if (!is_data_reusable(data, sizeof(*data)))
 		data = alloc_data(data, sizeof(*data));
-	set_data_header(data, sizeof(*data), PRIV_CRED_SETCRED, rules);
+	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;
*** 13 LINES SKIPPED ***


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a19b855.32eea.4f007537>