mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-12-16 08:56:45 -05:00
module-protocol-native: make demarshaling safe vs. reentering
The message structures returned by pw_protocol_native_connection_get_next point to data that is contained in the buffer of the connection. The data was invalidated when pw_protocol_native_connection_get_next was called the next time, which made the connection loop non-reentrant, in cases where it was re-entered from demarshal callbacks. Fix this by allocating new buffers when reentering and stashing the old buffers onto a stack. The returned message structure is also stored on the stack to make lifetimes to match.
This commit is contained in:
parent
09a690b123
commit
23f010541f
3 changed files with 156 additions and 3 deletions
|
|
@ -230,7 +230,10 @@ process_messages(struct client_data *data)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((res = demarshal[msg->opcode].func(resource, msg)) < 0)
|
pw_protocol_native_connection_enter(conn);
|
||||||
|
res = demarshal[msg->opcode].func(resource, msg);
|
||||||
|
pw_protocol_native_connection_leave(conn);
|
||||||
|
if (res < 0)
|
||||||
goto invalid_message;
|
goto invalid_message;
|
||||||
}
|
}
|
||||||
res = 0;
|
res = 0;
|
||||||
|
|
@ -759,7 +762,9 @@ process_remote(struct client *impl)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
proxy->refcount++;
|
proxy->refcount++;
|
||||||
|
pw_protocol_native_connection_enter(conn);
|
||||||
res = demarshal[msg->opcode].func(proxy, msg);
|
res = demarshal[msg->opcode].func(proxy, msg);
|
||||||
|
pw_protocol_native_connection_leave(conn);
|
||||||
pw_proxy_unref(proxy);
|
pw_proxy_unref(proxy);
|
||||||
|
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,12 @@ struct buffer {
|
||||||
struct pw_protocol_native_message msg;
|
struct pw_protocol_native_message msg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct reenter_item {
|
||||||
|
void *old_buffer_data;
|
||||||
|
struct pw_protocol_native_message return_msg;
|
||||||
|
struct spa_list link;
|
||||||
|
};
|
||||||
|
|
||||||
struct impl {
|
struct impl {
|
||||||
struct pw_protocol_native_connection this;
|
struct pw_protocol_native_connection this;
|
||||||
struct pw_context *context;
|
struct pw_context *context;
|
||||||
|
|
@ -70,6 +76,9 @@ struct impl {
|
||||||
struct buffer in, out;
|
struct buffer in, out;
|
||||||
struct spa_pod_builder builder;
|
struct spa_pod_builder builder;
|
||||||
|
|
||||||
|
struct spa_list reenter_stack;
|
||||||
|
uint32_t pending_reentering;
|
||||||
|
|
||||||
uint32_t version;
|
uint32_t version;
|
||||||
size_t hdr_size;
|
size_t hdr_size;
|
||||||
};
|
};
|
||||||
|
|
@ -225,6 +234,116 @@ static void clear_buffer(struct buffer *buf, bool fds)
|
||||||
buf->fds_offset = 0;
|
buf->fds_offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Prepare connection for calling from reentered context.
|
||||||
|
*
|
||||||
|
* This ensures that message buffers returned by get_next are not invalidated by additional
|
||||||
|
* calls made after enter. Leave invalidates the buffers at the higher stack level.
|
||||||
|
*
|
||||||
|
* \memberof pw_protocol_native_connection
|
||||||
|
*/
|
||||||
|
void pw_protocol_native_connection_enter(struct pw_protocol_native_connection *conn)
|
||||||
|
{
|
||||||
|
struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
|
||||||
|
|
||||||
|
/* Postpone processing until get_next is actually called */
|
||||||
|
++impl->pending_reentering;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pop_reenter_stack(struct impl *impl, uint32_t count)
|
||||||
|
{
|
||||||
|
while (count > 0) {
|
||||||
|
struct reenter_item *item;
|
||||||
|
|
||||||
|
item = spa_list_last(&impl->reenter_stack, struct reenter_item, link);
|
||||||
|
spa_list_remove(&item->link);
|
||||||
|
|
||||||
|
free(item->return_msg.fds);
|
||||||
|
free(item->old_buffer_data);
|
||||||
|
free(item);
|
||||||
|
|
||||||
|
--count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pw_protocol_native_connection_leave(struct pw_protocol_native_connection *conn)
|
||||||
|
{
|
||||||
|
struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
|
||||||
|
|
||||||
|
if (impl->pending_reentering > 0) {
|
||||||
|
--impl->pending_reentering;
|
||||||
|
} else {
|
||||||
|
pw_log_trace("connection %p: reenter: pop", impl);
|
||||||
|
pop_reenter_stack(impl, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ensure_stack_level(struct impl *impl, struct pw_protocol_native_message **msg)
|
||||||
|
{
|
||||||
|
void *data;
|
||||||
|
struct buffer *buf = &impl->in;
|
||||||
|
struct reenter_item *item, *new_item;
|
||||||
|
|
||||||
|
item = spa_list_last(&impl->reenter_stack, struct reenter_item, link);
|
||||||
|
|
||||||
|
if (SPA_LIKELY(impl->pending_reentering == 0)) {
|
||||||
|
new_item = item;
|
||||||
|
} else {
|
||||||
|
uint32_t new_count;
|
||||||
|
|
||||||
|
pw_log_trace("connection %p: reenter: push %d levels",
|
||||||
|
impl, impl->pending_reentering);
|
||||||
|
|
||||||
|
/* Append empty item(s) to the reenter stack */
|
||||||
|
for (new_count = 0; new_count < impl->pending_reentering; ++new_count) {
|
||||||
|
new_item = calloc(1, sizeof(struct reenter_item));
|
||||||
|
if (new_item == NULL) {
|
||||||
|
pop_reenter_stack(impl, new_count);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
spa_list_append(&impl->reenter_stack, &new_item->link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Stack level increased: we have to switch to a new message data buffer, because
|
||||||
|
* data of returned messages is contained in the buffer and might still be in
|
||||||
|
* use on the lower stack levels.
|
||||||
|
*
|
||||||
|
* We stash the buffer for the previous stack level, and allocate a new one for
|
||||||
|
* the new stack level. If there was a previous buffer for the previous level, we
|
||||||
|
* know its contents are no longer in use (the only active buffer at that stack
|
||||||
|
* level is buf->buffer_data), and we can recycle it as the new buffer (realloc
|
||||||
|
* instead of calloc).
|
||||||
|
*
|
||||||
|
* The current data contained in the buffer needs to be copied to the new buffer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
data = realloc(item->old_buffer_data, buf->buffer_maxsize);
|
||||||
|
if (data == NULL) {
|
||||||
|
pop_reenter_stack(impl, new_count);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
item->old_buffer_data = buf->buffer_data;
|
||||||
|
|
||||||
|
memcpy(data, buf->buffer_data, buf->buffer_size);
|
||||||
|
buf->buffer_data = data;
|
||||||
|
|
||||||
|
impl->pending_reentering = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure fds buffer is allocated */
|
||||||
|
if (SPA_UNLIKELY(new_item->return_msg.fds == NULL)) {
|
||||||
|
data = calloc(MAX_FDS, sizeof(int));
|
||||||
|
if (data == NULL)
|
||||||
|
return -ENOMEM;
|
||||||
|
new_item->return_msg.fds = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
*msg = &new_item->return_msg;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/** Make a new connection object for the given socket
|
/** Make a new connection object for the given socket
|
||||||
*
|
*
|
||||||
* \param fd the socket
|
* \param fd the socket
|
||||||
|
|
@ -236,6 +355,7 @@ struct pw_protocol_native_connection *pw_protocol_native_connection_new(struct p
|
||||||
{
|
{
|
||||||
struct impl *impl;
|
struct impl *impl;
|
||||||
struct pw_protocol_native_connection *this;
|
struct pw_protocol_native_connection *this;
|
||||||
|
struct reenter_item *reenter_item;
|
||||||
|
|
||||||
impl = calloc(1, sizeof(struct impl));
|
impl = calloc(1, sizeof(struct impl));
|
||||||
if (impl == NULL)
|
if (impl == NULL)
|
||||||
|
|
@ -259,14 +379,20 @@ struct pw_protocol_native_connection *pw_protocol_native_connection_new(struct p
|
||||||
impl->in.buffer_data = calloc(1, MAX_BUFFER_SIZE);
|
impl->in.buffer_data = calloc(1, MAX_BUFFER_SIZE);
|
||||||
impl->in.buffer_maxsize = MAX_BUFFER_SIZE;
|
impl->in.buffer_maxsize = MAX_BUFFER_SIZE;
|
||||||
|
|
||||||
if (impl->out.buffer_data == NULL || impl->in.buffer_data == NULL)
|
reenter_item = calloc(1, sizeof(struct reenter_item));
|
||||||
|
|
||||||
|
if (impl->out.buffer_data == NULL || impl->in.buffer_data == NULL || reenter_item == NULL)
|
||||||
goto no_mem;
|
goto no_mem;
|
||||||
|
|
||||||
|
spa_list_init(&impl->reenter_stack);
|
||||||
|
spa_list_append(&impl->reenter_stack, &reenter_item->link);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
no_mem:
|
no_mem:
|
||||||
free(impl->out.buffer_data);
|
free(impl->out.buffer_data);
|
||||||
free(impl->in.buffer_data);
|
free(impl->in.buffer_data);
|
||||||
|
free(reenter_item);
|
||||||
free(impl);
|
free(impl);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -298,6 +424,10 @@ void pw_protocol_native_connection_destroy(struct pw_protocol_native_connection
|
||||||
clear_buffer(&impl->in, true);
|
clear_buffer(&impl->in, true);
|
||||||
free(impl->out.buffer_data);
|
free(impl->out.buffer_data);
|
||||||
free(impl->in.buffer_data);
|
free(impl->in.buffer_data);
|
||||||
|
|
||||||
|
while (!spa_list_is_empty(&impl->reenter_stack))
|
||||||
|
pop_reenter_stack(impl, 1);
|
||||||
|
|
||||||
free(impl);
|
free(impl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -381,6 +511,11 @@ pw_protocol_native_connection_get_next(struct pw_protocol_native_connection *con
|
||||||
struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
|
struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
|
||||||
int len, res;
|
int len, res;
|
||||||
struct buffer *buf;
|
struct buffer *buf;
|
||||||
|
struct pw_protocol_native_message *return_msg;
|
||||||
|
int *fds;
|
||||||
|
|
||||||
|
if ((res = ensure_stack_level(impl, &return_msg)) < 0)
|
||||||
|
return res;
|
||||||
|
|
||||||
buf = &impl->in;
|
buf = &impl->in;
|
||||||
|
|
||||||
|
|
@ -396,7 +531,17 @@ pw_protocol_native_connection_get_next(struct pw_protocol_native_connection *con
|
||||||
if ((res = refill_buffer(conn, buf)) < 0)
|
if ((res = refill_buffer(conn, buf)) < 0)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
*msg = &buf->msg;
|
|
||||||
|
/* Returned msg struct should be safe vs. reentering */
|
||||||
|
fds = return_msg->fds;
|
||||||
|
*return_msg = buf->msg;
|
||||||
|
if (buf->msg.n_fds > 0) {
|
||||||
|
memcpy(fds, buf->msg.fds, buf->msg.n_fds * sizeof(int));
|
||||||
|
}
|
||||||
|
return_msg->fds = fds;
|
||||||
|
|
||||||
|
*msg = return_msg;
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,9 @@ pw_protocol_native_connection_flush(struct pw_protocol_native_connection *conn);
|
||||||
int
|
int
|
||||||
pw_protocol_native_connection_clear(struct pw_protocol_native_connection *conn);
|
pw_protocol_native_connection_clear(struct pw_protocol_native_connection *conn);
|
||||||
|
|
||||||
|
void pw_protocol_native_connection_enter(struct pw_protocol_native_connection *conn);
|
||||||
|
void pw_protocol_native_connection_leave(struct pw_protocol_native_connection *conn);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} /* extern "C" */
|
} /* extern "C" */
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue