angr/claripy

Bug in bitwise_or_simplifier?

Closed this issue · 9 comments

Hello,

I am encountering a crash in angr. The exact scenario is difficult to reproduce, but I think the following information might be sufficient.

Here is a backtrace of the Claripy exception:

(Pdb) traceback.print_exc()
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/angr/engines/vex/claripy/irop.py", line 375, in calculate
    return self.extend_size(self._calculate(args))
  File "/usr/local/lib/python3.7/site-packages/angr/engines/vex/claripy/irop.py", line 437, in _op_mapped
    return getattr(claripy.ast.BV, o)(*sized_args)
  File "/usr/local/lib/python3.7/site-packages/claripy/operations.py", line 53, in _op
    simp = _handle_annotations(simplifications.simpleton.simplify(name, fixed_args), args)
  File "/usr/local/lib/python3.7/site-packages/claripy/simplifications.py", line 42, in simplify
    return self._simplifiers[op](*args)
  File "/usr/local/lib/python3.7/site-packages/claripy/simplifications.py", line 604, in bitwise_or_simplifier
    elif (a == b).is_true():
  File "/usr/local/lib/python3.7/site-packages/claripy/operations.py", line 53, in _op
    simp = _handle_annotations(simplifications.simpleton.simplify(name, fixed_args), args)
  File "/usr/local/lib/python3.7/site-packages/claripy/simplifications.py", line 42, in simplify
    return self._simplifiers[op](*args)
  File "/usr/local/lib/python3.7/site-packages/claripy/simplifications.py", line 227, in eq_simplifier
    simp = SimplificationManager.zeroext_extract_comparing_against_constant_simplifier(operator.__eq__, a, b)
  File "/usr/local/lib/python3.7/site-packages/claripy/simplifications.py", line 950, in zeroext_extract_comparing_against_constant_simplifier
    return op(ast.all_operations.ZeroExt(to_extend, a_inner_expr), b)
  File "/usr/local/lib/python3.7/site-packages/claripy/operations.py", line 53, in _op
    simp = _handle_annotations(simplifications.simpleton.simplify(name, fixed_args), args)
  File "/usr/local/lib/python3.7/site-packages/claripy/simplifications.py", line 42, in simplify
    return self._simplifiers[op](*args)
  File "/usr/local/lib/python3.7/site-packages/claripy/simplifications.py", line 232, in eq_simplifier
    simp = SimplificationManager.zeroext_comparing_against_simplifier(operator.__eq__, a, b)
  File "/usr/local/lib/python3.7/site-packages/claripy/simplifications.py", line 968, in zeroext_comparing_against_simplifier
    b_highbits = b[b.size() - 1 : b.size() - a_zeroext_bits]
  File "/usr/local/lib/python3.7/site-packages/claripy/ast/bv.py", line 72, in __getitem__
    return Extract(left, right, self)
  File "/usr/local/lib/python3.7/site-packages/claripy/operations.py", line 50, in _op
    raise ClaripyOperationError(msg)
claripy.errors.ClaripyOperationError: Extract low must be <= high

And here are the args of the bitwise OR operation:

(Pdb) print(args)
(<BV8 (0 .. mem_c0000000_255_32{UNINITIALIZED}[14:0])[15:8]>, <BV8 128>)

Any help would be appreciated.

(Pdb) list
963             can be eliminated.
964             """
965             if op in {operator.__eq__, operator.__ne__} and a.op == "ZeroExt" and b.op == "BVV":
966                 # check if the high bits of b are zeros
967                 a_zeroext_bits = a.args[0]
968  ->             b_highbits = b[b.size() - 1 : b.size() - a_zeroext_bits]
969                 if (b_highbits == 0).is_true():
970                     # we can get rid of ZeroExt
971                     return op(a.args[1], b[b.size() - a_zeroext_bits - 1 : 0])
972                 elif (b_highbits == 0).is_false():
973                     # unsat
(Pdb) print(a_zeroext_bits)
-7
(Pdb) print(a)
<BV8 0#-7 .. mem_c0000000_383_32{UNINITIALIZED}[14:0]>
(Pdb) print(a)

Not sure where the -7 is coming from.

This code doesn't seem quite right to me:

https://github.com/angr/claripy/blob/master/claripy/simplifications.py#L934

I think a.size() - a_hi is supposed to be a_arg.size() - a_hi unless there is a simplification I'm not seeing.

I'm really unsure what the original code was shooting for. My expression looks something like this:

(0 .. stuff)[15:8] == 128:u8

Obviously the high bits of the concat are zeros. So we we could replace (0 .. stuff) with ZeroExt(stuff.size() + 1, stuff). But the extract is still there, so the final expression would be ZeroExt(stuff.size() + 1, stuff)[15:8] == 128:u8.

The current code appears to return ZeroExt(-7, stuff) == 128:u8.

The other concerning thing is that the comments say If the high bits of b are 0, Extract and Concat/ZeroExt can be eliminated. The high_bits_are_zeros flag is being set, but the extract can definitely not be eliminated! The only way I could see the extract being eliminated is if it only extracted zeros. Otherwise the extract needs to be there to get rid of the lower bits excluded by the extract.

This is probably my code. I'll take a look after my meetings today.

I'm going to bed now. I will come back tomorrow to explain my fix better.

Thanks for reporting!

Thanks for taking a look at this! Just took a quick look at the fix. I think the constraint that lo == 0 was the missing piece that I was having trouble inferring.

One quick thought though: it looks like the rule doesn't really depend on b anymore. So does there really need to be a == b or != b for this simplification to apply?

I think the constraint that lo == 0 was the missing piece that I was having trouble inferring.

That was my intent when I wrote this optimization. Obviously my test cases were too poor to catch that bug...

One quick thought though: it looks like the rule doesn't really depend on b anymore. So does there really need to be a == b or != b for this simplification to apply?

You are definitely right. However, I do not have enough evidence that shows if this optimization is necessary for other comparison operators during symbolic execution. Hence, I decided to fix the docstring, leave a TODO note, and merge the fix.

Thanks again @ltfish!

You are welcome!