Three-pillar fix from Hamilton review: Code quality — validate_signature() for D-Bus spec compliance, MCDBUS_TIMEOUT env var, replace 13 error-as-success returns with ToolError, monotonic clock deadline on tree walks, sanitize D-Bus error messages, fix resource connection leak via module-level BusManager, hasattr guards in conftest. Elicitation — ctx.elicit() confirmation for system bus call_method and all set_property calls, graceful degradation when client lacks elicitation support, MCDBUS_REQUIRE_ELICITATION for hard-fail mode. Permission docs — four-layer guide (systemd sandboxing, dbus-broker policy, polkit rules, xdg-dbus-proxy) with ready-to-deploy example configs validated against xmllint, bash -n, and systemd-analyze.
333 lines
11 KiB
Markdown
333 lines
11 KiB
Markdown
# D-Bus Permission Model
|
|
|
|
mcdbus does not implement its own access control. By design, it delegates
|
|
all security decisions to the operating system's existing D-Bus permission
|
|
stack. This means the same mechanisms that protect D-Bus from any other
|
|
client also protect it from mcdbus.
|
|
|
|
This guide covers four independent layers that can be composed to build
|
|
the level of isolation your deployment requires. They are all optional,
|
|
all independent, and all stack on top of each other.
|
|
|
|
## Layer 1: systemd Service Sandboxing
|
|
|
|
The single most effective thing you can do. Running mcdbus as a systemd
|
|
service gives you process-level isolation using Linux namespaces, seccomp,
|
|
and capability dropping -- all with a handful of declarative directives.
|
|
|
|
An example unit file is provided at [docs/examples/mcdbus.service](examples/mcdbus.service).
|
|
|
|
### What it does
|
|
|
|
The unit file uses `DynamicUser=yes`, which creates an ephemeral Unix user
|
|
for each service invocation. No home directory, no persistent UID, no
|
|
leftover state. Combined with filesystem and network restrictions, the
|
|
mcdbus process gets a minimal sandbox:
|
|
|
|
- **Filesystem:** `ProtectSystem=strict` makes `/usr`, `/boot`, and `/efi`
|
|
read-only. `ProtectHome=yes` hides `/home`, `/root`, and `/run/user`
|
|
entirely. `PrivateTmp=yes` gives mcdbus its own `/tmp` that is invisible
|
|
to other processes.
|
|
- **Network:** `RestrictAddressFamilies=AF_UNIX` limits socket creation to
|
|
Unix domain sockets. D-Bus only needs `AF_UNIX`, so this blocks any
|
|
TCP/UDP/raw socket activity. mcdbus cannot open network connections.
|
|
- **Capabilities:** `CapabilityBoundingSet=` (empty) drops every Linux
|
|
capability. mcdbus cannot change file ownership, load kernel modules,
|
|
bind to privileged ports, or do anything else that requires elevated
|
|
privileges.
|
|
- **Syscalls:** `SystemCallFilter=@system-service` restricts the process to
|
|
the set of syscalls that a typical well-behaved service needs. Things
|
|
like `mount`, `reboot`, and `kexec_load` are blocked.
|
|
- **Memory:** `MemoryDenyWriteExecute=yes` prevents mapping memory as both
|
|
writable and executable. Stops most classes of code injection.
|
|
|
|
### Deploying it
|
|
|
|
```bash
|
|
sudo cp docs/examples/mcdbus.service /etc/systemd/system/
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now mcdbus.service
|
|
```
|
|
|
|
### Testing it
|
|
|
|
systemd ships a built-in security auditor:
|
|
|
|
```bash
|
|
systemd-analyze security mcdbus.service
|
|
```
|
|
|
|
This produces a score from 0.0 (fully exposed) to 10.0 (fully locked down).
|
|
The example unit file should score above 7.0. The output also lists each
|
|
directive and its impact, so you can see exactly what is and is not restricted.
|
|
|
|
Check the journal for any sandbox-related denials:
|
|
|
|
```bash
|
|
journalctl -u mcdbus.service -f
|
|
```
|
|
|
|
### Environment variables
|
|
|
|
The unit file sets `MCDBUS_TIMEOUT=30` by default. You can override this
|
|
and add other mcdbus environment variables via a drop-in:
|
|
|
|
```bash
|
|
sudo systemctl edit mcdbus.service
|
|
```
|
|
|
|
```ini
|
|
[Service]
|
|
Environment=MCDBUS_TIMEOUT=60
|
|
Environment=MCDBUS_REQUIRE_ELICITATION=1
|
|
```
|
|
|
|
|
|
## Layer 2: D-Bus Bus Policy
|
|
|
|
D-Bus itself has a message filtering layer that operates at the bus daemon
|
|
(or bus broker) level. This is independent of what mcdbus does -- it
|
|
controls which messages any client can send or receive based on the
|
|
sender's Unix identity.
|
|
|
|
An example policy file is provided at [docs/examples/mcdbus.conf](examples/mcdbus.conf).
|
|
|
|
### How it works
|
|
|
|
D-Bus policy files are XML documents dropped into `/etc/dbus-1/system.d/`
|
|
(for the system bus) or `/etc/dbus-1/session.d/` (for the session bus).
|
|
Both `dbus-daemon` and `dbus-broker` read them.
|
|
|
|
The policy engine evaluates rules top to bottom. The last matching rule
|
|
wins. The general pattern is:
|
|
|
|
1. Default deny everything for the mcdbus user/group.
|
|
2. Selectively allow specific destinations, interfaces, and methods.
|
|
|
|
### Filter attributes
|
|
|
|
Each `<allow>` or `<deny>` element can filter on:
|
|
|
|
- `send_destination` -- the bus name being called (e.g. `org.freedesktop.Notifications`)
|
|
- `send_interface` -- the interface being invoked
|
|
- `send_member` -- the specific method name
|
|
- `send_type` -- message type (`method_call`, `signal`, etc.)
|
|
|
|
You can combine these. An `<allow>` with both `send_destination` and
|
|
`send_interface` only permits calls to that specific interface on that
|
|
specific service.
|
|
|
|
### Deploying it
|
|
|
|
For the system bus:
|
|
|
|
```bash
|
|
sudo cp docs/examples/mcdbus.conf /etc/dbus-1/system.d/
|
|
sudo systemctl reload dbus.service # dbus-daemon
|
|
# or: sudo systemctl reload dbus-broker.service
|
|
```
|
|
|
|
For the session bus, copy to `/etc/dbus-1/session.d/` instead. Session bus
|
|
policies apply to all users. If you only want to restrict a specific user,
|
|
use `user="mcdbus"` in the policy context.
|
|
|
|
### Testing it
|
|
|
|
Denied messages show up in the bus daemon's journal:
|
|
|
|
```bash
|
|
# dbus-broker
|
|
journalctl -u dbus-broker.service --since "5 min ago" | grep -i deny
|
|
|
|
# dbus-daemon
|
|
journalctl -u dbus.service --since "5 min ago" | grep -i deny
|
|
```
|
|
|
|
You can also use `dbus-monitor` to watch traffic in real time:
|
|
|
|
```bash
|
|
dbus-monitor --system "destination='org.freedesktop.UPower'"
|
|
```
|
|
|
|
|
|
## Layer 3: PolicyKit (polkit)
|
|
|
|
polkit provides per-action authorization. Where D-Bus bus policy controls
|
|
which messages can be *sent*, polkit controls whether a specific *action*
|
|
is *authorized* for a specific user. Many system services (systemd,
|
|
NetworkManager, UPower, udisks2) check polkit before performing privileged
|
|
operations, regardless of what the D-Bus bus policy says.
|
|
|
|
An example rules file is provided at [docs/examples/50-mcdbus.rules](examples/50-mcdbus.rules).
|
|
|
|
### How it works
|
|
|
|
polkit actions are identified by reverse-domain strings like
|
|
`org.freedesktop.systemd1.manage-units`. When a D-Bus service receives
|
|
a method call that requires authorization, it asks polkit whether the
|
|
calling user is allowed to perform that action.
|
|
|
|
polkit rules are JavaScript files in `/etc/polkit-1/rules.d/`. They are
|
|
evaluated in filename order (hence the `50-` prefix). Each rule function
|
|
receives an `action` and a `subject` and returns one of:
|
|
|
|
- `polkit.Result.YES` -- allow without prompting
|
|
- `polkit.Result.AUTH_ADMIN` -- require admin password
|
|
- `polkit.Result.NO` -- deny
|
|
|
|
### What the example does
|
|
|
|
The provided rules file checks if the calling user is in the `mcdbus`
|
|
Unix group. If so, it allows a curated set of read-only actions. Everything
|
|
else falls through to the system defaults (which typically require admin
|
|
authentication or deny outright).
|
|
|
|
### Creating the group
|
|
|
|
```bash
|
|
sudo groupadd mcdbus
|
|
sudo usermod -aG mcdbus $(whoami)
|
|
# Log out and back in for the group membership to take effect
|
|
```
|
|
|
|
If you are running mcdbus under systemd with `DynamicUser=yes`, the
|
|
dynamic user will not be in any supplementary groups by default. You can
|
|
either:
|
|
|
|
- Use `SupplementaryGroups=mcdbus` in the unit file, or
|
|
- Use `User=mcdbus` with a real system user instead of `DynamicUser=yes`
|
|
|
|
### Deploying it
|
|
|
|
```bash
|
|
sudo cp docs/examples/50-mcdbus.rules /etc/polkit-1/rules.d/
|
|
sudo systemctl restart polkit.service
|
|
```
|
|
|
|
### Testing it
|
|
|
|
List all registered polkit actions:
|
|
|
|
```bash
|
|
pkaction
|
|
```
|
|
|
|
Check whether a specific action would be allowed for a user:
|
|
|
|
```bash
|
|
pkcheck --action-id org.freedesktop.systemd1.manage-units \
|
|
--process $$ --allow-user-interaction
|
|
```
|
|
|
|
The `--allow-user-interaction` flag lets polkit prompt for a password if
|
|
the rules say `AUTH_ADMIN`. Without it, `AUTH_ADMIN` results are treated
|
|
as denial.
|
|
|
|
|
|
## Layer 4: xdg-dbus-proxy
|
|
|
|
`xdg-dbus-proxy` is a Flatpak utility that creates a filtered D-Bus
|
|
socket. You point it at the real bus socket, tell it which services to
|
|
expose, and it creates a new socket that only passes through matching
|
|
messages. The client (mcdbus) connects to the proxy socket and never
|
|
sees the rest of the bus.
|
|
|
|
An example wrapper script is provided at [docs/examples/mcdbus-proxy.sh](examples/mcdbus-proxy.sh).
|
|
|
|
### Policy levels
|
|
|
|
xdg-dbus-proxy supports three levels of access per service:
|
|
|
|
- `--see=NAME` -- the service appears in `ListNames` and can be introspected,
|
|
but method calls are blocked. Read-only visibility.
|
|
- `--talk=NAME` -- full bidirectional communication with the service.
|
|
Method calls, property reads/writes, and signals all pass through.
|
|
- `--own=NAME` -- the client can register (own) the given bus name.
|
|
mcdbus does not need this.
|
|
|
|
You can also filter at the interface and path level:
|
|
|
|
```bash
|
|
--talk=org.freedesktop.Notifications
|
|
--call=org.freedesktop.UPower=org.freedesktop.DBus.Properties.GetAll@/org/freedesktop/UPower/*
|
|
```
|
|
|
|
### Installing it
|
|
|
|
xdg-dbus-proxy ships with Flatpak:
|
|
|
|
```bash
|
|
# Arch
|
|
sudo pacman -S xdg-dbus-proxy
|
|
|
|
# Debian/Ubuntu
|
|
sudo apt install xdg-dbus-proxy
|
|
|
|
# Fedora
|
|
sudo dnf install xdg-dbus-proxy
|
|
```
|
|
|
|
### How the wrapper works
|
|
|
|
The script:
|
|
|
|
1. Creates a temporary directory for the proxy socket.
|
|
2. Starts `xdg-dbus-proxy` in the background, pointing at `$DBUS_SESSION_BUS_ADDRESS`.
|
|
3. Waits for the proxy socket to appear.
|
|
4. Sets `DBUS_SESSION_BUS_ADDRESS` to the proxy socket.
|
|
5. `exec`s mcdbus, so mcdbus runs with PID 1 semantics and gets signals correctly.
|
|
6. Cleans up the proxy socket and process on exit via a trap.
|
|
|
|
### Deploying it
|
|
|
|
```bash
|
|
chmod +x docs/examples/mcdbus-proxy.sh
|
|
./docs/examples/mcdbus-proxy.sh
|
|
```
|
|
|
|
Or integrate it into the systemd unit file:
|
|
|
|
```ini
|
|
[Service]
|
|
ExecStart=/usr/local/bin/mcdbus-proxy.sh
|
|
```
|
|
|
|
|
|
## Choosing Layers
|
|
|
|
All four layers are independent. You can use any combination.
|
|
|
|
**Layer 1 (systemd sandboxing)** is always recommended. It costs nothing
|
|
to deploy and provides broad process-level isolation. Even if mcdbus or
|
|
dbus-fast had a vulnerability, the sandbox limits what an attacker could
|
|
do with it.
|
|
|
|
**Layer 2 (D-Bus bus policy)** is useful when you want to restrict which
|
|
services mcdbus can talk to at the bus level. This is the coarsest filter
|
|
but also the most reliable -- it operates inside the bus daemon itself,
|
|
so there is no way for the client to bypass it.
|
|
|
|
**Layer 3 (polkit)** matters when the services mcdbus talks to perform
|
|
their own polkit authorization checks. Most system services do this.
|
|
If you want mcdbus to be able to read battery status but not manage
|
|
systemd units, polkit rules are where you express that.
|
|
|
|
**Layer 4 (xdg-dbus-proxy)** is the most granular option. It is useful
|
|
for session bus filtering (where D-Bus bus policy is less commonly
|
|
configured) and for deployments where you want per-interface or per-path
|
|
control. The trade-off is that it adds a proxy process.
|
|
|
|
For most deployments, Layer 1 alone is sufficient. For high-security
|
|
environments or shared systems, stacking Layers 1 + 2 + 3 provides
|
|
defense in depth. Layer 4 is there when you need fine-grained session
|
|
bus filtering that the other layers cannot express.
|
|
|
|
### Quick reference
|
|
|
|
| Layer | Scope | Granularity | Bus |
|
|
|-------|-------|-------------|-----|
|
|
| systemd | Process isolation | Filesystem, network, syscalls | Both |
|
|
| D-Bus policy | Message filtering | Service, interface, method | Both |
|
|
| polkit | Action authorization | Per-action, per-user/group | System (mostly) |
|
|
| xdg-dbus-proxy | Socket filtering | Service, interface, path | Session (mostly) |
|