mirror of
https://git.FreeBSD.org/src.git
synced 2024-11-23 07:31:31 +00:00
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:
parent
ac4ddc467b
commit
8aac90f18a
@ -297,6 +297,7 @@ MAN= aac.4 \
|
||||
mac_biba.4 \
|
||||
mac_bsdextended.4 \
|
||||
mac_ddb.4 \
|
||||
mac_do.4 \
|
||||
mac_ifoff.4 \
|
||||
mac_ipacl.4 \
|
||||
mac_lomac.4 \
|
||||
|
78
share/man/man4/mac_do.4
Normal file
78
share/man/man4/mac_do.4
Normal file
@ -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_bsdextended} \
|
||||
${_mac_ddb} \
|
||||
${_mac_do} \
|
||||
${_mac_ifoff} \
|
||||
${_mac_ipacl} \
|
||||
${_mac_lomac} \
|
||||
@ -587,6 +588,7 @@ _mac_bsdextended= mac_bsdextended
|
||||
.if ${KERN_OPTS:MDDB} || defined(ALL_MODULES)
|
||||
_mac_ddb= mac_ddb
|
||||
.endif
|
||||
_mac_do= mac_do
|
||||
_mac_ifoff= mac_ifoff
|
||||
_mac_ipacl= mac_ipacl
|
||||
_mac_lomac= mac_lomac
|
||||
|
6
sys/modules/mac_do/Makefile
Normal file
6
sys/modules/mac_do/Makefile
Normal file
@ -0,0 +1,6 @@
|
||||
.PATH: ${SRCTOP}/sys/security/mac_do
|
||||
|
||||
KMOD= mac_do
|
||||
SRCS= mac_do.c vnode_if.h
|
||||
|
||||
.include <bsd.kmod.mk>
|
545
sys/security/mac_do/mac_do.c
Normal file
545
sys/security/mac_do/mac_do.c
Normal file
@ -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 \
|
||||
m4 \
|
||||
mandoc \
|
||||
mdo \
|
||||
mesg \
|
||||
ministat \
|
||||
mkdep \
|
||||
|
4
usr.bin/mdo/Makefile
Normal file
4
usr.bin/mdo/Makefile
Normal file
@ -0,0 +1,4 @@
|
||||
PROG= mdo
|
||||
SRCS= mdo.c
|
||||
|
||||
.include <bsd.prog.mk>
|
44
usr.bin/mdo/mdo.1
Normal file
44
usr.bin/mdo/mdo.1
Normal file
@ -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
|
76
usr.bin/mdo/mdo.c
Normal file
76
usr.bin/mdo/mdo.c
Normal file
@ -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");
|
||||
}
|
Loading…
Reference in New Issue
Block a user