libink/dbus: give parked and outstanding calls a deadline

A call is parked until the resolver says who sent it, and an outbound
call sits in a pending slot until its reply lands.  Neither had a way
to give up.  A broker that answers GetConnectionUnixUser slowly, or
not at all, leaves the caller waiting forever and keeps the slot; four
of those and every later privileged call is refused with
LimitsExceeded until Finit restarts.

libink cannot time itself out, it has no event loop, so the deadline
is the embedder's to keep.  One sweep per connection covers both, and
the ordering between them stays in the library rather than in each
embedder: calls first, because one timing out usually resolves the
park it was made for, and AccessDenied tells that caller more than a
bare timeout.

The sweep is armed when a resolve is deferred and stops rearming as
soon as nothing is outstanding, so a system that never meets a broker
never wakes up for it.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg committed 2026-08-13 10:15:19 +02:00
1 parent a1969efac1
commit 853e226812
6 files changed
+172 -5

No files matched your search

+48
View File
@@ -106,6 +106,50 @@ static void peer_reap(void *arg)
* stopped, so nothing touches it in the meantime. */
static struct wq reap_work = { .cb = peer_reap, .delay = 10 };
/*
* Nothing in libink can time itself out, it has no event loop, so the
* deadline for a parked call and for a call we made on the broker is
* ours to keep. The sweep only runs while something is outstanding:
* expire_arm() starts it, and it stops rearming as soon as nothing is
* left, so a system that never talks to a broker never wakes up for
* this.
*/
#define DBUS_CALL_TIMEOUT_MS 5000
#define DBUS_SWEEP_MS 1000
static void expire_sweep(void *arg);
static struct wq expire_work = { .cb = expire_sweep, .delay = DBUS_SWEEP_MS };
static int expire_armed;
/* Idempotent: several parks in one turn of the loop share one sweep. */
static void expire_arm(void)
{
if (expire_armed)
return;
if (!schedule_work(&expire_work))
expire_armed = 1;
}
static void expire_sweep(void *arg)
{
struct peer *p, *tmp;
int live = 0;
(void)arg;
expire_armed = 0;
/* _SAFE because expiring a call runs its callback, and a
* callback that ends up writing to a peer can drop it, which
* unlinks it from this very list. */
TAILQ_FOREACH_SAFE(p, &peers, link, tmp) {
if (!p->dead)
live += link_connection_expire(p->conn, DBUS_CALL_TIMEOUT_MS);
}
if (live)
expire_arm();
}
/*
* A peer can be dropped from inside its own read loop: a handler emits
* a signal, the write to this very peer fails, and dbus_emit_signal()
@@ -1410,6 +1454,10 @@ static int sysbus_uid_resolver(link_connection_t *conn, const char *sender,
return -1;
}
/* Both the call and the park it belongs to now have a deadline
* to answer by, so make sure something is watching the clock. */
expire_arm();
return 1; /* parked; uid_reply_cb() answers */
}