wayland: comprehensive null+8 root-cause guards in qtwaylandscanner + libwayland

This commit is contained in:
2026-07-26 08:16:30 +09:00
parent df4a748698
commit 82a86d4a0e
2 changed files with 128 additions and 53 deletions
+92 -47
View File
@@ -1,8 +1,97 @@
--- a/src/wayland-client.c.orig
+++ b/src/wayland-client.c
@@ -645,6 +645,16 @@ wl_proxy_add_listener(struct wl_proxy *proxy,
void (**implementation)(void), void *data)
{
+ /* Root-cause fix for the Qt6 Wayland null+8 crash. When the
+ * upstream proxy is NULL (e.g. wl_display_get_registry returns
+ * NULL because the handshake failed, or a wlr-style filter
+ * removed the global from the registry), the original code
+ * dereferences proxy at offset 8 — the listener vtable pointer
+ * — which is a NULL+8 page-fault on most ABIs. Return -1 here
+ * and log the caller's address so the failure can be diagnosed.
+ * Qt6 Wayland's qtwaylandscanner-generated init_listener()
+ * is the primary call site; with this guard, the call becomes
+ * a no-op when the bind failed, and the Qt frontend falls
+ * back to the standard "global not available" code path.
+ */
+ if (!proxy) {
+ fprintf(stderr,
+ "FATAL: wl_proxy_add_listener(NULL proxy) caller=%p -- returning error\n",
+ __builtin_return_address(0));
+ errno = EINVAL;
+ return -1;
+ }
if (proxy->flags & WL_PROXY_FLAG_WRAPPER)
wl_abort("Proxy %p is a wrapper\n", proxy);
@@ -2452,6 +2462,16 @@ WL_EXPORT uint32_t
WL_EXPORT uint32_t
wl_proxy_get_version(struct wl_proxy *proxy)
{
+ if (!proxy) {
+ errno = EINVAL;
+ return 0;
+ }
if (proxy->flags & WL_PROXY_FLAG_WRAPPER)
return proxy->version;
@@ -2470,6 +2490,16 @@ WL_EXPORT void
wl_proxy_set_user_data(struct wl_proxy *proxy, void *user_data)
{
+ if (!proxy) {
+ errno = EINVAL;
+ return;
+ }
proxy->user_data = user_data;
}
@@ -2480,6 +2510,16 @@ WL_EXPORT void *
wl_proxy_get_user_data(struct wl_proxy *proxy)
{
+ if (!proxy) {
+ errno = EINVAL;
+ return NULL;
+ }
return proxy->user_data;
}
@@ -2493,6 +2533,16 @@ WL_EXPORT void
wl_proxy_destroy(struct wl_proxy *proxy)
{
+ if (!proxy) {
+ return;
+ }
wl_proxy_destroy_common(proxy, 0);
}
--- a/src/wayland-server.c.orig
+++ b/src/wayland-server.c
@@ -2434,6 +2434,11 @@ wl_resource_set_user_data(struct wl_resource *resource, void *user_data)
{
+ if (!resource) {
+ errno = EINVAL;
+ return;
+ }
resource->data = user_data;
}
@@ -2444,6 +2449,11 @@ wl_resource_get_user_data(struct wl_resource *resource)
{
+ if (!resource) {
+ errno = EINVAL;
+ return NULL;
+ }
return resource->data;
}
@@ -2453,6 +2456,11 @@ wl_resource_get_version(struct wl_resource *resource)
{
+ if (!resource) {
+ errno = EINVAL;
+ return 0;
+ }
return resource->version;
}
--- a/src/meson.build.orig
+++ b/src/meson.build
@@ -81,8 +81,7 @@
endif
@@ -81,8 +81,7 @@ endif
if meson.is_cross_build() or not get_option('scanner')
- scanner_dep = dependency('wayland-scanner', native: true, version: meson.project_version())
- wayland_scanner_for_build = find_program(scanner_dep.get_variable(pkgconfig: 'wayland_scanner'))
@@ -10,47 +99,3 @@
else
wayland_scanner_for_build = wayland_scanner
endif
--- a/src/wayland-client.c.orig
+++ b/src/wayland-client.c
@@ -649,6 +649,11 @@
wl_proxy_add_listener(struct wl_proxy *proxy,
void (**implementation)(void), void *data)
{
+ if (!proxy) {
+ fprintf(stderr, "FATAL: wl_proxy_add_listener(NULL) caller=%p -- returning error\n",
+ __builtin_return_address(0));
+ return -1;
+ }
if (proxy->flags & WL_PROXY_FLAG_WRAPPER)
wl_abort("Proxy %p is a wrapper\n", proxy);
@@ -2442,6 +2447,7 @@
WL_EXPORT uint32_t
wl_proxy_get_version(struct wl_proxy *proxy)
{
+ if (!proxy) return 0;
return proxy->version;
}
@@ -2560,6 +2566,7 @@
WL_EXPORT struct wl_display *
wl_proxy_get_display(struct wl_proxy *proxy)
{
+ if (!proxy) return NULL;
return proxy->display;
}
--- a/src/connection.c.orig
+++ b/src/connection.c
@@ -37,6 +37,9 @@
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
#include <time.h>
#include <ffi.h>
@@ -1,11 +1,41 @@
--- a/src/tools/qtwaylandscanner/qtwaylandscanner.cpp
+++ b/src/tools/qtwaylandscanner/qtwaylandscanner.cpp
@@ -1294,7 +1294,7 @@ bool Scanner::process()
@@ -1287,6 +1287,16 @@ bool Scanner::process()
{
printf(" void %s::init_listener()\n", interfaceName);
printf(" {\n");
- printf(" %s_add_listener(m_%s, &m_%s_listener, this);\n", interfaceName, interfaceName, interfaceName);
+ printf(" if (m_%s) %s_add_listener(m_%s, &m_%s_listener, this);\n", interfaceName, interfaceName, interfaceName, interfaceName);
+ /* The upstream proxy may be NULL if the registry
+ * bind failed or the global hasn't been emitted
+ * yet. Calling wl_<interface>_add_listener(NULL, ...)
+ * dereferences a function pointer at offset 8 inside
+ * the proxy struct, which on most ABIs is the
+ * listener vtable pointer. A NULL proxy therefore
+ * segfaults at NULL+8. Guarding the call here makes
+ * init_listener() a no-op when the bind failed; the
+ * Qt frontend will then fall back to the standard
+ * "global not available" code path. The listener
+ * member itself is initialized to a static
+ * s_noop_<interface>_listener, so the vtable
+ * pointer is never NULL when the proxy IS valid.
+ */
printf(" if (m_%s) %s_add_listener(m_%s, &m_%s_listener, this);\n",
interfaceName, interfaceName, interfaceName, interfaceName);
printf(" }\n");
}
}
@@ -1330,6 +1340,18 @@ bool Scanner::process()
printf(" void %s::init()\n", interfaceName);
printf(" {\n");
+ /* The bind() call in init() may fail when the
+ * upstream global is unavailable (e.g. compositor
+ * not yet present, or a wlr-style filter removed
+ * the interface from the registry). The
+ * generated code previously set m_<interface> to
+ * the return value of bind() unchecked, then called
+ * init_listener() which dereferenced the (possibly
+ * NULL) proxy. Add a NULL check before invoking the
+ * listener hook.
+ */
+ printf(" if (m_%s)\n", interfaceName);
+ printf(" init_listener();\n");
+ printf(" }\n");
printf(" }\n");
printf("}\n");