Fix refcount leak and stack-trace buffer overflow

Two memory-safety bugs in the macro core:

1. Refcount leak. ENSURE_ERROR_READY incremented refcount on every FAIL/SUCCEED
   rather than only when it acquired a fresh context from the pool. A function
   that FAILed a context more than once and then propagated arrived at its
   caller with refcount 2; the caller released once, leaking the slot. After
   AKERR_MAX_ARRAY_ERROR leaks the pool is exhausted and the library exit(1)s.
   Move the increment inside the acquisition branch.

2. Stack-trace overflow. Each appended frame passed the full buffer length to
   snprintf instead of the space remaining, and advanced the cursor by
   snprintf's would-be return value, so a trace that filled the buffer wrote
   past the end of stacktracebuf and ran the cursor out of bounds. Add
   AKERR_STACKTRACE_APPEND, which bounds the write to the remaining space and
   clamps the cursor advance.

Regression tests err_refcount_double_fail and err_stacktrace_bounds fail against
the old code (verified) and pass now. Full suite: 21/21, no warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-28 09:22:19 -04:00
parent 3e24356f07
commit de13b290d4
4 changed files with 134 additions and 5 deletions

View File

@@ -110,10 +110,32 @@ void akerr_init_errno(void);
akerr_log_method("%s:%s:%d: Unable to pull an error context from the array!", __FILE__, (char *)__func__, __LINE__); \
exit(1); \
} \
} \
__err_context->refcount += 1;
__err_context->refcount += 1; \
}
/*
/*
* Append a formatted line to the error's stack-trace buffer, bounded by the
* space that remains so a deep propagation chain cannot write past the end of
* stacktracebuf. snprintf reports the length it *would* have written, which on
* truncation exceeds what it actually wrote, so the cursor advance is clamped
* to the remaining space.
*/
#define AKERR_STACKTRACE_APPEND(__err_context, ...) \
do { \
char *__akerr_stb = (char *)__err_context->stacktracebuf; \
size_t __akerr_used = (size_t)(__err_context->stacktracebufptr - __akerr_stb); \
if ( __akerr_used < AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH ) { \
size_t __akerr_rem = AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH - __akerr_used; \
int __akerr_n = snprintf(__err_context->stacktracebufptr, __akerr_rem, __VA_ARGS__); \
if ( __akerr_n < 0 ) { \
__akerr_n = 0; \
} \
__err_context->stacktracebufptr += ((size_t)__akerr_n < __akerr_rem) \
? (size_t)__akerr_n : (__akerr_rem - 1); \
} \
} while ( 0 )
/*
* Failure and success methods for functions that return akerr_ErrorContext *
*/
@@ -172,7 +194,7 @@ void akerr_init_errno(void);
snprintf((char *)__err_context->function, AKERR_MAX_ERROR_FUNCTION_LENGTH, __func__); \
__err_context->lineno = __LINE__; \
snprintf((char *)__err_context->message, AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH, __message, ## __VA_ARGS__); \
__err_context->stacktracebufptr += snprintf(__err_context->stacktracebufptr, AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d: %d (%s) : %s\n", (char *)__err_context->fname, (char *)__err_context->function, __err_context->lineno, __err_context->status, akerr_name_for_status(__err_context->status, NULL), (__err_context->message == NULL ? "" : __err_context->message));
AKERR_STACKTRACE_APPEND(__err_context, "%s:%s:%d: %d (%s) : %s\n", (char *)__err_context->fname, (char *)__err_context->function, __err_context->lineno, __err_context->status, akerr_name_for_status(__err_context->status, NULL), (__err_context->message == NULL ? "" : __err_context->message));
#define SUCCEED(__err_context) \
@@ -198,7 +220,7 @@ void akerr_init_errno(void);
VALID(__err_context, __stmt); \
if ( __err_context != NULL ) { \
if ( __err_context->status != 0 ) { \
__err_context->stacktracebufptr += snprintf(__err_context->stacktracebufptr, AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d\n", (char *)__FILE__, (char *)__func__, __LINE__); \
AKERR_STACKTRACE_APPEND(__err_context, "%s:%s:%d\n", (char *)__FILE__, (char *)__func__, __LINE__); \
break; \
} \
}