1
0
mirror of https://git.FreeBSD.org/src.git synced 2026-06-02 11:24:32 +00:00

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
This commit is contained in:
Kushagra Srivastava
2025-08-14 16:41:57 +02:00
committed by Olivier Certner
parent 3118f1b99f
commit 9818224174
+428 -165
View File
@@ -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();
set_rules(pr, rules);
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_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)
{
struct rules *rules;
int error;
error = parse_rules(rules_string, &rules, parse_error);
static void
clone_rules(struct rules *dst, struct rules *const src)
{
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);
}
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");
return (EINVAL);
}
/* Absence of "mac.do.rules" at this point is OK. */
error = 0;
} else {
error = vfs_getopt(opts, "mac.do.rules", (void **)&rules_string, &rules_len);
if (error == ENOENT)
rules_string = NULL;
else {
if (error != 0)
return (error);
/* Not a proper string. */
if (size == 0 || rules_string[size - 1] != '\0') {
if (rules_len == 0 || rules_string[rules_len - 1] != '\0') {
vfs_opterror(opts, "'mac.do.rules' not a proper string");
return (EINVAL);
}
if (size > MAC_RULE_STRING_LEN) {
vfs_opterror(opts, "'mdo.rules' too long");
if (rules_len > MAC_RULE_STRING_LEN) {
vfs_opterror(opts, "'mac.do.rules' 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 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;
}
}
return (error);
/* 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);
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 (exec_paths_len > EXEC_PATHS_MAXLEN) {
vfs_opterror(opts, "'mac.do.exec_paths' too long");
return (ENAMETOOLONG);
}
}
/*
* 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';
/* 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 (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;
@@ -2102,10 +2365,10 @@ mac_do_init(struct mac_policy_conf *mpc)
struct prison *pr;
osd_jail_slot = osd_jail_register(dealloc_jail_osd, osd_methods);
set_empty_rules(&prison0);
set_default_conf(&prison0);
sx_slock(&allprison_lock);
TAILQ_FOREACH(pr, &allprison, pr_list)
set_empty_rules(pr);
set_default_conf(pr);
sx_sunlock(&allprison_lock);
osd_thread_slot = osd_thread_register(dealloc_thread_osd);