From cd2830b36866c31029de4c2ca1fb975ab1af35d0 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sat, 2 Aug 2025 11:05:46 +0800 Subject: [PATCH] Prevent crashes from invalid closures in work/timeout queues Platform-aware pointer validation is added to prevent invalid memory access when callbacks execute with freed widget pointers. It validates closures both at scheduling time and before execution. Fixes crashes in _twin_toplevel_paint() during mouse event handling. --- src/timeout.c | 13 +++++++++++++ src/twin_private.h | 34 ++++++++++++++++++++++++++++++++++ src/work.c | 16 +++++++++++++++- 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/timeout.c b/src/timeout.c index f659886..4b59bb4 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -50,6 +50,13 @@ void _twin_run_timeout(void) twin_timeout_t *first = (twin_timeout_t *) _twin_queue_set_order(&head); for (timeout = first; timeout && twin_time_compare(now, >=, timeout->time); timeout = (twin_timeout_t *) timeout->queue.order) { + /* Validate closure pointer before executing timeout callback */ + if (!twin_pointer_valid(timeout->closure)) { + /* Invalid closure pointer, remove this timeout */ + _twin_queue_delete(&head, &timeout->queue); + continue; + } + delay = (*timeout->proc)(now, timeout->closure); if (delay >= 0) { timeout->time = twin_now() + delay; @@ -68,6 +75,12 @@ twin_timeout_t *twin_set_timeout(twin_timeout_proc_t timeout_proc, if (!timeout) return NULL; + /* Basic validation of closure pointer at scheduling time */ + if (closure && !twin_pointer_valid(closure)) { + free(timeout); + return NULL; + } + if (!start) start = twin_now(); timeout->delay = delay; diff --git a/src/twin_private.h b/src/twin_private.h index ed6dba8..dade9c0 100644 --- a/src/twin_private.h +++ b/src/twin_private.h @@ -810,4 +810,38 @@ void twin_custom_widget_destroy(twin_custom_widget_t *custom); /* Path convex hull computation */ twin_path_t *twin_path_convex_hull(twin_path_t *path); +/* + * Memory pointer validation + * + * This defines the minimum valid pointer address for closure validation. + * Different environments have different memory layouts: + * + * - Unix-like systems (Linux/BSD/macOS/Solaris): First 4KB (0x1000) typically + * unmapped + * - Windows: First 64KB (0x10000) reserved + * - Bare-metal: May have valid memory starting at 0x0 + */ +#ifndef TWIN_POINTER_MIN_VALID +#if defined(_WIN32) || defined(_WIN64) +#define TWIN_POINTER_MIN_VALID 0x10000 /* Windows: 64KB */ +#elif defined(__unix__) || defined(__unix) || defined(unix) || \ + (defined(__APPLE__) && defined(__MACH__)) || defined(__linux__) || \ + defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ + defined(__DragonFly__) || defined(__sun) || defined(__HAIKU__) || \ + defined(__ANDROID__) +#define TWIN_POINTER_MIN_VALID 0x1000 /* Unix-like: 4KB */ +#else +#define TWIN_POINTER_MIN_VALID 0x100 /* Bare-metal/embedded: 256 bytes */ +#endif +#endif + +/* + * Validate a pointer for basic sanity + * Returns true if the pointer appears to be valid + */ +static inline bool twin_pointer_valid(const void *ptr) +{ + return ptr && (uintptr_t) ptr >= TWIN_POINTER_MIN_VALID; +} + #endif /* _TWIN_PRIVATE_H_ */ diff --git a/src/work.c b/src/work.c index af36e37..46b3e8a 100644 --- a/src/work.c +++ b/src/work.c @@ -32,9 +32,17 @@ void _twin_run_work(void) twin_work_t *first; first = (twin_work_t *) _twin_queue_set_order(&head); - for (work = first; work; work = (twin_work_t *) work->queue.order) + for (work = first; work; work = (twin_work_t *) work->queue.order) { + /* Validate closure pointer before executing callback */ + if (!twin_pointer_valid(work->closure)) { + /* Invalid closure pointer, remove this work item */ + _twin_queue_delete(&head, &work->queue); + continue; + } + if (!(*work->proc)(work->closure)) _twin_queue_delete(&head, &work->queue); + } _twin_queue_review_order(&first->queue); } @@ -46,6 +54,12 @@ twin_work_t *twin_set_work(twin_work_proc_t work_proc, if (!work) return NULL; + /* Basic validation of closure pointer at scheduling time */ + if (closure && !twin_pointer_valid(closure)) { + free(work); + return NULL; + } + work->proc = work_proc; work->priority = priority; work->closure = closure;