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