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 <[email protected]>
This commit is contained in:
Anthony SchemelandClaude Opus 5.5 authored and GitHub committed 2026-09-26 16:18:37 +02:00
1 parent ffa4ec336a
commit 67c857e163
11 files changed
+71 -5

No files matched your search

+3
View File
@@ -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;
+6 -1
View File
@@ -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))
+6
View File
@@ -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
+3
View File
@@ -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;
}
+2 -1
View File
@@ -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);
+3
View File
@@ -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;
}
+3
View File
@@ -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;
}
+31 -3
View File
@@ -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)
+3
View File
@@ -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);
}
+6
View File
@@ -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;
+5
View File
@@ -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);
}
}
}