Compare commits

...

15 commits
main ... 1.24.0

Author SHA1 Message Date
Simon Ser
736d12ac67 build: bump version to 1.24.0 2025-07-06 14:11:26 +02:00
Simon Ser
0fa6f267f2 build: bump version to 1.23.93
Signed-off-by: Simon Ser <contact@emersion.fr>
2025-06-21 13:46:19 +02:00
Demi Marie Obenour
398e1297ee connection: Do not busy-loop if a message exceeds the buffer size
If the length of a message exceeds the maximum length of the buffer, the
buffer size will reach its maximum value and stay there forever, with no
message ever being successfully processed.  Since libwayland uses
level-triggered epoll, this will cause the compositor to loop forever
and consume CPU time.  In libwayland 1.22 and below, there was an
explicit check that caused messages exceeding 4096 bytes to result in an
EOVERFLOW error, preventing the loop.  However, this check was removed
between d074d52902 ("connection: Dynamically resize connection buffers").

To prevent this problem, always limit the size of messages to 4096 bytes.
Since the default and minimum buffer size is 4096 bytes, this ensures
that a single message will always fit in the buffer.  It would be
possible to allow larger messages if the buffer size was larger, but the
maximum size of a message should not depend on the buffer size chosen by
the compositor.

Rejecting messages that exceed 4092 bytes seems to have the advantage of
reserving 4 bits, not 3, in the size field for future use.  However,
message sizes in the range [0x0, 0x7] are invalid, so one can obtain a
fourth bit by negating the meaning of bit 12 if bits 0 through 11
(inclusive) are 0.  Allowing 4096-byte messages provides the far more
important advantage that regressions compared to 1.22 are impossible
and regressions compared to 1.23 are extremely unlikely.  The only case
where a regression is possible is:

- The receiving side is using libwayland 1.23.
- The sending side is either using libwayland 1.23 or is not using
  libwayland.
- The sender sends a message exceeding 4096 bytes.
- If the sender of the large message is the client, the server has
  increased the buffer size from the default value.

This combination is considered extremely unlikely, as libwayland 1.22
and below would disconnect upon receiving such a large message.
4096-byte messages, however, have always worked, so there was no reason
to avoid sending them.

Fixes: d074d52902 ("connection: Dynamically resize connection buffers").
Fixes: #494
Signed-off-by: Demi Marie Obenour <demi@invisiblethingslab.com>
(cherry picked from commit adf84614ca)
2025-06-21 13:42:05 +02:00
Simon Ser
e35f1efb59 build: bump version to 1.23.92
Signed-off-by: Simon Ser <contact@emersion.fr>
2025-06-08 20:57:05 +02:00
Matt Turner
081f8af18a egl: Make wayland-egl symbols check depend on wayland_egl
Closes: https://gitlab.freedesktop.org/wayland/wayland/-/issues/515
Signed-off-by: Matt Turner <mattst88@gmail.com>
(cherry picked from commit 53fbc2b0c1)
2025-06-08 20:56:31 +02:00
Matt Turner
9099588de1 tests: Depend on exec-fd-leak-checker
Closes: https://gitlab.freedesktop.org/wayland/wayland/-/issues/514
Signed-off-by: Matt Turner <mattst88@gmail.com>
(cherry picked from commit fdac631d17)
2025-06-08 20:56:31 +02:00
Matt Turner
a834596d4c tests: Add support for specifying runtime dependencies
Signed-off-by: Matt Turner <mattst88@gmail.com>
(cherry picked from commit 6c1da92018)
2025-06-08 20:56:31 +02:00
Matt Turner
1d6fba989d tests: Make tests dict elements dicts themselves
Previously each value was a list of extra sources. The next commit will add an
additional field to each test, so they need to be dicts themselves.

Signed-off-by: Matt Turner <mattst88@gmail.com>
(cherry picked from commit ca83185e8a)
2025-06-08 20:56:31 +02:00
Tobias Stoeckmann
832c7be742 cursor: Properly check realloc for errors
Do not override realloc's input pointer before checking for errors,
otherwise it's not possible to keep old value, as intended.

Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
(cherry picked from commit 0de833da29)
2025-06-08 20:56:31 +02:00
Tobias Stoeckmann
86cfd575ca cursor: Ignore invalid cursor files
The header offset must not be smaller than file header length.
Ignore such invalid files.

Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
(cherry picked from commit 2978fd701a)
2025-06-08 20:56:31 +02:00
Tobias Stoeckmann
65ce8920e2 cursor: Gracefully handle huge cursor files
If cursor files require more than INT_MAX bytes, it is possible to
trigger out of boundary writes.

Since these sizes are most likely not desired anyway, gracefully
handle these situations like out of memory errors.

Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
(cherry picked from commit 5c2f31d8d6)
2025-06-08 20:56:31 +02:00
Tobias Stoeckmann
98d7bbee0a cursor: Gracefully handle out of memory condition
If the full path could not be constructed, avoid calling opendir(NULL)
which, depending on library, might trigger undefined behavior.

Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
(cherry picked from commit ce0ac4f29e)
2025-06-08 20:56:31 +02:00
Tobias Stoeckmann
cd566cd232 cursor: Fix undefined behavior with huge names
If an index.theme contains a theme name which gets close to INT_MAX,
then creation of full path can lead to a signed integer overflow,
which is undefined behavior.

Fix this by turning one of the values to size_t. Easy solution for a
probably never occurring issue.

Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
(cherry picked from commit 1bee7aa4a7)
2025-06-08 20:56:31 +02:00
Kirill Primak
504dbf2303 client: fix conversion specifier in the discarded event log message
Signed-off-by: Kirill Primak <vyivel@eclair.cafe>
(cherry picked from commit 6281ccbd3d)
2025-06-08 20:55:02 +02:00
Caitlyn Stewart
b26180dfea connection: fix segfault in wl_closure_invoke()
Signed-off-by: Caitlyn Stewart <caitlynrosestewart@gmail.com>
(cherry picked from commit 827d0c30ad)
2025-06-08 20:53:38 +02:00
10 changed files with 194 additions and 77 deletions

View file

@ -27,6 +27,7 @@
#include "xcursor.h"
#include "wayland-cursor.h"
#include "wayland-client.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
@ -284,7 +285,8 @@ wl_cursor_create_from_xcursor_images(struct xcursor_images *images,
{
struct cursor *cursor;
struct cursor_image *image;
int i, size;
size_t size;
int i;
cursor = malloc(sizeof *cursor);
if (!cursor)
@ -314,7 +316,12 @@ wl_cursor_create_from_xcursor_images(struct xcursor_images *images,
image->image.hotspot_y = images->images[i]->yhot;
image->image.delay = images->images[i]->delay;
size = image->image.width * image->image.height * 4;
size = (size_t) image->image.width * image->image.height * 4;
if (size > INT_MAX) {
free(image);
break;
}
image->offset = shm_pool_allocate(theme->pool, size);
if (image->offset < 0) {
free(image);
@ -344,6 +351,8 @@ load_callback(struct xcursor_images *images, void *data)
{
struct wl_cursor_theme *theme = data;
struct wl_cursor *cursor;
struct wl_cursor **p;
size_t s;
if (wl_cursor_theme_get_cursor(theme, images->name)) {
xcursor_images_destroy(images);
@ -353,15 +362,14 @@ load_callback(struct xcursor_images *images, void *data)
cursor = wl_cursor_create_from_xcursor_images(images, theme);
if (cursor) {
theme->cursor_count++;
theme->cursors =
realloc(theme->cursors,
theme->cursor_count * sizeof theme->cursors[0]);
s = theme->cursor_count + 1;
p = realloc(theme->cursors, s * sizeof theme->cursors[0]);
if (theme->cursors == NULL) {
theme->cursor_count--;
if (p == NULL) {
free(cursor);
} else {
theme->cursor_count = s;
theme->cursors = p;
theme->cursors[theme->cursor_count - 1] = cursor;
}
}
@ -389,6 +397,9 @@ wl_cursor_theme_load(const char *name, int size, struct wl_shm *shm)
if (!theme)
return NULL;
if (size < 0 || (size > 0 && INT_MAX / size / 4 < size))
return NULL;
if (!name)
name = "default";

View file

@ -259,6 +259,8 @@ xcursor_read_file_header(FILE *file)
return NULL;
if (!xcursor_read_uint(file, &head.ntoc))
return NULL;
if (head.header < XCURSOR_FILE_HEADER_LEN)
return NULL;
skip = head.header - XCURSOR_FILE_HEADER_LEN;
if (skip)
if (fseek(file, skip, SEEK_CUR) == EOF)
@ -571,7 +573,7 @@ xcursor_build_theme_dir(const char *dir, const char *theme)
* add space for any needed directory separators, one per component,
* and one for the trailing null
*/
full_size = 1 + homelen + 1 + dirlen + 1 + themelen + 1;
full_size = (size_t) 1 + homelen + 1 + dirlen + 1 + themelen + 1;
full = malloc(full_size);
if (!full)
return NULL;
@ -686,11 +688,15 @@ load_all_cursors_from_dir(const char *path, int size,
void *user_data)
{
FILE *f;
DIR *dir = opendir(path);
DIR *dir;
struct dirent *ent;
char *full;
struct xcursor_images *images;
if (!path)
return;
dir = opendir(path);
if (!dir)
return;

View file

@ -22,6 +22,7 @@ if get_option('tests')
test(
'wayland-egl symbols check',
find_program('wayland-egl-symbols-check'),
depends: wayland_egl,
env: [
'WAYLAND_EGL_LIB=@0@'.format(wayland_egl_shared.full_path()),
'NM=@0@'.format(nm_path)

View file

@ -1,6 +1,6 @@
project(
'wayland', 'c',
version: '1.23.91',
version: '1.24.0',
license: 'MIT',
meson_version: '>= 0.57.0',
default_options: [

View file

@ -1229,6 +1229,11 @@ wl_closure_invoke(struct wl_closure *closure, uint32_t flags,
count + 2, &ffi_type_void, ffi_types);
implementation = target->implementation;
if (!implementation) {
wl_abort("Implementation of resource %d of %s is NULL\n",
target->id, target->interface->name);
}
if (!implementation[opcode]) {
wl_abort("listener function for opcode %u of %s is NULL\n",
opcode, target->interface->name);

View file

@ -1564,6 +1564,28 @@ queue_event(struct wl_display *display, int len)
id = p[0];
opcode = p[1] & 0xffff;
size = p[1] >> 16;
/*
* If the message is larger than the maximum size of the
* connection buffer, the connection buffer will fill to
* its max size and stay there, with no message ever
* successfully being processed. If the user of
* libwayland-client uses a level-triggered event loop,
* this will cause the client to enter a loop that
* consumes CPU. To avoid this, immediately drop the
* connection. Since the maximum size of a message should
* not depend on the max buffer size chosen by the client,
* always compare the message size against the
* limit enforced by libwayland 1.22 and below (4096),
* rather than the actual value the client chose.
*/
if (size > WL_MAX_MESSAGE_SIZE) {
wl_log("Message length %u exceeds limit %d\n",
size, WL_MAX_MESSAGE_SIZE);
errno = E2BIG;
return -1;
}
if (len < size)
return 0;
@ -1579,7 +1601,7 @@ queue_event(struct wl_display *display, int len)
clock_gettime(CLOCK_REALTIME, &tp);
time = (tp.tv_sec * 1000000L) + (tp.tv_nsec / 1000);
fprintf(stderr, "[%7u.%03u] discarded [%s]#%d.[event %d]"
fprintf(stderr, "[%7u.%03u] discarded [%s]#%u.[event %d]"
"(%d fd, %d byte)\n",
time / 1000, time % 1000,
zombie ? "zombie" : "unknown",

View file

@ -49,6 +49,9 @@
#define WL_CLOSURE_MAX_ARGS 20
#define WL_BUFFER_DEFAULT_SIZE_POT 12
#define WL_BUFFER_DEFAULT_MAX_SIZE (1 << WL_BUFFER_DEFAULT_SIZE_POT)
#if WL_BUFFER_DEFAULT_MAX_SIZE < WL_MAX_MESSAGE_SIZE
# error default buffer cannot hold maximum-sized message
#endif
/**
* Argument types used in signatures.

View file

@ -397,6 +397,29 @@ wl_client_connection_data(int fd, uint32_t mask, void *data)
wl_connection_copy(connection, p, sizeof p);
opcode = p[1] & 0xffff;
size = p[1] >> 16;
/*
* If the message is larger than the maximum size of the
* connection buffer, the connection buffer will fill to
* its max size and stay there, with no message ever
* successfully being processed. Since libwayland-server
* uses level-triggered epoll, it will cause the server to
* enter a loop that consumes CPU. To avoid this,
* immediately disconnect the client with a protocol
* error. Since the maximum size of a message should not
* depend on the buffer size chosen by the compositor,
* always compare the message size against the
* limit enforced by libwayland 1.22 and below (4096),
* rather than the actual value the compositor chose.
*/
if (size > WL_MAX_MESSAGE_SIZE) {
wl_resource_post_error(client->display_resource,
WL_DISPLAY_ERROR_INVALID_METHOD,
"message length %u exceeds %d",
size, WL_MAX_MESSAGE_SIZE);
break;
}
if (len < size)
break;

View file

@ -90,6 +90,14 @@ extern "C" {
*/
struct wl_object;
/**
* The maximum size of a protocol message.
*
* If a message size exceeds this value, the connection will be dropped.
* Servers will send an invalid_method error before disconnecting.
*/
#define WL_MAX_MESSAGE_SIZE 4096
/**
* Protocol message signature
*

View file

@ -54,7 +54,7 @@ tests_protocol_c = custom_target(
output: 'tests-protocol.c'
)
executable(
exec_fd_leak_checker = executable(
'exec-fd-leak-checker',
'exec-fd-leak-checker.c',
dependencies: test_runner_dep
@ -96,78 +96,116 @@ if get_option('scanner')
endif
tests = {
'array-test': [],
'client-test': [ wayland_server_protocol_h ],
'display-test': [
wayland_client_protocol_h,
wayland_server_protocol_h,
tests_server_protocol_h,
tests_client_protocol_c,
tests_protocol_c,
],
'connection-test': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
'event-loop-test': [ wayland_server_protocol_h ],
'fixed-test': [],
'interface-test': [ wayland_client_protocol_h ],
'list-test': [],
'map-test': [],
'sanity-test' : [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
'socket-test': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
'queue-test': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
'signal-test': [ wayland_server_protocol_h ],
'newsignal-test': [
# wayland-server.c is needed here to access wl_priv_* functions
files('../src/wayland-server.c'),
wayland_server_protocol_h,
],
'resources-test': [ wayland_server_protocol_h ],
'message-test': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
'compositor-introspection-test': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
'protocol-logger-test': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
'headers-test': [
wayland_client_protocol_h,
wayland_server_protocol_h,
'headers-protocol-test.c',
wayland_client_protocol_core_h,
wayland_server_protocol_core_h,
'headers-protocol-core-test.c',
],
'os-wrappers-test': [],
'proxy-test': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
'enum-validator-test': [],
'array-test': {},
'client-test': {
'extra_sources': [ wayland_server_protocol_h ],
},
'display-test': {
'extra_sources': [
wayland_client_protocol_h,
wayland_server_protocol_h,
tests_server_protocol_h,
tests_client_protocol_c,
tests_protocol_c,
],
},
'connection-test': {
'extra_sources': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
},
'event-loop-test': {
'extra_sources': [ wayland_server_protocol_h ],
},
'fixed-test': {},
'interface-test': {
'extra_sources': [ wayland_client_protocol_h ],
},
'list-test': {},
'map-test': {},
'sanity-test' : {
'extra_sources': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
'runtime_deps': [ exec_fd_leak_checker ],
},
'socket-test': {
'extra_sources': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
},
'queue-test': {
'extra_sources': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
},
'signal-test': {
'extra_sources': [ wayland_server_protocol_h ],
},
'newsignal-test': {
'extra_sources': [
# wayland-server.c is needed here to access wl_priv_* functions
files('../src/wayland-server.c'),
wayland_server_protocol_h,
],
},
'resources-test': {
'extra_sources': [ wayland_server_protocol_h ],
},
'message-test': {
'extra_sources': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
},
'compositor-introspection-test': {
'extra_sources': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
},
'protocol-logger-test': {
'extra_sources': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
},
'headers-test': {
'extra_sources': [
wayland_client_protocol_h,
wayland_server_protocol_h,
'headers-protocol-test.c',
wayland_client_protocol_core_h,
wayland_server_protocol_core_h,
'headers-protocol-core-test.c',
],
},
'os-wrappers-test': {
'runtime_deps': [ exec_fd_leak_checker ],
},
'proxy-test': {
'extra_sources': [
wayland_client_protocol_h,
wayland_server_protocol_h,
],
},
'enum-validator-test': {},
}
foreach test_name, test_extra_sources: tests
foreach test_name, test_extras : tests
test_extra_sources = test_extras.get('extra_sources', [])
test_runtime_deps = test_extras.get('runtime_deps', [])
test_sources = [ test_name + '.c' ] + test_extra_sources
test_deps = [test_runner_dep, epoll_dep]
bin = executable(test_name, test_sources, dependencies: test_deps)
test(
test_name,
bin,
depends: test_runtime_deps,
env: [
'TEST_SRC_DIR=@0@'.format(meson.current_source_dir()),
'TEST_BUILD_DIR=@0@'.format(meson.current_build_dir()),