Environment
- Collections-C, commit
dbedaf9599716c9aa16c6e955fdc3ef76d5c5cca (master, checked 2026-08-26)
src/cc_list.c, src/include/cc_list.h, test/unit/list_test.c
- Found via static analysis; confirmed against this commit
Summary
cc_list_reduce() special-cases list_size == 1 and invokes the callback with a literal NULL as its second argument:
/* src/cc_list.c:1240-1249 */
size_t list_size = cc_list_size(list);
if (list_size == 0)
return CC_ERR_OUT_OF_RANGE;
if (list_size == 1)
{
fn(list->head->data, NULL, result); /* e2 == NULL */
return CC_OK;
}
fn(list->head->data, list->head->next->data, result);
Neither the prototype (src/include/cc_list.h:147) nor the function's doc comment (src/cc_list.c:1228-1236) documents any convention that e2 may be NULL. Any callback written under the natural assumption that e2 is an element pointer — including the library's own test callbacks — deterministically segfaults on a single-element list:
/* test/unit/list_test.c:42-48 */
void sum_reduce(void* e1, void* e2, void* res)
{
int i = *(int*)e1;
int j = *(int*)e2; /* SIGSEGV when e2 == NULL */
*(int*)res = i + j;
}
/* test/unit/list_test.c:50-56 — bool_and() has the same unconditional *(bool*)e2 */
The existing reduce tests never cover this branch: test_reduce1 (list_test.c:1166) uses the pre_filled_lists fixture (4 elements: 1, 2, 3, 4), and test_reduce2 (list_test.c:1179) builds its own 4-element list — so the defect is latent in CI.
Steps to reproduce
CC_List *list = cc_list_new();
int one = 1;
int res = 0;
cc_list_add(list, &one);
cc_list_reduce(list, sum_reduce, &res); /* SIGSEGV, e2 == NULL */
Expected: cc_list_reduce() works on a single-element list (either e2 holds the single element, or a documented NULL contract that callbacks are told to check).
Actual: deterministic NULL pointer dereference (CWE-476) inside the callback.
Suggested fix (library side, preferred)
if (list_size == 1)
{
fn(list->head->data, list->head->data, result); /* e2 = the only element */
return CC_OK;
}
and document the argument semantics in the doc comment. If passing e2 = e1 is not desired, then at minimum the NULL-e2 contract must be documented and the project's own test callbacks made NULL-safe.
Impact: public API (cc_list_reduce), deterministic non-OOM crash of the calling program; silent because CI never exercises the single-element branch.
Environment
dbedaf9599716c9aa16c6e955fdc3ef76d5c5cca(master, checked 2026-08-26)src/cc_list.c,src/include/cc_list.h,test/unit/list_test.cSummary
cc_list_reduce()special-caseslist_size == 1and invokes the callback with a literalNULLas its second argument:Neither the prototype (src/include/cc_list.h:147) nor the function's doc comment (src/cc_list.c:1228-1236) documents any convention that
e2may be NULL. Any callback written under the natural assumption thate2is an element pointer — including the library's own test callbacks — deterministically segfaults on a single-element list:The existing reduce tests never cover this branch:
test_reduce1(list_test.c:1166) uses thepre_filled_listsfixture (4 elements: 1, 2, 3, 4), andtest_reduce2(list_test.c:1179) builds its own 4-element list — so the defect is latent in CI.Steps to reproduce
Expected:
cc_list_reduce()works on a single-element list (eithere2holds the single element, or a documented NULL contract that callbacks are told to check).Actual: deterministic NULL pointer dereference (CWE-476) inside the callback.
Suggested fix (library side, preferred)
and document the argument semantics in the doc comment. If passing
e2 = e1is not desired, then at minimum theNULL-e2contract must be documented and the project's own test callbacks made NULL-safe.Impact: public API (
cc_list_reduce), deterministic non-OOM crash of the calling program; silent because CI never exercises the single-element branch.