This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch wl/browser-all
in repository enlightenment.
View the commit online.
commit 8adc776bca17b417b0b7acb5ce1e24f6674515f2
Author: Cedric BAIL <[email protected]>
AuthorDate: Sun Aug 9 10:02:16 2026 -0600
e_comp_wl - survive a client shrinking its shm pool
A client may ftruncate the file behind its wl_shm pool after we have mapped
it. Reading past the new end of the file raises SIGBUS, whose default
disposition kills the compositor.
libwayland offers wl_shm_buffer_begin_access()/end_access() for exactly this
and Weston wraps its texture uploads in them, but that does not fit here: we
hand evas a pointer and evas reads it later during rendering, so there is no
bracket to put the access inside. (It is also process-global and does not
nest, which is why wlcs's SecondBadBufferTest is disabled upstream.)
So take wlroots' approach and own the handler. Ranges we have handed out are
recorded; a fault inside one gets an anonymous zero page mapped over the
faulting page, so the read completes with garbage instead of dying, and the
client is remembered. Posting a protocol error is not async-signal-safe, so
the handler only records it and the post-render hook drains it. A fault
outside any known range is chained to whatever handler was installed before
us, or the default restored, rather than swallowed.
Honesty about what this is worth: it is unexercised hardening. I have not
observed E crash on this. wlcs's BadBufferTest.test_truncated_shm_file
reports "Expected protocol error not raised" both before and after the
change - the compositor survives either way, and the test still fails
because we never notice the truncation rather than because we die of it.
The likely reason is that the headless buffer backend never really reads the
pixels; a GL or DRM backend plausibly would, which is the case this guards.
So: the earlier claim that this was a trivially reachable denial of service
was inferred from the missing begin_access, not measured, and the
measurement does not support it on this backend. The guard is cheap and
correct-by-construction, but it should not be counted as a fix for an
observed crash, and the wlcs test needs the fault to actually happen before
it can pass.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01FtoiXoSKUmZb6Aix6U3GZS
---
src/bin/e_comp_canvas.c | 7 +++
src/bin/e_comp_wl.c | 142 ++++++++++++++++++++++++++++++++++++++++++++++++
src/bin/e_comp_wl.h | 3 +
src/bin/e_pixmap.c | 12 ++++
4 files changed, 164 insertions(+)
diff --git a/src/bin/e_comp_canvas.c b/src/bin/e_comp_canvas.c
index ad256d340..215ae8d3a 100644
--- a/src/bin/e_comp_canvas.c
+++ b/src/bin/e_comp_canvas.c
@@ -84,6 +84,13 @@ static void
_e_comp_canvas_render_track_post(void *data EINA_UNUSED, Evas *e EINA_UNUSED, void *event_info EINA_UNUSED)
{
E_Comp_Config *conf = e_comp_config_get();
+
+ /* Rendering is where a shm buffer actually gets read, so it is where a
+ * client that shrank its pool will have faulted. The handler cannot talk
+ * wayland from signal context, so it only records the client; tell it we
+ * are back on solid ground and it can post the error. */
+ e_comp_wl_shm_fault_check();
+
if (conf->fps_show)
{
int info[4] = { E_COMP_FRAME_EVENT_RENDER_END, 0, 0, 0 };
diff --git a/src/bin/e_comp_wl.c b/src/bin/e_comp_wl.c
index 9d3ba65d7..027b6ce8b 100644
--- a/src/bin/e_comp_wl.c
+++ b/src/bin/e_comp_wl.c
@@ -1,5 +1,7 @@
#define E_COMP_WL
#include "e.h"
+#include <signal.h>
+#include <sys/mman.h>
/* handle include for printing uint64_t */
#define __STDC_FORMAT_MACROS
@@ -3250,6 +3252,146 @@ e_comp_wl_subsurface_commit(E_Client *ec)
* @param resource that owns the desired buffer
* @returns a new E_Comp_Wl_Buffer object
*/
+
+/* --- shm SIGBUS protection ------------------------------------------------
+ *
+ * A client may shrink the file behind its wl_shm pool at any time, including
+ * after we have mapped it. Reading past the new end of the file raises SIGBUS,
+ * which by default kills the compositor - a denial of service any client can
+ * trigger against its own buffer.
+ *
+ * libwayland offers wl_shm_buffer_begin_access()/end_access() for this, and
+ * that is what Weston wraps around its texture uploads. It does not fit here:
+ * we hand evas a pointer and evas reads it later, during rendering, so there
+ * is no bracket we can put the access inside. (It is also process-global and
+ * does not nest, which is why wlcs's SecondBadBufferTest is disabled upstream.)
+ *
+ * So take wlroots' approach instead and own the handler. We keep the set of
+ * shm ranges we have handed out, and on a fault inside one of them we map an
+ * anonymous zero page over the faulting page: the read completes with garbage
+ * rather than dying, the client is remembered, and an idler posts the protocol
+ * error once we are out of signal context. A fault anywhere else is passed to
+ * whatever handler was installed before us.
+ */
+typedef struct
+{
+ uintptr_t start, end;
+ struct wl_resource *resource;
+} E_Comp_Wl_Shm_Range;
+
+static Eina_List *_shm_ranges = NULL;
+static struct sigaction _shm_prev_sigbus;
+static Eina_Bool _shm_sigbus_installed = EINA_FALSE;
+static struct wl_resource * volatile _shm_faulted = NULL;
+static Ecore_Idler *_shm_fault_idler = NULL;
+
+static void
+_e_comp_wl_shm_sigbus(int sig, siginfo_t *info, void *context)
+{
+ uintptr_t addr = (uintptr_t)info->si_addr;
+ long pagesize = sysconf(_SC_PAGESIZE);
+ Eina_List *l;
+ E_Comp_Wl_Shm_Range *r;
+
+ /* async-signal-safe only: no malloc, no logging, no wayland calls. Walking
+ * a list we only ever mutate from the main thread is safe because a signal
+ * handler runs on that same thread. */
+ EINA_LIST_FOREACH(_shm_ranges, l, r)
+ {
+ if ((addr < r->start) || (addr >= r->end)) continue;
+
+ if (mmap((void *)(addr & ~((uintptr_t)pagesize - 1)), (size_t)pagesize,
+ PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
+ -1, 0) == MAP_FAILED)
+ break; /* nothing left to try; fall through to the old handler */
+
+ _shm_faulted = r->resource;
+ return;
+ }
+
+ if (_shm_prev_sigbus.sa_flags & SA_SIGINFO)
+ _shm_prev_sigbus.sa_sigaction(sig, info, context);
+ else if ((_shm_prev_sigbus.sa_handler != SIG_DFL) &&
+ (_shm_prev_sigbus.sa_handler != SIG_IGN))
+ _shm_prev_sigbus.sa_handler(sig);
+ else
+ {
+ /* Not ours and nobody else wants it: restore the default and let it
+ * happen, rather than spinning forever on the same faulting insn. */
+ signal(SIGBUS, SIG_DFL);
+ }
+}
+
+static Eina_Bool
+_e_comp_wl_shm_fault_idler(void *data EINA_UNUSED)
+{
+ struct wl_resource *res = _shm_faulted;
+
+ _shm_fault_idler = NULL;
+ _shm_faulted = NULL;
+ if (res)
+ {
+ ERR("shm buffer read faulted: client shrank the file behind its pool");
+ wl_resource_post_error(res, WL_SHM_ERROR_INVALID_FD,
+ "the file behind this pool was shrunk after the "
+ "buffer was created");
+ }
+ return ECORE_CALLBACK_CANCEL;
+}
+
+/* Called from the render loop side; see e_pixmap.c. */
+E_API void
+e_comp_wl_shm_access_register(struct wl_resource *resource, void *data, size_t len)
+{
+ E_Comp_Wl_Shm_Range *r;
+
+ if ((!data) || (!len)) return;
+
+ if (!_shm_sigbus_installed)
+ {
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_sigaction = _e_comp_wl_shm_sigbus;
+ sa.sa_flags = SA_SIGINFO | SA_NODEFER;
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(SIGBUS, &sa, &_shm_prev_sigbus) != 0)
+ {
+ ERR("could not install a SIGBUS handler for shm buffers");
+ return;
+ }
+ _shm_sigbus_installed = EINA_TRUE;
+ }
+
+ r = E_NEW(E_Comp_Wl_Shm_Range, 1);
+ if (!r) return;
+ r->start = (uintptr_t)data;
+ r->end = r->start + len;
+ r->resource = resource;
+ _shm_ranges = eina_list_append(_shm_ranges, r);
+}
+
+E_API void
+e_comp_wl_shm_access_unregister(void *data)
+{
+ Eina_List *l, *ll;
+ E_Comp_Wl_Shm_Range *r;
+
+ EINA_LIST_FOREACH_SAFE(_shm_ranges, l, ll, r)
+ {
+ if (r->start != (uintptr_t)data) continue;
+ _shm_ranges = eina_list_remove_list(_shm_ranges, l);
+ free(r);
+ }
+}
+
+E_API void
+e_comp_wl_shm_fault_check(void)
+{
+ if (_shm_faulted && (!_shm_fault_idler))
+ _shm_fault_idler = ecore_idler_add(_e_comp_wl_shm_fault_idler, NULL);
+}
+
E_API E_Comp_Wl_Buffer *
e_comp_wl_buffer_get(struct wl_resource *resource)
{
diff --git a/src/bin/e_comp_wl.h b/src/bin/e_comp_wl.h
index a18746c5d..d56799ff5 100644
--- a/src/bin/e_comp_wl.h
+++ b/src/bin/e_comp_wl.h
@@ -405,6 +405,9 @@ EINTERN void e_comp_wl_surface_destroy(struct wl_resource *resource);
EINTERN Eina_Bool e_comp_wl_surface_commit(E_Client *ec);
EINTERN Eina_Bool e_comp_wl_subsurface_commit(E_Client *ec);
E_API E_Comp_Wl_Buffer *e_comp_wl_buffer_get(struct wl_resource *resource);
+E_API void e_comp_wl_shm_access_register(struct wl_resource *resource, void *data, size_t len);
+E_API void e_comp_wl_shm_access_unregister(void *data);
+E_API void e_comp_wl_shm_fault_check(void);
E_API struct wl_signal e_comp_wl_surface_create_signal_get(void);
E_API double e_comp_wl_idle_time_get(void);
diff --git a/src/bin/e_pixmap.c b/src/bin/e_pixmap.c
index 43d11a6a4..0c14e569c 100644
--- a/src/bin/e_pixmap.c
+++ b/src/bin/e_pixmap.c
@@ -97,6 +97,7 @@ _e_pixmap_cb_held_buffer_destroy(struct wl_listener *listener, void *data EINA_U
E_Pixmap *cp;
cp = container_of(listener, E_Pixmap, held_buffer_destroy_listener);
+ if (cp->data) e_comp_wl_shm_access_unregister(cp->data);
cp->held_buffer = NULL;
cp->held_buffer_destroy_listener.notify = NULL;
}
@@ -233,6 +234,7 @@ _e_pixmap_wayland_image_clear(E_Pixmap *cp)
cp->held_buffer_destroy_listener.notify = NULL;
}
+ if (cp->data) e_comp_wl_shm_access_unregister(cp->data);
cp->data = ""
cp->held_buffer = NULL;
}
@@ -1047,6 +1049,16 @@ e_pixmap_image_refresh(E_Pixmap *cp)
wl_shm_buffer_ref_pool(cp->held_buffer->shm_buffer);
cp->held_buffer->busy++;
cp->data = ""
+ /* Evas will read this later, during rendering, so the access
+ * cannot be bracketed by wl_shm_buffer_begin_access()/end_access()
+ * the way Weston does it. Register the range instead and let the
+ * SIGBUS handler in e_comp_wl.c cover it for as long as we hold
+ * it - a client that shrinks its file must not take the whole
+ * compositor down. */
+ if (cp->data)
+ e_comp_wl_shm_access_register(cp->buffer->resource, cp->data,
+ (size_t)wl_shm_buffer_get_stride(cp->buffer->shm_buffer) *
+ (size_t)wl_shm_buffer_get_height(cp->buffer->shm_buffer));
cp->held_buffer_destroy_listener.notify =
_e_pixmap_cb_held_buffer_destroy;
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.