mac_do: add a new MAC/do policy and mdo(1) utility
This policy enables a user to become another user without having to be root (hence no setuid binary). it is configured via rules using sysctl security.mac.do.rules For example: security.mac.do.rules=uid=1001:80,gid=0:any The above rule means the user identifier by the uid 1001 is able to become user 80 Any user of the group 0 are allowed to become any user on the system. The mdo(1) utility expects the MAC/do policy to be installed and its rules defined. Reviewed by: des Differential Revision: https://reviews.freebsd.org/D45145
This commit is contained in:
@@ -297,6 +297,7 @@ MAN= aac.4 \
|
|||||||
mac_biba.4 \
|
mac_biba.4 \
|
||||||
mac_bsdextended.4 \
|
mac_bsdextended.4 \
|
||||||
mac_ddb.4 \
|
mac_ddb.4 \
|
||||||
|
mac_do.4 \
|
||||||
mac_ifoff.4 \
|
mac_ifoff.4 \
|
||||||
mac_ipacl.4 \
|
mac_ipacl.4 \
|
||||||
mac_lomac.4 \
|
mac_lomac.4 \
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
.\"-
|
||||||
|
.\" Copyright (c) 2024 Baptiste Daroussin <bapt@FreeBSD.org>
|
||||||
|
.\"
|
||||||
|
.\" SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
.\"
|
||||||
|
.Dd May 22, 2024
|
||||||
|
.Dt MAC_DO 4
|
||||||
|
.Os
|
||||||
|
.Sh NAME
|
||||||
|
.Nm mac_do
|
||||||
|
.Nd "policy allowing user to execute program as another user"
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
To compile the
|
||||||
|
.Nm
|
||||||
|
policy into your kernel, place the following lines
|
||||||
|
in your kernel configruation file:
|
||||||
|
.Bd -ragged -offset indent
|
||||||
|
.Cd "options MAC"
|
||||||
|
.Cd "options MAC_DO"
|
||||||
|
.Ed
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
The
|
||||||
|
.Nm
|
||||||
|
policy grants users the ability to run processs as other users
|
||||||
|
according to predefined rules.
|
||||||
|
.Pp
|
||||||
|
The exact set of kernel privileges granted are:
|
||||||
|
.Bl -inset -compact -offset indent
|
||||||
|
.It Dv PRIV_CRED_SETGROUPS
|
||||||
|
.It Dv PRIV_CRET_SETUID
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
The following
|
||||||
|
.Xr sysctl 8
|
||||||
|
MIBs are available:
|
||||||
|
.Bl -tag -width indent
|
||||||
|
.It Va security.mac.do.enabled
|
||||||
|
Enable the
|
||||||
|
.Nm
|
||||||
|
policy.
|
||||||
|
(Default: 1).
|
||||||
|
.It Va security.mac.do.rules
|
||||||
|
The set of rules.
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
The rules consist of a list of elements separated by
|
||||||
|
.So , Sc .
|
||||||
|
Each element is of the form
|
||||||
|
.Sm off
|
||||||
|
.Do
|
||||||
|
.Op Cm uid | Cm gid
|
||||||
|
.Li =
|
||||||
|
.Ar fid
|
||||||
|
.Li :
|
||||||
|
.Ar tid
|
||||||
|
.Dc
|
||||||
|
.Sm on .
|
||||||
|
Where
|
||||||
|
.Ar fid
|
||||||
|
is the uid or gid of the user or group the rule applies to, and
|
||||||
|
.Ar tid
|
||||||
|
is the uid of the targetted user.
|
||||||
|
Two special forms are accepted for
|
||||||
|
.Ar tid :
|
||||||
|
.Va any
|
||||||
|
or
|
||||||
|
.Va * ,
|
||||||
|
which allow to target any user.
|
||||||
|
.Sh EXAMPLES
|
||||||
|
The following rule:
|
||||||
|
.Pp
|
||||||
|
.Dl security.mac.do.rules=uid=1001:80,gid=0:any
|
||||||
|
.Pp
|
||||||
|
means the user with the uid 1001 can execute processes as user with uid 80,
|
||||||
|
all the users which belongs to the group gid 0 can execute processes as any user.
|
||||||
|
.Sh SEE ALSO
|
||||||
|
.Xr mac 4 ,
|
||||||
|
.Xr mdo 1
|
||||||
@@ -226,6 +226,7 @@ SUBDIR= \
|
|||||||
${_mac_biba} \
|
${_mac_biba} \
|
||||||
${_mac_bsdextended} \
|
${_mac_bsdextended} \
|
||||||
${_mac_ddb} \
|
${_mac_ddb} \
|
||||||
|
${_mac_do} \
|
||||||
${_mac_ifoff} \
|
${_mac_ifoff} \
|
||||||
${_mac_ipacl} \
|
${_mac_ipacl} \
|
||||||
${_mac_lomac} \
|
${_mac_lomac} \
|
||||||
@@ -587,6 +588,7 @@ _mac_bsdextended= mac_bsdextended
|
|||||||
.if ${KERN_OPTS:MDDB} || defined(ALL_MODULES)
|
.if ${KERN_OPTS:MDDB} || defined(ALL_MODULES)
|
||||||
_mac_ddb= mac_ddb
|
_mac_ddb= mac_ddb
|
||||||
.endif
|
.endif
|
||||||
|
_mac_do= mac_do
|
||||||
_mac_ifoff= mac_ifoff
|
_mac_ifoff= mac_ifoff
|
||||||
_mac_ipacl= mac_ipacl
|
_mac_ipacl= mac_ipacl
|
||||||
_mac_lomac= mac_lomac
|
_mac_lomac= mac_lomac
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
.PATH: ${SRCTOP}/sys/security/mac_do
|
||||||
|
|
||||||
|
KMOD= mac_do
|
||||||
|
SRCS= mac_do.c vnode_if.h
|
||||||
|
|
||||||
|
.include <bsd.kmod.mk>
|
||||||
@@ -0,0 +1,545 @@
|
|||||||
|
/*-
|
||||||
|
* Copyright(c) 2024 Baptiste Daroussin <bapt@FreeBSD.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/malloc.h>
|
||||||
|
#include <sys/jail.h>
|
||||||
|
#include <sys/kernel.h>
|
||||||
|
#include <sys/lock.h>
|
||||||
|
#include <sys/module.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
|
#include <sys/mutex.h>
|
||||||
|
#include <sys/priv.h>
|
||||||
|
#include <sys/proc.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/sx.h>
|
||||||
|
#include <sys/sysctl.h>
|
||||||
|
#include <sys/systm.h>
|
||||||
|
#include <sys/ucred.h>
|
||||||
|
#include <sys/vnode.h>
|
||||||
|
|
||||||
|
#include <security/mac/mac_policy.h>
|
||||||
|
|
||||||
|
SYSCTL_DECL(_security_mac);
|
||||||
|
|
||||||
|
static SYSCTL_NODE(_security_mac, OID_AUTO, do,
|
||||||
|
CTLFLAG_RW|CTLFLAG_MPSAFE, 0, "mac_do policy controls");
|
||||||
|
|
||||||
|
static int do_enabled = 1;
|
||||||
|
SYSCTL_INT(_security_mac_do, OID_AUTO, enabled, CTLFLAG_RWTUN,
|
||||||
|
&do_enabled, 0, "Enforce do policy");
|
||||||
|
|
||||||
|
static MALLOC_DEFINE(M_DO, "do_rule", "Rules for mac_do");
|
||||||
|
|
||||||
|
#define MAC_RULE_STRING_LEN 1024
|
||||||
|
|
||||||
|
static unsigned mac_do_osd_jail_slot;
|
||||||
|
|
||||||
|
#define RULE_UID 1
|
||||||
|
#define RULE_GID 2
|
||||||
|
#define RULE_ANY 3
|
||||||
|
|
||||||
|
struct rule {
|
||||||
|
int from_type;
|
||||||
|
union {
|
||||||
|
uid_t f_uid;
|
||||||
|
gid_t f_gid;
|
||||||
|
};
|
||||||
|
int to_type;
|
||||||
|
uid_t t_uid;
|
||||||
|
TAILQ_ENTRY(rule) r_entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mac_do_rule {
|
||||||
|
char string[MAC_RULE_STRING_LEN];
|
||||||
|
TAILQ_HEAD(rulehead, rule) head;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct mac_do_rule rules0;
|
||||||
|
|
||||||
|
static void
|
||||||
|
toast_rules(struct rulehead *head)
|
||||||
|
{
|
||||||
|
struct rule *r;
|
||||||
|
|
||||||
|
while ((r = TAILQ_FIRST(head)) != NULL) {
|
||||||
|
TAILQ_REMOVE(head, r, r_entries);
|
||||||
|
free(r, M_DO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
parse_rule_element(char *element, struct rule **rule)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
char *type, *id, *p;
|
||||||
|
struct rule *new;
|
||||||
|
|
||||||
|
new = malloc(sizeof(*new), M_DO, M_ZERO|M_WAITOK);
|
||||||
|
|
||||||
|
type = strsep(&element, "=");
|
||||||
|
if (type == NULL) {
|
||||||
|
error = EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (strcmp(type, "uid") == 0) {
|
||||||
|
new->from_type = RULE_UID;
|
||||||
|
} else if (strcmp(type, "gid") == 0) {
|
||||||
|
new->from_type = RULE_GID;
|
||||||
|
} else {
|
||||||
|
error = EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
id = strsep(&element, ":");
|
||||||
|
if (id == NULL) {
|
||||||
|
error = EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (new->from_type == RULE_UID)
|
||||||
|
new->f_uid = strtol(id, &p, 10);
|
||||||
|
if (new->from_type == RULE_GID)
|
||||||
|
new->f_gid = strtol(id, &p, 10);
|
||||||
|
if (*p != '\0') {
|
||||||
|
error = EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (*element == '\0') {
|
||||||
|
error = EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (strcmp(element, "any") == 0 || strcmp(element, "*") == 0) {
|
||||||
|
new->to_type = RULE_ANY;
|
||||||
|
} else {
|
||||||
|
new->to_type = RULE_UID;
|
||||||
|
new->t_uid = strtol(element, &p, 10);
|
||||||
|
if (*p != '\0') {
|
||||||
|
error = EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
if (error != 0) {
|
||||||
|
free(new, M_DO);
|
||||||
|
*rule = NULL;
|
||||||
|
} else
|
||||||
|
*rule = new;
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
parse_rules(char *string, struct rulehead *head)
|
||||||
|
{
|
||||||
|
struct rule *new;
|
||||||
|
char *element;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
while ((element = strsep(&string, ",")) != NULL) {
|
||||||
|
if (strlen(element) == 0)
|
||||||
|
continue;
|
||||||
|
error = parse_rule_element(element, &new);
|
||||||
|
if (error)
|
||||||
|
goto out;
|
||||||
|
TAILQ_INSERT_TAIL(head, new, r_entries);
|
||||||
|
}
|
||||||
|
out:
|
||||||
|
if (error != 0)
|
||||||
|
toast_rules(head);
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct mac_do_rule *
|
||||||
|
mac_do_rule_find(struct prison *spr, struct prison **prp)
|
||||||
|
{
|
||||||
|
struct prison *pr;
|
||||||
|
struct mac_do_rule *rules;
|
||||||
|
|
||||||
|
for (pr = spr;; pr = pr->pr_parent) {
|
||||||
|
mtx_lock(&pr->pr_mtx);
|
||||||
|
if (pr == &prison0) {
|
||||||
|
rules = &rules0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
rules = osd_jail_get(pr, mac_do_osd_jail_slot);
|
||||||
|
if (rules != NULL)
|
||||||
|
break;
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
}
|
||||||
|
*prp = pr;
|
||||||
|
|
||||||
|
return (rules);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sysctl_rules(SYSCTL_HANDLER_ARGS)
|
||||||
|
{
|
||||||
|
char *copy_string, *new_string;
|
||||||
|
struct rulehead head, saved_head;
|
||||||
|
struct prison *pr;
|
||||||
|
struct mac_do_rule *rules;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
rules = mac_do_rule_find(req->td->td_ucred->cr_prison, &pr);
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
if (req->newptr == NULL)
|
||||||
|
return (sysctl_handle_string(oidp, rules->string, MAC_RULE_STRING_LEN, req));
|
||||||
|
|
||||||
|
new_string = malloc(MAC_RULE_STRING_LEN, M_DO,
|
||||||
|
M_WAITOK|M_ZERO);
|
||||||
|
mtx_lock(&pr->pr_mtx);
|
||||||
|
strlcpy(new_string, rules->string, MAC_RULE_STRING_LEN);
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
|
||||||
|
error = sysctl_handle_string(oidp, new_string, MAC_RULE_STRING_LEN, req);
|
||||||
|
if (error)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
copy_string = strdup(new_string, M_DO);
|
||||||
|
TAILQ_INIT(&head);
|
||||||
|
error = parse_rules(copy_string, &head);
|
||||||
|
free(copy_string, M_DO);
|
||||||
|
if (error)
|
||||||
|
goto out;
|
||||||
|
TAILQ_INIT(&saved_head);
|
||||||
|
mtx_lock(&pr->pr_mtx);
|
||||||
|
TAILQ_CONCAT(&saved_head, &rules->head, r_entries);
|
||||||
|
TAILQ_CONCAT(&rules->head, &head, r_entries);
|
||||||
|
strlcpy(rules->string, new_string, MAC_RULE_STRING_LEN);
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
toast_rules(&saved_head);
|
||||||
|
|
||||||
|
out:
|
||||||
|
free(new_string, M_DO);
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
SYSCTL_PROC(_security_mac_do, OID_AUTO, rules,
|
||||||
|
CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE,
|
||||||
|
0, 0, sysctl_rules, "A",
|
||||||
|
"Rules");
|
||||||
|
|
||||||
|
static void
|
||||||
|
destroy(struct mac_policy_conf *mpc)
|
||||||
|
{
|
||||||
|
osd_jail_deregister(mac_do_osd_jail_slot);
|
||||||
|
toast_rules(&rules0.head);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mac_do_alloc_prison(struct prison *pr, struct mac_do_rule **lrp)
|
||||||
|
{
|
||||||
|
struct prison *ppr;
|
||||||
|
struct mac_do_rule *rules, *new_rules;
|
||||||
|
void **rsv;
|
||||||
|
|
||||||
|
rules = mac_do_rule_find(pr, &ppr);
|
||||||
|
if (ppr == pr)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
mtx_unlock(&ppr->pr_mtx);
|
||||||
|
new_rules = malloc(sizeof(*new_rules), M_PRISON, M_WAITOK|M_ZERO);
|
||||||
|
rsv = osd_reserve(mac_do_osd_jail_slot);
|
||||||
|
rules = mac_do_rule_find(pr, &ppr);
|
||||||
|
if (ppr == pr) {
|
||||||
|
free(new_rules, M_PRISON);
|
||||||
|
osd_free_reserved(rsv);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
mtx_lock(&pr->pr_mtx);
|
||||||
|
osd_jail_set_reserved(pr, mac_do_osd_jail_slot, rsv, new_rules);
|
||||||
|
TAILQ_INIT(&new_rules->head);
|
||||||
|
done:
|
||||||
|
if (lrp != NULL)
|
||||||
|
*lrp = rules;
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
mtx_unlock(&ppr->pr_mtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mac_do_dealloc_prison(void *data)
|
||||||
|
{
|
||||||
|
struct mac_do_rule *r = data;
|
||||||
|
|
||||||
|
toast_rules(&r->head);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mac_do_prison_set(void *obj, void *data)
|
||||||
|
{
|
||||||
|
struct prison *pr = obj;
|
||||||
|
struct vfsoptlist *opts = data;
|
||||||
|
struct rulehead head, saved_head;
|
||||||
|
struct mac_do_rule *rules;
|
||||||
|
char *rules_string, *copy_string;
|
||||||
|
int error, jsys, len;
|
||||||
|
|
||||||
|
error = vfs_copyopt(opts, "mdo", &jsys, sizeof(jsys));
|
||||||
|
if (error == ENOENT)
|
||||||
|
jsys = -1;
|
||||||
|
error = vfs_getopt(opts, "mdo.rules", (void **)&rules_string, &len);
|
||||||
|
if (error == ENOENT)
|
||||||
|
rules = NULL;
|
||||||
|
else
|
||||||
|
jsys = JAIL_SYS_NEW;
|
||||||
|
switch (jsys) {
|
||||||
|
case JAIL_SYS_INHERIT:
|
||||||
|
mtx_lock(&pr->pr_mtx);
|
||||||
|
osd_jail_del(pr, mac_do_osd_jail_slot);
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
break;
|
||||||
|
case JAIL_SYS_NEW:
|
||||||
|
mac_do_alloc_prison(pr, &rules);
|
||||||
|
if (rules_string == NULL)
|
||||||
|
break;
|
||||||
|
copy_string = strdup(rules_string, M_DO);
|
||||||
|
TAILQ_INIT(&head);
|
||||||
|
error = parse_rules(copy_string, &head);
|
||||||
|
free(copy_string, M_DO);
|
||||||
|
if (error)
|
||||||
|
return (1);
|
||||||
|
TAILQ_INIT(&saved_head);
|
||||||
|
mtx_lock(&pr->pr_mtx);
|
||||||
|
TAILQ_CONCAT(&saved_head, &rules->head, r_entries);
|
||||||
|
TAILQ_CONCAT(&rules->head, &head, r_entries);
|
||||||
|
strlcpy(rules->string, rules_string, MAC_RULE_STRING_LEN);
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
toast_rules(&saved_head);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SYSCTL_JAIL_PARAM_SYS_NODE(mdo, CTLFLAG_RW, "Jail MAC/do parameters");
|
||||||
|
SYSCTL_JAIL_PARAM_STRING(_mdo, rules, CTLFLAG_RW, MAC_RULE_STRING_LEN,
|
||||||
|
"Jail MAC/do rules");
|
||||||
|
|
||||||
|
static int
|
||||||
|
mac_do_prison_get(void *obj, void *data)
|
||||||
|
{
|
||||||
|
struct prison *ppr, *pr = obj;
|
||||||
|
struct vfsoptlist *opts = data;
|
||||||
|
struct mac_do_rule *rules;
|
||||||
|
int jsys, error;
|
||||||
|
|
||||||
|
rules = mac_do_rule_find(pr, &ppr);
|
||||||
|
error = vfs_setopt(opts, "mdo", &jsys, sizeof(jsys));
|
||||||
|
if (error != 0 && error != ENOENT)
|
||||||
|
goto done;
|
||||||
|
error = vfs_setopts(opts, "mdo.rules", rules->string);
|
||||||
|
if (error != 0 && error != ENOENT)
|
||||||
|
goto done;
|
||||||
|
mtx_unlock(&ppr->pr_mtx);
|
||||||
|
error = 0;
|
||||||
|
done:
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mac_do_prison_create(void *obj, void *data __unused)
|
||||||
|
{
|
||||||
|
struct prison *pr = obj;
|
||||||
|
|
||||||
|
mac_do_alloc_prison(pr, NULL);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mac_do_prison_remove(void *obj, void *data __unused)
|
||||||
|
{
|
||||||
|
struct prison *pr = obj;
|
||||||
|
struct mac_do_rule *r;
|
||||||
|
|
||||||
|
mtx_lock(&pr->pr_mtx);
|
||||||
|
r = osd_jail_get(pr, mac_do_osd_jail_slot);
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
toast_rules(&r->head);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
mac_do_prison_check(void *obj, void *data)
|
||||||
|
{
|
||||||
|
struct vfsoptlist *opts = data;
|
||||||
|
char *rules_string;
|
||||||
|
int error, jsys, len;
|
||||||
|
|
||||||
|
error = vfs_copyopt(opts, "mdo", &jsys, sizeof(jsys));
|
||||||
|
if (error != ENOENT) {
|
||||||
|
if (error != 0)
|
||||||
|
return (error);
|
||||||
|
if (jsys != JAIL_SYS_NEW && jsys != JAIL_SYS_INHERIT)
|
||||||
|
return (EINVAL);
|
||||||
|
}
|
||||||
|
error = vfs_getopt(opts, "mdo.rules", (void **)&rules_string, &len);
|
||||||
|
if (error != ENOENT) {
|
||||||
|
if (error != 0)
|
||||||
|
return (error);
|
||||||
|
if (len > MAC_RULE_STRING_LEN) {
|
||||||
|
vfs_opterror(opts, "mdo.rules too long");
|
||||||
|
return (ENAMETOOLONG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (error == ENOENT)
|
||||||
|
error = 0;
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
init(struct mac_policy_conf *mpc)
|
||||||
|
{
|
||||||
|
static osd_method_t methods[PR_MAXMETHOD] = {
|
||||||
|
[PR_METHOD_CREATE] = mac_do_prison_create,
|
||||||
|
[PR_METHOD_GET] = mac_do_prison_get,
|
||||||
|
[PR_METHOD_SET] = mac_do_prison_set,
|
||||||
|
[PR_METHOD_CHECK] = mac_do_prison_check,
|
||||||
|
[PR_METHOD_REMOVE] = mac_do_prison_remove,
|
||||||
|
};
|
||||||
|
struct prison *pr;
|
||||||
|
|
||||||
|
mac_do_osd_jail_slot = osd_jail_register(mac_do_dealloc_prison, methods);
|
||||||
|
TAILQ_INIT(&rules0.head);
|
||||||
|
sx_slock(&allprison_lock);
|
||||||
|
TAILQ_FOREACH(pr, &allprison, pr_list)
|
||||||
|
mac_do_alloc_prison(pr, NULL);
|
||||||
|
sx_sunlock(&allprison_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
rule_is_valid(struct ucred *cred, struct rule *r)
|
||||||
|
{
|
||||||
|
if (r->from_type == RULE_UID && r->f_uid == cred->cr_uid)
|
||||||
|
return (true);
|
||||||
|
if (r->from_type == RULE_GID && r->f_gid == cred->cr_gid)
|
||||||
|
return (true);
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
priv_grant(struct ucred *cred, int priv)
|
||||||
|
{
|
||||||
|
struct rule *r;
|
||||||
|
struct prison *pr;
|
||||||
|
struct mac_do_rule *rule;
|
||||||
|
|
||||||
|
if (do_enabled == 0)
|
||||||
|
return (EPERM);
|
||||||
|
|
||||||
|
rule = mac_do_rule_find(cred->cr_prison, &pr);
|
||||||
|
TAILQ_FOREACH(r, &rule->head, r_entries) {
|
||||||
|
if (rule_is_valid(cred, r)) {
|
||||||
|
switch (priv) {
|
||||||
|
case PRIV_CRED_SETGROUPS:
|
||||||
|
case PRIV_CRED_SETUID:
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
return (0);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
return (EPERM);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
check_setgroups(struct ucred *cred, int ngrp, gid_t *groups)
|
||||||
|
{
|
||||||
|
struct rule *r;
|
||||||
|
char *fullpath = NULL;
|
||||||
|
char *freebuf = NULL;
|
||||||
|
struct prison *pr;
|
||||||
|
struct mac_do_rule *rule;
|
||||||
|
|
||||||
|
if (do_enabled == 0)
|
||||||
|
return (0);
|
||||||
|
if (cred->cr_uid == 0)
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0)
|
||||||
|
return (EPERM);
|
||||||
|
if (strcmp(fullpath, "/usr/bin/mdo") != 0) {
|
||||||
|
free(freebuf, M_TEMP);
|
||||||
|
return (EPERM);
|
||||||
|
}
|
||||||
|
free(freebuf, M_TEMP);
|
||||||
|
|
||||||
|
rule = mac_do_rule_find(cred->cr_prison, &pr);
|
||||||
|
TAILQ_FOREACH(r, &rule->head, r_entries) {
|
||||||
|
if (rule_is_valid(cred, r)) {
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
|
||||||
|
return (EPERM);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
check_setuid(struct ucred *cred, uid_t uid)
|
||||||
|
{
|
||||||
|
struct rule *r;
|
||||||
|
int error;
|
||||||
|
char *fullpath = NULL;
|
||||||
|
char *freebuf = NULL;
|
||||||
|
struct prison *pr;
|
||||||
|
struct mac_do_rule *rule;
|
||||||
|
|
||||||
|
if (do_enabled == 0)
|
||||||
|
return (0);
|
||||||
|
if (cred->cr_uid == uid || cred->cr_uid == 0)
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
if (vn_fullpath(curproc->p_textvp, &fullpath, &freebuf) != 0)
|
||||||
|
return (EPERM);
|
||||||
|
if (strcmp(fullpath, "/usr/bin/mdo") != 0) {
|
||||||
|
free(freebuf, M_TEMP);
|
||||||
|
return (EPERM);
|
||||||
|
}
|
||||||
|
free(freebuf, M_TEMP);
|
||||||
|
|
||||||
|
error = EPERM;
|
||||||
|
rule = mac_do_rule_find(cred->cr_prison, &pr);
|
||||||
|
TAILQ_FOREACH(r, &rule->head, r_entries) {
|
||||||
|
if (r->from_type == RULE_UID) {
|
||||||
|
if (cred->cr_uid != r->f_uid)
|
||||||
|
continue;
|
||||||
|
if (r->to_type == RULE_ANY) {
|
||||||
|
error = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (r->to_type == RULE_UID && uid == r->t_uid) {
|
||||||
|
error = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (r->from_type == RULE_GID) {
|
||||||
|
if (cred->cr_gid != r->f_gid)
|
||||||
|
continue;
|
||||||
|
if (r->to_type == RULE_ANY) {
|
||||||
|
error = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (r->to_type == RULE_UID && uid == r->t_uid) {
|
||||||
|
error = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mtx_unlock(&pr->pr_mtx);
|
||||||
|
return (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct mac_policy_ops do_ops = {
|
||||||
|
.mpo_destroy = destroy,
|
||||||
|
.mpo_init = init,
|
||||||
|
.mpo_cred_check_setuid = check_setuid,
|
||||||
|
.mpo_cred_check_setgroups = check_setgroups,
|
||||||
|
.mpo_priv_grant = priv_grant,
|
||||||
|
};
|
||||||
|
|
||||||
|
MAC_POLICY_SET(&do_ops, mac_do, "MAC/do",
|
||||||
|
MPC_LOADTIME_FLAG_UNLOADOK, NULL);
|
||||||
|
MODULE_VERSION(mac_do, 1);
|
||||||
@@ -87,6 +87,7 @@ SUBDIR= alias \
|
|||||||
lzmainfo \
|
lzmainfo \
|
||||||
m4 \
|
m4 \
|
||||||
mandoc \
|
mandoc \
|
||||||
|
mdo \
|
||||||
mesg \
|
mesg \
|
||||||
ministat \
|
ministat \
|
||||||
mkdep \
|
mkdep \
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
PROG= mdo
|
||||||
|
SRCS= mdo.c
|
||||||
|
|
||||||
|
.include <bsd.prog.mk>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
.\"-
|
||||||
|
.\" Copyright(c) 2024 Baptiste Daroussin <bapt@FreeBSD.org>
|
||||||
|
.\"
|
||||||
|
.\" SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
.\"
|
||||||
|
.Dd May 22, 2024
|
||||||
|
.Dt MDO 1
|
||||||
|
.Os
|
||||||
|
.Sh NAME
|
||||||
|
.Nm mdo
|
||||||
|
.Nd execute commands as another user
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
.Nm
|
||||||
|
.Op Fl u Ar username
|
||||||
|
.Op Fl i
|
||||||
|
.Op command Op args
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
The
|
||||||
|
.Nm
|
||||||
|
utility executes the specified
|
||||||
|
.Ar command
|
||||||
|
as user
|
||||||
|
.Ar username .
|
||||||
|
.Pp
|
||||||
|
If no
|
||||||
|
.Ar username
|
||||||
|
is provided it defaults to the
|
||||||
|
.Va root
|
||||||
|
user.
|
||||||
|
If no
|
||||||
|
.Ar command
|
||||||
|
is specified, it will execute the shell specified as
|
||||||
|
.Va SHELL
|
||||||
|
environnement variable, falling back on
|
||||||
|
.Pa /bin/sh .
|
||||||
|
.Pp
|
||||||
|
The
|
||||||
|
.Fl i
|
||||||
|
option can be used to only call
|
||||||
|
.Fn setuid
|
||||||
|
and keep the group from the calling user.
|
||||||
|
.Sh SEE ALSO
|
||||||
|
.Xr su 1
|
||||||
|
.Xr mac_do 4
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*-
|
||||||
|
* Copyright(c) 2024 Baptiste Daroussin <bapt@FreeBSD.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/limits.h>
|
||||||
|
|
||||||
|
#include <err.h>
|
||||||
|
#include <paths.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static void
|
||||||
|
usage(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "usage: mdo [-u username] [-i] [--] [command [args]]\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct passwd *pw;
|
||||||
|
const char *username = "root";
|
||||||
|
bool uidonly = false;
|
||||||
|
int ch;
|
||||||
|
|
||||||
|
while ((ch = getopt(argc, argv, "u:i")) != -1) {
|
||||||
|
switch (ch) {
|
||||||
|
case 'u':
|
||||||
|
username = optarg;
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
uidonly = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
argc -= optind;
|
||||||
|
argv += optind;
|
||||||
|
|
||||||
|
if ((pw = getpwnam(username)) == NULL) {
|
||||||
|
if (strspn(username, "0123456789") == strlen(username)) {
|
||||||
|
const char *errp = NULL;
|
||||||
|
uid_t uid = strtonum(username, 0, UID_MAX, &errp);
|
||||||
|
if (errp != NULL)
|
||||||
|
err(EXIT_FAILURE, "%s", errp);
|
||||||
|
pw = getpwuid(uid);
|
||||||
|
}
|
||||||
|
if (pw == NULL)
|
||||||
|
err(EXIT_FAILURE, "invalid username '%s'", username);
|
||||||
|
}
|
||||||
|
if (!uidonly) {
|
||||||
|
if (initgroups(pw->pw_name, pw->pw_gid) == -1)
|
||||||
|
err(EXIT_FAILURE, "failed to call initgroups");
|
||||||
|
if (setgid(pw->pw_gid) == -1)
|
||||||
|
err(EXIT_FAILURE, "failed to call setgid");
|
||||||
|
}
|
||||||
|
if (setuid(pw->pw_uid) == -1)
|
||||||
|
err(EXIT_FAILURE, "failed to call setuid");
|
||||||
|
if (*argv == NULL) {
|
||||||
|
const char *sh = getenv("SHELL");
|
||||||
|
if (sh == NULL)
|
||||||
|
sh = _PATH_BSHELL;
|
||||||
|
execlp(sh, sh, "-i", NULL);
|
||||||
|
} else {
|
||||||
|
execvp(argv[0], argv);
|
||||||
|
}
|
||||||
|
err(EXIT_FAILURE, "exec failed");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user