Merge branch 'master' into no_ram_storage

This commit is contained in:
Julien Cretin
2020-12-10 15:48:45 +01:00
2 changed files with 38 additions and 205 deletions

View File

@@ -26,6 +26,9 @@ use std::convert::TryInto;
// NOTE: We should be able to improve coverage by only checking the last operation. Because // NOTE: We should be able to improve coverage by only checking the last operation. Because
// operations before the last could be checked with a shorter entropy. // operations before the last could be checked with a shorter entropy.
// NOTE: Maybe we should split the fuzz target in smaller parts (like one per init). We should also
// name the fuzz targets with action names.
/// Checks the store against a sequence of manipulations. /// Checks the store against a sequence of manipulations.
/// ///
/// The entropy to generate the sequence of manipulation should be provided in `data`. Debugging /// The entropy to generate the sequence of manipulation should be provided in `data`. Debugging
@@ -181,7 +184,7 @@ impl<'a> Fuzzer<'a> {
println!("Power on the store."); println!("Power on the store.");
} }
self.increment(StatKey::PowerOnCount); self.increment(StatKey::PowerOnCount);
let interruption = self.interruption(driver.delay_map()); let interruption = self.interruption(driver.count_operations());
match driver.partial_power_on(interruption) { match driver.partial_power_on(interruption) {
Err((storage, _)) if self.init.is_dirty() => { Err((storage, _)) if self.init.is_dirty() => {
self.entropy.consume_all(); self.entropy.consume_all();
@@ -198,7 +201,7 @@ impl<'a> Fuzzer<'a> {
if self.debug { if self.debug {
println!("{:?}", operation); println!("{:?}", operation);
} }
let interruption = self.interruption(driver.delay_map(&operation)); let interruption = self.interruption(driver.count_operations(&operation));
match driver.partial_apply(operation, interruption) { match driver.partial_apply(operation, interruption) {
Err((store, _)) if self.init.is_dirty() => { Err((store, _)) if self.init.is_dirty() => {
self.entropy.consume_all(); self.entropy.consume_all();
@@ -334,59 +337,48 @@ impl<'a> Fuzzer<'a> {
/// Generates an interruption. /// Generates an interruption.
/// ///
/// The `delay_map` describes the number of modified bits by the upcoming sequence of store /// The `max_delay` describes the number of storage operations.
/// operations. fn interruption(&mut self, max_delay: Option<usize>) -> StoreInterruption {
// TODO(ia0): We use too much CPU to compute the delay map. We should be able to just count the
// number of storage operations by checking the remaining delay. We can then use the entropy
// directly from the corruption function because it's called at most once.
fn interruption(
&mut self,
delay_map: Result<Vec<usize>, (usize, BufferStorage)>,
) -> StoreInterruption {
if self.init.is_dirty() { if self.init.is_dirty() {
// We only test that the store can power on without crashing. If it would get // We only test that the store can power on without crashing. If it would get
// interrupted then it's like powering up with a different initial state, which would be // interrupted then it's like powering up with a different initial state, which would be
// tested with another fuzzing input. // tested with another fuzzing input.
return StoreInterruption::none(); return StoreInterruption::none();
} }
let delay_map = match delay_map { let max_delay = match max_delay {
Ok(x) => x, Some(x) => x,
Err((delay, storage)) => { None => return StoreInterruption::none(),
print!("{}", storage);
panic!("delay={}", delay);
}
}; };
let delay = self.entropy.read_range(0, delay_map.len() - 1); let delay = self.entropy.read_range(0, max_delay);
let mut complete_bits = BitStack::default();
for _ in 0..delay_map[delay] {
complete_bits.push(self.entropy.read_bit());
}
if self.debug { if self.debug {
if delay == delay_map.len() - 1 { if delay == max_delay {
assert!(complete_bits.is_empty());
println!("Do not interrupt."); println!("Do not interrupt.");
} else { } else {
println!( println!("Interrupt after {} operations.", delay);
"Interrupt after {} operations with complete mask {}.",
delay, complete_bits
);
} }
} }
if delay < delay_map.len() - 1 { if delay < max_delay {
self.increment(StatKey::InterruptionCount); self.increment(StatKey::InterruptionCount);
} }
let corrupt = Box::new(move |old: &mut [u8], new: &[u8]| { let corrupt = Box::new(move |old: &mut [u8], new: &[u8]| {
let mut count = 0;
let mut total = 0;
for (old, new) in old.iter_mut().zip(new.iter()) { for (old, new) in old.iter_mut().zip(new.iter()) {
for bit in 0..8 { for bit in 0..8 {
let mask = 1 << bit; let mask = 1 << bit;
if *old & mask == *new & mask { if *old & mask == *new & mask {
continue; continue;
} }
if complete_bits.pop().unwrap() { total += 1;
if self.entropy.read_bit() {
count += 1;
*old ^= mask; *old ^= mask;
} }
} }
} }
if self.debug {
println!("Flip {} bits out of {}.", count, total);
}
}); });
StoreInterruption { delay, corrupt } StoreInterruption { delay, corrupt }
} }
@@ -432,113 +424,3 @@ impl Init {
} }
} }
} }
/// Compact stack of bits.
// NOTE: This would probably go away once the delay map is simplified.
#[derive(Default, Clone, Debug)]
struct BitStack {
/// Bits stored in little-endian (for bytes and bits).
///
/// The last byte only contains `len` bits.
data: Vec<u8>,
/// Number of bits stored in the last byte.
///
/// It is 0 if the last byte is full, not 8.
len: usize,
}
impl BitStack {
/// Returns whether the stack is empty.
fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the length of the stack.
fn len(&self) -> usize {
if self.len == 0 {
8 * self.data.len()
} else {
8 * (self.data.len() - 1) + self.len
}
}
/// Pushes a bit to the stack.
fn push(&mut self, value: bool) {
if self.len == 0 {
self.data.push(0);
}
if value {
*self.data.last_mut().unwrap() |= 1 << self.len;
}
self.len += 1;
if self.len == 8 {
self.len = 0;
}
}
/// Pops a bit from the stack.
fn pop(&mut self) -> Option<bool> {
if self.len == 0 {
if self.data.is_empty() {
return None;
}
self.len = 8;
}
self.len -= 1;
let result = self.data.last().unwrap() & 1 << self.len;
if self.len == 0 {
self.data.pop().unwrap();
}
Some(result != 0)
}
}
impl std::fmt::Display for BitStack {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
let mut bits = self.clone();
while let Some(bit) = bits.pop() {
write!(f, "{}", bit as usize)?;
}
write!(f, " ({} bits)", self.len())?;
Ok(())
}
}
#[test]
fn bit_stack_ok() {
let mut bits = BitStack::default();
assert_eq!(bits.pop(), None);
bits.push(true);
assert_eq!(bits.pop(), Some(true));
assert_eq!(bits.pop(), None);
bits.push(false);
assert_eq!(bits.pop(), Some(false));
assert_eq!(bits.pop(), None);
bits.push(true);
bits.push(false);
assert_eq!(bits.pop(), Some(false));
assert_eq!(bits.pop(), Some(true));
assert_eq!(bits.pop(), None);
bits.push(false);
bits.push(true);
assert_eq!(bits.pop(), Some(true));
assert_eq!(bits.pop(), Some(false));
assert_eq!(bits.pop(), None);
let n = 27;
for i in 0..n {
assert_eq!(bits.len(), i);
bits.push(true);
}
for i in (0..n).rev() {
assert_eq!(bits.pop(), Some(true));
assert_eq!(bits.len(), i);
}
assert_eq!(bits.pop(), None);
}

View File

@@ -311,31 +311,15 @@ impl StoreDriverOff {
}) })
} }
/// Returns a mapping from delay time to number of modified bits. /// Returns the number of storage operations to power on.
/// ///
/// For example if the `i`-th value is `n`, it means that the `i`-th operation modifies `n` bits /// Returns `None` if the store cannot power on successfully.
/// in the storage. For convenience, the vector always ends with `0` for one past the last pub fn count_operations(&self) -> Option<usize> {
/// operation. This permits to choose a random index in the vector and then a random set of bit let initial_delay = usize::MAX;
/// positions among the number of modified bits to simulate any possible corruption (including
/// no corruption with the last index).
pub fn delay_map(&self) -> Result<Vec<usize>, (usize, BufferStorage)> {
let mut result = Vec::new();
loop {
let delay = result.len();
let mut storage = self.storage.clone(); let mut storage = self.storage.clone();
storage.arm_interruption(delay); storage.arm_interruption(initial_delay);
match Store::new(storage) { let mut store = Store::new(storage).ok()?;
Err((StoreError::StorageError, x)) => storage = x, Some(initial_delay - store.storage_mut().disarm_interruption())
Err((StoreError::InvalidStorage, mut storage)) => {
storage.reset_interruption();
return Err((delay, storage));
}
Ok(_) | Err(_) => break,
}
result.push(count_modified_bits(&mut storage));
}
result.push(0);
Ok(result)
} }
} }
@@ -422,29 +406,15 @@ impl StoreDriverOn {
}) })
} }
/// Returns a mapping from delay time to number of modified bits. /// Returns the number of storage operations to apply a store operation.
/// ///
/// See the documentation of [`StoreDriverOff::delay_map`] for details. /// Returns `None` if the store cannot apply the operation successfully.
/// pub fn count_operations(&self, operation: &StoreOperation) -> Option<usize> {
/// [`StoreDriverOff::delay_map`]: struct.StoreDriverOff.html#method.delay_map let initial_delay = usize::MAX;
pub fn delay_map(
&self,
operation: &StoreOperation,
) -> Result<Vec<usize>, (usize, BufferStorage)> {
let mut result = Vec::new();
loop {
let delay = result.len();
let mut store = self.store.clone(); let mut store = self.store.clone();
store.storage_mut().arm_interruption(delay); store.storage_mut().arm_interruption(initial_delay);
match store.apply(operation).1 { store.apply(operation).1.ok()?;
Err(StoreError::StorageError) => (), Some(initial_delay - store.storage_mut().disarm_interruption())
Err(StoreError::InvalidStorage) => return Err((delay, store.extract_storage())),
Ok(()) | Err(_) => break,
}
result.push(count_modified_bits(store.storage_mut()));
}
result.push(0);
Ok(result)
} }
/// Powers off the store. /// Powers off the store.
@@ -629,22 +599,3 @@ impl<'a> StoreInterruption<'a> {
} }
} }
} }
/// Counts the number of bits modified by an interrupted operation.
///
/// # Panics
///
/// Panics if an interruption did not trigger.
fn count_modified_bits(storage: &mut BufferStorage) -> usize {
let mut modified_bits = 0;
storage.corrupt_operation(Box::new(|before, after| {
modified_bits = before
.iter()
.zip(after.iter())
.map(|(x, y)| (x ^ y).count_ones() as usize)
.sum();
}));
// We should never write the same slice or erase an erased page.
assert!(modified_bits > 0);
modified_bits
}