Page MenuHomeFreeBSD

D45145.id138469.diff
No OneTemporary

D45145.id138469.diff

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -288,6 +288,7 @@
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
--- /dev/null
+++ b/share/man/man4/mac_do.4
@@ -0,0 +1,60 @@
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2024 Baptiste Daroussin <bapt@FreeBSD.org>
+.Dd May 9, 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 mac_do 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
+.Pp
+.Sh DESCRIPTION
+The
+.Nm
+policy grants some user the ability to run process as another user
+according to rules.
+.Pp
+The exact set of kernel privileges granted are:
+.Bl -inset -compact -offset indent
+.It Dv PRIV_CRED_SETGROUPS
+.It Dv PROV_CRET_SETUID
+.El
+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 in a list of element separated by
+.So , Sc .
+Each elements is in the following form
+.Dq [uid|gid]=<fid>:<tid>
+Where
+.Ar fid
+is the uid or the gid depending on the modifier specicied of the users allowed
+to become
+.Ar tid . and
+.Ar tid is the uid of the targetted user.
+2 special form are accepted for
+.Ar tid :
+.Va any
+or
+.Va *
+which allows to target any users.
+.Sh SEE ALSO
+.Xr mac 4 ,
+.Xr mdo 1
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -226,6 +226,7 @@
${_mac_biba} \
${_mac_bsdextended} \
${_mac_ddb} \
+ ${_mac_do} \
${_mac_ifoff} \
${_mac_ipacl} \
${_mac_lomac} \
@@ -587,6 +588,7 @@
.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
--- /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 <bsd.kmod.mk>
diff --git a/sys/security/mac_do/mac_do.c b/sys/security/mac_do/mac_do.c
new file mode 100644
--- /dev/null
+++ b/sys/security/mac_do/mac_do.c
@@ -0,0 +1,335 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright(c) 2024 Baptiste Daroussin <bapt@FreeBSD.org
+ */
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/ucred.h>
+#include <sys/proc.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/priv.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 struct mtx rule_mtx;
+static char rule_string[MAC_RULE_STRING_LEN];
+
+#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;
+};
+
+static TAILQ_HEAD(rulehead, rule) rule_head;
+
+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 int
+sysctl_rules(SYSCTL_HANDLER_ARGS)
+{
+ char *copy_string, *new_string;
+ struct rulehead head, saved_head;
+ int error;
+
+ if (req->newptr == NULL)
+ return (sysctl_handle_string(oidp, rule_string, MAC_RULE_STRING_LEN, req));
+
+ new_string = malloc(MAC_RULE_STRING_LEN, M_DO,
+ M_WAITOK|M_ZERO);
+ mtx_lock(&rule_mtx);
+ strcpy(new_string, rule_string);
+ mtx_unlock(&rule_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(&rule_mtx);
+ TAILQ_CONCAT(&saved_head, &rule_head, r_entries);
+ TAILQ_CONCAT(&rule_head, &head, r_entries);
+ strcpy(rule_string, new_string);
+ mtx_unlock(&rule_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)
+{
+ mtx_destroy(&rule_mtx);
+ toast_rules(&rule_head);
+}
+
+static void
+init(struct mac_policy_conf *mpc)
+{
+ mtx_init(&rule_mtx, "rule_mtx", NULL, MTX_DEF);
+ TAILQ_INIT(&rule_head);
+}
+
+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;
+
+ if (do_enabled == 0)
+ return (EPERM);
+
+
+ mtx_lock(&rule_mtx);
+ 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(&rule_mtx);
+ return (0);
+ default:
+ break;
+ }
+ }
+ }
+ mtx_unlock(&rule_mtx);
+ return (EPERM);
+}
+
+static int
+check_setgroups(struct ucred *cred, int ngrp, gid_t *groups)
+{
+ struct rule *r;
+ char *fullpath = NULL;
+ char *freebuf = NULL;
+
+ 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);
+
+ mtx_lock(&rule_mtx);
+ TAILQ_FOREACH(r, &rule_head, r_entries) {
+ if (rule_is_valid(cred, r)) {
+ mtx_unlock(&rule_mtx);
+ return (0);
+ }
+ }
+ mtx_unlock(&rule_mtx);
+
+ return (EPERM);
+}
+
+static int
+check_setuid(struct ucred *cred, uid_t uid)
+{
+ struct rule *r;
+ int error;
+ char *fullpath = NULL;
+ char *freebuf = NULL;
+
+ 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;
+ mtx_lock(&rule_mtx);
+ 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(&rule_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
--- a/usr.bin/Makefile
+++ b/usr.bin/Makefile
@@ -87,6 +87,7 @@
lzmainfo \
m4 \
mandoc \
+ mdo \
mesg \
ministat \
mkdep \
diff --git a/usr.bin/mdo/Makefile b/usr.bin/mdo/Makefile
new file mode 100644
--- /dev/null
+++ b/usr.bin/mdo/Makefile
@@ -0,0 +1,4 @@
+PROG= mdo
+SRCS= mdo.c
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/mdo/mdo.1 b/usr.bin/mdo/mdo.1
new file mode 100644
--- /dev/null
+++ b/usr.bin/mdo/mdo.1
@@ -0,0 +1,42 @@
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright(c) 2024 Baptiste Daroussin <bapt@FreeBSD.org
+.Dd May 9, 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 on the
+.Va root
+user.
+If no
+.Ar command
+is specificied, it will execute the shell specificed 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
--- /dev/null
+++ b/usr.bin/mdo/mdo.c
@@ -0,0 +1,76 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright(c) 2024 Baptiste Daroussin <bapt@FreeBSD.org
+ */
+
+#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, "Impossible to call initgroups");
+ if (setgid(pw->pw_gid) == -1)
+ err(EXIT_FAILURE, "Impossible to call setgid");
+ }
+ if (setuid(pw->pw_uid) == -1)
+ err(EXIT_FAILURE, "Impossible 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");
+}

File Metadata

Mime Type
text/plain
Expires
Fri, Feb 13, 8:50 PM (4 h, 49 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
28680886
Default Alt Text
D45145.id138469.diff (12 KB)

Event Timeline