[pulseaudio-discuss] [PATCH 1/2] access: add access control hooks

Wim Taymans wim.taymans at gmail.com
Mon Feb 23 07:26:36 PST 2015


Add hooks to check if a certain operation is allowed.
Make a simple module that implements a simple policy that only allows
actions on the client's own sink_input, source_output and client.
---
 src/Makefile.am                 |   8 ++
 src/modules/module-access.c     | 308 ++++++++++++++++++++++++++++++++++++++++
 src/pulsecore/access.h          |  98 +++++++++++++
 src/pulsecore/core-subscribe.c  |   2 +-
 src/pulsecore/core.c            |   5 +
 src/pulsecore/core.h            |   3 +
 src/pulsecore/protocol-native.c | 152 ++++++++++++++++++++
 7 files changed, 575 insertions(+), 1 deletion(-)
 create mode 100644 src/modules/module-access.c
 create mode 100644 src/pulsecore/access.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 67f8627..bd32a20 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -911,6 +911,7 @@ lib_LTLIBRARIES += libpulsecore- at PA_MAJORMINOR@.la
 
 # Pure core stuff
 libpulsecore_ at PA_MAJORMINOR@_la_SOURCES = \
+		pulsecore/access.h \
 		pulsecore/asyncmsgq.c pulsecore/asyncmsgq.h \
 		pulsecore/asyncq.c pulsecore/asyncq.h \
 		pulsecore/auth-cookie.c pulsecore/auth-cookie.h \
@@ -1139,6 +1140,7 @@ modlibexec_LTLIBRARIES += \
 endif
 
 modlibexec_LTLIBRARIES += \
+		module-access.la \
 		module-cli.la \
 		module-cli-protocol-tcp.la \
 		module-simple-protocol-tcp.la \
@@ -1435,6 +1437,7 @@ endif
 
 # These are generated by an M4 script
 SYMDEF_FILES = \
+		module-access-symdef.h \
 		module-cli-symdef.h \
 		module-cli-protocol-tcp-symdef.h \
 		module-cli-protocol-unix-symdef.h \
@@ -1557,6 +1560,11 @@ module_simple_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_SIMPLE
 module_simple_protocol_unix_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_simple_protocol_unix_la_LIBADD = $(MODULE_LIBADD) libprotocol-simple.la
 
+# Access control
+module_access_la_SOURCES = modules/module-access.c
+module_access_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_access_la_LIBADD = $(MODULE_LIBADD)
+
 # CLI protocol
 
 module_cli_la_SOURCES = modules/module-cli.c
diff --git a/src/modules/module-access.c b/src/modules/module-access.c
new file mode 100644
index 0000000..dc04b96
--- /dev/null
+++ b/src/modules/module-access.c
@@ -0,0 +1,308 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2004-2006 Lennart Poettering
+            2015 Wim Taymans <wtaymans at redhat.com>
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/module.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/sink-input.h>
+#include <pulsecore/core-util.h>
+
+#include "module-access-symdef.h"
+
+PA_MODULE_AUTHOR("Wim Taymans");
+PA_MODULE_DESCRIPTION("Controls access to server resources");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(true);
+PA_MODULE_USAGE("");
+
+static const char* const valid_modargs[] = {
+    NULL,
+};
+
+typedef struct event_item event_item;
+
+struct event_item {
+    PA_LLIST_FIELDS(event_item);
+
+    uint32_t client_index;
+    int facility;
+    uint32_t object_index;
+};
+
+struct userdata {
+    pa_core *core;
+    pa_hook_slot *hook[PA_ACCESS_HOOK_MAX];
+
+    PA_LLIST_HEAD(event_item, events);
+};
+
+static pa_hook_result_t access_check_owner (pa_core *c, pa_access_data *d, struct userdata *u) {
+    pa_hook_result_t result = PA_HOOK_STOP;
+    uint32_t idx = PA_INVALID_INDEX;
+
+    switch (d->hook) {
+        case PA_ACCESS_HOOK_GET_CLIENT_INFO:
+        case PA_ACCESS_HOOK_KILL_CLIENT: {
+            idx = d->object_index;
+            break;
+        }
+
+        case PA_ACCESS_HOOK_GET_SINK_INPUT_INFO:
+        case PA_ACCESS_HOOK_MOVE_SINK_INPUT:
+        case PA_ACCESS_HOOK_SET_SINK_INPUT_VOLUME:
+        case PA_ACCESS_HOOK_SET_SINK_INPUT_MUTE:
+        case PA_ACCESS_HOOK_KILL_SINK_INPUT: {
+            const pa_sink_input *si = pa_idxset_get_by_index(c->sink_inputs, d->object_index);
+            idx = (si && si->client) ? si->client->index : PA_INVALID_INDEX;
+            break;
+        }
+
+        case PA_ACCESS_HOOK_GET_SOURCE_OUTPUT_INFO:
+        case PA_ACCESS_HOOK_MOVE_SOURCE_OUTPUT:
+        case PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_VOLUME:
+        case PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_MUTE:
+        case PA_ACCESS_HOOK_KILL_SOURCE_OUTPUT: {
+            const pa_source_output *so = pa_idxset_get_by_index(c->source_outputs, d->object_index);
+            idx = (so && so->client) ? so->client->index : PA_INVALID_INDEX;
+            break;
+        }
+        default:
+            break;
+    }
+    if (idx == d->client_index)
+        result = PA_HOOK_OK;
+    else
+        pa_log("blocked operation %d/%d of client %d to client %d", d->hook, d->object_index, idx, d->client_index);
+
+    return result;
+}
+
+static const pa_access_hook_t event_hook[PA_SUBSCRIPTION_EVENT_FACILITY_MASK+1] = {
+    [PA_SUBSCRIPTION_EVENT_SINK] = PA_ACCESS_HOOK_GET_SINK_INFO,
+    [PA_SUBSCRIPTION_EVENT_SOURCE] = PA_ACCESS_HOOK_GET_SOURCE_INFO,
+    [PA_SUBSCRIPTION_EVENT_SINK_INPUT] = PA_ACCESS_HOOK_GET_SINK_INPUT_INFO,
+    [PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT] = PA_ACCESS_HOOK_GET_SOURCE_OUTPUT_INFO,
+    [PA_SUBSCRIPTION_EVENT_MODULE] = PA_ACCESS_HOOK_GET_MODULE_INFO,
+    [PA_SUBSCRIPTION_EVENT_CLIENT] = PA_ACCESS_HOOK_GET_CLIENT_INFO,
+    [PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE] = PA_ACCESS_HOOK_GET_SAMPLE_INFO,
+    [PA_SUBSCRIPTION_EVENT_SERVER] = PA_ACCESS_HOOK_GET_SERVER_INFO,
+    [PA_SUBSCRIPTION_EVENT_CARD] = PA_ACCESS_HOOK_GET_CARD_INFO
+};
+
+static void add_event(struct userdata *u, uint32_t cidx, int facility, uint32_t oidx) {
+    event_item *i;
+
+    i = pa_xnew0(event_item, 1);
+    PA_LLIST_INIT(event_item, i);
+    i->client_index = cidx;
+    i->facility = facility;
+    i->object_index = oidx;
+
+    PA_LLIST_PREPEND(event_item, u->events, i);
+}
+
+static event_item *find_event(struct userdata *u, uint32_t cidx, int facility, uint32_t oidx) {
+    event_item *i;
+
+    PA_LLIST_FOREACH(i, u->events) {
+        if (i->client_index == cidx && i->facility == facility && i->object_index == oidx)
+            return i;
+    }
+    return NULL;
+}
+
+static bool remove_event(struct userdata *u, uint32_t cidx, int facility, uint32_t oidx) {
+    event_item *i = find_event(u, cidx, facility, oidx);
+    if (i) {
+      PA_LLIST_REMOVE(event_item, u->events, i);
+      pa_xfree(i);
+      return true;
+    }
+    return false;
+}
+
+static pa_hook_result_t filter_event (pa_core *c, pa_access_data *d, struct userdata *u) {
+    int facility;
+
+    facility = d->event & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
+
+    switch (d->event & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
+        case PA_SUBSCRIPTION_EVENT_REMOVE:
+            /* if the client saw this object before, let the remove go through */
+            if (remove_event(u, d->client_index, facility, d->object_index)) {
+                pa_log("pass event %02x/%d to client %d", d->event, d->object_index, d->client_index);
+                return PA_HOOK_OK;
+            }
+            break;
+
+        case PA_SUBSCRIPTION_EVENT_CHANGE:
+            /* if the client saw this object before, let it go through */
+            if (find_event(u, d->client_index, facility, d->object_index)) {
+                pa_log("pass event %02x/%d to client %d", d->event, d->object_index, d->client_index);
+                return PA_HOOK_OK;
+            }
+
+            /* fallthrough to do hook check and register event */
+        case PA_SUBSCRIPTION_EVENT_NEW: {
+            pa_access_data data = *d;
+
+            /* new object, check if the client is allowed to inspect it */
+            data.hook = event_hook[facility];
+            if (data.hook && pa_hook_fire(&c->access[data.hook], &data) == PA_HOOK_OK) {
+                /* client can inspect the object, remember for later */
+                add_event(u, d->client_index, facility, d->object_index);
+                pa_log("pass event %02x/%d to client %d", d->event, d->object_index, d->client_index);
+                return PA_HOOK_OK;
+            }
+            break;
+        }
+        default:
+            break;
+    }
+    pa_log("blocked event %02x/%d for client %d", d->event, d->object_index, d->client_index);
+    return PA_HOOK_STOP;
+}
+
+static pa_hook_result_t access_block (pa_core *c, pa_access_data *d, struct userdata *u) {
+    pa_log("blocked operation %d/%d for client %d", d->hook, d->object_index, d->client_index);
+    return PA_HOOK_STOP;
+}
+
+static void install_cb(struct userdata *u, pa_access_hook_t id, pa_hook_cb_t cb) {
+    if (u->hook[id])
+        pa_hook_slot_free(u->hook[id]);
+    if (cb)
+        u->hook[id] = pa_hook_connect(&u->core->access[id], PA_HOOK_EARLY - 1, cb, u);
+    else
+        u->hook[id] = NULL;
+}
+
+static void allow(struct userdata *u, pa_access_hook_t id) {
+    install_cb(u, id, NULL);
+}
+
+static void block(struct userdata *u, pa_access_hook_t id) {
+    install_cb(u, id, (pa_hook_cb_t) access_block);
+}
+
+static void check_owner(struct userdata *u, pa_access_hook_t id) {
+    install_cb(u, id, (pa_hook_cb_t) access_check_owner);
+}
+
+static void allow_all(struct userdata *u) {
+    int i;
+    for (i = 0; i < PA_ACCESS_HOOK_MAX; i++)
+        allow(u, i);
+}
+
+static void block_all(struct userdata *u) {
+    int i;
+    for (i = 0; i < PA_ACCESS_HOOK_MAX; i++)
+        block(u, i);
+}
+
+int pa__init(pa_module*m) {
+    pa_modargs *ma = NULL;
+    struct userdata *u;
+
+    pa_assert(m);
+
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log("Failed to parse module arguments");
+        goto fail;
+    }
+
+    u = pa_xnew0(struct userdata, 1);
+    u->core = m->core;
+    m->userdata = u;
+
+    block_all(u);
+
+    allow (u, PA_ACCESS_HOOK_GET_SINK_INFO);
+    allow (u, PA_ACCESS_HOOK_GET_SOURCE_INFO);
+    allow (u, PA_ACCESS_HOOK_GET_SERVER_INFO);
+    allow (u, PA_ACCESS_HOOK_GET_MODULE_INFO);
+    allow (u, PA_ACCESS_HOOK_GET_CARD_INFO);
+    allow (u, PA_ACCESS_HOOK_STAT);
+    allow (u, PA_ACCESS_HOOK_GET_SAMPLE_INFO);
+    allow (u, PA_ACCESS_HOOK_PLAY_SAMPLE);
+    allow (u, PA_ACCESS_HOOK_CONNECT_PLAYBACK);
+
+    check_owner(u, PA_ACCESS_HOOK_GET_CLIENT_INFO);
+    check_owner(u, PA_ACCESS_HOOK_KILL_CLIENT);
+
+    check_owner(u, PA_ACCESS_HOOK_GET_SINK_INPUT_INFO);
+    check_owner(u, PA_ACCESS_HOOK_MOVE_SINK_INPUT);
+    check_owner(u, PA_ACCESS_HOOK_SET_SINK_INPUT_VOLUME);
+    check_owner(u, PA_ACCESS_HOOK_SET_SINK_INPUT_MUTE);
+    check_owner(u, PA_ACCESS_HOOK_KILL_SINK_INPUT);
+
+    check_owner(u, PA_ACCESS_HOOK_GET_SOURCE_OUTPUT_INFO);
+    check_owner(u, PA_ACCESS_HOOK_MOVE_SOURCE_OUTPUT);
+    check_owner(u, PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_VOLUME);
+    check_owner(u, PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_MUTE);
+    check_owner(u, PA_ACCESS_HOOK_KILL_SOURCE_OUTPUT);
+
+    install_cb(u, PA_ACCESS_HOOK_FILTER_SUBSCRIBE_EVENT, (pa_hook_cb_t) filter_event);
+
+    pa_modargs_free(ma);
+    return 0;
+
+fail:
+    pa__done(m);
+
+    if (ma)
+        pa_modargs_free(ma);
+    return -1;
+}
+
+void pa__done(pa_module*m) {
+    struct userdata* u;
+    event_item *i;
+
+    pa_assert(m);
+
+    if (!(u = m->userdata))
+        return;
+
+    allow_all(u);
+
+    while ((i = u->events)) {
+        PA_LLIST_REMOVE(event_item, u->events, i);
+        pa_xfree(i);
+    }
+
+    pa_xfree(u);
+}
diff --git a/src/pulsecore/access.h b/src/pulsecore/access.h
new file mode 100644
index 0000000..7346ddf
--- /dev/null
+++ b/src/pulsecore/access.h
@@ -0,0 +1,98 @@
+#ifndef fooaccesshfoo
+#define fooaccesshfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2004-2006 Lennart Poettering
+            2015 Wim Taymans <wtaymans at redhat.com>
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#include <pulsecore/client.h>
+
+typedef enum pa_access_hook {
+    /* context */
+    PA_ACCESS_HOOK_EXIT_DAEMON,
+    PA_ACCESS_HOOK_SET_DEFAULT_SINK,
+    PA_ACCESS_HOOK_SET_DEFAULT_SOURCE,
+
+    /* introspection */
+    PA_ACCESS_HOOK_GET_SINK_INFO,
+    PA_ACCESS_HOOK_SET_SINK_VOLUME,
+    PA_ACCESS_HOOK_SET_SINK_MUTE,
+    PA_ACCESS_HOOK_SUSPEND_SINK,
+    PA_ACCESS_HOOK_SET_SINK_PORT,
+
+    PA_ACCESS_HOOK_GET_SOURCE_INFO,
+    PA_ACCESS_HOOK_SET_SOURCE_VOLUME,
+    PA_ACCESS_HOOK_SET_SOURCE_MUTE,
+    PA_ACCESS_HOOK_SUSPEND_SOURCE,
+    PA_ACCESS_HOOK_SET_SOURCE_PORT,
+
+    PA_ACCESS_HOOK_GET_SERVER_INFO,
+
+    PA_ACCESS_HOOK_GET_MODULE_INFO,
+    PA_ACCESS_HOOK_LOAD_MODULE,
+    PA_ACCESS_HOOK_UNLOAD_MODULE,
+
+    PA_ACCESS_HOOK_GET_CLIENT_INFO,
+    PA_ACCESS_HOOK_KILL_CLIENT,
+
+    PA_ACCESS_HOOK_GET_CARD_INFO,
+    PA_ACCESS_HOOK_SET_CARD_PROFILE,
+    PA_ACCESS_HOOK_SET_PORT_LATENCY_OFFSET,
+
+    PA_ACCESS_HOOK_GET_SINK_INPUT_INFO,
+    PA_ACCESS_HOOK_MOVE_SINK_INPUT,
+    PA_ACCESS_HOOK_SET_SINK_INPUT_VOLUME,
+    PA_ACCESS_HOOK_SET_SINK_INPUT_MUTE,
+    PA_ACCESS_HOOK_KILL_SINK_INPUT,
+
+    PA_ACCESS_HOOK_GET_SOURCE_OUTPUT_INFO,
+    PA_ACCESS_HOOK_MOVE_SOURCE_OUTPUT,
+    PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_VOLUME,
+    PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_MUTE,
+    PA_ACCESS_HOOK_KILL_SOURCE_OUTPUT,
+
+    PA_ACCESS_HOOK_STAT,
+
+    PA_ACCESS_HOOK_GET_SAMPLE_INFO,
+    /* sample cache */
+    PA_ACCESS_HOOK_CONNECT_UPLOAD,
+    PA_ACCESS_HOOK_REMOVE_SAMPLE,
+    PA_ACCESS_HOOK_PLAY_SAMPLE,
+    /* stream */
+    PA_ACCESS_HOOK_CONNECT_PLAYBACK,
+    PA_ACCESS_HOOK_CONNECT_RECORD,
+    /* extension */
+    PA_ACCESS_HOOK_EXTENSION,
+
+    PA_ACCESS_HOOK_FILTER_SUBSCRIBE_EVENT,
+
+    PA_ACCESS_HOOK_MAX
+} pa_access_hook_t;
+
+typedef struct pa_access_data {
+    pa_access_hook_t hook;
+    uint32_t client_index;
+    uint32_t object_index;
+    pa_subscription_event_type_t event;
+    const char *name;
+} pa_access_data;
+
+#endif
diff --git a/src/pulsecore/core-subscribe.c b/src/pulsecore/core-subscribe.c
index 61c779b..5a88b7e 100644
--- a/src/pulsecore/core-subscribe.c
+++ b/src/pulsecore/core-subscribe.c
@@ -206,7 +206,7 @@ void pa_subscription_post(pa_core *c, pa_subscription_event_type_t t, uint32_t i
     pa_subscription_event *e;
     pa_assert(c);
 
-    /* No need for queuing subscriptions of no one is listening */
+    /* No need for queuing subscriptions if no one is listening */
     if (!c->subscriptions)
         return;
 
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
index 0e67005..e80bb57 100644
--- a/src/pulsecore/core.c
+++ b/src/pulsecore/core.c
@@ -150,6 +150,9 @@ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, size_t shm_size) {
     for (j = 0; j < PA_CORE_HOOK_MAX; j++)
         pa_hook_init(&c->hooks[j], c);
 
+    for (j = 0; j < PA_ACCESS_HOOK_MAX; j++)
+        pa_hook_init(&c->access[j], c);
+
     pa_random(&c->cookie, sizeof(c->cookie));
 
 #ifdef SIGPIPE
@@ -221,6 +224,8 @@ static void core_free(pa_object *o) {
 
     for (j = 0; j < PA_CORE_HOOK_MAX; j++)
         pa_hook_done(&c->hooks[j]);
+    for (j = 0; j < PA_ACCESS_HOOK_MAX; j++)
+        pa_hook_done(&c->access[j]);
 
     pa_xfree(c);
 }
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index 7d896bb..9dc5688 100644
--- a/src/pulsecore/core.h
+++ b/src/pulsecore/core.h
@@ -50,6 +50,7 @@ typedef enum pa_suspend_cause {
 #include <pulsecore/source.h>
 #include <pulsecore/core-subscribe.h>
 #include <pulsecore/msgobject.h>
+#include <pulsecore/access.h>
 
 typedef enum pa_server_type {
     PA_SERVER_TYPE_UNSET,
@@ -197,6 +198,8 @@ struct pa_core {
 
     /* hooks */
     pa_hook hooks[PA_CORE_HOOK_MAX];
+    /* access hooks */
+    pa_hook access[PA_ACCESS_HOOK_MAX];
 };
 
 PA_DECLARE_PUBLIC_CLASS(pa_core);
diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c
index f54f2a4..4697dcc 100644
--- a/src/pulsecore/protocol-native.c
+++ b/src/pulsecore/protocol-native.c
@@ -294,6 +294,7 @@ static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_
 static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
 static void command_set_port_latency_offset(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
 static void command_enable_srbchannel(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
+static int check_access(pa_native_connection *c, uint32_t command, uint32_t idx, pa_subscription_event_type_t event, const char *name);
 
 static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = {
     [PA_COMMAND_ERROR] = NULL,
@@ -1972,6 +1973,12 @@ if (!(expression)) { \
 } \
 } while(0);
 
+#define CHECK_ACCESS(c, command, tag, idx, name) \
+  CHECK_VALIDITY(c->pstream, check_access(c, command, idx, 0, name), tag, PA_ERR_ACCESS)
+
+#define CHECK_ACCESS_GOTO(c, command, tag, idx, name, label) \
+  CHECK_VALIDITY_GOTO(c->pstream, check_access(c, command, idx, 0, name), tag, PA_ERR_ACCESS, label)
+
 static pa_tagstruct *reply_new(uint32_t tag) {
     pa_tagstruct *reply;
 
@@ -2049,6 +2056,8 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u
     CHECK_VALIDITY_GOTO(c->pstream, !sink_name || sink_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID, finish);
     CHECK_VALIDITY_GOTO(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID, finish);
 
+    CHECK_ACCESS_GOTO(c, command, tag, PA_INVALID_INDEX, NULL, finish);
+
     p = pa_proplist_new();
 
     if (name)
@@ -2370,6 +2379,8 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin
     CHECK_VALIDITY_GOTO(c->pstream, source_index == PA_INVALID_INDEX || !source_name, tag, PA_ERR_INVALID, finish);
     CHECK_VALIDITY_GOTO(c->pstream, !source_name || source_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID, finish);
 
+    CHECK_ACCESS_GOTO(c, command, tag, PA_INVALID_INDEX, NULL, finish);
+
     p = pa_proplist_new();
 
     if (name)
@@ -2575,6 +2586,7 @@ static void command_exit(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta
     }
 
     CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+    CHECK_ACCESS(c, command, tag, PA_INVALID_INDEX, NULL);
     ret = pa_core_exit(c->protocol->core, false, 0);
     CHECK_VALIDITY(c->pstream, ret >= 0, tag, PA_ERR_ACCESS);
 
@@ -2893,6 +2905,7 @@ static void command_stat(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta
     }
 
     CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
+    CHECK_ACCESS (c, command, tag, PA_INVALID_INDEX, NULL);
 
     stat = pa_mempool_get_stat(c->protocol->core->mempool);
 
@@ -3019,6 +3032,8 @@ static void command_create_upload_stream(pa_pdispatch *pd, uint32_t command, uin
     CHECK_VALIDITY(c->pstream, (length % pa_frame_size(&ss)) == 0 && length > 0, tag, PA_ERR_INVALID);
     CHECK_VALIDITY(c->pstream, length <= PA_SCACHE_ENTRY_SIZE_MAX, tag, PA_ERR_TOOLARGE);
 
+    CHECK_ACCESS(c, command, tag, PA_INVALID_INDEX, NULL);
+
     p = pa_proplist_new();
 
     if ((c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) ||
@@ -3117,6 +3132,8 @@ static void command_play_sample(pa_pdispatch *pd, uint32_t command, uint32_t tag
 
     CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
 
+    CHECK_ACCESS(c, command, tag, sink->index, name);
+
     p = pa_proplist_new();
 
     if ((c->version >= 13 && pa_tagstruct_get_proplist(t, p) < 0) ||
@@ -3160,6 +3177,8 @@ static void command_remove_sample(pa_pdispatch *pd, uint32_t command, uint32_t t
     CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
     CHECK_VALIDITY(c->pstream, name && pa_namereg_is_valid_name(name), tag, PA_ERR_INVALID);
 
+    CHECK_ACCESS(c, command, tag, PA_INVALID_INDEX, name);
+
     if (pa_scache_remove_item(c->protocol->core, name) < 0) {
         pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY);
         return;
@@ -3608,6 +3627,8 @@ static void command_get_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, p
         return;
     }
 
+    CHECK_ACCESS (c, command, tag, idx, name);
+
     reply = reply_new(tag);
     if (sink)
         sink_fill_tagstruct(c, reply, sink);
@@ -3668,6 +3689,9 @@ static void command_get_info_list(pa_pdispatch *pd, uint32_t command, uint32_t t
 
     if (i) {
         PA_IDXSET_FOREACH(p, i, idx) {
+            if (!check_access(c, command, idx, 0, NULL))
+              continue;
+
             if (command == PA_COMMAND_GET_SINK_INFO_LIST)
                 sink_fill_tagstruct(c, reply, p);
             else if (command == PA_COMMAND_GET_SOURCE_INFO_LIST)
@@ -3710,6 +3734,8 @@ static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t
 
     CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS);
 
+    CHECK_ACCESS(c, command, tag, PA_INVALID_INDEX, NULL);
+
     reply = reply_new(tag);
     pa_tagstruct_puts(reply, PACKAGE_NAME);
     pa_tagstruct_puts(reply, PACKAGE_VERSION);
@@ -3726,8 +3752,12 @@ static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t
     pa_tagstruct_put_sample_spec(reply, &fixed_ss);
 
     def_sink = pa_namereg_get_default_sink(c->protocol->core);
+    if (def_sink && !check_access(c, PA_COMMAND_GET_SINK_INFO, def_sink->index, 0, NULL))
+        def_sink = NULL;
     pa_tagstruct_puts(reply, def_sink ? def_sink->name : NULL);
     def_source = pa_namereg_get_default_source(c->protocol->core);
+    if (def_source && !check_access(c, PA_COMMAND_GET_SOURCE_INFO, def_source->index, 0, NULL))
+        def_source = NULL;
     pa_tagstruct_puts(reply, def_source ? def_source->name : NULL);
 
     pa_tagstruct_putu32(reply, c->protocol->core->cookie);
@@ -3744,6 +3774,9 @@ static void subscription_cb(pa_core *core, pa_subscription_event_type_t e, uint3
 
     pa_native_connection_assert_ref(c);
 
+    if (!check_access (c, PA_COMMAND_SUBSCRIBE_EVENT, idx, e, NULL))
+      return;
+
     t = pa_tagstruct_new(NULL, 0);
     pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE_EVENT);
     pa_tagstruct_putu32(t, (uint32_t) -1);
@@ -3846,6 +3879,8 @@ static void command_set_volume(
 
     client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY));
 
+    CHECK_ACCESS(c, command, tag, idx, name);
+
     if (sink) {
         CHECK_VALIDITY(c->pstream, volume.channels == 1 || pa_cvolume_compatible(&volume, &sink->sample_spec), tag, PA_ERR_INVALID);
 
@@ -3941,6 +3976,8 @@ static void command_set_mute(
 
     CHECK_VALIDITY(c->pstream, si || so || sink || source, tag, PA_ERR_NOENTITY);
 
+    CHECK_ACCESS (c, command, tag, idx, name);
+
     client_name = pa_strnull(pa_proplist_gets(c->client->proplist, PA_PROP_APPLICATION_PROCESS_BINARY));
 
     if (sink) {
@@ -4404,6 +4441,7 @@ static void command_set_default_sink_or_source(pa_pdispatch *pd, uint32_t comman
 
         source = pa_namereg_get(c->protocol->core, s, PA_NAMEREG_SOURCE);
         CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY);
+        CHECK_ACCESS(c, command, tag, source->index, s);
 
         pa_namereg_set_default_source(c->protocol->core, source);
     } else {
@@ -4412,6 +4450,7 @@ static void command_set_default_sink_or_source(pa_pdispatch *pd, uint32_t comman
 
         sink = pa_namereg_get(c->protocol->core, s, PA_NAMEREG_SINK);
         CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
+        CHECK_ACCESS(c, command, tag, sink->index, s);
 
         pa_namereg_set_default_sink(c->protocol->core, sink);
     }
@@ -4479,6 +4518,7 @@ static void command_kill(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta
 
         client = pa_idxset_get_by_index(c->protocol->core->clients, idx);
         CHECK_VALIDITY(c->pstream, client, tag, PA_ERR_NOENTITY);
+        CHECK_ACCESS (c, command, tag, idx, NULL);
 
         pa_native_connection_ref(c);
         pa_client_kill(client);
@@ -4488,6 +4528,7 @@ static void command_kill(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta
 
         s = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx);
         CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+        CHECK_ACCESS (c, command, tag, idx, NULL);
 
         pa_native_connection_ref(c);
         pa_sink_input_kill(s);
@@ -4498,6 +4539,7 @@ static void command_kill(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_ta
 
         s = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx);
         CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY);
+        CHECK_ACCESS (c, command, tag, idx, NULL);
 
         pa_native_connection_ref(c);
         pa_source_output_kill(s);
@@ -4527,6 +4569,8 @@ static void command_load_module(pa_pdispatch *pd, uint32_t command, uint32_t tag
     CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name) && !strchr(name, '/'), tag, PA_ERR_INVALID);
     CHECK_VALIDITY(c->pstream, !argument || pa_utf8_valid(argument), tag, PA_ERR_INVALID);
 
+    CHECK_ACCESS(c, command, tag, PA_INVALID_INDEX, name);
+
     if (!(m = pa_module_load(c->protocol->core, name, argument))) {
         pa_pstream_send_error(c->pstream, tag, PA_ERR_MODINITFAILED);
         return;
@@ -4555,6 +4599,8 @@ static void command_unload_module(pa_pdispatch *pd, uint32_t command, uint32_t t
     m = pa_idxset_get_by_index(c->protocol->core->modules, idx);
     CHECK_VALIDITY(c->pstream, m, tag, PA_ERR_NOENTITY);
 
+    CHECK_ACCESS(c, command, tag, idx, NULL);
+
     pa_module_unload_request(m, false);
     pa_pstream_send_simple_ack(c->pstream, tag);
 }
@@ -4593,6 +4639,7 @@ static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag
             sink = pa_namereg_get(c->protocol->core, name_device, PA_NAMEREG_SINK);
 
         CHECK_VALIDITY(c->pstream, si && sink, tag, PA_ERR_NOENTITY);
+        CHECK_ACCESS (c, command, tag, idx, sink->name);
 
         if (pa_sink_input_move_to(si, sink, true) < 0) {
             pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
@@ -4612,6 +4659,7 @@ static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag
             source = pa_namereg_get(c->protocol->core, name_device, PA_NAMEREG_SOURCE);
 
         CHECK_VALIDITY(c->pstream, so && source, tag, PA_ERR_NOENTITY);
+        CHECK_ACCESS (c, command, tag, idx, source->name);
 
         if (pa_source_output_move_to(so, source, true) < 0) {
             pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID);
@@ -4647,6 +4695,8 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa
 
         if (idx == PA_INVALID_INDEX && name && !*name) {
 
+            CHECK_ACCESS (c, command, tag, idx, name);
+
             pa_log_debug("%s all sinks", b ? "Suspending" : "Resuming");
 
             if (pa_sink_suspend_all(c->protocol->core, b, PA_SUSPEND_USER) < 0) {
@@ -4663,6 +4713,8 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa
 
             CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
 
+            CHECK_ACCESS (c, command, tag, sink->index, name);
+
             pa_log_debug("%s of sink %s requested by client %" PRIu32 ".",
                          b ? "Suspending" : "Resuming", sink->name, c->client->index);
 
@@ -4677,6 +4729,8 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa
 
         if (idx == PA_INVALID_INDEX && name && !*name) {
 
+            CHECK_ACCESS (c, command, tag, idx, name);
+
             pa_log_debug("%s all sources", b ? "Suspending" : "Resuming");
 
             if (pa_source_suspend_all(c->protocol->core, b, PA_SUSPEND_USER) < 0) {
@@ -4694,6 +4748,8 @@ static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa
 
             CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY);
 
+            CHECK_ACCESS (c, command, tag, source->index, name);
+
             pa_log_debug("%s of source %s requested by client %" PRIu32 ".",
                          b ? "Suspending" : "Resuming", source->name, c->client->index);
 
@@ -4737,6 +4793,8 @@ static void command_extension(pa_pdispatch *pd, uint32_t command, uint32_t tag,
     CHECK_VALIDITY(c->pstream, m, tag, PA_ERR_NOEXTENSION);
     CHECK_VALIDITY(c->pstream, m->load_once || idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID);
 
+    CHECK_ACCESS(c, command, tag, m->index, name);
+
     cb = (pa_native_protocol_ext_cb_t) (unsigned long) pa_hashmap_get(c->protocol->extensions, m);
     CHECK_VALIDITY(c->pstream, cb, tag, PA_ERR_NOEXTENSION);
 
@@ -4779,6 +4837,8 @@ static void command_set_card_profile(pa_pdispatch *pd, uint32_t command, uint32_
 
     CHECK_VALIDITY(c->pstream, profile, tag, PA_ERR_NOENTITY);
 
+    CHECK_ACCESS (c, command, tag, card->index, profile_name);
+
     if ((ret = pa_card_set_profile(card, profile, true)) < 0) {
         pa_pstream_send_error(c->pstream, tag, -ret);
         return;
@@ -4819,6 +4879,8 @@ static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command,
 
         CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY);
 
+        CHECK_ACCESS(c, command, tag, sink->index, name);
+
         if ((ret = pa_sink_set_port(sink, port, true)) < 0) {
             pa_pstream_send_error(c->pstream, tag, -ret);
             return;
@@ -4835,6 +4897,8 @@ static void command_set_sink_or_source_port(pa_pdispatch *pd, uint32_t command,
 
         CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY);
 
+        CHECK_ACCESS(c, command, tag, source->index, name);
+
         if ((ret = pa_source_set_port(source, port, true)) < 0) {
             pa_pstream_send_error(c->pstream, tag, -ret);
             return;
@@ -4879,6 +4943,8 @@ static void command_set_port_latency_offset(pa_pdispatch *pd, uint32_t command,
     port = pa_hashmap_get(card->ports, port_name);
     CHECK_VALIDITY(c->pstream, port, tag, PA_ERR_NOENTITY);
 
+    CHECK_ACCESS(c, command, tag, card->index, port_name);
+
     pa_device_port_set_latency_offset(port, offset);
 
     pa_pstream_send_simple_ack(c->pstream, tag);
@@ -5419,3 +5485,89 @@ pa_client* pa_native_connection_get_client(pa_native_connection *c) {
 
     return c->client;
 }
+
+static const pa_access_hook_t map_table[PA_COMMAND_MAX] = {
+    /* CLIENT -> SERVER */
+    [PA_COMMAND_EXIT] = PA_ACCESS_HOOK_EXIT_DAEMON,
+    [PA_COMMAND_SET_DEFAULT_SINK] = PA_ACCESS_HOOK_SET_DEFAULT_SINK,
+    [PA_COMMAND_SET_DEFAULT_SOURCE] = PA_ACCESS_HOOK_SET_DEFAULT_SOURCE,
+
+    [PA_COMMAND_GET_SINK_INFO] = PA_ACCESS_HOOK_GET_SINK_INFO,
+    [PA_COMMAND_GET_SINK_INFO_LIST] = PA_ACCESS_HOOK_GET_SINK_INFO,
+    [PA_COMMAND_SET_SINK_VOLUME] = PA_ACCESS_HOOK_SET_SINK_VOLUME,
+    [PA_COMMAND_SET_SINK_MUTE] = PA_ACCESS_HOOK_SET_SINK_MUTE,
+    [PA_COMMAND_SUSPEND_SINK] = PA_ACCESS_HOOK_SUSPEND_SINK,
+    [PA_COMMAND_SET_SINK_PORT] = PA_ACCESS_HOOK_SET_SINK_PORT,
+
+    [PA_COMMAND_GET_SOURCE_INFO] = PA_ACCESS_HOOK_GET_SOURCE_INFO,
+    [PA_COMMAND_GET_SOURCE_INFO_LIST] = PA_ACCESS_HOOK_GET_SOURCE_INFO,
+    [PA_COMMAND_SET_SOURCE_VOLUME] = PA_ACCESS_HOOK_SET_SOURCE_VOLUME,
+    [PA_COMMAND_SET_SOURCE_MUTE] = PA_ACCESS_HOOK_SET_SOURCE_MUTE,
+    [PA_COMMAND_SUSPEND_SOURCE] = PA_ACCESS_HOOK_SUSPEND_SOURCE,
+    [PA_COMMAND_SET_SOURCE_PORT] = PA_ACCESS_HOOK_SET_SOURCE_PORT,
+
+    [PA_COMMAND_GET_SERVER_INFO] = PA_ACCESS_HOOK_GET_SERVER_INFO,
+
+    [PA_COMMAND_GET_MODULE_INFO] = PA_ACCESS_HOOK_GET_MODULE_INFO,
+    [PA_COMMAND_GET_MODULE_INFO_LIST] = PA_ACCESS_HOOK_GET_MODULE_INFO,
+    [PA_COMMAND_LOAD_MODULE] = PA_ACCESS_HOOK_LOAD_MODULE,
+    [PA_COMMAND_UNLOAD_MODULE] = PA_ACCESS_HOOK_UNLOAD_MODULE,
+
+    [PA_COMMAND_GET_CLIENT_INFO] = PA_ACCESS_HOOK_GET_CLIENT_INFO,
+    [PA_COMMAND_GET_CLIENT_INFO_LIST] = PA_ACCESS_HOOK_GET_CLIENT_INFO,
+    [PA_COMMAND_KILL_CLIENT] = PA_ACCESS_HOOK_KILL_CLIENT,
+
+    [PA_COMMAND_GET_CARD_INFO] = PA_ACCESS_HOOK_GET_CARD_INFO,
+    [PA_COMMAND_GET_CARD_INFO_LIST] = PA_ACCESS_HOOK_GET_CARD_INFO,
+    [PA_COMMAND_SET_CARD_PROFILE] = PA_ACCESS_HOOK_SET_CARD_PROFILE,
+    [PA_COMMAND_SET_PORT_LATENCY_OFFSET] = PA_ACCESS_HOOK_SET_PORT_LATENCY_OFFSET,
+
+    [PA_COMMAND_GET_SINK_INPUT_INFO] = PA_ACCESS_HOOK_GET_SINK_INPUT_INFO,
+    [PA_COMMAND_GET_SINK_INPUT_INFO_LIST] = PA_ACCESS_HOOK_GET_SINK_INPUT_INFO,
+    [PA_COMMAND_MOVE_SINK_INPUT] = PA_ACCESS_HOOK_MOVE_SINK_INPUT,
+    [PA_COMMAND_SET_SINK_INPUT_VOLUME] = PA_ACCESS_HOOK_SET_SINK_INPUT_VOLUME,
+    [PA_COMMAND_SET_SINK_INPUT_MUTE] = PA_ACCESS_HOOK_SET_SINK_INPUT_MUTE,
+    [PA_COMMAND_KILL_SINK_INPUT] = PA_ACCESS_HOOK_KILL_SINK_INPUT,
+
+    [PA_COMMAND_GET_SOURCE_OUTPUT_INFO] = PA_ACCESS_HOOK_GET_SOURCE_OUTPUT_INFO,
+    [PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST] = PA_ACCESS_HOOK_GET_SOURCE_OUTPUT_INFO,
+    [PA_COMMAND_MOVE_SOURCE_OUTPUT] = PA_ACCESS_HOOK_MOVE_SOURCE_OUTPUT,
+    [PA_COMMAND_SET_SOURCE_OUTPUT_VOLUME] = PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_VOLUME,
+    [PA_COMMAND_SET_SOURCE_OUTPUT_MUTE] = PA_ACCESS_HOOK_SET_SOURCE_OUTPUT_MUTE,
+    [PA_COMMAND_KILL_SOURCE_OUTPUT] = PA_ACCESS_HOOK_KILL_SOURCE_OUTPUT,
+
+    [PA_COMMAND_STAT] = PA_ACCESS_HOOK_STAT,
+
+    [PA_COMMAND_GET_SAMPLE_INFO] = PA_ACCESS_HOOK_GET_SAMPLE_INFO,
+    [PA_COMMAND_GET_SAMPLE_INFO_LIST] = PA_ACCESS_HOOK_GET_SAMPLE_INFO,
+
+    [PA_COMMAND_CREATE_UPLOAD_STREAM] = PA_ACCESS_HOOK_CONNECT_UPLOAD,
+    [PA_COMMAND_REMOVE_SAMPLE] = PA_ACCESS_HOOK_REMOVE_SAMPLE,
+    [PA_COMMAND_PLAY_SAMPLE] = PA_ACCESS_HOOK_PLAY_SAMPLE,
+
+    [PA_COMMAND_CREATE_PLAYBACK_STREAM] = PA_ACCESS_HOOK_CONNECT_PLAYBACK,
+    [PA_COMMAND_CREATE_RECORD_STREAM] = PA_ACCESS_HOOK_CONNECT_RECORD,
+
+    [PA_COMMAND_EXTENSION] = PA_ACCESS_HOOK_EXTENSION,
+
+    /* SERVER -> CLIENT */
+    [PA_COMMAND_SUBSCRIBE_EVENT] = PA_ACCESS_HOOK_FILTER_SUBSCRIBE_EVENT
+};
+
+static int check_access(pa_native_connection *c, uint32_t command, uint32_t idx, pa_subscription_event_type_t event, const char *name) {
+    int res;
+    pa_access_data data;
+
+    data.client_index = c->client->index;
+    data.object_index = idx;
+    data.event = event;
+    data.name = name;
+    data.hook = map_table[command];
+
+    if (data.hook && pa_hook_fire(&c->protocol->core->access[data.hook], &data) == PA_HOOK_OK)
+        res = 1;
+    else
+        res = 0;
+
+    return res;
+}
-- 
2.1.0



More information about the pulseaudio-discuss mailing list