About

This post takes a look at a recent addition to FreeBSD: mdo(1). The mdo utility allows a given user or group member to execute a command as another user.

Background

Historically UNIX tools that let one user execute a command as another (for example: doas(1) and sudo(8)) have relied on a setuid binary owned by root.

When invoked, the binary runs with an effective user ID of 0 and then drops privileges to the target user via a system call like setuid(2). The real and effective user/group IDs are updated, and execve(2) replaces the process while keeping those target privileges. That pattern has been standard for decades.

But mdo(1) is different. It avoids setuid binaries completely and instead relies on the mac_do(4) policy (a kernel module) to allow user/group transitions through the setcred(2) system call. In comparison to setuid binaries this approach is innovative and a breath of fresh air.

Policy

Overview

The mdo(1) utility depends on the mac_do(4) policy. This policy must be loaded into the kernel before mdo(1) can be used from the command line. The mac(4) man page provides more information about FreeBSD's Mandatory Access Control (MAC) framework.

Policy: Temporary

We can quickly try mdo without rebooting the system by loading the policy temporarily but the recommended approach is to load the policy earlier in the boot process:

root@freebsd# kldload mac_do

Policy: Persistent

We can choose to have the mdo policy persist across system reboots and loaded into the kernel early in the boot process by updating /boot/loader.conf. This is the recommended approach:

# /boot/loader.conf
mac_do_load="YES"

Rules

Overview

A rule defines which user or group members can execute a command as another user. These rules are stored as a semicolon-separated list and can be managed via sysctl(8) under the security.mac.do.rules node.

Each rule consists of two parts: a left-hand side and a right-hand side, separated by the > character. The left-hand side describes the source user ID or group ID that is allowed to run a command, and the right-hand side describes the target ID that can be inherited by user(s) described in the left-hand side of the rule.

Although security.mac.do.rules is roughly analogous to OpenBSD's doas.conf file it currently cannot express the same rules. For example, the doas.conf file lets you choose which program can be executed and with what arguments where as mdo(1) rules are focused purely on what id transitions are allowed.

Let's look at some examples of how to define rules:

root@freebsd# sysctl security.mac.do.rules="uid=$(id -u alice)>uid=$(id -u bob)"
root@freebsd# gid=$(getent group beatles | awk -F: '{print $3}')
root@freebsd# sysctl security.mac.do.rules="gid=${gid}>uid=$(id -u bob)"
root@freebsd# sysctl security.mac.do.rules="uid=$(id -u alice)>uid=0"
root@freebsd# sysctl security.mac.do.rules=uid="$(id -u alice)>any"

Explanation

Conclusion

Overview

With the mac_do policy loaded and the relevant rules in place, alice can now use the mdo(1) utility to launch the weechat program as bob:

alice@freebsd$ mdo -u bob /usr/local/bin/weechat

Explanation