buffer: introduce wlr_buffer_acquire()

A footgun in the wlr_buffer API is that there's no difference
between acquiring a buffer and increasing the lock count. In other
words, transitioning a buffer from the released state to the
acquired state is not explicit. This may result in hard-to-debug
failures if there is a dangling released wlr_buffer somewhere
(e.g. wlr_client_buffer.source [1]) and some piece of code calls
wlr_buffer_lock(). In that case, the buffer will be acquired and
released again. In the context of a wlr_buffer issued from a
Wayland protocol wl_buffer object, this can cause the underlying
memory to be used after wl_buffer.release has been sent to the
client, and a double wl_buffer.release event to be sent.

Make it so acquiring a buffer is an explicit operation to make sure
the caller means the state transition and is prepared for a new
release event. wlr_buffer_acquire() forbids calls on already-acquired
buffers, and wlr_buffer_lock() now forbids calls on released buffers.

[1]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4904
This commit is contained in:
Simon Ser 2024-11-21 17:30:27 +01:00
parent f233d25e86
commit 30ccac3a02
5 changed files with 32 additions and 6 deletions

View file

@ -45,7 +45,14 @@ void wlr_buffer_drop(struct wlr_buffer *buffer) {
buffer_consider_destroy(buffer);
}
struct wlr_buffer *wlr_buffer_acquire(struct wlr_buffer *buffer) {
assert(buffer->n_locks == 0);
buffer->n_locks++;
return buffer;
}
struct wlr_buffer *wlr_buffer_lock(struct wlr_buffer *buffer) {
assert(buffer->n_locks > 0);
buffer->n_locks++;
return buffer;
}

View file

@ -89,7 +89,7 @@ struct wlr_client_buffer *wlr_client_buffer_create(struct wlr_buffer *buffer,
client_buffer->renderer_destroy.notify = client_buffer_handle_renderer_destroy;
// Ensure the buffer will be released before being destroyed
wlr_buffer_lock(&client_buffer->base);
wlr_buffer_acquire(&client_buffer->base);
wlr_buffer_drop(&client_buffer->base);
return client_buffer;

View file

@ -57,5 +57,12 @@ struct wlr_buffer *wlr_buffer_try_from_resource(struct wl_resource *resource) {
return NULL;
}
return wlr_buffer_lock(buffer);
// If the buffer is released, we want to acquire it. If the buffer is
// already acquired (e.g. because it has been attached to another surface),
// we want to lock it.
if (buffer->n_locks > 0) {
return wlr_buffer_lock(buffer);
} else {
return wlr_buffer_acquire(buffer);
}
}