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)