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);

Reply via email to