Files
finit/libink/README.md
T
Joachim Wiberg 3231ade38a dbus: fixes from a code review of the branch
A pass over the whole branch before merge, mostly in libink since
that is the new code and the part exposed to the wire.  Grouped here
rather than scattered so the review is easy to read in one place.

libink parser and dispatch:

 - Bound reader lengths so a 32-bit size_t can't wrap a wire length
   past the guard and read out of bounds.  Reachable pre-auth on any
   bus, so it matters on the 32-bit targets Finit runs on.
 - Drop a peer when a reply send fails instead of limping on with a
   half-written frame; a built-in whose send failed used to fall
   through and put a second frame on the wire.

initctl:

 - Copy a D-Bus error name out of the reply before closing the client;
   the reply points into memory the close frees.  Both error paths now
   share one helper so this can't creep back.

Authorization:

 - Take the caller's groups from the kernel (SO_PEERCRED plus
   SO_PEERGROUPS) rather than getpwuid()/getgrouplist(), which go
   through NSS and can block PID 1 on a slow LDAP or SSSD backend.
   The check is now a lookup against the group resolved once at init,
   with no NSS and no 256 KiB array on the stack.  A caller reaching
   us through a broker carries no group set, so system-bus privileged
   methods are root-only; the local bus keeps group support.  See
   libink/README.md for the note on lifting that.

Shutdown:

 - Call dbus_exit() from the shutdown path so the server, its peers,
   and the socket are let go cleanly.  The teardown existed but nobody
   called it.

Tests, CI, docs:

 - A fuzz target for the message parser, run as a quick sweep in the
   suite and properly under libFuzzer in CI, with the corpus carried
   between runs.  The -as-uid tests drop groups the way a login does
   so SO_PEERGROUPS sees the right set, and widen the test socket to
   reach the per-method check behind the 0660 gate.  Bring the GitHub
   actions up to versions that run on Node 24, and tidy a few small
   things a /simplify pass turned up.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 08:57:38 +02:00

80 lines
3.7 KiB
Markdown

libink — brokerless D-Bus for Finit
===================================
libink is a small C library implementing the [D-Bus wire protocol][spec],
both the server and the client side, without a broker and without any
dependency on `libdbus`, `sd-bus`, or GIO. It was born inside Finit to
let PID 1 be a bus of its own: clients connect straight to the listening
socket, authenticate with the standard SASL EXTERNAL handshake, and get
kernel-authenticated credentials via `SO_PEERCRED`.
For what the bus exposes and how to talk to it, see the User Guide,
[D-Bus Integration](../doc/dbus.md). This file covers the library
itself.
Status
------
libink is an internal implementation detail of Finit: built as a libtool
convenience library, linked statically into `finit` and `initctl`,
nothing installed. There is deliberately no ABI promise yet — that
comes if/when libink is extracted into a project of its own. External
D-Bus clients need none of this; the wire protocol is the compatibility
surface, any standard D-Bus library works.
Layout
------
| File | Contents |
|-----------------|-------------------------------------------------------|
| `server.c` | Listening socket, accept, peer credential capture |
| `auth.c` | SASL EXTERNAL handshake, uid verification |
| `connection.c` | Per-peer state machine, message framing |
| `proto.c` | Wire header parse/build |
| `marshal.c` | Body (de)marshalling: basic types, arrays, variants |
| `dispatch.c` | Object tree, vtable registration, method dispatch |
| `builtin.c` | `org.freedesktop.DBus.*` stock interfaces |
| `match.c` | AddMatch/RemoveMatch rule parsing and signal filter |
| `path.c` | systemd-style `_HH` object path encoding |
| `client.c` | Outgoing connections, method calls, reply/signal wait |
| `io.c` | Shared EINTR-resilient read/write loops |
Public API symbols carry the `link_*` prefix (`link.h`), internal ones
`__*` (`internal.h`). Method handlers are registered as vtables of
`link_method_t`/`link_property_t`; the framework emits variant
signatures from the property table so the declared type is the single
source of truth.
The boundary to Finit is deliberate: nothing under `libink/` includes a
Finit header. All glue lives in `src/dbus.c` — object registration,
signal emission from the service/condition/runlevel hook points, and
the uev event loop bridge. `initctl` uses the client half of the same
library, so one wire-format implementation serves both ends. If libink
is ever spun out, that file is the cut line.
Future work
-----------
Privileged methods over the *system* bus are root-only. Finit learns
the caller's uid from the broker with `GetConnectionUnixUser`, but that
reply carries no group list, so it cannot honour `--with-group`
membership there the way it does on the local bus (where the kernel
hands over the group set via `SO_PEERGROUPS`). Closing the gap means
asking the broker `GetConnectionCredentials` and reading its
`UnixGroupIDs`, which is a variant holding an `au` array — the reader
(`marshal.c`) only decodes single-character variant signatures today,
so it needs extending first. Finit's own direct users reach it over
the local bus, so this has not been pressing.
Testing
-------
The `test/dbus-*.sh` suite exercises the library end to end against a
live Finit in a namespace, driven by `test/src/dbus-auth-client.c`.
Wire-format conformance against third-party tools (`dbus-send`,
`dbus-monitor`) and fuzzing of the parsers are tracked as pre-merge
work — this is PID 1's attack surface.
[spec]: https://dbus.freedesktop.org/doc/dbus-specification.html