summaryrefslogtreecommitdiff
path: root/src/libsystemd-bus
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2013-12-10 16:41:39 +0000
committerLennart Poettering <lennart@poettering.net>2013-12-10 16:52:49 +0000
commitadacb9575a09981fcf11279f2f661e3fc21e58ff (patch)
tree2be66e6581ee0ff80dbfa16821f2e448e9bab0cd /src/libsystemd-bus
parent85719154e7412de2d84f64b50b6b98b13981b65f (diff)
bus: introduce "trusted" bus concept and encode access control in object vtables
Introduces a new concept of "trusted" vs. "untrusted" busses. For the latter libsystemd-bus will automatically do per-method access control, for the former all access is automatically granted. Per-method access control is encoded in the vtables: by default all methods are only accessible to privileged clients. If the SD_BUS_VTABLE_UNPRIVILEGED flag is set for a method it is accessible to unprivileged clients too. By default whether a client is privileged is determined via checking for its CAP_SYS_ADMIN capability, but this can be altered via the SD_BUS_VTABLE_CAPABILITY() macro that can be ORed into the flags field of the method. Writable properties are also subject to SD_BUS_VTABLE_UNPRIVILEGED and SD_BUS_VTABLE_CAPABILITY() for controlling write access to them. Note however that read access is unrestricted, as PropertiesChanged messages might send out the values anyway as an unrestricted broadcast. By default the system bus is set to "untrusted" and the user bus is "trusted" since per-method access control on the latter is unnecessary. On dbus1 busses we check the UID of the caller rather than the configured capability since the capability cannot be determined without race. On kdbus the capability is checked if possible from the attached meta-data of a message and otherwise queried from the sending peer. This also decorates the vtables of the various daemons we ship with these flags.
Diffstat (limited to 'src/libsystemd-bus')
-rw-r--r--src/libsystemd-bus/bus-internal.h1
-rw-r--r--src/libsystemd-bus/bus-introspect.c5
-rw-r--r--src/libsystemd-bus/bus-objects.c85
-rw-r--r--src/libsystemd-bus/libsystemd-bus.sym1
-rw-r--r--src/libsystemd-bus/sd-bus.c18
5 files changed, 104 insertions, 6 deletions
diff --git a/src/libsystemd-bus/bus-internal.h b/src/libsystemd-bus/bus-internal.h
index 4881e0427c..1be7488ab9 100644
--- a/src/libsystemd-bus/bus-internal.h
+++ b/src/libsystemd-bus/bus-internal.h
@@ -159,6 +159,7 @@ struct sd_bus {
bool match_callbacks_modified:1;
bool filter_callbacks_modified:1;
bool nodes_modified:1;
+ bool trusted:1;
int use_memfd;
diff --git a/src/libsystemd-bus/bus-introspect.c b/src/libsystemd-bus/bus-introspect.c
index 115b3ceae8..8bec017d63 100644
--- a/src/libsystemd-bus/bus-introspect.c
+++ b/src/libsystemd-bus/bus-introspect.c
@@ -77,7 +77,7 @@ static void introspect_write_flags(struct introspect *i, int type, int flags) {
if (flags & SD_BUS_VTABLE_DEPRECATED)
fputs(" <annotation name=\"org.freedesktop.DBus.Deprecated\" value=\"true\"/>\n", i->f);
- if (type == _SD_BUS_VTABLE_METHOD && flags & SD_BUS_VTABLE_METHOD_NO_REPLY)
+ if (type == _SD_BUS_VTABLE_METHOD && (flags & SD_BUS_VTABLE_METHOD_NO_REPLY))
fputs(" <annotation name=\"org.freedesktop.DBus.Method.NoReply\" value=\"true\"/>\n", i->f);
if (type == _SD_BUS_VTABLE_PROPERTY || type == _SD_BUS_VTABLE_WRITABLE_PROPERTY) {
@@ -86,6 +86,9 @@ static void introspect_write_flags(struct introspect *i, int type, int flags) {
else if (flags & SD_BUS_VTABLE_PROPERTY_INVALIDATE_ONLY)
fputs(" <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"invalidates\"/>\n", i->f);
}
+
+ if ((type == _SD_BUS_VTABLE_METHOD || type == _SD_BUS_VTABLE_WRITABLE_PROPERTY) && (flags & SD_BUS_VTABLE_UNPRIVILEGED))
+ fputs(" <annotation name=\"org.freedesktop.systemd1.Unprivileged\" value=\"true\"/>\n", i->f);
}
static int introspect_write_arguments(struct introspect *i, const char *signature, const char *direction) {
diff --git a/src/libsystemd-bus/bus-objects.c b/src/libsystemd-bus/bus-objects.c
index 7cd34c991b..941c2810d8 100644
--- a/src/libsystemd-bus/bus-objects.c
+++ b/src/libsystemd-bus/bus-objects.c
@@ -19,6 +19,8 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+#include <sys/capability.h>
+
#include "strv.h"
#include "set.h"
#include "bus-internal.h"
@@ -264,6 +266,64 @@ static int node_callbacks_run(
return 0;
}
+#define CAPABILITY_SHIFT(x) (((x) >> __builtin_ctzll(_SD_BUS_VTABLE_CAPABILITY_MASK)) & 0xFFFF)
+
+static int check_access(sd_bus *bus, sd_bus_message *m, struct vtable_member *c, sd_bus_error *error) {
+ _cleanup_bus_creds_unref_ sd_bus_creds *creds = NULL;
+ uint64_t cap;
+ uid_t uid;
+ int r;
+
+ assert(bus);
+ assert(m);
+ assert(c);
+
+ /* If the entire bus is trusted let's grant access */
+ if (bus->trusted)
+ return 0;
+
+ /* If the member is marked UNPRIVILEGED let's grant access */
+ if (c->vtable->flags & SD_BUS_VTABLE_UNPRIVILEGED)
+ return 0;
+
+ /* If we are not connected to kdbus we cannot retrieve the
+ * effective capability set without race. Since we need this
+ * for a security decision we cannot use racy data, hence
+ * don't request it. */
+ if (bus->is_kernel)
+ r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_UID|SD_BUS_CREDS_EFFECTIVE_CAPS, &creds);
+ else
+ r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_UID, &creds);
+ if (r < 0)
+ return r;
+
+ /* Check have the caller has the requested capability
+ * set. Note that the flags value contains the capability
+ * number plus one, which we need to subtract here. We do this
+ * so that we have 0 as special value for "default
+ * capability". */
+ cap = CAPABILITY_SHIFT(c->vtable->flags);
+ if (cap == 0)
+ cap = CAPABILITY_SHIFT(c->parent->vtable[0].flags);
+ if (cap == 0)
+ cap = CAP_SYS_ADMIN;
+ else
+ cap --;
+
+ r = sd_bus_creds_has_effective_cap(creds, cap);
+ if (r > 0)
+ return 1;
+
+ /* Caller has same UID as us, then let's grant access */
+ r = sd_bus_creds_get_uid(creds, &uid);
+ if (r >= 0) {
+ if (uid == getuid())
+ return 1;
+ }
+
+ return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Access to %s.%s() not permitted.", c->interface, c->member);
+}
+
static int method_callbacks_run(
sd_bus *bus,
sd_bus_message *m,
@@ -284,6 +344,10 @@ static int method_callbacks_run(
if (require_fallback && !c->parent->is_fallback)
return 0;
+ r = check_access(bus, m, c, &error);
+ if (r < 0)
+ return bus_maybe_reply_error(m, r, &error);
+
r = node_vtable_get_userdata(bus, m->path, c->parent, &u, &error);
if (r <= 0)
return bus_maybe_reply_error(m, r, &error);
@@ -498,6 +562,11 @@ static int property_get_set_callbacks_run(
if (r < 0)
return r;
+ /* Note that we do not do an access check here. Read
+ * access to properties is always unrestricted, since
+ * PropertiesChanged signals broadcast contents
+ * anyway. */
+
r = invoke_property_get(bus, c->vtable, m->path, c->interface, c->member, reply, u, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
@@ -525,6 +594,10 @@ static int property_get_set_callbacks_run(
if (r < 0)
return r;
+ r = check_access(bus, m, c, &error);
+ if (r < 0)
+ return bus_maybe_reply_error(m, r, &error);
+
r = invoke_property_set(bus, c->vtable, m->path, c->interface, c->member, m, u, &error);
if (r < 0)
return bus_maybe_reply_error(m, r, &error);
@@ -1199,12 +1272,12 @@ int bus_process_object(sd_bus *bus, sd_bus_message *m) {
if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL)
return 0;
- if (!m->path)
- return 0;
-
if (hashmap_isempty(bus->nodes))
return 0;
+ assert(m->path);
+ assert(m->member);
+
pl = strlen(m->path);
do {
char prefix[pl+1];
@@ -1636,7 +1709,8 @@ static int add_object_vtable_internal(
!signature_is_single(v->x.property.signature, false) ||
!(v->x.property.get || bus_type_is_basic(v->x.property.signature[0]) || streq(v->x.property.signature, "as")) ||
v->flags & SD_BUS_VTABLE_METHOD_NO_REPLY ||
- (v->flags & SD_BUS_VTABLE_PROPERTY_INVALIDATE_ONLY && !(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE))) {
+ (v->flags & SD_BUS_VTABLE_PROPERTY_INVALIDATE_ONLY && !(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) ||
+ (v->flags & SD_BUS_VTABLE_UNPRIVILEGED && v->type == _SD_BUS_VTABLE_PROPERTY)) {
r = -EINVAL;
goto fail;
}
@@ -1666,7 +1740,8 @@ static int add_object_vtable_internal(
case _SD_BUS_VTABLE_SIGNAL:
if (!member_name_is_valid(v->x.signal.member) ||
- !signature_is_valid(strempty(v->x.signal.signature), false)) {
+ !signature_is_valid(strempty(v->x.signal.signature), false) ||
+ v->flags & SD_BUS_VTABLE_UNPRIVILEGED) {
r = -EINVAL;
goto fail;
}
diff --git a/src/libsystemd-bus/libsystemd-bus.sym b/src/libsystemd-bus/libsystemd-bus.sym
index 389efc3273..7bc1ef9ad2 100644
--- a/src/libsystemd-bus/libsystemd-bus.sym
+++ b/src/libsystemd-bus/libsystemd-bus.sym
@@ -25,6 +25,7 @@ global:
sd_bus_set_bus_client;
sd_bus_set_server;
sd_bus_set_anonymous;
+ sd_bus_set_trusted;
sd_bus_negotiate_fds;
sd_bus_negotiate_attach_timestamp;
sd_bus_negotiate_attach_creds;
diff --git a/src/libsystemd-bus/sd-bus.c b/src/libsystemd-bus/sd-bus.c
index fef122bf3e..81bfe0d922 100644
--- a/src/libsystemd-bus/sd-bus.c
+++ b/src/libsystemd-bus/sd-bus.c
@@ -318,6 +318,15 @@ _public_ int sd_bus_set_anonymous(sd_bus *bus, int b) {
return 0;
}
+_public_ int sd_bus_set_trusted(sd_bus *bus, int b) {
+ assert_return(bus, -EINVAL);
+ assert_return(bus->state == BUS_UNSET, -EPERM);
+ assert_return(!bus_pid_changed(bus), -ECHILD);
+
+ bus->trusted = !!b;
+ return 0;
+}
+
static int hello_callback(sd_bus *bus, sd_bus_message *reply, void *userdata, sd_bus_error *error) {
const char *s;
int r;
@@ -1005,6 +1014,11 @@ _public_ int sd_bus_open_system(sd_bus **ret) {
b->bus_client = true;
+ /* Let's do per-method access control on the system bus. We
+ * need the caller's UID and capability set for that. */
+ b->trusted = false;
+ b->attach_flags |= KDBUS_ATTACH_CAPS | KDBUS_ATTACH_CREDS;
+
r = sd_bus_start(b);
if (r < 0)
goto fail;
@@ -1065,6 +1079,10 @@ _public_ int sd_bus_open_user(sd_bus **ret) {
b->bus_client = true;
+ /* We don't do any per-method access control on the user
+ * bus. */
+ b->trusted = true;
+
r = sd_bus_start(b);
if (r < 0)
goto fail;