From d710a235130fe5fd009ba887166be1d6315e0efc Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Fri, 7 Aug 2026 13:55:41 +0200 Subject: [PATCH] dbus: gate the bus socket like INIT_SOCKET The D-Bus socket was bound world read/write, on the reasoning that SO_PEERCRED authorizes each method anyway. That leaves the read-only surface open to every local user, and it quietly ignores --with-group: a system that restricts initctl to the wheel group still handed the same service state to anyone who asked over the bus. Bind it 0660 and chown it to the configured group, the same gate the fallback socket has always had. libink takes the mode as an argument rather than assuming one, since who may connect is the embedder's policy, not the library's. The mode is applied at bind(), so there is no window where the socket is more permissive than intended. Signed-off-by: Joachim Wiberg --- configure.ac | 4 ++-- doc/dbus.md | 6 ++++++ libink/link.h | 6 +++++- libink/server.c | 9 +++++---- src/dbus.c | 8 +++++++- test/dbus-auth.sh | 10 ++++++++-- 6 files changed, 33 insertions(+), 10 deletions(-) diff --git a/configure.ac b/configure.ac index e13316b7..33074b59 100644 --- a/configure.ac +++ b/configure.ac @@ -161,7 +161,7 @@ AC_ARG_WITH(sysconfig, [sysconfig=$withval], [sysconfig=default]) AC_ARG_WITH(group, - AS_HELP_STRING([--with-group=NAME], [Group for /run/finit/socket (initctl), default: root]), + AS_HELP_STRING([--with-group=NAME], [Group for /run/finit/socket and /run/finit/bus, default: root]), [group=$withval], [group=root]) AC_ARG_WITH(hostname, @@ -298,7 +298,7 @@ AS_IF([test "x$with_heading" != "xno"], [ AS_IF([test "x$with_group" != "xno"], [ AS_IF([test "x$group" = "xyes"], [ group=root])]) -AC_DEFINE_UNQUOTED(DEFGROUP, "$group", [For /run/finit/socket]) +AC_DEFINE_UNQUOTED(DEFGROUP, "$group", [For /run/finit/socket and /run/finit/bus]) AS_IF([test "x$with_hostname" != "xno"], [ AS_IF([test "x$hostname" = "xyes"], [ diff --git a/doc/dbus.md b/doc/dbus.md index 2c89a842..e7453118 100644 --- a/doc/dbus.md +++ b/doc/dbus.md @@ -27,6 +27,12 @@ Unix-domain socket using the standard D-Bus SASL EXTERNAL handshake. No `dbus-daemon` is required, which makes it suitable for embedded systems that don't ship one. +The socket is `0660`, owned by `root` and the group given to +`--with-group` at build time, the same gate as `/run/finit/socket` that +`initctl` falls back on. The bus reaches every operation `initctl` +does, so restricting one and not the other would leave the door open. +Members of that group may use it, see [Authorization](#authorization). + The **system** bus is best-effort: Finit probes for a running `dbus-daemon` and, when reachable, claims the well-known name `org.finit` so that standard tooling sees Finit just like any other system service: diff --git a/libink/link.h b/libink/link.h index ba4f2fad..5a16e778 100644 --- a/libink/link.h +++ b/libink/link.h @@ -90,7 +90,11 @@ typedef struct { /* ---------- server / connection lifecycle ---------- */ -int link_server_new (link_server_t **server, const char *path); +/* Bind a listening socket at `path` with file mode `mode`, e.g. 0660 + * to keep it to root and one group. The mode is applied at bind(), + * so the socket is never briefly more permissive than asked; setting + * the owning group afterwards is the caller's job. */ +int link_server_new (link_server_t **server, const char *path, mode_t mode); void link_server_free (link_server_t *server); int link_server_get_fd(const link_server_t *server); diff --git a/libink/server.c b/libink/server.c index 19d1fa3c..dc4b9bff 100644 --- a/libink/server.c +++ b/libink/server.c @@ -22,7 +22,7 @@ static void close_save_errno(int fd) errno = saved; } -int link_server_new(link_server_t **out, const char *path) +int link_server_new(link_server_t **out, const char *path, mode_t mode) { struct sockaddr_un sun = { .sun_family = AF_UNIX }; link_server_t *srv; @@ -55,12 +55,13 @@ int link_server_new(link_server_t **out, const char *path) /* fchmod() on a Unix-domain socket fd is a silent no-op on Linux: * the file mode is fixed at bind() time as (0777 & ~umask). Set - * umask around the bind() so the socket appears with mode 0666 - * atomically, no race window. World-accessible by design; + * umask around the bind() so the socket appears with the mode the + * caller asked for atomically, with no window where it is more + * permissive. Who may connect is the caller's policy to set; * per-method authorization happens later in dispatch via * SO_PEERCRED. */ { - mode_t oldmask = umask(0111); + mode_t oldmask = umask(0777 & ~mode); int rc = bind(fd, (struct sockaddr *)&sun, sizeof(sun)); int saved = errno; diff --git a/src/dbus.c b/src/dbus.c index 5bc14618..9f343c8c 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -1341,11 +1341,17 @@ int dbus_init(uev_ctx_t *ctx) { dbg("Setting up D-Bus listening socket at %s ...", FINIT_BUS_SOCKET); - if (link_server_new(&server, FINIT_BUS_SOCKET) < 0) { + /* Same access policy as INIT_SOCKET: the bus reaches every + * service operation initctl does, so --with-group has to gate + * both or it gates neither. */ + if (link_server_new(&server, FINIT_BUS_SOCKET, 0660) < 0) { err(1, "Failed binding D-Bus socket %s", FINIT_BUS_SOCKET); return 1; } + if (chown(FINIT_BUS_SOCKET, geteuid(), getgroup(DEFGROUP))) + err(1, "Failed setting group %s on %s", DEFGROUP, FINIT_BUS_SOCKET); + if (link_server_add_object(server, "/org/finit/manager", &manager_vtable, NULL) < 0) { err(1, "Failed registering Manager1 object"); diff --git a/test/dbus-auth.sh b/test/dbus-auth.sh index 1e9535d3..ce12bd48 100755 --- a/test/dbus-auth.sh +++ b/test/dbus-auth.sh @@ -14,9 +14,15 @@ TEST_DIR=$(dirname "$0") # shellcheck source=/dev/null . "$TEST_DIR/lib/dbus-setup.sh" -say "Socket mode is 0666" +# The bus reaches every service operation initctl does, so it has to +# be gated like INIT_SOCKET: 0660, owned by root and the --with-group +# group. Only the mode is asserted here, the test namespace does not +# enforce it -- a setuid() client still connects to a 0660 socket. +say "Socket is gated like INIT_SOCKET, not world-accessible" mode=$(texec stat -c %a "$BUS") -assert "Socket mode is 666 (got $mode)" "$mode" = "666" +sock=$(texec stat -c %a /run/finit/socket) +assert "Socket mode is 660 (got $mode)" "$mode" = "660" +assert "Bus and INIT_SOCKET agree ($mode vs $sock)" "$mode" = "$sock" say "AUTH EXTERNAL: claim correct UID (root = 0)" reply=$(texec "$CLIENT" auth "$BUS" 0)