From e8a129f5d1e930e54de6a4ff813eb8960cea40b4 Mon Sep 17 00:00:00 2001 From: sleeptightAnsiC <91839286+sleeptightAnsiC@users.noreply.github.com> Date: Wed, 25 Mar 2026 23:19:20 +0100 Subject: [PATCH] Fix crash in nk_buffer_realloc and forbid the use of realloc In short: the default implementation of nk_allocator (aka nk_malloc) is fundamentally broken, and should have used realloc instead of malloc. This led to the strange workaround in nk_buffer_realloc that would crash whenever you use custom allocator with correct realloc assumption. We cannot change nk_malloc at this point, as people could have implemented their own allocators based on it, but we can ensure that Nuklear's internal code will never try to reallocate memory. Fixes: https://github.com/Immediate-Mode-UI/Nuklear/issues/768 Reported-by: David Delassus Co-authored-by: Rob Loach --- demo/sdl3_renderer/nuklear_sdl3_renderer.h | 9 -------- nuklear.h | 24 +++++++++++++++------- src/nuklear_buffer.c | 24 +++++++++++++++------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/demo/sdl3_renderer/nuklear_sdl3_renderer.h b/demo/sdl3_renderer/nuklear_sdl3_renderer.h index 25698ea93..c02698b7c 100644 --- a/demo/sdl3_renderer/nuklear_sdl3_renderer.h +++ b/demo/sdl3_renderer/nuklear_sdl3_renderer.h @@ -112,16 +112,7 @@ NK_INTERN void * nk_sdl_alloc(nk_handle user, void *old, nk_size size) { NK_UNUSED(user); - /* FIXME: nk_sdl_alloc should use SDL_realloc here, not SDL_malloc - * but this could cause a double-free due to bug within Nuklear, see: - * https://github.com/Immediate-Mode-UI/Nuklear/issues/768 - * */ -#if 0 return SDL_realloc(old, size); -#else - NK_UNUSED(old); - return SDL_malloc(size); -#endif } NK_INTERN void diff --git a/nuklear.h b/nuklear.h index ed61bbd85..f8d87428f 100644 --- a/nuklear.h +++ b/nuklear.h @@ -8524,7 +8524,16 @@ NK_LIB void* nk_malloc(nk_handle unused, void *old,nk_size size) { NK_UNUSED(unused); - NK_UNUSED(old); + /* + * Due to historical reasons, Nuklear always calls alloc(user,old,size) + * with the "old" pointer always being NULL. For full explanation, see: + * https://github.com/Immediate-Mode-UI/Nuklear/issues/768 + * + * FIXME: The best would be to replace this function with "nk_realloc", + * but some custom allocators could still depend on "malloc" assumption + * so changing this now (without major bump) would break existing code. + */ + NK_ASSERT(!old && "Nuklear's internal code must never call realloc !"); return malloc(size); } NK_LIB void @@ -8617,15 +8626,16 @@ nk_buffer_realloc(struct nk_buffer *b, nk_size capacity, nk_size *size) return 0; buffer_size = b->memory.size; - temp = b->pool.alloc(b->pool.userdata, b->memory.ptr, capacity); + NK_ASSERT(capacity >= buffer_size && "shrinking was never supported here"); + + /* HACK: this simulates realloc with malloc+memcpy+free + * for backwards compatibility reasons, see the note in nk_malloc */ + temp = b->pool.alloc(b->pool.userdata, 0, capacity); NK_ASSERT(temp); if (!temp) return 0; - *size = capacity; - if (temp != b->memory.ptr) { - NK_MEMCPY(temp, b->memory.ptr, buffer_size); - b->pool.free(b->pool.userdata, b->memory.ptr); - } + NK_MEMCPY(temp, b->memory.ptr, buffer_size); + b->pool.free(b->pool.userdata, b->memory.ptr); if (b->size == buffer_size) { /* no back buffer so just set correct size */ diff --git a/src/nuklear_buffer.c b/src/nuklear_buffer.c index e3e9b0f26..0002e4d0d 100644 --- a/src/nuklear_buffer.c +++ b/src/nuklear_buffer.c @@ -11,7 +11,16 @@ NK_LIB void* nk_malloc(nk_handle unused, void *old,nk_size size) { NK_UNUSED(unused); - NK_UNUSED(old); + /* + * Due to historical reasons, Nuklear always calls alloc(user,old,size) + * with the "old" pointer always being NULL. For full explanation, see: + * https://github.com/Immediate-Mode-UI/Nuklear/issues/768 + * + * FIXME: The best would be to replace this function with "nk_realloc", + * but some custom allocators could still depend on "malloc" assumption + * so changing this now (without major bump) would break existing code. + */ + NK_ASSERT(!old && "Nuklear's internal code must never call realloc !"); return malloc(size); } NK_LIB void @@ -104,15 +113,16 @@ nk_buffer_realloc(struct nk_buffer *b, nk_size capacity, nk_size *size) return 0; buffer_size = b->memory.size; - temp = b->pool.alloc(b->pool.userdata, b->memory.ptr, capacity); + NK_ASSERT(capacity >= buffer_size && "shrinking was never supported here"); + + /* HACK: this simulates realloc with malloc+memcpy+free + * for backwards compatibility reasons, see the note in nk_malloc */ + temp = b->pool.alloc(b->pool.userdata, 0, capacity); NK_ASSERT(temp); if (!temp) return 0; - *size = capacity; - if (temp != b->memory.ptr) { - NK_MEMCPY(temp, b->memory.ptr, buffer_size); - b->pool.free(b->pool.userdata, b->memory.ptr); - } + NK_MEMCPY(temp, b->memory.ptr, buffer_size); + b->pool.free(b->pool.userdata, b->memory.ptr); if (b->size == buffer_size) { /* no back buffer so just set correct size */