Skip to content

Fix NULL-pointer dereference in sym_movcond_internal (#56)#76

Open
Emad-Mahmodi wants to merge 2 commits into
eurecom-s3:masterfrom
Emad-Mahmodi:fix/issue-56-movcond-null-deref
Open

Fix NULL-pointer dereference in sym_movcond_internal (#56)#76
Emad-Mahmodi wants to merge 2 commits into
eurecom-s3:masterfrom
Emad-Mahmodi:fix/issue-56-movcond-null-deref

Conversation

@Emad-Mahmodi

Copy link
Copy Markdown

Summary

Fixes #56. SymQEMU crashes with QEMU internal SIGSEGV {code=MAPERR, addr=0x14}
after generating a few test cases. The root cause is a NULL-pointer dereference
in sym_movcond_internal (accel/tcg/tcg-runtime-sym.c).

Backtrace

The fault is a read at 0x14 with this == NULL (offset of Expr::bits_):

signal SIGSEGV: address not mapped to object (fault address=0x14)
#0 qsym::Expr::bits() const expr.h:129 // return bits_; (offset 0x14)
#1 ::_sym_bits_helper(SymExpr)
#2 sym_movcond_internal tcg-runtime-sym.c
#3 helper_sym_movcond_i64
#4 code_gen_buffer
#5 cpu_tb_exec cpu-exec.c

Root cause

The early return only fires when all four operand expressions are NULL:

if (c1_expr == NULL && c2_expr == NULL && v1_expr == NULL && v2_expr == NULL)
    return NULL;

A movcond whose comparison operands are both concrete (c1_expr and c2_expr
are NULL) but whose selected value is symbolic (v1_expr or v2_expr
non-NULL) does not return early. It then reaches:

if (c1_expr == NULL) {
    c1_expr = _sym_build_integer(c1, _sym_bits_helper(c2_expr));
}

Here c2_expr is also NULL, so _sym_bits_helper(NULL) calls Expr::bits()
on a NULL this, reading bits_ at offset 0x14 → SIGSEGV.

Concrete example

A movcond of the form result = concrete_cond ? symbolic_value : 0 gives:

operand concrete value expression
c1, c2 (condition) e.g. 1, 0 both NULL
v1 (value) symbolic non-NULL
v2 (value) 0 NULL

c1_expr == NULL is true, so _sym_bits_helper(c2_expr) runs with
c2_expr == NULL and crashes.

This explains why it reproduces with some toolchains but not others (LLVM 15 vs
17, as noted in the issue): whether the guest binary contains such a movcond
depends on how the compiler lowered the code. The bug is latent in all cases
and triggers only when that pattern is emitted.

Fix

The bit width does not need to be borrowed from a sibling operand — the function
already receives it as result_bits, and the assertions immediately below
confirm all four operands must equal result_bits.

-    if (c1_expr == NULL) c1_expr = _sym_build_integer(c1, _sym_bits_helper(c2_expr));
-    if (c2_expr == NULL) c2_expr = _sym_build_integer(c2, _sym_bits_helper(c1_expr));
-    if (v1_expr == NULL) v1_expr = _sym_build_integer(v1, _sym_bits_helper(c1_expr));
-    if (v2_expr == NULL) v2_expr = _sym_build_integer(v2, _sym_bits_helper(c1_expr));
+    if (c1_expr == NULL) c1_expr = _sym_build_integer(c1, result_bits);
+    if (c2_expr == NULL) c2_expr = _sym_build_integer(c2, result_bits);
+    if (v1_expr == NULL) v1_expr = _sym_build_integer(v1, result_bits);
+    if (v2_expr == NULL) v2_expr = _sym_build_integer(v2, result_bits);

Regression test

Added tests/symqemu/binaries/movcond_issue56/ (a binary reproducing the
issue's program) and test_movcond_issue56 in tests/symqemu/test.py. Since
the harness runs SymQEMU with check=True, the crash fails the test on the
unpatched build and passes once fixed; the reproducer now runs to completion
instead of crashing after ~14 test cases.

A movcond with concrete comparison operands (c1_expr and c2_expr both
NULL) but a symbolic selected value (v1_expr or v2_expr non-NULL) does
not hit the all-NULL early return. The concrete-operand fixup then took
the bit width from a sibling operand via _sym_bits_helper(c2_expr),
which is itself NULL in that case, so Expr::bits() dereferenced a NULL
'this' (SIGSEGV, fault address 0x14).

Use the result_bits argument the helper already provides instead, which
is also consistent with the assertions that immediately follow.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Symqemu raise SIGSEGV

1 participant