-
Notifications
You must be signed in to change notification settings - Fork 122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
panic in Result::unwrap() due to "InvalidShiftAmount" when analyzing STM32 Firmware #469
Comments
Well, shifting a 32-bit value by 32 bits is a weird way of computing zero. So this is either a very weird assembly instruction or a bug during disassembly of some particular instruction. Currently, I do not have time to look at it, but maybe @vobst has some time? |
@Enkelmann I will take a look :) |
tl;dr: Piecing together a full register assignment from a subregister assignment produces a semantically invalid expression if the subregister has the same name as the full register. Bug (long version)The offending CPU instruction and its low pcode are:
For reference, here are also the semantics according to the ARM (ARM Reference Manual).
The instruction essentially adds the four individual bytes of the registers separately. Now this is what we get out of the Ghidra plugin
Here we also see an unrelated issue: Reading single bytes of registers is accomplished in pcode by directly accessing the Register Address Space via offsets, e.g., Now the unnormalized IR looks like this:
Here we can see that:
got changed to
Which happens since we convert assignments to subregisters to assignments to the full registers by piecing together a large value from the current register value and the small value that is being assigned. However, here clearly something went wrong as fn replace_output_subregister(&mut self, def: Term<Def>) {
match &def.term {
Def::Assign { var, value } => {
if let Some(register) = self.register_map.get(&var.name) {
// var = Variable { name: "r5", size: ByteSize(1), is_temp: false }
// value = ($Ucf900:4(temp))[0-0]
// register = RegisterProperties { register: "r5", base_register: "r5", lsb: ByteSize(0), size: ByteSize(4) }
if var.name != register.base_register || var.size < register.size { // false || true
if self.is_next_def_cast_to_base_register(var) { // false
...
} else {
// base_register = RegisterProperties { register: "r5", base_register: "r5", lsb: ByteSize(0), size: ByteSize(4) }
let base_register: &RegisterProperties =
self.register_map.get(®ister.base_register).unwrap();
let output_var: Variable = base_register.into();
let output_expression =
piece_base_register_assignment_expression_together( // panic in here
value,
base_register,
register,
);
... So the problem is that FixAs far as I can tell the code in
both of which are violated there. I guess an obvious way to fix this would be to set Discussion
|
Assignments to subregisters that have the same name as the full register may lead to expressions that have SUBPIECE operations that would result in zero-sized values. In general, the subregister substitution produces semantically incorrect IR code in those cases. I observed that this occurs when assignments are made to the lower bytes of a larger register. Those cases should be handled by this patch. If this may also happen for assignments to other subregisters, then this patch is incomplete (see FIXME). Reported-by: https://github.com/ljungnickel Closes: fkie-cad#469 Signed-off-by: Valentin Obst <[email protected]>
cwe_checker --verbose PLC.elf thread 'main' panicked at src/cwe_checker_lib/src/intermediate_representation/bitvector.rs:74:14: called Result::unwrap() on an Err value: Error { kind: InvalidShiftAmount { shift_amount: ShiftAmount(32), width: BitWidth(32) }, message: "Encountered invalid shift amount of ShiftAmount(32) on bit-width with BitWidth(32) bits.", annotation: None } stack backtrace: 0: rust_begin_unwind 1: core::panicking::panic_fmt 2: core::result::unwrap_failed 3: <apint::apint::ApInt as cwe_checker_lib::intermediate_representation::bitvector::BitvectorExtended>::subpiece 4: cwe_checker_lib::abstract_domain::interval::simple_interval::Interval::subpiece_higher 5: <cwe_checker_lib::abstract_domain::interval::IntervalDomain as cwe_checker_lib::abstract_domain::RegisterDomain>::subpiece 6: cwe_checker_lib::abstract_domain::data::arithmetics::<impl cwe_checker_lib::abstract_domain::RegisterDomain for cwe_checker_lib::abstract_domain::data::DataDomain<T>>::subpiece 7: cwe_checker_lib::analysis::pointer_inference::state::access_handling::<impl cwe_checker_lib::analysis::pointer_inference::state::State>::eval_recursive 8: cwe_checker_lib::analysis::pointer_inference::state::access_handling::<impl cwe_checker_lib::analysis::pointer_inference::state::State>::eval_recursive 9: cwe_checker_lib::analysis::pointer_inference::context::trait_impls::<impl cwe_checker_lib::analysis::forward_interprocedural_fixpoint::Context for cwe_checker_lib::analysis::pointer_inference::context::Context>::update_def 10: cwe_checker_lib::analysis::fixpoint::Computation<T>::compute_with_max_steps 11: cwe_checker_lib::analysis::pointer_inference::PointerInference::compute 12: cwe_checker_lib::analysis::pointer_inference::run 13: cwe_checker_lib::pipeline::results::AnalysisResults::compute_pointer_inference 14: cwe_checker::main note: Some details are omitted, run with RUST_BACKTRACE=full for a verbose backtrace.
The firmware under test is from here: https://github.com/fuzzware-fuzzer/fuzzware/tree/main/examples/P2IM/PLC
My setup is working flawlessly for other ELF / BIN + firmware-config files, I guess there is something weird going on in this ELF.
PLC.zip
The text was updated successfully, but these errors were encountered: