[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