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:
54
tests/err_stacktrace_bounds.c
Normal file
54
tests/err_stacktrace_bounds.c
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "akerror.h"
|
||||
#include "err_capture.h"
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Regression test for the stack-trace buffer overflow. Each frame appended a
|
||||
* line with snprintf, but passed the *full* buffer length as the size rather
|
||||
* than the space remaining, and advanced the cursor by snprintf's would-be
|
||||
* return value. A trace that filled the buffer therefore wrote past the end of
|
||||
* stacktracebuf and ran the cursor out of bounds.
|
||||
*
|
||||
* We place a context in a struct with a guard region right after it, position
|
||||
* the trace cursor near the end of the buffer, append one more frame, and
|
||||
* require that nothing was written past the buffer and the cursor stayed in
|
||||
* bounds.
|
||||
*/
|
||||
|
||||
static struct {
|
||||
akerr_ErrorContext ctx;
|
||||
unsigned char guard[512];
|
||||
} probe;
|
||||
|
||||
akerr_ErrorContext *append_frame(akerr_ErrorContext *e)
|
||||
{
|
||||
FAIL_RETURN(e, AKERR_VALUE,
|
||||
"an error message long enough to overflow a nearly full stack trace buffer");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
akerr_init();
|
||||
|
||||
memset(&probe, 0x00, sizeof(probe));
|
||||
memset(probe.guard, 0xAA, sizeof(probe.guard));
|
||||
|
||||
akerr_ErrorContext *e = &probe.ctx;
|
||||
e->refcount = 1;
|
||||
/* Two bytes short of full: any real frame would overflow the old code. */
|
||||
e->stacktracebufptr = probe.ctx.stacktracebuf
|
||||
+ AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH - 2;
|
||||
|
||||
(void)append_frame(e);
|
||||
|
||||
/* Nothing may have been written past the end of stacktracebuf. */
|
||||
for ( unsigned i = 0; i < sizeof(probe.guard); i++ ) {
|
||||
AKERR_CHECK(probe.guard[i] == 0xAA);
|
||||
}
|
||||
/* The cursor must remain within the buffer. */
|
||||
AKERR_CHECK(e->stacktracebufptr
|
||||
<= probe.ctx.stacktracebuf + AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH);
|
||||
|
||||
fprintf(stderr, "err_stacktrace_bounds ok\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user