Files
finit/libink
Joachim Wiberg 63aabaa6df libink/dbus: identify the caller behind a broker
On the local bus SO_PEERCRED says who is calling and the kernel is the
one saying it.  Behind a broker one connection carries every caller,
so that credential describes dbus-daemon and nothing else, and every
privileged method was refused there, root included.

Ask the bus driver instead.  libink parks the call and hands us the
sender; we ask GetConnectionUnixUser and answer when the reply lands,
through the same event loop as everything else.  Nothing blocks:
blocking in PID 1 is why libuEv exists.  That needs calls libink can
make on a connection it already has, so it gained those too.

Answers are cached, since a bus never reuses a unique name while it
runs.  Not across a restart though: a new dbus-daemon numbers from
scratch and :1.7 becomes somebody else, so the cache goes when the
broker does.  A sender name too long to key on is refused rather than
truncated, two callers sharing a truncated key would share an
identity.

Privilege is no longer uid 0 alone.  The socket is already owned by
the --with-group group, so refusing its members every method that
changes anything left a wheel user able to open the bus and unable to
reboot.  Both gates now say the same thing.

Group membership needs NSS, which the C library loads with dlopen(),
so the lookup is compiled out where Finit is built to link statically.
That leaves such a build root-only, which is worth saying out loud
rather than leaving to be discovered.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:06 +02:00
..
2026-08-13 09:28:26 +02:00
…
…
…
…
…
2026-08-13 09:28:26 +02:00

libink — brokerless D-Bus for Finit

libink is a small C library implementing the D-Bus wire protocol, 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. 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.

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.