From nobody Mon Jun 29 19:25:32 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4gpx8D4Jfyz6jjTX for ; Mon, 29 Jun 2026 19:25:48 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4gpx8C6y0Jz40NB for ; Mon, 29 Jun 2026 19:25:47 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1782761148; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=BR0yLkqiS3Oq/Si7+GvuHMo506CJpQ2p8Uyns2OTYzo=; b=uGS0Sv1HBIsfbXXezZToyn2WLLSNYzNzTMylUTbwEjGChiy9ssvxrpTwmZOIlrguA0UHHP 6ojdZZN/rQF8HYKmTPx2FPB/cKG9nu3qwQR/oWvTQ3d3+W5VHN7aercBsWmdbDZPYGqvxe iBSd1+grXynoYYTM/poxFBnciZeJIxkz3C7yjbtHWpzwvfJZlTGou8IMIccJwHcPLU1JWf 69rcxe6r7rBarcRjhtaGNr/0qPfqMqYaNWQCLVlsB+6Wd2GdgclGx16xbPDy65GRoiTiQL eM9aj5ttiXdJRrotXQXwrdDh4UOWC8yaHFdbs62ls6cM5rHTQ6l0J3qHe+dLpw== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1782761147; a=rsa-sha256; cv=none; b=hhRa9gKoRV8SROxnwksEZgLJ5lgaKNQV+J80vW7dQ8kNp2msja1y9eeF4gnk/kWygO2/Rj kxT9h1gRUJHBtVIGpTZ3L1+JLIE4DOkzNllNKhhylXP3w2M1Hl8VS3JnywGWFOR/6HLpGn VhOsTNK8Ht1KYCu1gCx77pAcVCc7xYVRoHiiQJGJQK5ar77winkPmQSRp536lXhNrE7aUv y2sJzpT7DP1cMVAF1PhVhWeFe64nYkDYAgv634wkFBUa/xUF5TgXmxE4ZKPhsCGC9H47DM 87+Rp3e1+k2RC44B44Mj4QEqk0qLrKWkXK+IHvHNC1bS2ek5aY4vCjA0M1EFdA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1782761147; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=BR0yLkqiS3Oq/Si7+GvuHMo506CJpQ2p8Uyns2OTYzo=; b=OMHgPyeoKYEiORtEQUczCg1A1I9BxSwfhICNjvglGdD5yOdmnV4JG82xH/bVpJ81z1Qntu gA7bnWCV8gKh/zS17XnTcS4+0QjzoMiJcfeiVXBj4HpN9VpRga2nGF4+IViClXZBKGTEoz 3IBpJYad2Ut40+R7ia/M73b+4p1dlXbG/yze+2RiWJzYixexPCa01U4Bgadivlob4k+B8H GC2Wx1giXiRqVJm9g7TcNEQzQ8ZvKADM6viDY7UaxlZbk4NDtuKTdGm2ROnSlChRUWHoL5 +A8KNaSIpFnskD/shH6iOm1JYTrrtJw99g5kKUeAPvm1AjRNOMvQpbknrd5utQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4gpx8C6126z19cb for ; Mon, 29 Jun 2026 19:25:47 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 21fba by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Mon, 29 Jun 2026 19:25:32 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org Cc: Kushagra Srivastava From: Olivier Certner Subject: git: 8cbd56ec1c54 - stable/15 - MAC/do: Executable paths feature (GSoC 2025's final state) List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: olce X-Git-Repository: src X-Git-Refname: refs/heads/stable/15 X-Git-Reftype: branch X-Git-Commit: 8cbd56ec1c543dfe41680bc9fd5f3c4d6183e3f1 Auto-Submitted: auto-generated Date: Mon, 29 Jun 2026 19:25:32 +0000 Message-Id: <6a42c6ac.21fba.12ab3105@gitrepo.freebsd.org> The branch stable/15 has been updated by olce: URL: https://cgit.FreeBSD.org/src/commit/?id=8cbd56ec1c543dfe41680bc9fd5f3c4d6183e3f1 commit 8cbd56ec1c543dfe41680bc9fd5f3c4d6183e3f1 Author: Kushagra Srivastava AuthorDate: 2025-08-14 14:41:57 +0000 Commit: Olivier Certner CommitDate: 2026-06-29 19:23:29 +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 (cherry picked from commit 9818224174c41a434028446fb6fa5516a6291755) --- 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 790701e57e56..33904f09a6f4 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; @@ -169,6 +172,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); }; @@ -325,19 +339,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 @@ -1009,12 +1038,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; @@ -1026,7 +1054,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. */ @@ -1046,31 +1073,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); @@ -1080,34 +1166,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 */ /* @@ -1119,7 +1207,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 @@ -1135,8 +1223,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); } /* @@ -1148,9 +1236,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); @@ -1160,7 +1248,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); @@ -1171,40 +1259,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); } /* @@ -1212,18 +1300,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 @@ -1232,12 +1401,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); @@ -1245,13 +1414,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); @@ -1267,13 +1437,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); } @@ -1282,10 +1491,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) : @@ -1298,6 +1511,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); @@ -1315,13 +1532,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; @@ -1339,60 +1559,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 @@ -1400,9 +1642,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 @@ -1416,49 +1659,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); } /* @@ -1495,7 +1739,7 @@ struct mac_do_data_header { */ int priv; /* Rules to apply. */ - struct rules *rules; + struct conf *conf; }; /* @@ -1538,7 +1782,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; } @@ -1560,7 +1804,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; @@ -1569,7 +1813,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. */ @@ -1935,7 +2179,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; @@ -1952,7 +2196,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()")); @@ -1990,7 +2234,7 @@ static int check_proc(void) { char *path, *to_free; - int error; + int error = EPERM; /* * Only grant privileges if requested by the right executable. @@ -2012,7 +2256,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); } @@ -2020,9 +2283,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; /* @@ -2044,8 +2307,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); /* @@ -2054,7 +2317,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. */ *** 15 LINES SKIPPED ***