Files
libakerror/tests/err_refcount_double_fail.c
Andrew Kesterson de13b290d4 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>
2026-07-28 09:32:55 -04:00

52 lines
1.4 KiB
C

#include "akerror.h"
#include "err_capture.h"
/*
* Regression test for the refcount leak: ENSURE_ERROR_READY must increment
* refcount only when it *acquires* a fresh context, not on every FAIL/SUCCEED.
* A function that calls FAIL more than once on the same context and then
* propagates used to arrive at the caller with refcount 2; the caller released
* once, leaking the slot. After enough leaks the pool is exhausted and the
* library exit(1)s.
*/
akerr_ErrorContext *validate(void)
{
PREPARE_ERROR(e);
ATTEMPT {
FAIL(e, AKERR_VALUE, "condition 1 failed"); /* acquires the context */
FAIL(e, AKERR_KEY, "condition 2 failed"); /* must NOT re-acquire it */
} CLEANUP {
} PROCESS(e) {
} FINISH(e, true); /* unhandled -> propagate */
SUCCEED_RETURN(e);
}
/* One raise -> catch -> handle cycle; returns NULL (context released). */
akerr_ErrorContext *one_cycle(void)
{
PREPARE_ERROR(e);
ATTEMPT {
CATCH(e, validate());
} CLEANUP {
} PROCESS(e) {
} HANDLE(e, AKERR_KEY) {
} HANDLE(e, AKERR_VALUE) {
} FINISH(e, false);
return e;
}
int main(void)
{
akerr_init();
AKERR_CHECK(akerr_slots_in_use() == 0);
for ( int i = 0; i < 32; i++ ) {
(void)one_cycle();
}
AKERR_CHECK(akerr_slots_in_use() == 0);
fprintf(stderr, "err_refcount_double_fail ok\n");
return 0;
}