Unverified Commit 2808701c by van den Bosch Committed by GitHub

Stack alignment analysis (#317)

parent 2c469338
......@@ -9,5 +9,6 @@ pub mod function_signature;
pub mod graph;
pub mod interprocedural_fixpoint_generic;
pub mod pointer_inference;
pub mod stack_alignment_substitution;
pub mod string_abstraction;
pub mod vsa_results;
//! Substitutes stack pointer alignment operations utilising logical AND with an arithmetic SUB operation.
//!
//! The first basic block of every function is searched for a logical AND operation on the stack pointer.
//! By journaling changes to the stack pointer an offset is calculated which is going to be used to alter the operation
//! into a subtraction.
//!
//! # Log Messages
//! Following cases trigger log messages:
//! - alignment is untypical for the architecture
//! - the argument for the AND operation is not a constant
//! - an operation alters the stack pointer, which can not be journaled.
use anyhow::{anyhow, Result};
use apint::ApInt;
use itertools::Itertools;
use crate::{intermediate_representation::*, utils::log::LogMessage};
/// Substitutes AND operation by SUB operation with calculated constants.
/// Constants are derived by a journaled stackpointer value and the bitmask provided by the operation.
fn substitute(
exp: &mut Expression,
expected_alignment: i64,
journaled_sp: &mut i64,
tid: Tid,
) -> Vec<LogMessage> {
let mut log: Vec<LogMessage> = vec![];
if let Expression::BinOp { op, lhs, rhs } = exp {
match (&**lhs, &**rhs) {
(Expression::Var(sp), Expression::Const(bitmask))
| (Expression::Const(bitmask), Expression::Var(sp)) => {
if let BinOpType::IntAnd = op {
if ApInt::try_to_i64(&ApInt::into_negate(bitmask.clone())).unwrap()
!= expected_alignment
{
log.push(LogMessage::new_info("Unexpected alignment").location(tid));
}
let offset =
*journaled_sp - (*journaled_sp & bitmask.clone().try_to_i64().unwrap());
let sp = sp.clone();
*op = BinOpType::IntSub;
*rhs = Box::new(Expression::Const(
(ApInt::from_i64(offset)).into_resize_unsigned(bitmask.bytesize()),
));
*lhs = Box::new(Expression::Var(sp));
} else {
log.push(LogMessage::new_info("Unsubstitutable Operation on SP").location(tid))
};
}
_ => log.push(
LogMessage::new_info(
"Unsubstitutable Operation on SP. Operants are not register and constant.",
)
.location(tid),
),
}
} else {
log.push(LogMessage::new_info("Unsubstitutable Operation on SP").location(tid))
}
log
}
/// Updates current stackpointer value by given Constant.
fn journal_sp_value(
journaled_sp: &mut i64,
is_plus: bool,
(rhs, lhs): (&Expression, &Expression),
sp_register: &Variable,
) -> Result<()> {
match (rhs, lhs) {
(Expression::Var(sp), Expression::Const(constant))
| (Expression::Const(constant), Expression::Var(sp)) => {
if sp == sp_register {
match is_plus {
true => *journaled_sp += constant.try_to_i64().unwrap(),
false => *journaled_sp -= constant.try_to_i64().unwrap(),
}
Ok(())
} else {
Err(anyhow!("Input not stackpointer register and constant."))
}
}
_ => Err(anyhow!("Input not register and constant.")),
}
}
/// Substitutes logical AND on the stackpointer register by SUB.
/// Expressions are changed to use constants w.r.t the provided bit mask.
pub fn substitute_and_on_stackpointer(project: &mut Project) -> Option<Vec<LogMessage>> {
// for sanity check
let sp_alignment = match project.cpu_architecture.as_str() {
"x86_32" => 16,
"x86_64" => 16,
"arm32" => 4,
_ => 0,
};
let mut log: Vec<LogMessage> = vec![];
'sub_loop: for sub in project.program.term.subs.values_mut() {
let journaled_sp: &mut i64 = &mut 0;
// only for the first block SP can be reasonable tracked
if let Some(blk) = sub.term.blocks.first_mut() {
for def in blk.term.defs.iter_mut() {
if let Def::Assign { var, value } = &mut def.term {
if *var == project.stack_pointer_register {
if let Expression::BinOp { op, lhs, rhs } = value {
match op {
BinOpType::IntAdd => {
if journal_sp_value(
journaled_sp,
true,
(lhs, rhs),
&project.stack_pointer_register,
)
.is_err()
{
continue 'sub_loop;
}
}
BinOpType::IntSub => {
if journal_sp_value(
journaled_sp,
false,
(lhs, rhs),
&project.stack_pointer_register,
)
.is_err()
{
continue 'sub_loop;
}
}
_ => {
let mut msg = substitute(
value,
sp_alignment,
journaled_sp,
def.tid.clone(),
);
log.append(&mut msg);
if !log
.iter()
.filter(|x| {
x.text.contains("Unsubstitutable Operation on SP")
})
.collect_vec()
.is_empty()
{
// Lost track of SP
continue 'sub_loop;
}
}
}
} else {
log.push(
LogMessage::new_info("Unexpected assignment on SP")
.location(def.tid.clone()),
);
continue 'sub_loop;
}
}
}
}
}
}
if log.is_empty() {
return None;
}
Some(log)
}
#[cfg(test)]
mod tests;
use super::*;
use std::borrow::BorrowMut;
/// Creates a x64 or ARM32 Project for easy addidion of assignments.
fn setup(mut defs: Vec<Term<Def>>, is_x64: bool) -> Project {
let mut proj = match is_x64 {
true => Project::mock_x64(),
false => Project::mock_arm32(),
};
let mut blk = Blk::mock();
blk.term.defs.append(defs.as_mut());
let mut sub = Sub::mock("Sub");
sub.term.blocks.push(blk);
proj.program.term.subs.insert(Tid::new("sub_tid"), sub);
proj
}
#[test]
/// Tests the return of log messages for all alignments, including unexpected alignments for x64 and arm32.
fn unexpected_alignment() {
for i in 0..31 {
// case x64
let def_x64 = vec![Def::assign(
"tid1",
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::IntAnd,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u32(
0xFFFFFFFF << i,
))),
},
)];
let mut proj_x64 = setup(def_x64, true);
let log = substitute_and_on_stackpointer(proj_x64.borrow_mut());
if 2_i32.pow(i) == 16 {
assert!(log.is_none());
} else {
assert!(log.is_some());
for msg in log.unwrap() {
assert!(msg.text.contains("Unexpected alignment"));
}
}
// case ARM32
let def_arm = vec![Def::assign(
"tid1",
Project::mock_arm32().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::IntAnd,
lhs: Box::new(Expression::Var(
Project::mock_arm32().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u32(
0xFFFFFFFF << i,
))),
},
)];
let mut proj_arm = setup(def_arm, false);
let log = substitute_and_on_stackpointer(proj_arm.borrow_mut());
if 2_i32.pow(i) == 4 {
assert!(log.is_none());
} else {
assert!(log.is_some());
for msg in log.unwrap() {
assert!(msg.text.contains("Unexpected alignment"));
}
}
}
}
#[test]
/// Tests the substituted offset meets the alignment for x64. Tests only the logical AND case.
fn compute_correct_offset_x64() {
for i in 0..=33 {
let sub_from_sp = Def::assign(
"tid_alter_sp",
Project::mock_x64().stack_pointer_register.clone(),
Expression::minus(
Expression::Var(Project::mock_x64().stack_pointer_register.clone()),
Expression::const_from_apint(ApInt::from_u64(i)),
),
);
let byte_alignment_as_and = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::IntAnd,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u64(
0xFFFFFFFF_FFFFFFFF << 4, // 16 Byte alignment
))),
},
);
let mut proj = setup(
vec![sub_from_sp.clone(), byte_alignment_as_and.clone()],
true,
);
let log = substitute_and_on_stackpointer(proj.borrow_mut());
for sub in proj.program.term.subs.into_values() {
for blk in sub.term.blocks {
for def in blk.term.defs {
if def.tid == byte_alignment_as_and.tid.clone() {
let expected_offset: u64 = match i % 16 {
0 => 0,
_ => (16 - (i % 16)).into(),
};
// translated alignment as substraction
let expected_def = Def::Assign {
var: proj.stack_pointer_register.clone(),
value: Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(proj.stack_pointer_register.clone())),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u64(
expected_offset,
))),
},
};
assert_eq!(expected_def, def.term);
assert!(log.is_none());
}
}
}
}
}
}
#[test]
/// Tests the substituted offset meets the alignment for arm32. Tests only the logical AND case.
fn compute_correct_offset_arm32() {
for i in 0..=33 {
let sub_from_sp = Def::assign(
"tid_alter_sp",
Project::mock_arm32().stack_pointer_register.clone(),
Expression::minus(
Expression::Var(Project::mock_arm32().stack_pointer_register.clone()),
Expression::const_from_apint(ApInt::from_u32(i)),
),
);
let byte_alignment_as_and = Def::assign(
"tid_to_be_substituted",
Project::mock_arm32().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::IntAnd,
lhs: Box::new(Expression::Var(
Project::mock_arm32().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u32(
0xFFFFFFFF << 2, // 4 Byte alignment
))),
},
);
let mut proj = setup(
vec![sub_from_sp.clone(), byte_alignment_as_and.clone()],
false,
);
let log = substitute_and_on_stackpointer(proj.borrow_mut());
for sub in proj.program.term.subs.into_values() {
for blk in sub.term.blocks {
for def in blk.term.defs {
if def.tid == byte_alignment_as_and.tid.clone() {
let expected_offset = match i % 4 {
0 => 0,
_ => 4 - (i % 4),
};
// translated alignment as substraction
let expected_def = Def::Assign {
var: proj.stack_pointer_register.clone(),
value: Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(proj.stack_pointer_register.clone())),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u32(
expected_offset,
))),
},
};
assert_eq!(expected_def, def.term);
assert!(log.is_none());
}
}
}
}
}
}
#[test]
/// Checks behaviour on supported and unsupported binary operations.
fn check_bin_operations() {
for biopty in vec![
BinOpType::Piece,
BinOpType::IntAdd,
BinOpType::IntSub,
BinOpType::IntAnd,
BinOpType::IntOr,
] {
let unsupported_def_x64 = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: biopty,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_i32(0)),
},
);
let unsupported_def_arm32 = Def::assign(
"tid_to_be_substituted",
Project::mock_arm32().stack_pointer_register.clone(),
Expression::BinOp {
op: biopty,
lhs: Box::new(Expression::Var(
Project::mock_arm32().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_i32(0)),
},
);
let mut proj_x64 = setup(vec![unsupported_def_x64.clone()], true);
let log_x64 = substitute_and_on_stackpointer(proj_x64.borrow_mut());
let mut proj_arm32 = setup(vec![unsupported_def_arm32.clone()], false);
let log_arm32 = substitute_and_on_stackpointer(proj_arm32.borrow_mut());
for log in vec![log_arm32, log_x64] {
match biopty {
BinOpType::IntAnd => {
assert_eq!(log.clone().unwrap().len(), 1);
assert!(log
.unwrap()
.pop()
.unwrap()
.text
.contains("Unexpected alignment"));
}
BinOpType::IntAdd | BinOpType::IntSub => {
assert!(log.is_none())
}
_ => {
assert_eq!(log.clone().unwrap().len(), 1);
assert!(log
.unwrap()
.pop()
.unwrap()
.text
.contains("Unsubstitutable Operation on SP"));
}
}
}
}
}
#[test]
/// Checks if the substitution on logical operations ends if an unsubstitutable operation occured.
fn substitution_ends_if_unsubstituable() {
let alignment_16_byte_as_and = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::IntAnd,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u64(
0xFFFFFFFF_FFFFFFFF << 4, // 16 Byte alignment
))),
},
);
let unsubstitutable = Def::assign(
"tid_unsubstitutable",
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::Piece,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_i64(0)),
},
);
let mut proj = setup(
vec![
alignment_16_byte_as_and.clone(),
unsubstitutable.clone(),
alignment_16_byte_as_and.clone(),
],
true,
);
let log = substitute_and_on_stackpointer(proj.borrow_mut());
assert!(log.is_some());
assert!(log
.unwrap()
.pop()
.unwrap()
.text
.contains("Unsubstitutable Operation on SP"));
let exp_16_byte_alignment_substituted = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u64(0))),
},
);
for sub in proj.program.term.subs.into_values() {
for blk in sub.term.blocks {
assert_eq!(
blk.term.defs,
vec![
exp_16_byte_alignment_substituted.clone(),
unsubstitutable.clone(),
alignment_16_byte_as_and.clone()
]
);
}
}
}
#[test]
/// Tests if the substitution supports commutativity of the expression.
fn supports_commutative_and() {
let var_and_bitmask = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::IntAnd,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u64(
0xFFFFFFFF_FFFFFFFF << 4, // 16 Byte alignment
))),
},
);
let bitmask_and_var = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::IntAnd,
lhs: Box::new(Expression::const_from_apint(ApInt::from_u64(
0xFFFFFFFF_FFFFFFFF << 4, // 16 Byte alignment
))),
rhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
},
);
let mut proj = setup(vec![bitmask_and_var], true);
let log = substitute_and_on_stackpointer(proj.borrow_mut());
assert!(log.is_none());
let expected_def = Def::assign(
"tid_to_be_substituted",
Project::mock_x64().stack_pointer_register.clone(),
Expression::BinOp {
op: BinOpType::IntSub,
lhs: Box::new(Expression::Var(
Project::mock_x64().stack_pointer_register.clone(),
)),
rhs: Box::new(Expression::const_from_apint(ApInt::from_u64(0))),
},
);
for sub in proj.program.term.subs.into_values() {
for blk in sub.term.blocks {
for def in blk.term.defs {
assert_eq!(def, expected_def);
}
}
}
}
......@@ -194,6 +194,7 @@ mod tests {
use crate::intermediate_representation::{Def, Expression, Variable};
impl Blk {
/// Creates empty block with tid "block".
pub fn mock() -> Term<Blk> {
Term {
tid: Tid::new("block"),
......
......@@ -227,11 +227,17 @@ impl Project {
/// - Remove dead register assignments
#[must_use]
pub fn normalize(&mut self) -> Vec<LogMessage> {
let logs = self.remove_references_to_nonexisting_tids_and_retarget_non_returning_calls();
let mut logs =
self.remove_references_to_nonexisting_tids_and_retarget_non_returning_calls();
make_block_to_sub_mapping_unique(self);
self.propagate_input_expressions();
self.substitute_trivial_expressions();
crate::analysis::dead_variable_elimination::remove_dead_var_assignments(self);
logs.append(
crate::analysis::stack_alignment_substitution::substitute_and_on_stackpointer(self)
.unwrap_or_default()
.as_mut(),
);
logs
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment