From nobody Wed May 22 12:02:28 2024 X-Original-To: dev-commits-src-main@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 4Vkqg847Mfz5LZv7; Wed, 22 May 2024 12:02:28 +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 "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4Vkqg83bLJz572h; Wed, 22 May 2024 12:02:28 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1716379348; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=jqfYKFfowWJY7TaXNxYb7i8tKZSDbnphe2s3qLNf9kE=; b=CT5VAMRCEbl9V/IF5iMiwnVjlLpVFH1vBoVx2XTa/zH5D1Vk3iTM4UU1a+PX8XL9HW62dv RHavGvdvOAHaS2jtbGAoNjjn/DWhLQiVnbCTytsdtH1K/EbidWJzAi0tDJzeTEYtmtAfxY ItPrvULGbcSFfCGaLPJCnGZnpPa9EWAHUWqAP57wqkY5vL07e00yVoLayd16YP2e/0FSGZ zTYdiOvh1LtGt0N5zpokEhVsT6oXY/CyJMvphB+V2mJoKwj6t9kBssvjc24quCZqP0Hatu DMbVSVu//jpDHOFGVXkl/CXg72MneUzvPAj+D0S8MJ+f899Xx3rdRkwBd8f+fA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1716379348; a=rsa-sha256; cv=none; b=Lr4CYUg0JPmlzbGydiqymJEoM9F2AaCTK0lvOJxG8KNsWNNI/PVi1icRxoz3MUfTE52DLL Xt2cgqi6ellA18MtzZGGIjmk08McYc4aI1ceqRLCIlywi71udb38mbfiL8NVZFRopx8nrC PB8qNAEPK/q4FV/54FzGoQneOgiXMLR8FhU0AkW/iiM4yAZgDbnVgqx7U3FzlteD54+bWF 198t4V79+any/UlcnO85853bvNlUNfKq1pJMCXplpkrDf+UKh8YeizfMXgQJntXydUx/5q gRsrOMKTBpYFxJJSzLhR6BkK7T/Zoao5xOHSzsFhG94VKCGeF+BE9N2+sEkbKQ== 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=1716379348; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=jqfYKFfowWJY7TaXNxYb7i8tKZSDbnphe2s3qLNf9kE=; b=xvf+FkH/7X0vMrzHjNGgDdXSjLrGojIX3afnulFRd0M3St6aUpc2X5bCmlOpyvZ63yjPy/ Tf4JYZN3NpeVSkLDAZPBoIP7adH9KzHWi+1KEzfejXNxCE6DUlNO1HzAJ/s/zzT7yIwka0 BPNS1N3umZP/kXb8634i9Z1kpuXbKQUOmOVRRBQY2/KhP1v7I4WfiGR46+xPmxohWkqoeO l8bnucmrNWn2dJs6enG7pBQ2dv/sVdRGSYlLf9jz0knSo/VhYTnpSznV7f+Jicm9eNzDs+ aVrlYD24BYjBWNkM+mXAJ64+3px82FZ63j5bLdw8rzu0kMjHJzP0IMMjvETfFA== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (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 did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 4Vkqg83BxZz14vy; Wed, 22 May 2024 12:02:28 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.17.1/8.17.1) with ESMTP id 44MC2S5w064291; Wed, 22 May 2024 12:02:28 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.17.1/8.17.1/Submit) id 44MC2Sff064288; Wed, 22 May 2024 12:02:28 GMT (envelope-from git) Date: Wed, 22 May 2024 12:02:28 GMT Message-Id: <202405221202.44MC2Sff064288@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Baptiste Daroussin Subject: git: 8aac90f18aef - main - mac_do: add a new MAC/do policy and mdo(1) utility List-Id: Commit messages for the main branch of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: bapt X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 8aac90f18aef7c9eea906c3ff9a001ca7b94f375 Auto-Submitted: auto-generated The branch main has been updated by bapt: URL: https://cgit.FreeBSD.org/src/commit/?id=8aac90f18aef7c9eea906c3ff9a001ca7b94f375 commit 8aac90f18aef7c9eea906c3ff9a001ca7b94f375 Author: Baptiste Daroussin AuthorDate: 2024-05-09 22:03:28 +0000 Commit: Baptiste Daroussin CommitDate: 2024-05-22 12:01:41 +0000 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 --- share/man/man4/Makefile | 1 + share/man/man4/mac_do.4 | 78 +++++++ sys/modules/Makefile | 2 + sys/modules/mac_do/Makefile | 6 + sys/security/mac_do/mac_do.c | 545 +++++++++++++++++++++++++++++++++++++++++++ usr.bin/Makefile | 1 + usr.bin/mdo/Makefile | 4 + usr.bin/mdo/mdo.1 | 44 ++++ usr.bin/mdo/mdo.c | 76 ++++++ 9 files changed, 757 insertions(+) diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index 164efe3ddf66..69424a4be271 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -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 \ diff --git a/share/man/man4/mac_do.4 b/share/man/man4/mac_do.4 new file mode 100644 index 000000000000..9a9ebe1ca989 --- /dev/null +++ b/share/man/man4/mac_do.4 @@ -0,0 +1,78 @@ +.\"- +.\" Copyright (c) 2024 Baptiste Daroussin +.\" +.\" 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 diff --git a/sys/modules/Makefile b/sys/modules/Makefile index 2bd8914ab0d5..ad274e7f7b47 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -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 diff --git a/sys/modules/mac_do/Makefile b/sys/modules/mac_do/Makefile new file mode 100644 index 000000000000..532a5b9faa71 --- /dev/null +++ b/sys/modules/mac_do/Makefile @@ -0,0 +1,6 @@ +.PATH: ${SRCTOP}/sys/security/mac_do + +KMOD= mac_do +SRCS= mac_do.c vnode_if.h + +.include diff --git a/sys/security/mac_do/mac_do.c b/sys/security/mac_do/mac_do.c new file mode 100644 index 000000000000..8685954b7db6 --- /dev/null +++ b/sys/security/mac_do/mac_do.c @@ -0,0 +1,545 @@ +/*- + * Copyright(c) 2024 Baptiste Daroussin + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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); diff --git a/usr.bin/Makefile b/usr.bin/Makefile index 0a584a18d14a..a9d5c1563797 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -87,6 +87,7 @@ SUBDIR= alias \ lzmainfo \ m4 \ mandoc \ + mdo \ mesg \ ministat \ mkdep \ diff --git a/usr.bin/mdo/Makefile b/usr.bin/mdo/Makefile new file mode 100644 index 000000000000..e20f2719fc82 --- /dev/null +++ b/usr.bin/mdo/Makefile @@ -0,0 +1,4 @@ +PROG= mdo +SRCS= mdo.c + +.include diff --git a/usr.bin/mdo/mdo.1 b/usr.bin/mdo/mdo.1 new file mode 100644 index 000000000000..115ce44f4531 --- /dev/null +++ b/usr.bin/mdo/mdo.1 @@ -0,0 +1,44 @@ +.\"- +.\" Copyright(c) 2024 Baptiste Daroussin +.\" +.\" 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 diff --git a/usr.bin/mdo/mdo.c b/usr.bin/mdo/mdo.c new file mode 100644 index 000000000000..22e2838daa08 --- /dev/null +++ b/usr.bin/mdo/mdo.c @@ -0,0 +1,76 @@ +/*- + * Copyright(c) 2024 Baptiste Daroussin + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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"); +}