From 67c857e16343eca0838c82113281e9a485a0bd52 Mon Sep 17 00:00:00 2001 From: Anthony Schemel Date: Sat, 26 Sep 2026 16:18:37 +0200 Subject: [PATCH] misc: fix crashes and leaks in wayland, drm, replay index and drivers (#19630) Small, independent fixes for crashes and leaks on reachable paths: - wayland: gfx_ctx_wl_get_video_size_common dereferenced a NULL output when no window output was set and all_outputs was empty. The DnD drop handler leaked the received buffer when fmemopen failed, and always leaked the line getline allocated. - drm: drm_plane_setup logged a NULL drmModeGetPlaneResources result and then dereferenced it. init_drm read `connector` uninitialised when the card reported no connectors. - uint32s_index (replay statestream): uint32s_bucket_free left the freed pointer and length in the bucket. RHMAP_CLEAR keeps value slots, so a later RHMAP_PTR on the same slot could see them and free the vector again. uint32s_index_pop now also refuses an empty index and checks RHMAP_HAS before RHMAP_PTR, which would otherwise create a bucket for a missing hash. - ozone: ozone_context_reset_horizontal_list allocated a node for an entry without one but never stored it in the list, so it leaked. - discord: discord_json_next_strdup leaked the previous string when a key repeated. - core_backup: core_backup_add_entry leaked an empty filename. - audioworklet, caca, sixel, vga: the init functions leaked their struct when the second allocation or the font renderer failed. Tested on Linux: full build. The touched Linux files show no new warnings; the C89 build of the Wayland and DRM files fails on this host in system headers and existing lines, with no diagnostic on a changed line. drm_gfx.c was compiled although HAVE_PLAIN_DRM is off. samples/tasks/core_backup passes. The audioworklet, caca, sixel and vga drivers are not buildable here and were not compiled. Co-authored-by: Claude Opus 5.5 --- audio/drivers/audioworklet.c | 3 +++ core_backup.c | 7 ++++++- gfx/common/wayland_common.c | 6 ++++++ gfx/drivers/caca_gfx.c | 3 +++ gfx/drivers/drm_gfx.c | 3 ++- gfx/drivers/sixel_gfx.c | 3 +++ gfx/drivers/vga_gfx.c | 3 +++ input/bsv/uint32s_index.c | 34 +++++++++++++++++++++++++++++++--- input/common/wayland_common.c | 3 +++ menu/drivers/ozone.c | 6 ++++++ network/discord.c | 5 +++++ 11 files changed, 71 insertions(+), 5 deletions(-) diff --git a/audio/drivers/audioworklet.c b/audio/drivers/audioworklet.c index 0e2b2cedc7..f826c61b64 100644 --- a/audio/drivers/audioworklet.c +++ b/audio/drivers/audioworklet.c @@ -300,7 +300,10 @@ static void *audioworklet_init(const char *device, unsigned rate, return NULL; audioworklet->worklet_stack = memalign(16, WORKLET_STACK_SIZE); if (!audioworklet->worklet_stack) + { + free(audioworklet); return NULL; + } audioworklet_static_data = audioworklet; audioworklet->latency = latency; diff --git a/core_backup.c b/core_backup.c index 10d8628bb6..411a02b625 100644 --- a/core_backup.c +++ b/core_backup.c @@ -379,8 +379,13 @@ static bool core_backup_add_entry(core_backup_list_t *backup_list, return false; backup_filename = strdup(path_basename(backup_path)); - if (!backup_filename || !*backup_filename) + if (!backup_filename) return false; + if (!*backup_filename) + { + free(backup_filename); + return false; + } /* Ensure base backup filename matches core */ if (!string_starts_with(backup_filename, core_filename)) diff --git a/gfx/common/wayland_common.c b/gfx/common/wayland_common.c index 5f57767f20..c1dc80e5a4 100644 --- a/gfx/common/wayland_common.c +++ b/gfx/common/wayland_common.c @@ -414,6 +414,12 @@ void gfx_ctx_wl_get_video_size_common(void *data, unsigned *dims) break; }; + /* all_outputs may legitimately be empty (last monitor was hot- + * unplugged before frame size was queried); leave the caller's + * dims unchanged rather than NULL-deref oi. */ + if (!oi) + return; + *dims = oi->dims; } else diff --git a/gfx/drivers/caca_gfx.c b/gfx/drivers/caca_gfx.c index 2286583192..c6f8c7b46d 100644 --- a/gfx/drivers/caca_gfx.c +++ b/gfx/drivers/caca_gfx.c @@ -89,7 +89,10 @@ static void *caca_font_init(void *data, if (!font_renderer_create_default( &font->font_driver, &font->font_data, font_path, font_size, FONT_ATLAS_FORMAT_A8)) + { + free(font); return NULL; + } return font; } diff --git a/gfx/drivers/drm_gfx.c b/gfx/drivers/drm_gfx.c index 0562afa775..331b876ce2 100644 --- a/gfx/drivers/drm_gfx.c +++ b/gfx/drivers/drm_gfx.c @@ -476,6 +476,7 @@ static void drm_plane_setup(struct drm_surface *surface) if (!plane_resources) { RARCH_ERR("[DRM] No scaling planes available.\n"); + return; } RARCH_LOG("[DRM] Number of planes on FD %d is %d.\n", @@ -639,7 +640,7 @@ static int modeset_create_dumbfb(int fd, struct modeset_buf *buf, static bool init_drm(void) { uint i; - drmModeConnector *connector; + drmModeConnector *connector = NULL; struct modeset_buf buf; drm.fd = open("/dev/dri/card0", O_RDWR); diff --git a/gfx/drivers/sixel_gfx.c b/gfx/drivers/sixel_gfx.c index 9859e2fe69..8c9e42bc4d 100644 --- a/gfx/drivers/sixel_gfx.c +++ b/gfx/drivers/sixel_gfx.c @@ -93,7 +93,10 @@ static void *sixel_font_init(void *data, if (!font_renderer_create_default( &font->font_driver, &font->font_data, font_path, font_size, FONT_ATLAS_FORMAT_A8)) + { + free(font); return NULL; + } return font; } diff --git a/gfx/drivers/vga_gfx.c b/gfx/drivers/vga_gfx.c index 5d932fc217..4f8ed3a724 100644 --- a/gfx/drivers/vga_gfx.c +++ b/gfx/drivers/vga_gfx.c @@ -79,7 +79,10 @@ static void *vga_font_init(void *data, if (!font_renderer_create_default( &font->font_driver, &font->font_data, font_path, font_size, FONT_ATLAS_FORMAT_A8)) + { + free(font); return NULL; + } return font; } diff --git a/input/bsv/uint32s_index.c b/input/bsv/uint32s_index.c index 289e66157a..113faf7b0e 100644 --- a/input/bsv/uint32s_index.c +++ b/input/bsv/uint32s_index.c @@ -51,7 +51,16 @@ uint32s_index_t *uint32s_index_new(size_t object_size, void uint32s_bucket_free(struct uint32s_bucket *bucket) { if (bucket->len > 3) + { free(bucket->contents.vec.idxs); + bucket->contents.vec.idxs = NULL; + bucket->contents.vec.cap = 0; + } + /* RHMAP_CLEAR leaves value-array entries untouched; subsequent + * RHMAP_PTR (which auto-creates) on the cleared slot would re-read + * the stale len/vec.idxs and double-free. Reset len to 0 so the + * bucket is unambiguously inline-empty after free. */ + bucket->len = 0; } bool uint32s_bucket_get(uint32s_index_t *index, struct uint32s_bucket *bucket, uint32_t *object, size_t size_bytes, uint32_t *out_idx) @@ -280,9 +289,28 @@ uint32_t *uint32s_index_get(uint32s_index_t *index, uint32_t which) void uint32s_index_pop(uint32s_index_t *index) { - uint32_t idx = RBUF_LEN(index->objects)-1; - uint32_t hash = index->hashes[idx]; - if (index->objects[idx] != NULL) { + uint32_t idx; + uint32_t hash; + /* Trust boundary: BSV replay file controls index state. Bail before + * RBUF_LEN(...)-1 underflows to UINT32_MAX (turning the next line + * into a wild read of index->hashes[]). */ + if (RBUF_LEN(index->objects) == 0) + { + RARCH_ERR("[STATESTREAM] trying to pop from empty index\n"); + return; + } + idx = RBUF_LEN(index->objects) - 1; + hash = index->hashes[idx]; + /* RHMAP_PTR has add-on-miss semantics; gate on RHMAP_HAS so a + * corrupted hashes[] entry that points to a no-longer-present hash + * can't auto-create a stale-content bucket and fall through into + * uint32s_bucket_remove with garbage len. The counts are still + * adjusted below, so uint32s_index_remove_after keeps making + * progress. */ + if (index->objects[idx] != NULL && !RHMAP_HAS(index->index, hash)) + RARCH_ERR("[STATESTREAM] pop: missing hash %x for idx %u\n", + hash, idx); + else if (index->objects[idx] != NULL) { struct uint32s_bucket *bucket = RHMAP_PTR(index->index, hash); uint32_t old_len = bucket->len; if (old_len == 0) diff --git a/input/common/wayland_common.c b/input/common/wayland_common.c index 5a811d4194..0fb9d1f240 100644 --- a/input/common/wayland_common.c +++ b/input/common/wayland_common.c @@ -1110,6 +1110,7 @@ static void wl_data_device_handle_drop(void *data, if (!(stream = fmemopen(buffer, __len, "r"))) { RARCH_WARN("[Wayland] Failed to open DnD buffer.\n"); + free(buffer); return; } @@ -1127,6 +1128,8 @@ static void wl_data_device_handle_drop(void *data, #endif } + /* getline allocates line on the first call and reuses it. */ + free(line); fclose(stream); free(buffer); } diff --git a/menu/drivers/ozone.c b/menu/drivers/ozone.c index d8cdc6e076..bbab78789f 100644 --- a/menu/drivers/ozone.c +++ b/menu/drivers/ozone.c @@ -5338,8 +5338,14 @@ static void ozone_context_reset_horizontal_list(ozone_handle_t *ozone) ozone_node_t *node = (ozone_node_t*)ozone->horizontal_list.list[i].userdata; if (!node) + { if (!(node = ozone_alloc_node())) continue; + /* Store the new node in the list, which owns and frees it. + * Otherwise it is only reachable from playlist_db_node_map, + * which does not free its values, or from nothing at all. */ + ozone->horizontal_list.list[i].userdata = node; + } if (!(path = ozone->horizontal_list.list[i].path)) continue; diff --git a/network/discord.c b/network/discord.c index cef2a452bc..c90459bd30 100644 --- a/network/discord.c +++ b/network/discord.c @@ -859,7 +859,12 @@ static void discord_json_next_strdup(rjson_t *r, char **out) { const char *s = rjson_get_string(r, NULL); if (s) + { + /* A payload that repeats a key would otherwise leak the + * previous copy. */ + free(*out); *out = strdup(s); + } } }