I reviewed dbus-broker 37-5ubuntu3 as checked into stonking. This shouldn't be
considered a full audit but rather a quick gauge of maintainability. This
review follows up on the partial review of v33-1 in comment #5; the package
has since gained the AppArmor mediation patches (now also in Debian, see
comment #15/#18) and the `dbus-run-session` blocker has been resolved by the
decision to retain `src:dbus` in main (comment #17/#25).
dbus-broker is an implementation of a D-Bus message bus as defined by the
D-Bus specification. It is a drop-in replacement for `dbus-daemon` (the
reference implementation) intended to provide higher performance and
reliability. It is written exclusively for Linux and makes use of modern
kernel features. The system bus runs as the unprivileged `messagebus` user;
the launcher (`dbus-broker-launch`) is started by systemd socket activation,
forks the broker, and drops privileges. It parses untrusted D-Bus wire
protocol from local peers via the embedded `libcdvar-1` type-driven
deserializer.
- CVE History
- $UCT/scripts/pkg_history / pkg_status: previous issues were
CVE-2022-31212 and CVE-2022-31213 (memory corruption from untrusted
D-Bus input, https://sec-consult.com/vulnerability-lab/advisory/...).
Both were fixed upstream in a timely fashion (resolved in Ubuntu series
> Kinetic); v37 is well past the fixes. Upstream responded quickly to
the report.
- Upstream now runs static analyzers and fuzzers via GitHub Actions and is
scanned by Coverity (https://scan.coverity.com/projects/dbus-broker).
- Upstream still does **not** have a formal Security Policy / coordinated
disclosure process. Recommend one is added (see non-blocking notes).
- Build-Depends
- debhelper-compat (= 13), dh-package-notes, libapparmor-dev (>= 3.0),
libaudit-dev, libcap-ng-dev, libdbus-1-dev, libexpat1-dev,
libselinux-dev, libsystemd-dev, meson (>= 0.60), pkgconf,
python3-docutils <!nodoc>, systemd-dev
(see dbus-broker_37-5ubuntu3.dsc).
- No encryption libraries. `libsystemd-dev` is needed only by the launcher
(`dbus-broker-launch`); the broker itself is not systemd-dependent.
- Runtime ELF deps: libapparmor.so.1, libaudit.so.1, libc.so.6,
libcap-ng.so.0, libexpat.so.1, libm.so.6, libselinux.so.1,
libsystemd.so.0 — all standard and already in main.
- pre/post inst/rm scripts
- `postinst`: on `triggered`, reloads `dbus-broker.service` if active; on
`configure`, recommends a reboot (writes `/run/reboot-required[.pkgs]`)
when replacing `dbus-daemon` or upgrading across versions; for upgrades
from << 37 writes an ephemeral systemd override to switch the unit type
to `notify` before reloading (removed-after:trixie). Standard
`dh_installsystemd`/`dh_installsystemduser` snippets enable the system
and user units.
- `postrm`: `dh_installsystemd` daemon-reload on remove, purge on purge;
on `remove` drops the `dbus.service` alias so other implementations can
take over.
- `prerm`: intentionally empty (overrides the `dh_installsystemd` stop-on-
remove snippet) so a running system/session is not killed on package
removal (matches the dbus-daemon behaviour, Debian #980541).
- No bashisms; shellcheck is clean.
- init scripts
- none (systemd-only, consistent with Ubuntu not supporting alternative
inits).
- systemd units
- `dbus-broker.service` (system): `Type=notify-reload`, `Sockets=dbus.socket`,
`OOMScoreAdjust=-900`, `LimitNOFILE=16384`, `ProtectSystem=full`,
`PrivateTmp=true`, `PrivateDevices=true`,
`ExecStart=.../dbus-broker-launch --scope system --audit`, aliases as
`dbus.service` (src/units/system/dbus-broker.service.in).
- `dbus-broker.service` (user): `Type=notify-reload`, `Sockets=dbus.socket`,
`Slice=session.slice`, `ExecStart=.../dbus-broker-launch --scope user`
(src/units/user/dbus-broker.service.in).
- Both are socket-activated via `dbus.socket` and do not bind paths
themselves.
- dbus services
- none shipped by this package.
- setuid binaries
- none. No FS capabilities (setcap/getcap) on the shipped binaries
(packaging.txt: `setuid()` / `setuid_fs()` / `fs capabilities` empty).
- binaries in PATH
- `/usr/bin/dbus-broker` (236032 B), `/usr/bin/dbus-broker-launch`
(129496 B), both root:root 0755 (packaging.txt).
- Both are fully hardened: PIE, stack protector, Fortify, RELRO,
immediate binding, CFI. `dbus-broker` has stack-clash protection;
`dbus-broker-launch` is missing `-fstack-clash-protection` (minor, see
non-blocking notes).
- sudo fragments
- none.
- polkit files
- none. Authorization is handled by the internal D-Bus policy engine
(`src/bus/policy.c`, `src/launch/policy.c`) plus optional
AppArmor/SELinux MAC.
- udev rules
- none.
- unit tests / autopkgtests
- Build-time test suite runs (meson `test`); the build log shows the
unit tests OK (a few `ERROR` lines are sandbox/test-environment
artifacts on `memfd`/error-injection, not source failures; `Fail: 0`).
- `Testsuite: autopkgtest` in the .dsc; autopkgtest runs the upstream
test suite and is currently passing on all release architectures.
- The dfuzzer-based D-Bus service fuzzing noted in comment #6 remains a
pre-existing confinement gap (neither dbus nor dbus-broker ship
AppArmor profiles by default) and does not affect the MIR outcome.
- cron jobs
- none.
- Build logs
- Clean. No compiler errors. The only compiler warnings are 15x benign
`-Waddress` in `subprojects/libcdvar-1/src/test-api.c:38-52` ("address
of `c_dvar_type_*` will always evaluate as true", test code). No
`-Wformat-security`, `-Wstringop-overflow`, `-Warray-bounds` or
similar. `lintian --pedantic` reports only
`newer-standards-version 4.7.4`, `orig-tarball-missing-upstream-
signature`, and a stray `.github`/`.gitmodules` in the source tree
(should be cleaned from the upstream orig tarball).
- Processes spawned
- The only production exec is `execve("/usr/bin/dbus-broker", argv,
environ)` in `launcher_run_child()` (src/launch/launcher.c:365). The
binary path is a compile-time constant (`BINDIR "/dbus-broker"`,
launcher.c:46); `argv` is a fixed array of flags plus numeric strings
produced by `snprintf` into stack buffers with bounds-checked return
values (launcher.c:350-363). No `popen`/`system`/`posix_spawn`
anywhere. No shell is involved, so no shell-injection surface.
- `fork()` is used in `launcher_fork()` (launcher.c:391); the child is
watched via `sd_event_add_child`.
- Memory management
- Defensive throughout. Allocations consistently use the
`sizeof(*ptr) + n` idiom with `n` derived from a validated length,
e.g. `src/broker/controller.c:52`, `src/bus/name.c:234`,
`src/launch/config.c:61`, `src/util/selinux.c:121`.
- `realloc` results are checked and assigned to a temporary before
overwriting the live pointer (`src/util/sockopt.c:44,105,177`,
`src/util/fs.c:102`, `subprojects/libcdvar-1/src/c-dvar-writer.c:37`).
- `strcpy`/`memcpy` are used only after the destination was allocated
with `strlen(src)+1` and the length pre-computed, e.g. the AppArmor
query-string builders in `src/util/apparmor.c:230-298`, match-rule
copies in `src/bus/match.c:475,531,589`, policy strings
`src/bus/policy.c:193`. `asprintf` return values are always checked.
- Heavy use overall (it is a message broker), cursory view looks OK.
- File IO
- All `open`/`fopen` use `O_CLOEXEC` / `"re"` and fixed or root-controlled
paths: service files `src/launch/launcher.c:538`, config includes
`src/launch/config.c:1305` (`O_RDONLY|O_CLOEXEC|O_NOCTTY`),
`/etc/passwd`+`/etc/group` via `nss-cache.c:251,273`, AppArmor
`/sys/module/apparmor/...` and `/sys/kernel/security/apparmor/...`
(`src/util/apparmor.c:45,87`), `/proc/self/...` (`src/util/proc.c`).
- No `umask` manipulation, no `O_CREAT` on attacker-controlled paths,
no `chmod` of attacker files (`fchmod` in `src/util/misc.c:109`
preserves the existing mode on an already-open fd).
- Config include recursion is guarded against cycles
(`src/launch/config.c:1292-1297`).
- Logging
- `src/util/log.c`. `log_vappendf` (log.c:601) uses `vsnprintf` into a
sized buffer with truncation checks; no `syslog(3)` direct calls
(messages go to journald or a syslog/stream socket).
- Every `*printf`-style log call uses a **compile-time constant format
string**. The one place a dynamic string reaches the logger (the
AppArmor audit-log fallback, patch 0021) is written defensively as
`log_commitf(ctx, "%s", message)` (`src/util/apparmor.c:202`), i.e. the
attacker-influenced message is an argument, not the format. No tainted
format-string risk.
- Environment variable usage
- Only `XDG_RUNTIME_DIR`, `XDG_DATA_HOME`, `XDG_DATA_DIRS` in the
launcher (`src/launch/launcher.c:863,887,918`), used solely as
directory prefixes for read-only `.service`-file lookup, with
`passwd->pw_dir`-based defaults (`launcher.c:895`). Nothing is used as
a command, a shell path, or a format string; values are not re-exported
to untrusted children. The launcher is started by systemd with a
sanitized environment.
- Use of privileged functions
- Two privilege-drop paths, both with the **correct** ordering
`setgroups(0,NULL)` → `setgid(gid)` → `setuid(uid)`:
- launcher parent: `launcher.c:1455` → `util_drop_permissions()`
(`src/util/misc.c:254-269`); `setgroups` failure is non-fatal for
dbus-daemon compatibility (launcher runs as `messagebus` with no
supplementary groups).
- broker child: `launcher.c:301` → `util_audit_drop_permissions()`
(`src/util/audit.c:33-91`); when `euid==0` uses
`capng_change_id(uid, gid, CAPNG_DROP_SUPP_GRP)` and then raises
only `CAP_AUDIT_WRITE` as ambient (`audit.c:79,84`).
- UID/GID are resolved once from `/etc/passwd`+`/etc/group` at config-
parse time and stored as `uint32`; not re-resolved before use → no
TOCTOU on credentials. The child drops privileges *before* opening the
journal socket and before `execve` (`launcher.c:300-304`).
- `ioctl(fd, SIOCOUTQ, &v)` in `log_stream_send()`
(`src/util/log.c:338`) and `socket_dispatch_write()`
(`src/dbus/socket.c:746`): read-only queries of the socket send-buffer
byte count, on internal fds, return value checked. Used only to decide
whether to drop a log message in lossy mode. Safe (still present and
correct, as in the 2023 review).
- Use of cryptography / random number sources etc
- No cryptographic code (no OpenSSL/GnuTLS; the `== encryption ==`
section of the code scan is empty). No TLS, no certificate handling.
- Randomness is sourced from `getauxval(AT_RANDOM)` (16 kernel-provided
bytes): bus GUID seeding (`src/bus/bus.c:41-43`) and the libexpat hash
salt (`src/launch/config.c:1254-1256`, with an explicit comment that
this avoids a hidden `/dev/urandom` dependency). These are identifiers,
not security tokens; dbus-daemon uses the same approach.
- Use of temp files
- No `mkstemp`/`mkstemps`/`tmpnam`/`tempnam`/`tmpfile`. The only `/tmp`
reference is in test code (`test/dbus/util-broker.c:279`,
`unix:tmpdir=/tmp` which is an abstract AF_UNIX socket, not a
predictable file).
- Use of networking
- All sockets are `AF_UNIX`; no `AF_INET`/`AF_INET6` in production code.
- The untrusted-input boundary is `recvmsg` in
`src/dbus/socket.c:545` with `MSG_DONTWAIT|MSG_CMSG_CLOEXEC` (received
fds are close-on-exec and counted/validated in `src/util/fdlist.c`).
- Incoming message headers are parsed in `src/dbus/message.c:51-91`
(`message_new_incoming`): endianness validated, sizes read in 64-bit,
`n_data` capped at `MESSAGE_SIZE_MAX` (128 MiB, `src/dbus/message.h:20`).
Header fields and body are decoded via the embedded `libcdvar-1`
reader which performs strict type/bounds checks and enforces
full-consumption (`c_dvar_end_read`, `c-dvar-reader.c:694-695`); array
reads verify `u32 <= n_buffer` (`c-dvar-reader.c:239-242`). Invalid
messages yield `MESSAGE_E_*` and the peer is disconnected
(`src/bus/peer.c:95,105`). The listener socket is pre-bound by systemd
and inherited via `sd_listen_fds`; the broker does not `bind` to
attacker-named paths.
- Use of WebKit
- none.
- Use of PolicyKit
- none
- Any significant cppcheck results
- 27 warnings. 13 are `unknownMacro` (cppcheck was not configured for the
`c_rbtree_for_each_entry*`/`c_list_for_each_entry*` iterator macros
from the vendored `libcstdaux`/`libcrbtree`/`libclist` subprojects) —
analyzer-configuration noise across production files, not defects.
6 `unknownEvaluationOrder` and 1 `danglingLifetime` are in
`subprojects/*/test-*.c` (intentional macro self-test / self-referential
`C_LIST_INIT`), ignore.
- One plausibly-real finding: `memleak` of `reply` on the
`USER_E_QUOTA`/`error_fold(r)` path in `src/bus/reply.c:64` (pending-
reply tracking). Worth a quick maintainer look; may be owned by a
registry that frees it later.
- `uninitvar` at `src/bus/peer.c:206` is a false positive (`r` is
assigned in the loop body; cppcheck modeled the zero-iteration case).
- Any significant Coverity results
- 47 defects (50 suppressed by filters). Most are in `test/` or
`subprojects/*/test-*.c` (ASSERT_SIDE_EFFECT, CONSTANT_EXPRESSION_RESULT,
OVERRUN in `libcshquote` tests that allocate `strlen(s)` for
`memcmp`-with-length, DC.WEAK_CRYPTO `rand()` in test binaries, etc.)
— ignore.
- Production findings worth noting (all likely false positives but on
security-critical paths):
- `src/util/selinux.c:125` UNINIT: `registry->n_refs` assigned without
`atomic_init` (SELinux context registry). The one plausibly-*real*
item; should use `atomic_init`/`c_refcount_init`.
- `subprojects/libcdvar-1/src/c-dvar-type.c:255` INTEGER_OVERFLOW with
array-index deref in the **type-signature parser** (parses untrusted
D-Bus type strings). Likely a Coverity modeling artifact (depth is
guarded), but highest-value manual-review target.
- `subprojects/libcrbtree-3/src/c-rbtree.c` (7× NULL_FIELD): backs the
policy/name-ownership/SELinux trees; RB-tree invariants normally
guarantee non-NULL siblings, likely FP.
- `src/broker/controller-dbus.c:173,440,686,739,779` and
`src/bus/driver.c:487` (6× Double free): the
`c_dvar_end_write`→`c_freep`→`c_dvar_deinit` cleanup idiom; almost
certainly FP (ownership transfers only on the success path), but on
the untrusted-D-Bus path — confirm `c_dvar_end_write` does not
populate `*datap` on its error return.
- `src/dbus/message.c:307` DEADCODE (defensive `default:` in the
header-field switch) and `:24,:562` `PW.STD_ALIGNOF_WITH_EXPR_ARG`
(GCC extension) — ignore.
- No genuine buffer-overflow, format-string, command-injection or
untrusted-pointer-deref was found in production code.
- Any significant shellcheck results
- none (clean).
- Any significant bandit results
- none (empty — no Python shipped beyond doc-build requirements).
- Any significant govulncheck results
- N/A (no Go code; scanner reports `no go.mod file`).
- Any significant Semgrep results
- none (empty).
(Flawfinder: 381 hits, none at severity 5; 53 at severity >=3, ~half in
test code. The reviewer-worthy shipped-code `strcpy` clusters in
`src/util/apparmor.c`, `src/bus/match.c`, `src/bus/policy.c`,
`src/dbus/address.c` are bounded by pre-computed `strlen+1` allocations;
`random()` in `src/bus/bus.c` and `src/launch/config.c` is sourced from
`AT_RANDOM` and non-security; `getenv` in `src/launch/launcher.c` is
trusted, read-only dir lookup. No exploitable pattern identified.
OSV-scanner: 2 distinct Medium CVEs in PyPI `idna` 3.9.0, only in the
docs-build `requirements.txt` of two vendored subprojects — do not ship
or run; bump to >=3.15 cosmetically.)
The 21-patch debian series (`original_series`) is dominated by AppArmor
work and is now in sync with Debian (comment #18). It enables real AppArmor
D-Bus mediation (previously stubbed out), ships two genuine bug fixes — a
NULL-pointer deref when a message has no interface (patch 0009,
`src/util/apparmor.c`) and a GID-based connect-policy keying bug
(`policy_at_gid()` was passed `->uid`, patch 0019) — and adds a defensive
audit-log fallback so AVC denials are visible without a running audit
daemon (patch 0021, using `"%s"`). The `c-util` subprojects are statically
linked and considered part of the dbus-broker release train per `NEWS.md`
("Any critical update to any subproject will cause a new release of
dbus-broker... Distributions are not required to monitor the subprojects
manually"), so the embedded sources are acceptable.
The previous blocker — `gdm` depending on `dbus-run-session` — is resolved
by the decision (comment #17/#25) to retain `src:dbus` in main (which
provides the policy/config files and `dbus-run-session`), so dbus-broker
can be promoted without demoting `src:dbus`.
Security team ACK for promoting dbus-broker to main, with the following
non-blocking notes:
1. Investigate `src/util/selinux.c:125` (Coverity UNINIT atomic refcount)
and `subprojects/libcdvar-1/src/c-dvar-type.c:255` (INTEGER_OVERFLOW
on the untrusted type-signature parse path). We will take a look more
closely on this one and confirmif real. Keep `libcdvar-1` and
`src/dbus/message.c` as priority fuzzing targets going forward.
2. Confirm the `c_dvar_end_write` ownership-transfer semantics
(controller-dbus.c / driver.c double-free pattern) are as expected.
3. It would be great if upstream could add a formal Security Policy /
coordinated-disclosure process (private disclosure was previously a
concern, comment #5).
4. Add `-fstack-clash-protection` to `dbus-broker-launch` (the broker
binary already has it).
5. Bump `idna` to >=3.15 in the two docs-only `requirements.txt` of the
vendored subprojects (cosmetic; does not ship or run).
Recommended TODOs:
- Consider migrating the launcher's `setgroups`/`setgid`/`setuid` to the
systemd-unit-based privilege drop (consistent with the MIR-team
recommendation in comment #1), keeping the current code as the
non-systemd fallback.
- Consider porting the AppArmor autopkgtest from `src:dbus` now that the
AppArmor mediation is bundled.
--
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2015538
Title:
[MIR] dbus-broker
To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/dbus-broker/+bug/2015538/+subscriptions
--
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs