expand_domacro() in libexec/tradcpp/macro.c handles a defined()
with the wrong argument count via an error path that doesn't
drain es->args:
if (stringarray_num(&es->args) != 1) {
complain(p, "Too many arguments for defined()");
complain_fail();
expand_send(es, p, "0", 1);
return;
}
name = stringarray_get(&es->args, 0);
m = macrotable_find(name, false);
val = (m != NULL) ? "1" : "0";
debuglog(p, "defined(%s): %s", name, val);
expand_send(es, p, val, 1);
expstate_destroyargs(es);
return;
The success path calls expstate_destroyargs(es) before returning;
the error path does not. es->args stays populated. On shutdown
array_cleanup() in libexec/tradcpp/array.c asserts a->num == 0
and tradcpp aborts with SIGABRT.
Minimum reproducer (17 bytes):
echo '#if defined(a,b)' | tradcpp /dev/stdin
Output:
<stdin>:1:5: Too many arguments for defined()
assertion "a->num == 0" failed: file "array.c", line 63,
function "array_cleanup"
Abort trap
Reproduced on OpenBSD 7.9 arm64, amd64 and i386 with plain cc
(no sanitizers). Found by AFL++ fuzzing.
OK?
Index: libexec/tradcpp/macro.c
===================================================================
RCS file: /cvs/src/libexec/tradcpp/macro.c,v
retrieving revision 1.4
diff -u -p -r1.4 macro.c
--- libexec/tradcpp/macro.c 23 Aug 2019 04:38:55 -0000 1.4
+++ libexec/tradcpp/macro.c
@@ -885,6 +885,7 @@ expand_domacro(struct expstate *es, stru
complain(p, "Too many arguments for defined()");
complain_fail();
expand_send(es, p, "0", 1);
+ expstate_destroyargs(es);
return;
}
name = stringarray_get(&es->args, 0);