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:
Pauli Virtanen 2021-01-10 16:41:49 +02:00 committed by Wim Taymans
parent 09a690b123
commit 23f010541f
3 changed files with 156 additions and 3 deletions

View file

@ -230,7 +230,10 @@ process_messages(struct client_data *data)
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;
}
res = 0;
@ -759,7 +762,9 @@ process_remote(struct client *impl)
continue;
}
proxy->refcount++;
pw_protocol_native_connection_enter(conn);
res = demarshal[msg->opcode].func(proxy, msg);
pw_protocol_native_connection_leave(conn);
pw_proxy_unref(proxy);
if (res < 0) {

View file

@ -63,6 +63,12 @@ struct buffer {
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 pw_protocol_native_connection this;
struct pw_context *context;
@ -70,6 +76,9 @@ struct impl {
struct buffer in, out;
struct spa_pod_builder builder;
struct spa_list reenter_stack;
uint32_t pending_reentering;
uint32_t version;
size_t hdr_size;
};
@ -225,6 +234,116 @@ static void clear_buffer(struct buffer *buf, bool fds)
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
*
* \param fd the socket
@ -236,6 +355,7 @@ struct pw_protocol_native_connection *pw_protocol_native_connection_new(struct p
{
struct impl *impl;
struct pw_protocol_native_connection *this;
struct reenter_item *reenter_item;
impl = calloc(1, sizeof(struct impl));
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_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;
spa_list_init(&impl->reenter_stack);
spa_list_append(&impl->reenter_stack, &reenter_item->link);
return this;
no_mem:
free(impl->out.buffer_data);
free(impl->in.buffer_data);
free(reenter_item);
free(impl);
return NULL;
}
@ -298,6 +424,10 @@ void pw_protocol_native_connection_destroy(struct pw_protocol_native_connection
clear_buffer(&impl->in, true);
free(impl->out.buffer_data);
free(impl->in.buffer_data);
while (!spa_list_is_empty(&impl->reenter_stack))
pop_reenter_stack(impl, 1);
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);
int len, res;
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;
@ -396,7 +531,17 @@ pw_protocol_native_connection_get_next(struct pw_protocol_native_connection *con
if ((res = refill_buffer(conn, buf)) < 0)
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;
}

View file

@ -99,6 +99,9 @@ pw_protocol_native_connection_flush(struct pw_protocol_native_connection *conn);
int
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
} /* extern "C" */
#endif