spa: add spa_alloca that does overflow and limit checks

Make a function like alloca but with overflow checks and a max
allocation size.

Use this function where we can and also make sure that all alloca calls
are in some way limited.
This commit is contained in:
Wim Taymans 2026-04-27 10:53:44 +02:00
parent a9f1ad414e
commit ed2c0ad4ee
10 changed files with 84 additions and 51 deletions

View file

@ -457,6 +457,16 @@ struct spa_error_location {
_strp; \ _strp; \
}) })
#define spa_alloca(n, size, max_size) \
({ \
void *_res = NULL; \
if ((size_t)n > (size_t)max_size / (size_t)size) \
errno = ENOMEM; \
else \
_res = alloca((size_t)n * (size_t)size); \
_res; \
})
/** /**
* \} * \}
*/ */

View file

@ -35,6 +35,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.audioadapter");
#define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1) #define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1)
#define MAX_RETRY 64 #define MAX_RETRY 64
#define MAX_BLOCKS 4096
/** \cond */ /** \cond */
@ -356,6 +357,7 @@ static void emit_node_info(struct impl *this, bool full)
if (this->info.props) if (this->info.props)
n_items = this->info.props->n_items; n_items = this->info.props->n_items;
n_items = SPA_MIN(n_items, 1024u);
items = alloca((n_items + 2) * sizeof(struct spa_dict_item)); items = alloca((n_items + 2) * sizeof(struct spa_dict_item));
for (i = 0; i < n_items; i++) for (i = 0; i < n_items; i++)
items[i] = this->info.props->items[i]; items[i] = this->info.props->items[i];
@ -513,6 +515,9 @@ static int negotiate_buffers(struct impl *this)
align = SPA_MAX(align, this->max_align); align = SPA_MAX(align, this->max_align);
if (blocks > MAX_BLOCKS)
return -ENOMEM;
datas = alloca(sizeof(struct spa_data) * blocks); datas = alloca(sizeof(struct spa_data) * blocks);
memset(datas, 0, sizeof(struct spa_data) * blocks); memset(datas, 0, sizeof(struct spa_data) * blocks);
aligns = alloca(sizeof(uint32_t) * blocks); aligns = alloca(sizeof(uint32_t) * blocks);
@ -1942,11 +1947,12 @@ static int load_converter(struct impl *this, const struct spa_dict *info,
struct spa_dict_item *items; struct spa_dict_item *items;
struct spa_dict cinfo; struct spa_dict cinfo;
char direction[16]; char direction[16];
uint32_t i; uint32_t i, n_items;
items = alloca((info->n_items + 1) * sizeof(struct spa_dict_item)); n_items = SPA_MIN(info->n_items, 1024u);
items = alloca((n_items + 1) * sizeof(struct spa_dict_item));
cinfo = SPA_DICT(items, 0); cinfo = SPA_DICT(items, 0);
for (i = 0; i < info->n_items; i++) for (i = 0; i < n_items; i++)
items[cinfo.n_items++] = info->items[i]; items[cinfo.n_items++] = info->items[i];
snprintf(direction, sizeof(direction), "%s", snprintf(direction, sizeof(direction), "%s",

View file

@ -34,6 +34,9 @@
#define MAXLENGTH (4u*1024*1024) /* 4MB */ #define MAXLENGTH (4u*1024*1024) /* 4MB */
/* pulseaudio has a 128 char limit for this but we can allow more */
#define MAX_NAME 1024u
#define SCACHE_ENTRY_SIZE_MAX (1024*1024*16) #define SCACHE_ENTRY_SIZE_MAX (1024*1024*16)
#define MODULE_INDEX_MASK 0xfffffffu #define MODULE_INDEX_MASK 0xfffffffu

View file

@ -532,12 +532,13 @@ static void add_stream_group(struct message *m, struct spa_dict *dict, const cha
else else
return; return;
write_string(m, key);
l = strlen(prefix) + strlen(id) + strlen(str) + 6; /* "-by-" , ":" and \0 */ l = strlen(prefix) + strlen(id) + strlen(str) + 6; /* "-by-" , ":" and \0 */
if (l < 0 || l > 1024) if (l < 0 || l > 4096)
return; return;
write_string(m, key);
b = alloca(l); b = alloca(l);
snprintf(b, l, "%s-by-%s:%s", prefix, id, str); spa_scnprintf(b, l, "%s-by-%s:%s", prefix, id, str);
write_u32(m, l); write_u32(m, l);
write_arbitrary(m, b, l); write_arbitrary(m, b, l);
} }

View file

@ -753,8 +753,11 @@ static int reply_create_record_stream(struct stream *stream, struct pw_manager_o
peer_index = peer->index; peer_index = peer->index;
if (!pw_manager_object_is_source(peer)) { if (!pw_manager_object_is_source(peer)) {
size_t len = (name ? strlen(name) : 5) + 10; size_t len = (name ? strlen(name) : 5) + 10;
peer_name = tmp = alloca(len); if (len <= MAX_NAME) {
snprintf(tmp, len, "%s.monitor", name ? name : "sink"); peer_name = tmp = alloca(len);
spa_scnprintf(tmp, len, "%s.monitor", name ? name : "sink");
} else
peer_name = NULL;
} else { } else {
peer_name = name; peer_name = name;
} }
@ -866,7 +869,7 @@ static void manager_added(void *data, struct pw_manager_object *o)
size_t len = strlen(peer_name) + 10; size_t len = strlen(peer_name) + 10;
if (len <= 1024) { if (len <= 1024) {
char *tmp = alloca(len); char *tmp = alloca(len);
snprintf(tmp, len, "%s.monitor", peer_name); spa_scnprintf(tmp, len, "%s.monitor", peer_name);
peer_name = tmp; peer_name = tmp;
} }
} }
@ -3174,9 +3177,8 @@ static int do_set_port_latency_offset(struct client *client, uint32_t command, u
return -ENOENT; return -ENOENT;
collect_card_info(card, &card_info); collect_card_info(card, &card_info);
if (card_info.n_ports > MAX_ALLOCA_SIZE / sizeof(*port_info)) if ((port_info = spa_alloca(card_info.n_ports, sizeof(*port_info), MAX_ALLOCA_SIZE)) == NULL)
return -ENOMEM; return -errno;
port_info = alloca(card_info.n_ports * sizeof(*port_info));
card_info.active_profile = SPA_ID_INVALID; card_info.active_profile = SPA_ID_INVALID;
n_ports = collect_port_info(card, &card_info, NULL, port_info); n_ports = collect_port_info(card, &card_info, NULL, port_info);
@ -3315,9 +3317,9 @@ static int do_remove_proplist(struct client *client, uint32_t command, uint32_t
} }
dict.n_items = props->dict.n_items; dict.n_items = props->dict.n_items;
if (dict.n_items > MAX_ALLOCA_SIZE / sizeof(struct spa_dict_item)) if ((dict.items = items = spa_alloca(dict.n_items,
return -ENOMEM; sizeof(struct spa_dict_item), MAX_ALLOCA_SIZE)) == NULL)
dict.items = items = alloca(sizeof(struct spa_dict_item) * dict.n_items); return -errno;
for (i = 0; i < dict.n_items; i++) { for (i = 0; i < dict.n_items; i++) {
items[i].key = props->dict.items[i].key; items[i].key = props->dict.items[i].key;
items[i].value = NULL; items[i].value = NULL;
@ -3600,9 +3602,8 @@ static int fill_card_info(struct client *client, struct message *m,
TAG_U32, card_info.n_profiles, /* n_profiles */ TAG_U32, card_info.n_profiles, /* n_profiles */
TAG_INVALID); TAG_INVALID);
if (card_info.n_profiles > MAX_ALLOCA_SIZE / sizeof(*profile_info)) if ((profile_info = spa_alloca(card_info.n_profiles, sizeof(*profile_info), MAX_ALLOCA_SIZE)) == NULL)
return -ENOMEM; return -errno;
profile_info = alloca(card_info.n_profiles * sizeof(*profile_info));
n_profiles = collect_profile_info(o, &card_info, profile_info); n_profiles = collect_profile_info(o, &card_info, profile_info);
for (n = 0; n < n_profiles; n++) { for (n = 0; n < n_profiles; n++) {
@ -3631,9 +3632,8 @@ static int fill_card_info(struct client *client, struct message *m,
uint32_t n_ports; uint32_t n_ports;
struct port_info *port_info, *pi; struct port_info *port_info, *pi;
if (card_info.n_ports > MAX_ALLOCA_SIZE / sizeof(*port_info)) if ((port_info = spa_alloca(card_info.n_ports, sizeof(*port_info), MAX_ALLOCA_SIZE)) == NULL)
return -ENOMEM; return -errno;
port_info = alloca(card_info.n_ports * sizeof(*port_info));
card_info.active_profile = SPA_ID_INVALID; card_info.active_profile = SPA_ID_INVALID;
n_ports = collect_port_info(o, &card_info, NULL, port_info); n_ports = collect_port_info(o, &card_info, NULL, port_info);
@ -3649,8 +3649,7 @@ static int fill_card_info(struct client *client, struct message *m,
pi = &port_info[n]; pi = &port_info[n];
if (pi->info && pi->n_props > 0 && if (pi->info && pi->n_props > 0 &&
pi->n_props <= MAX_ALLOCA_SIZE / sizeof(*items)) { (items = spa_alloca(pi->n_props, sizeof(*items), MAX_ALLOCA_SIZE)) != NULL) {
items = alloca(pi->n_props * sizeof(*items));
dict.items = items; dict.items = items;
pdict = collect_props(pi->info, &dict); pdict = collect_props(pi->info, &dict);
} }
@ -3757,7 +3756,7 @@ static int fill_sink_info(struct client *client, struct message *m,
if (name == NULL) if (name == NULL)
name = "unknown"; name = "unknown";
size = strlen(name) + 10; size = SPA_MIN(strlen(name) + 10, MAX_NAME);
monitor_name = alloca(size); monitor_name = alloca(size);
if (pw_manager_object_is_source(o)) if (pw_manager_object_is_source(o))
snprintf(monitor_name, size, "%s", name); snprintf(monitor_name, size, "%s", name);
@ -3838,9 +3837,8 @@ static int fill_sink_info(struct client *client, struct message *m,
uint32_t n_ports, n; uint32_t n_ports, n;
struct port_info *port_info, *pi; struct port_info *port_info, *pi;
if (card_info.n_ports > MAX_ALLOCA_SIZE / sizeof(*port_info)) if ((port_info = spa_alloca(card_info.n_ports, sizeof(*port_info), MAX_ALLOCA_SIZE)) == NULL)
return -ENOMEM; return -errno;
port_info = alloca(card_info.n_ports * sizeof(*port_info));
n_ports = collect_port_info(card, &card_info, &dev_info, port_info); n_ports = collect_port_info(card, &card_info, &dev_info, port_info);
message_put(m, message_put(m,
@ -3956,11 +3954,11 @@ static int fill_source_info(struct client *client, struct message *m,
if (name == NULL) if (name == NULL)
name = "unknown"; name = "unknown";
size = strlen(name) + 10; size = SPA_MIN(strlen(name) + 10, MAX_NAME);
monitor_name = alloca(size); monitor_name = alloca(size);
snprintf(monitor_name, size, "%s.monitor", name); snprintf(monitor_name, size, "%s.monitor", name);
size = strlen(desc) + 20; size = SPA_MIN(strlen(desc) + 20, MAX_NAME);
monitor_desc = alloca(size); monitor_desc = alloca(size);
snprintf(monitor_desc, size, "Monitor of %s", desc); snprintf(monitor_desc, size, "Monitor of %s", desc);
@ -4036,9 +4034,8 @@ static int fill_source_info(struct client *client, struct message *m,
uint32_t n_ports, n; uint32_t n_ports, n;
struct port_info *port_info, *pi; struct port_info *port_info, *pi;
if (card_info.n_ports > MAX_ALLOCA_SIZE / sizeof(*port_info)) if ((port_info = spa_alloca(card_info.n_ports, sizeof(*port_info), MAX_ALLOCA_SIZE)) == NULL)
return -ENOMEM; return -errno;
port_info = alloca(card_info.n_ports * sizeof(*port_info));
n_ports = collect_port_info(card, &card_info, &dev_info, port_info); n_ports = collect_port_info(card, &card_info, &dev_info, port_info);
message_put(m, message_put(m,
@ -4103,12 +4100,10 @@ static int fill_node_info_proplist(struct message *m, const struct spa_dict *nod
n_items += client_props->n_items; n_items += client_props->n_items;
} }
if (n_items > MAX_ALLOCA_SIZE / sizeof(struct spa_dict_item)) if ((dict.items = items = spa_alloca(n_items, sizeof(struct spa_dict_item), MAX_ALLOCA_SIZE)) == NULL)
return -ENOMEM; return -errno;
dict.n_items = n = 0; dict.n_items = n = 0;
dict.items = items = alloca(n_items * sizeof(struct spa_dict_item));
spa_dict_for_each(it, node_props) spa_dict_for_each(it, node_props)
items[n++] = *it; items[n++] = *it;
dict.n_items = n; dict.n_items = n;

View file

@ -560,12 +560,14 @@ static int handle_server_hello(struct client *client, struct spa_json *payload)
while ((l = spa_json_object_next(payload, key, sizeof(key), &v)) > 0) { while ((l = spa_json_object_next(payload, key, sizeof(key), &v)) > 0) {
if (spa_streq(key, "server_id")) { if (spa_streq(key, "server_id")) {
t = alloca(l+1); if ((t = spa_alloca(1, l+1, 1024)) == NULL)
return -errno;
spa_json_parse_stringn(v, l, t, l+1); spa_json_parse_stringn(v, l, t, l+1);
pw_properties_set(client->props, "sendspin.server-id", t); pw_properties_set(client->props, "sendspin.server-id", t);
} }
else if (spa_streq(key, "name")) { else if (spa_streq(key, "name")) {
t = alloca(l+1); if ((t = spa_alloca(1, l+1, 1024)) == NULL)
return -errno;
spa_json_parse_stringn(v, l, t, l+1); spa_json_parse_stringn(v, l, t, l+1);
pw_properties_set(client->props, "sendspin.server-name", t); pw_properties_set(client->props, "sendspin.server-name", t);
} }
@ -579,7 +581,8 @@ static int handle_server_hello(struct client *client, struct spa_json *payload)
spa_json_enter(payload, &it[0]); spa_json_enter(payload, &it[0]);
while ((l = spa_json_next(&it[0], &v)) > 0) { while ((l = spa_json_next(&it[0], &v)) > 0) {
t = alloca(l+1); if ((t = spa_alloca(1, l+1, 128)) == NULL)
continue;
spa_json_parse_stringn(v, l, t, l+1); spa_json_parse_stringn(v, l, t, l+1);
if (spa_streq(t, "player@v1")) if (spa_streq(t, "player@v1"))
@ -589,7 +592,8 @@ static int handle_server_hello(struct client *client, struct spa_json *payload)
} }
} }
else if (spa_streq(key, "connection_reason")) { else if (spa_streq(key, "connection_reason")) {
t = alloca(l+1); if ((t = spa_alloca(1, l+1, 4096)) == NULL)
return -errno;
spa_json_parse_stringn(v, l, t, l+1); spa_json_parse_stringn(v, l, t, l+1);
if (spa_streq(t, "discovery")) if (spa_streq(t, "discovery"))

View file

@ -745,7 +745,8 @@ static int parse_player_v1_support(struct client *c, struct spa_json *payload)
spa_json_enter(payload, &it[0]); spa_json_enter(payload, &it[0]);
while ((l = spa_json_next(&it[0], &v)) > 0) { while ((l = spa_json_next(&it[0], &v)) > 0) {
t = alloca(l+1); if ((t = spa_alloca(1, l+1, 64)) == NULL)
continue;
spa_json_parse_stringn(v, l, t, l+1); spa_json_parse_stringn(v, l, t, l+1);
if (spa_streq(t, "volume")) if (spa_streq(t, "volume"))
c->supported_commands |= COMMAND_VOLUME; c->supported_commands |= COMMAND_VOLUME;
@ -766,12 +767,14 @@ static int handle_client_hello(struct client *c, struct spa_json *payload)
while ((l = spa_json_object_next(payload, key, sizeof(key), &v)) > 0) { while ((l = spa_json_object_next(payload, key, sizeof(key), &v)) > 0) {
if (spa_streq(key, "client_id")) { if (spa_streq(key, "client_id")) {
t = alloca(l+1); if ((t = spa_alloca(1, l+1, 1024)) == NULL)
return -errno;
spa_json_parse_stringn(v, l, t, l+1); spa_json_parse_stringn(v, l, t, l+1);
pw_properties_set(c->props, "sendspin.client-id", t); pw_properties_set(c->props, "sendspin.client-id", t);
} }
else if (spa_streq(key, "name")) { else if (spa_streq(key, "name")) {
t = alloca(l+1); if ((t = spa_alloca(1, l+1, 1024)) == NULL)
return -errno;
spa_json_parse_stringn(v, l, t, l+1); spa_json_parse_stringn(v, l, t, l+1);
pw_properties_set(c->props, "sendspin.client-name", t); pw_properties_set(c->props, "sendspin.client-name", t);
} }
@ -785,7 +788,8 @@ static int handle_client_hello(struct client *c, struct spa_json *payload)
spa_json_enter(payload, &it[0]); spa_json_enter(payload, &it[0]);
while ((l = spa_json_next(&it[0], &v)) > 0) { while ((l = spa_json_next(&it[0], &v)) > 0) {
t = alloca(l+1); if ((t = spa_alloca(1, l+1, 64)) == NULL)
continue;
spa_json_parse_stringn(v, l, t, l+1); spa_json_parse_stringn(v, l, t, l+1);
if (spa_streq(t, "player@v1")) if (spa_streq(t, "player@v1"))

View file

@ -252,6 +252,9 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags,
if ((res = param_filter(result, &input, &output, SPA_PARAM_Meta, &b)) > 0) if ((res = param_filter(result, &input, &output, SPA_PARAM_Meta, &b)) > 0)
n_params += res; n_params += res;
if (n_params > 4096)
return -EINVAL;
metas = alloca(sizeof(struct spa_meta) * n_params * 2); metas = alloca(sizeof(struct spa_meta) * n_params * 2);
n_metas = 0; n_metas = 0;

View file

@ -359,6 +359,8 @@ int pw_conf_save_state(const char *prefix, const char *name, const struct pw_pro
return sfd; return sfd;
size_t tmp_name_size = strlen(name) + 5; size_t tmp_name_size = strlen(name) + 5;
if (tmp_name_size > PATH_MAX)
return -EINVAL;
tmp_name = alloca(tmp_name_size); tmp_name = alloca(tmp_name_size);
snprintf(tmp_name, tmp_name_size, "%s.tmp", name); snprintf(tmp_name, tmp_name_size, "%s.tmp", name);
if ((fd = openat(sfd, tmp_name, O_CLOEXEC | O_CREAT | O_WRONLY | O_TRUNC, 0600)) < 0) { if ((fd = openat(sfd, tmp_name, O_CLOEXEC | O_CREAT | O_WRONLY | O_TRUNC, 0600)) < 0) {

View file

@ -31,6 +31,8 @@ PW_LOG_TOPIC_EXTERN(log_node);
#define DEFAULT_SYNC_TIMEOUT ((uint64_t)(5 * SPA_NSEC_PER_SEC)) #define DEFAULT_SYNC_TIMEOUT ((uint64_t)(5 * SPA_NSEC_PER_SEC))
#define MAX_COMMAND (64*1024u)
/** \cond */ /** \cond */
struct impl { struct impl {
struct pw_impl_node this; struct pw_impl_node this;
@ -1959,14 +1961,17 @@ static void node_event(void *data, const struct spa_event *event)
size_t size = SPA_POD_SIZE(&event->pod); size_t size = SPA_POD_SIZE(&event->pod);
/* turn the event and all the arguments into a command */ /* turn the event and all the arguments into a command */
command = alloca(size); if ((command = spa_alloca(1, size, MAX_COMMAND)) != NULL) {
memcpy(command, event, size); memcpy(command, event, size);
command->body.body.type = SPA_TYPE_COMMAND_Node; command->body.body.type = SPA_TYPE_COMMAND_Node;
command->body.body.id = SPA_NODE_COMMAND_RequestProcess; command->body.body.id = SPA_NODE_COMMAND_RequestProcess;
/* send the request process to the driver but only on the /* send the request process to the driver but only on the
* server size */ * server size */
handle_request_process_command(node->driver_node, command); handle_request_process_command(node->driver_node, command);
} else {
pw_log_warn("%p: ignore large command", node);
}
} }
break; break;
default: default: