I'm two-thirds of the way debugging this NULL pointer dereference in *cur_scope
I found fuzzing with afl++. The minimal test case is
n[sizeof({
It may not crash outright unless you use -fstack-protector (ProPolice), which
is enabled on Debian's package for example. Bisecting (0.9.27 was in the clear)
I found this is the responsible commit:
commit 4a70b2bc2d080baa4082d9d902f9172f88021019
Author: Michael Matz <matz@suse.de>
Date: Wed Jan 15 23:32:40 2020 +0100
Fix handling of unevaluated subexpression of const
we were emitting error messages for something like
'static int i = 2 || 1/0', even though the exception would be in
the unevaluated part. This doesn't destroy const-ness, so we must
accept it. This requires splitting the nocode_wanted values a bit more,
so that nocode_wanted due to const_wanted can be differentiated from
nocode_wanted due to non-evaluation.
tccgen.c | 9 +++++----
Here's a portion of the non-test-suite changes that are pertinent:
@@ -53,6 +53,7 @@ static SValue _vstack[1 + VSTACK_SIZE];
ST_DATA int nocode_wanted; /* no code generation wanted */
+#define unevalmask 0xffff /* unevaluated subexpression */
#define NODATA_WANTED (nocode_wanted > 0) /* no static data output wanted
either */
#define STATIC_DATA_WANTED (nocode_wanted & 0xC0000000) /* only static data
output */
@@ -5104,7 +5105,7 @@ ST_FUNC void unary(void)
}
} else if (tok == '{') {
int saved_nocode_wanted = nocode_wanted;
- if (const_wanted)
+ if (const_wanted && !(nocode_wanted & unevalmask))
tcc_error("expected constant");
/* save all registers */
save_regs(0);
@@ -6152,9 +6153,9 @@ ST_FUNC void gexpr(void)
static void expr_const1(void)
{
const_wanted++;
- nocode_wanted++;
+ nocode_wanted += unevalmask + 1;
expr_cond();
- nocode_wanted--;
+ nocode_wanted -= unevalmask + 1;
Notice how the change in unary() pertains to the '{' character. Commenting out the
'&& !(nocode_wanted & unevalmask)' check is sufficient to fix this, but surely
it was put there for a reason.
Anyway, unary() doesn't stop the bad input right away and hands the input off
to block():
} else if (t == '{') {
struct scope o;
new_scope(&o);
The NULL pointer dereference happens in new_scope() right here (I put the
following assertion myself which fails even without ProPolice)
void new_scope(struct scope *o)
{
/* copy and link previous scope */
assert(cur_scope != NULL);
*o = *cur_scope;
Here's a backtrace excerpt:
#5 new_scope (o=<optimized out>) at tccgen.c:6425
#6 0x000055555556affa in block (is_expr=is_expr@entry=1) at tccgen.c:6538
#7 0x000055555556ccae in unary () at tccgen.c:5117
I think most variable names aren't very descriptive however and `bt full` looks
like alphabet soup, so I guess cur_scope is probably uninitialized but this is
as far as I've got. If someone could nudge me along or feels like finishing the
job, either would be great.
Sincerely,
John
------------------------------------------------------------------------
_______________________________________________
Tinycc-devel mailing list
Tinycc-devel@nongnu.org
https://lists.nongnu.org/mailman/listinfo/tinycc-devel