mirror of
https://github.com/FeitianTech/OpenSK.git
synced 2026-08-28 10:41:01 -07:00
Merge branch 'master' into apdu-parser
This commit is contained in:
@@ -29,3 +29,5 @@ jobs:
|
||||
run: cargo fuzz build
|
||||
- name: Cargo fuzz build (libraries/cbor)
|
||||
run: cd libraries/cbor && cargo fuzz build && cd ../..
|
||||
- name: Cargo fuzz build (libraries/persistent_store)
|
||||
run: cd libraries/persistent_store && cargo fuzz build && cd ../..
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
- name: Set up OpenSK
|
||||
run: ./setup.sh
|
||||
|
||||
- run: echo "::set-env name=RUSTFLAGS::-C target-feature=+aes"
|
||||
- run: echo "RUSTFLAGS=-C target-feature=+aes" >> $GITHUB_ENV
|
||||
|
||||
- name: Unit testing of crypto library (release mode)
|
||||
uses: actions-rs/cargo@v1
|
||||
|
||||
@@ -928,6 +928,13 @@ if __name__ == "__main__":
|
||||
const="console_test",
|
||||
help=("Compiles and installs the console_test example that tests the "
|
||||
"console driver with messages of various lengths."))
|
||||
apps_group.add_argument(
|
||||
"--nfct_test",
|
||||
dest="application",
|
||||
action="store_const",
|
||||
const="nfct_test",
|
||||
help=("Compiles and installs the nfct_test example that tests the "
|
||||
"NFC driver."))
|
||||
|
||||
main_parser.set_defaults(features=["with_ctap1"])
|
||||
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
extern crate lang_items;
|
||||
extern crate libtock_drivers;
|
||||
|
||||
use core::fmt::Write;
|
||||
use libtock_drivers::console::Console;
|
||||
|
||||
#[cfg(not(feature = "with_nfc"))]
|
||||
mod example {
|
||||
use super::Console;
|
||||
use super::Write;
|
||||
|
||||
pub fn nfc(console: &mut Console) {
|
||||
writeln!(console, "NFC feature flag is missing!").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "with_nfc")]
|
||||
mod example {
|
||||
use super::Console;
|
||||
use super::Write;
|
||||
use libtock_core::result::CommandError;
|
||||
use libtock_drivers::nfc::NfcTag;
|
||||
use libtock_drivers::nfc::RecvOp;
|
||||
use libtock_drivers::result::FlexUnwrap;
|
||||
use libtock_drivers::result::TockError;
|
||||
use libtock_drivers::timer;
|
||||
use libtock_drivers::timer::Timer;
|
||||
use libtock_drivers::timer::Timestamp;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
enum ReturnCode {
|
||||
/// Operation completed successfully
|
||||
SUCCESS,
|
||||
/// Generic failure condition
|
||||
FAIL,
|
||||
/// Underlying system is busy; retry
|
||||
EBUSY,
|
||||
/// The component is powered down
|
||||
EOFF,
|
||||
/// An invalid parameter was passed
|
||||
EINVAL,
|
||||
/// Operation canceled by a call
|
||||
ECANCEL,
|
||||
/// Memory required not available
|
||||
ENOMEM,
|
||||
/// Operation or command is unsupported
|
||||
ENOSUPPORT,
|
||||
}
|
||||
|
||||
impl From<isize> for ReturnCode {
|
||||
fn from(original: isize) -> ReturnCode {
|
||||
match original {
|
||||
0 => ReturnCode::SUCCESS,
|
||||
-1 => ReturnCode::FAIL,
|
||||
-2 => ReturnCode::EBUSY,
|
||||
-4 => ReturnCode::EOFF,
|
||||
-6 => ReturnCode::EINVAL,
|
||||
-8 => ReturnCode::ECANCEL,
|
||||
-9 => ReturnCode::ENOMEM,
|
||||
_ => ReturnCode::ENOSUPPORT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to write on console the received packet.
|
||||
fn print_rx_buffer(buf: &mut [u8]) {
|
||||
if let Some((last, bytes)) = buf.split_last() {
|
||||
let mut console = Console::new();
|
||||
write!(console, "RX:").unwrap();
|
||||
for byte in bytes {
|
||||
write!(console, " {:02x?}", byte).unwrap();
|
||||
}
|
||||
writeln!(console, " {:02x?}", last).unwrap();
|
||||
console.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/// Function to identify the time elapsed for a transmission request.
|
||||
fn bench_transmit(
|
||||
console: &mut Console,
|
||||
timer: &Timer,
|
||||
title: &str,
|
||||
mut buf: &mut [u8],
|
||||
) -> ReturnCode {
|
||||
let amount = buf.len();
|
||||
let start = Timestamp::<f64>::from_clock_value(timer.get_current_clock().flex_unwrap());
|
||||
match NfcTag::transmit(&mut buf, amount) {
|
||||
Ok(_) => (),
|
||||
Err(TockError::Command(CommandError {
|
||||
return_code: -8, /* ECANCEL: No Field*/
|
||||
..
|
||||
})) => return ReturnCode::ECANCEL,
|
||||
Err(_) => writeln!(Console::new(), " -- tx error!").unwrap(),
|
||||
}
|
||||
let end = Timestamp::<f64>::from_clock_value(timer.get_current_clock().flex_unwrap());
|
||||
let elapsed = (end - start).ms();
|
||||
writeln!(
|
||||
console,
|
||||
"{}\n{:.2} ms elapsed for {} bytes ({:.2} kbit/s)",
|
||||
title,
|
||||
elapsed,
|
||||
amount,
|
||||
(amount as f64) / elapsed * 8.
|
||||
)
|
||||
.unwrap();
|
||||
console.flush();
|
||||
ReturnCode::SUCCESS
|
||||
}
|
||||
|
||||
fn receive_packet(console: &mut Console, mut buf: &mut [u8; 256]) -> ReturnCode {
|
||||
match NfcTag::receive(&mut buf) {
|
||||
Ok(RecvOp {
|
||||
recv_amount: amount,
|
||||
..
|
||||
}) => {
|
||||
if amount <= buf.len() {
|
||||
print_rx_buffer(&mut buf[..amount]);
|
||||
}
|
||||
}
|
||||
Err(TockError::Command(CommandError { return_code, .. })) => return return_code.into(),
|
||||
Err(_) => {
|
||||
writeln!(console, " -- RX Err").unwrap();
|
||||
return ReturnCode::ECANCEL;
|
||||
}
|
||||
}
|
||||
ReturnCode::SUCCESS
|
||||
}
|
||||
|
||||
fn transmit_reply(mut console: &mut Console, timer: &Timer, buf: &[u8]) -> ReturnCode {
|
||||
let mut return_code = ReturnCode::SUCCESS;
|
||||
match buf[0] {
|
||||
0xe0 /* RATS */=> {
|
||||
let mut answer_to_select = [0x05, 0x78, 0x80, 0xB1, 0x00];
|
||||
return_code = bench_transmit(&mut console, &timer, "TX: ATS", &mut answer_to_select);
|
||||
}
|
||||
0xc2 /* DESELECT */ => {
|
||||
// Ignore the request
|
||||
let mut command_error = [0x6A, 0x81];
|
||||
return_code = bench_transmit(&mut console, &timer, "TX: DESELECT", &mut command_error);
|
||||
}
|
||||
0x02 | 0x03 /* APDU Prefix */ => match buf[2] {
|
||||
// If the received packet is applet selection command (FIDO 2)
|
||||
0xa4 /* SELECT */ => if buf[3] == 0x04 && buf[5] == 0x08 && buf[6] == 0xa0 {
|
||||
// Vesion: "FIDO_2_0"
|
||||
let mut reply = [buf[0], 0x46, 0x49, 0x44, 0x4f, 0x5f, 0x32, 0x5f, 0x30, 0x90, 0x00,];
|
||||
return_code = bench_transmit(&mut console, &timer, "TX: Version Str", &mut reply);
|
||||
} else if (buf[6] == 0xd2 && buf[7] == 0x76) || (buf[6] == 0xe1 && (buf[7] == 0x03 || buf[7] == 0x04)){
|
||||
let mut reply = [buf[0], 0x90, 0x00];
|
||||
return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply);
|
||||
} else /* Unknown file */ {
|
||||
let mut reply = [buf[0], 0x6a, 0x82];
|
||||
return_code = bench_transmit(&mut console, &timer, "TX: 0x6A82", &mut reply);
|
||||
}
|
||||
0xb0 /* READ */ => match buf[5] {
|
||||
0x02 => {
|
||||
let mut reply = [buf[0], 0x12, 0x90, 0x00,];
|
||||
return_code = bench_transmit(&mut console, &timer, "TX: File Size", &mut reply);
|
||||
}
|
||||
0x12 => {
|
||||
let mut reply = [buf[0], 0xd1, 0x01, 0x0e, 0x55, 0x77, 0x77, 0x77, 0x2e, 0x6f, 0x70, 0x65,
|
||||
0x6e, 0x73, 0x6b, 0x2e, 0x64, 0x65, 0x76, 0x90, 0x00,];
|
||||
return_code = bench_transmit(&mut console, &timer, "TX: NDEF", &mut reply);
|
||||
}
|
||||
0x0f => {
|
||||
let mut reply = [buf[0], 0x00, 0x0f, 0x20, 0x00, 0x7f, 0x00, 0x7f, 0x04, 0x06, 0xe1, 0x04,
|
||||
0x00, 0x7f, 0x00, 0x00, 0x90, 0x00,];
|
||||
return_code = bench_transmit(&mut console, &timer, "TX: CC", &mut reply);
|
||||
}
|
||||
_ => {
|
||||
let mut reply = [buf[0], 0x90, 0x00];
|
||||
return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let mut reply = [buf[0], 0x90, 0x00];
|
||||
return_code = bench_transmit(&mut console, &timer, "TX: 0x9000", &mut reply);
|
||||
}
|
||||
}
|
||||
0x26 | 0x52 | 0x50 /* REQA | WUPA | Halt */ => {
|
||||
return ReturnCode::EOFF;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
return_code
|
||||
}
|
||||
|
||||
pub fn nfc(mut console: &mut Console) {
|
||||
// Setup the timer with a dummy callback (we only care about reading the current time, but the
|
||||
// API forces us to set an alarm callback too).
|
||||
let mut with_callback = timer::with_callback(|_, _| {});
|
||||
let timer = with_callback.init().flex_unwrap();
|
||||
|
||||
writeln!(
|
||||
console,
|
||||
"Clock frequency: {} Hz",
|
||||
timer.clock_frequency().hz()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut state_change_counter = 0;
|
||||
loop {
|
||||
let mut rx_buf = [0; 256];
|
||||
match receive_packet(&mut console, &mut rx_buf) {
|
||||
ReturnCode::EOFF => {
|
||||
// Not configured
|
||||
while !NfcTag::enable_emulation() {}
|
||||
// Configure Type 4 tag
|
||||
while !NfcTag::configure(4) {}
|
||||
}
|
||||
ReturnCode::ECANCEL /* field lost */ => {
|
||||
NfcTag::disable_emulation();
|
||||
}
|
||||
ReturnCode::EBUSY /* awaiting select*/ => (),
|
||||
ReturnCode::ENOMEM => {
|
||||
writeln!(console, " -- Amount more than buffer limit").unwrap()
|
||||
}
|
||||
ReturnCode::FAIL => writeln!(console, " -- Invalid CRC").unwrap(),
|
||||
ReturnCode::EINVAL /* covered in driver interface */ => (),
|
||||
ReturnCode::ENOSUPPORT => (),
|
||||
ReturnCode::SUCCESS => {
|
||||
// If the reader restarts the communication then disable the tag.
|
||||
match transmit_reply(&mut console, &timer, &rx_buf) {
|
||||
ReturnCode::ECANCEL | ReturnCode::EOFF => {
|
||||
if NfcTag::disable_emulation() {
|
||||
writeln!(console, " -- TAG DISABLED").unwrap();
|
||||
}
|
||||
state_change_counter += 1;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
if state_change_counter > 100 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut console = Console::new();
|
||||
writeln!(console, "****************************************").unwrap();
|
||||
writeln!(console, "nfct_test application is installed").unwrap();
|
||||
example::nfc(&mut console);
|
||||
writeln!(console, "****************************************").unwrap();
|
||||
}
|
||||
@@ -147,7 +147,7 @@ fn process_message<CheckUserPresence>(
|
||||
pub fn process_ctap_any_type(data: &[u8]) {
|
||||
// Initialize ctap state and hid and get the allocated cid.
|
||||
let mut rng = ThreadRng256 {};
|
||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||
let mut ctap_hid = CtapHid::new();
|
||||
let cid = initialize(&mut ctap_state, &mut ctap_hid);
|
||||
// Wrap input as message with the allocated cid.
|
||||
@@ -165,7 +165,7 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) {
|
||||
}
|
||||
// Initialize ctap state and hid and get the allocated cid.
|
||||
let mut rng = ThreadRng256 {};
|
||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
||||
let mut ctap_hid = CtapHid::new();
|
||||
let cid = initialize(&mut ctap_state, &mut ctap_hid);
|
||||
// Wrap input as message with allocated cid and command type.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/Cargo.lock
|
||||
/artifacts/
|
||||
/corpus/
|
||||
/target/
|
||||
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "fuzz-store"
|
||||
version = "0.0.0"
|
||||
authors = ["Julien Cretin <cretin@google.com>"]
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.3"
|
||||
persistent_store = { path = "..", features = ["std"] }
|
||||
rand_core = "0.5"
|
||||
rand_pcg = "0.2"
|
||||
strum = { version = "0.19", features = ["derive"] }
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[[bin]]
|
||||
name = "store"
|
||||
path = "fuzz_targets/store.rs"
|
||||
@@ -0,0 +1,116 @@
|
||||
// Copyright 2019-2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use fuzz_store::{fuzz, StatKey, Stats};
|
||||
use std::io::Write;
|
||||
use std::io::{stdout, Read};
|
||||
use std::path::Path;
|
||||
|
||||
fn usage(program: &str) {
|
||||
println!(
|
||||
r#"Usage: {} {{ [<artifact_file>] | <corpus_directory> <bucket_predicate>.. }}
|
||||
|
||||
If <artifact_file> is not provided, it is read from standard input.
|
||||
|
||||
When <bucket_predicate>.. are provided, only runs matching all predicates are shown. The format of
|
||||
each <bucket_predicate> is <bucket_key>=<bucket_value>."#,
|
||||
program
|
||||
);
|
||||
}
|
||||
|
||||
fn debug(data: &[u8]) {
|
||||
println!("{:02x?}", data);
|
||||
fuzz(data, true, None);
|
||||
}
|
||||
|
||||
/// Bucket predicate.
|
||||
struct Predicate {
|
||||
/// Bucket key.
|
||||
key: StatKey,
|
||||
|
||||
/// Bucket value.
|
||||
value: usize,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Predicate {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
let predicate: Vec<&str> = input.split('=').collect();
|
||||
if predicate.len() != 2 {
|
||||
return Err("Predicate should have exactly one equal sign.".to_string());
|
||||
}
|
||||
let key = predicate[0]
|
||||
.parse()
|
||||
.map_err(|_| format!("Predicate key `{}` is not recognized.", predicate[0]))?;
|
||||
let value: usize = predicate[1]
|
||||
.parse()
|
||||
.map_err(|_| format!("Predicate value `{}` is not a number.", predicate[1]))?;
|
||||
if value != 0 && !value.is_power_of_two() {
|
||||
return Err(format!(
|
||||
"Predicate value `{}` is not a bucket.",
|
||||
predicate[1]
|
||||
));
|
||||
}
|
||||
Ok(Predicate { key, value })
|
||||
}
|
||||
}
|
||||
|
||||
fn analyze(corpus: &Path, predicates: Vec<Predicate>) {
|
||||
let mut stats = Stats::default();
|
||||
let mut count = 0;
|
||||
let total = std::fs::read_dir(corpus).unwrap().count();
|
||||
for entry in std::fs::read_dir(corpus).unwrap() {
|
||||
let data = std::fs::read(entry.unwrap().path()).unwrap();
|
||||
let mut stat = Stats::default();
|
||||
fuzz(&data, false, Some(&mut stat));
|
||||
if predicates
|
||||
.iter()
|
||||
.all(|p| stat.get_count(p.key, p.value).is_some())
|
||||
{
|
||||
stats.merge(&stat);
|
||||
}
|
||||
count += 1;
|
||||
print!("\u{1b}[K{} / {}\r", count, total);
|
||||
stdout().flush().unwrap();
|
||||
}
|
||||
// NOTE: To avoid reloading the corpus each time we want to check a different filter, we can
|
||||
// start an interactive loop here taking filters as input and printing the filtered stats. We
|
||||
// would keep all individual stats for each run in a vector.
|
||||
print!("{}", stats);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
// No arguments reads from stdin.
|
||||
if args.len() <= 1 {
|
||||
let stdin = std::io::stdin();
|
||||
let mut data = Vec::new();
|
||||
stdin.lock().read_to_end(&mut data).unwrap();
|
||||
return debug(&data);
|
||||
}
|
||||
let path = Path::new(&args[1]);
|
||||
// File argument assumes artifact.
|
||||
if path.is_file() && args.len() == 2 {
|
||||
return debug(&std::fs::read(path).unwrap());
|
||||
}
|
||||
// Directory argument assumes corpus.
|
||||
if path.is_dir() {
|
||||
match args[2..].iter().map(|x| x.parse()).collect() {
|
||||
Ok(predicates) => return analyze(path, predicates),
|
||||
Err(error) => eprintln!("Error: {}", error),
|
||||
}
|
||||
}
|
||||
usage(&args[0]);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2019-2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
fuzz_store::fuzz(data, false, None);
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright 2019-2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::num_bits;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Histogram with logarithmic buckets.
|
||||
///
|
||||
/// This is used to compute coverage statistics of the fuzzing runs of a corpus. This is not used
|
||||
/// during actual fuzzing, only when replaying a corpus to compute statistics.
|
||||
#[derive(Default)]
|
||||
pub struct Histogram {
|
||||
/// Maps each bucket to its count.
|
||||
///
|
||||
/// Buckets are numbers sharing the same highest bit. The first buckets are: only 0, only 1, 2
|
||||
/// to 3, 4 to 7, 8 to 15. Buckets are identified by their lower-bound.
|
||||
buckets: HashMap<usize, usize>,
|
||||
}
|
||||
|
||||
impl Histogram {
|
||||
/// Increases the count of the bucket of an item.
|
||||
///
|
||||
/// The bucket of `item` is the highest power of two, lower or equal to `item`. If `item` is
|
||||
/// zero, then its bucket is also zero.
|
||||
pub fn add(&mut self, item: usize) {
|
||||
*self.buckets.entry(get_bucket(item)).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
/// Merges another histogram into this one.
|
||||
pub fn merge(&mut self, other: &Histogram) {
|
||||
for (&bucket, &count) in &other.buckets {
|
||||
*self.buckets.entry(bucket).or_insert(0) += count;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the bit-width of one past the highest non-empty bucket.
|
||||
///
|
||||
/// In other words, all non-empty buckets of the histogram have a bit-width smaller than the
|
||||
/// returned width.
|
||||
pub fn width_lim(&self) -> usize {
|
||||
self.buckets.keys().max().map_or(0, |&x| num_bits(x) + 1)
|
||||
}
|
||||
|
||||
/// Returns the count of a bucket.
|
||||
pub fn get(&self, bucket: usize) -> Option<usize> {
|
||||
self.buckets.get(&bucket).cloned()
|
||||
}
|
||||
|
||||
/// Returns the total count.
|
||||
pub fn count(&self) -> usize {
|
||||
self.buckets.values().sum()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the bucket of an item.
|
||||
fn get_bucket(item: usize) -> usize {
|
||||
let bucket = bucket_from_width(num_bits(item));
|
||||
assert!(bucket <= item && (item == 0 || item / 2 < bucket));
|
||||
bucket
|
||||
}
|
||||
|
||||
/// Returns the bucket of an item given its bit-width.
|
||||
pub fn bucket_from_width(width: usize) -> usize {
|
||||
if width == 0 {
|
||||
0
|
||||
} else {
|
||||
1 << (width - 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_bucket_ok() {
|
||||
assert_eq!(get_bucket(0), 0);
|
||||
assert_eq!(get_bucket(1), 1);
|
||||
assert_eq!(get_bucket(2), 2);
|
||||
assert_eq!(get_bucket(3), 2);
|
||||
assert_eq!(get_bucket(4), 4);
|
||||
assert_eq!(get_bucket(7), 4);
|
||||
assert_eq!(get_bucket(8), 8);
|
||||
assert_eq!(get_bucket(15), 8);
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
// Copyright 2019-2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Fuzzing library for the persistent store.
|
||||
//!
|
||||
//! The overall design principles are (in order of precedence):
|
||||
//! - Determinism: fuzzing is a function from seeds (byte slices) to sequences of store
|
||||
//! manipulations (things like creating a store, applying operations, interrupting operations,
|
||||
//! interrupting reboots, checking invariant, etc). We can replay this function on the same input
|
||||
//! to get the same sequence of manipulations (for the same fuzzing and store code).
|
||||
//! - Coverage: fuzzing tries to coverage as much different behaviors as possible for small seeds.
|
||||
//! Ideally, each seed bit would control a branch decision in the tree of execution paths.
|
||||
//! - Surjectivity: all sequences of manipulations are reachable by fuzzing for some seed. The only
|
||||
//! situation where coverage takes precedence over surjectivity is for the value of insert updates
|
||||
//! where a pseudo-random generator is used to avoid wasting entropy.
|
||||
|
||||
mod histogram;
|
||||
mod stats;
|
||||
mod store;
|
||||
|
||||
pub use stats::{StatKey, Stats};
|
||||
pub use store::fuzz;
|
||||
|
||||
/// Bit-level entropy source based on a byte slice shared reference.
|
||||
///
|
||||
/// This is used to convert the byte slice provided by the fuzzer into the entropy used by the
|
||||
/// fuzzing code to generate a sequence of store manipulations, among other things. Entropy
|
||||
/// operations use the shortest necessary sequence of bits from the byte slice, such that fuzzer
|
||||
/// mutations of the byte slice have local impact or cascading effects towards future operations
|
||||
/// only.
|
||||
///
|
||||
/// The entropy has the following properties (in order of precedence):
|
||||
/// - It always returns a result.
|
||||
/// - It is deterministic: for a given slice and a given sequence of operations, the same results
|
||||
/// are returned. This permits to replay and debug fuzzing artifacts.
|
||||
/// - It uses the slice as a bit stream. In particular, it doesn't do big number arithmetic. This
|
||||
/// permits to have a simple implementation.
|
||||
/// - It doesn't waste information: for a given operation, the minimum integer number of bits is
|
||||
/// used to produce the result. As a consequence fractional bits can be wasted at each operation.
|
||||
/// - It uses the information uniformly: each bit is used exactly once, except when only a fraction
|
||||
/// of it is used. In particular, a bit is not used more than once. A consequence of each bit
|
||||
/// being used essentially once, is that the results are mostly uniformly distributed.
|
||||
///
|
||||
/// # Invariant
|
||||
///
|
||||
/// - The bit is a valid position in the slice, or one past: `bit <= 8 * data.len()`.
|
||||
struct Entropy<'a> {
|
||||
/// The byte slice shared reference providing the entropy.
|
||||
data: &'a [u8],
|
||||
|
||||
/// The bit position in the byte slice of the next entropy bit.
|
||||
bit: usize,
|
||||
}
|
||||
|
||||
impl Entropy<'_> {
|
||||
/// Creates a bit-level entropy given a byte slice.
|
||||
fn new(data: &[u8]) -> Entropy {
|
||||
let bit = 0;
|
||||
Entropy { data, bit }
|
||||
}
|
||||
|
||||
/// Consumes the remaining entropy.
|
||||
fn consume_all(&mut self) {
|
||||
self.bit = 8 * self.data.len();
|
||||
}
|
||||
|
||||
/// Returns whether there is entropy remaining.
|
||||
fn is_empty(&self) -> bool {
|
||||
assert!(self.bit <= 8 * self.data.len());
|
||||
self.bit == 8 * self.data.len()
|
||||
}
|
||||
|
||||
/// Reads a bit.
|
||||
fn read_bit(&mut self) -> bool {
|
||||
if self.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let b = self.bit;
|
||||
self.bit += 1;
|
||||
self.data[b / 8] & 1 << (b % 8) != 0
|
||||
}
|
||||
|
||||
/// Reads a number with a given bit-width.
|
||||
///
|
||||
/// # Preconditions
|
||||
///
|
||||
/// - The number should fit in the return type: `n <= 8 * size_of::<usize>()`.
|
||||
fn read_bits(&mut self, n: usize) -> usize {
|
||||
assert!(n <= 8 * std::mem::size_of::<usize>());
|
||||
let mut r = 0;
|
||||
for i in 0..n {
|
||||
r |= (self.read_bit() as usize) << i;
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
/// Reads a byte.
|
||||
fn read_byte(&mut self) -> u8 {
|
||||
self.read_bits(8) as u8
|
||||
}
|
||||
|
||||
/// Reads a slice.
|
||||
fn read_slice(&mut self, length: usize) -> Vec<u8> {
|
||||
let mut result = Vec::with_capacity(length);
|
||||
for _ in 0..length {
|
||||
result.push(self.read_byte());
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reads a number between `min` and `max` (inclusive bounds).
|
||||
///
|
||||
/// The distribution is uniform if the range width is a power of two. Otherwise, the minimum
|
||||
/// amount of entropy is used (the next power of two) and the distribution is the closest to
|
||||
/// uniform for that entropy.
|
||||
///
|
||||
/// # Preconditions
|
||||
///
|
||||
/// - The bounds should be correctly ordered: `min <= max`.
|
||||
/// - The upper-bound should not be too large: `max < usize::max_value()`.
|
||||
fn read_range(&mut self, min: usize, max: usize) -> usize {
|
||||
assert!(min <= max && max < usize::max_value());
|
||||
let count = max - min + 1;
|
||||
let delta = self.read_bits(num_bits(count - 1)) % count;
|
||||
min + delta
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of bits necessary to represent a number.
|
||||
fn num_bits(x: usize) -> usize {
|
||||
8 * std::mem::size_of::<usize>() - x.leading_zeros() as usize
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_bits_ok() {
|
||||
assert_eq!(num_bits(0), 0);
|
||||
assert_eq!(num_bits(1), 1);
|
||||
assert_eq!(num_bits(2), 2);
|
||||
assert_eq!(num_bits(3), 2);
|
||||
assert_eq!(num_bits(4), 3);
|
||||
assert_eq!(num_bits(7), 3);
|
||||
assert_eq!(num_bits(8), 4);
|
||||
assert_eq!(num_bits(15), 4);
|
||||
assert_eq!(num_bits(16), 5);
|
||||
assert_eq!(
|
||||
num_bits(usize::max_value()),
|
||||
8 * std::mem::size_of::<usize>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_bit_ok() {
|
||||
let mut entropy = Entropy::new(&[0b10110010]);
|
||||
assert!(!entropy.read_bit());
|
||||
assert!(entropy.read_bit());
|
||||
assert!(!entropy.read_bit());
|
||||
assert!(!entropy.read_bit());
|
||||
assert!(entropy.read_bit());
|
||||
assert!(entropy.read_bit());
|
||||
assert!(!entropy.read_bit());
|
||||
assert!(entropy.read_bit());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_bits_ok() {
|
||||
let mut entropy = Entropy::new(&[0x83, 0x92]);
|
||||
assert_eq!(entropy.read_bits(4), 0x3);
|
||||
assert_eq!(entropy.read_bits(8), 0x28);
|
||||
assert_eq!(entropy.read_bits(2), 0b01);
|
||||
assert_eq!(entropy.read_bits(2), 0b10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_range_ok() {
|
||||
let mut entropy = Entropy::new(&[0b00101011]);
|
||||
assert_eq!(entropy.read_range(0, 7), 0b011);
|
||||
assert_eq!(entropy.read_range(1, 8), 1 + 0b101);
|
||||
assert_eq!(entropy.read_range(4, 6), 4 + 0b00);
|
||||
let mut entropy = Entropy::new(&[0b00101011]);
|
||||
assert_eq!(entropy.read_range(0, 8), 0b1011 % 9);
|
||||
assert_eq!(entropy.read_range(3, 15), 3 + 0b0010);
|
||||
let mut entropy = Entropy::new(&[0x12, 0x34, 0x56, 0x78]);
|
||||
assert_eq!(entropy.read_range(0, usize::max_value() - 1), 0x78563412);
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// Copyright 2019-2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Helpers to compute and display fuzzing coverage statistics.
|
||||
//!
|
||||
//! This is not used during actual fuzzing, only when replaying a corpus to compute statistics.
|
||||
|
||||
use crate::histogram::{bucket_from_width, Histogram};
|
||||
use std::collections::HashMap;
|
||||
use strum::{Display, EnumIter, EnumString, IntoEnumIterator};
|
||||
|
||||
/// Statistics for each fuzzing run.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, EnumIter, EnumString, Display)]
|
||||
pub enum StatKey {
|
||||
/// The available entropy in bytes.
|
||||
Entropy,
|
||||
|
||||
/// The size of a page in bytes.
|
||||
PageSize,
|
||||
|
||||
/// The number of pages.
|
||||
NumPages,
|
||||
|
||||
/// The maximum number times a page can be erased.
|
||||
MaxPageErases,
|
||||
|
||||
/// The dirty length of the initial storage in bytes.
|
||||
///
|
||||
/// This is the length of the prefix of the storage that is written using entropy before the
|
||||
/// store is initialized. This permits to check the store against an invalid storage: it should
|
||||
/// not crash but may misbehave.
|
||||
DirtyLength,
|
||||
|
||||
/// The number of used erase cycles of the initial storage.
|
||||
///
|
||||
/// This permits to check the store as if it already consumed lifetime. In particular it permits
|
||||
/// to check the store when lifetime is almost out.
|
||||
InitCycles,
|
||||
|
||||
/// The number of words written during fuzzing.
|
||||
///
|
||||
/// This permits to get an idea of how much lifetime was exercised during fuzzing.
|
||||
UsedLifetime,
|
||||
|
||||
/// Whether the store reached the end of the lifetime during fuzzing.
|
||||
FinishedLifetime,
|
||||
|
||||
/// The number of times the store was fully compacted.
|
||||
///
|
||||
/// The store is considered fully compacted when all pages have been compacted once. So each
|
||||
/// page has been compacted at least that number of times.
|
||||
NumCompactions,
|
||||
|
||||
/// The number of times the store was powered on.
|
||||
PowerOnCount,
|
||||
|
||||
/// The number of times a transaction was applied.
|
||||
TransactionCount,
|
||||
|
||||
/// The number of times a clear operation was applied.
|
||||
ClearCount,
|
||||
|
||||
/// The number of times a prepare operation was applied.
|
||||
PrepareCount,
|
||||
|
||||
/// The number of times an insert update was applied.
|
||||
InsertCount,
|
||||
|
||||
/// The number of times a remove update was applied.
|
||||
RemoveCount,
|
||||
|
||||
/// The number of times a store operation was interrupted.
|
||||
InterruptionCount,
|
||||
}
|
||||
|
||||
/// Statistics about multiple fuzzing runs.
|
||||
#[derive(Default)]
|
||||
pub struct Stats {
|
||||
/// Maps each statistics to its histogram.
|
||||
stats: HashMap<StatKey, Histogram>,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
/// Adds a measure for a statistics.
|
||||
pub fn add(&mut self, key: StatKey, value: usize) {
|
||||
self.stats.entry(key).or_default().add(value);
|
||||
}
|
||||
|
||||
/// Merges another statistics into this one.
|
||||
pub fn merge(&mut self, other: &Stats) {
|
||||
for (&key, other) in &other.stats {
|
||||
self.stats.entry(key).or_default().merge(other);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the count of a bucket for a given key.
|
||||
pub fn get_count(&self, key: StatKey, bucket: usize) -> Option<usize> {
|
||||
self.stats.get(&key).and_then(|h| h.get(bucket))
|
||||
}
|
||||
|
||||
/// Returns the bit-width of one past the highest non-empty bucket.
|
||||
///
|
||||
/// In other words, all non-empty buckets of the histogram have a bit-width smaller than the
|
||||
/// returned width.
|
||||
fn width_lim(&self) -> usize {
|
||||
self.stats
|
||||
.values()
|
||||
.map(|h| h.width_lim())
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Stats {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
let mut matrix: Vec<Vec<String>> = Vec::new();
|
||||
let bits = self.width_lim();
|
||||
|
||||
let mut header = Vec::new();
|
||||
header.push(String::new());
|
||||
for width in 0..bits {
|
||||
header.push(format!(" {}", bucket_from_width(width)));
|
||||
}
|
||||
header.push(" count".into());
|
||||
matrix.push(header);
|
||||
|
||||
for key in StatKey::iter() {
|
||||
let mut row = Vec::new();
|
||||
row.push(format!("{}:", key));
|
||||
for width in 0..bits {
|
||||
row.push(match self.get_count(key, bucket_from_width(width)) {
|
||||
None => String::new(),
|
||||
Some(x) => format!(" {}", x),
|
||||
});
|
||||
}
|
||||
let count = self.stats.get(&key).map_or(0, |h| h.count());
|
||||
row.push(format!(" {}", count));
|
||||
matrix.push(row);
|
||||
}
|
||||
|
||||
write_matrix(f, matrix)
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a string aligned to the right for a given width.
|
||||
fn align(f: &mut std::fmt::Formatter, x: &str, n: usize) -> Result<(), std::fmt::Error> {
|
||||
for _ in 0..n.saturating_sub(x.len()) {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "{}", x)
|
||||
}
|
||||
|
||||
/// Prints a matrix with columns of minimal width to fit all elements.
|
||||
fn write_matrix(
|
||||
f: &mut std::fmt::Formatter,
|
||||
mut m: Vec<Vec<String>>,
|
||||
) -> Result<(), std::fmt::Error> {
|
||||
if m.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let num_cols = m.iter().map(|r| r.len()).max().unwrap();
|
||||
let mut col_len = vec![0; num_cols];
|
||||
for row in &mut m {
|
||||
row.resize(num_cols, String::new());
|
||||
for col in 0..num_cols {
|
||||
col_len[col] = std::cmp::max(col_len[col], row[col].len());
|
||||
}
|
||||
}
|
||||
for row in m {
|
||||
for col in 0..num_cols {
|
||||
align(f, &row[col], col_len[col])?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,9 +23,9 @@ use alloc::vec;
|
||||
/// for tests and fuzzing, for which it has dedicated functionalities.
|
||||
///
|
||||
/// This storage tracks how many times words are written between page erase cycles, how many times
|
||||
/// pages are erased, and whether an operation flips bits in the wrong direction (optional).
|
||||
/// Operations panic if those conditions are broken. This storage also permits to interrupt
|
||||
/// operations for inspection or to corrupt the operation.
|
||||
/// pages are erased, and whether an operation flips bits in the wrong direction. Operations panic
|
||||
/// if those conditions are broken (optional). This storage also permits to interrupt operations for
|
||||
/// inspection or to corrupt the operation.
|
||||
#[derive(Clone)]
|
||||
pub struct BufferStorage {
|
||||
/// Content of the storage.
|
||||
@@ -59,8 +59,13 @@ pub struct BufferOptions {
|
||||
/// How many times a page can be erased.
|
||||
pub max_page_erases: usize,
|
||||
|
||||
/// Whether bits cannot be written from 0 to 1.
|
||||
pub strict_write: bool,
|
||||
/// Whether the storage should check the flash invariant.
|
||||
///
|
||||
/// When set, the following conditions would panic:
|
||||
/// - A bit is written from 0 to 1.
|
||||
/// - A word is written more than `max_word_writes`.
|
||||
/// - A page is erased more than `max_page_erases`.
|
||||
pub strict_mode: bool,
|
||||
}
|
||||
|
||||
/// Corrupts a slice given actual and expected value.
|
||||
@@ -214,7 +219,10 @@ impl BufferStorage {
|
||||
///
|
||||
/// Panics if the maximum number of erase cycles per page is reached.
|
||||
fn incr_page_erases(&mut self, page: usize) {
|
||||
assert!(self.page_erases[page] < self.max_page_erases());
|
||||
// Check that pages are not erased too many times.
|
||||
if self.options.strict_mode {
|
||||
assert!(self.page_erases[page] < self.max_page_erases());
|
||||
}
|
||||
self.page_erases[page] += 1;
|
||||
let num_words = self.page_size() / self.word_size();
|
||||
for word in 0..num_words {
|
||||
@@ -252,7 +260,10 @@ impl BufferStorage {
|
||||
continue;
|
||||
}
|
||||
let word = index / word_size + i;
|
||||
assert!(self.word_writes[word] < self.max_word_writes());
|
||||
// Check that words are not written too many times.
|
||||
if self.options.strict_mode {
|
||||
assert!(self.word_writes[word] < self.max_word_writes());
|
||||
}
|
||||
self.word_writes[word] += 1;
|
||||
}
|
||||
}
|
||||
@@ -306,8 +317,8 @@ impl Storage for BufferStorage {
|
||||
self.interruption.tick(&operation)?;
|
||||
// Check and update counters.
|
||||
self.incr_word_writes(range.start, value, value);
|
||||
// Check strict write.
|
||||
if self.options.strict_write {
|
||||
// Check that bits are correctly flipped.
|
||||
if self.options.strict_mode {
|
||||
for (byte, &val) in range.clone().zip(value.iter()) {
|
||||
assert_eq!(self.storage[byte] & val, val);
|
||||
}
|
||||
@@ -472,7 +483,7 @@ mod tests {
|
||||
page_size: 16,
|
||||
max_word_writes: 2,
|
||||
max_page_erases: 3,
|
||||
strict_write: true,
|
||||
strict_mode: true,
|
||||
};
|
||||
// Those words are decreasing bit patterns. Bits are only changed from 1 to 0 and at least one
|
||||
// bit is changed.
|
||||
|
||||
@@ -181,6 +181,12 @@ pub enum StoreInvariant {
|
||||
},
|
||||
}
|
||||
|
||||
impl From<StoreError> for StoreInvariant {
|
||||
fn from(error: StoreError) -> StoreInvariant {
|
||||
StoreInvariant::StoreError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl StoreDriver {
|
||||
/// Provides read-only access to the storage.
|
||||
pub fn storage(&self) -> &BufferStorage {
|
||||
@@ -249,6 +255,10 @@ impl StoreDriverOff {
|
||||
}
|
||||
|
||||
/// Powers on the store without interruption.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the store cannot be powered on.
|
||||
pub fn power_on(self) -> Result<StoreDriverOn, StoreInvariant> {
|
||||
Ok(self
|
||||
.partial_power_on(StoreInterruption::none())
|
||||
@@ -506,8 +516,8 @@ impl StoreDriverOn {
|
||||
/// Checks that the store and model are in sync.
|
||||
fn check_model(&self) -> Result<(), StoreInvariant> {
|
||||
let mut model_content = self.model.content().clone();
|
||||
for handle in self.store.iter().unwrap() {
|
||||
let handle = handle.unwrap();
|
||||
for handle in self.store.iter()? {
|
||||
let handle = handle?;
|
||||
let model_value = match model_content.remove(&handle.get_key()) {
|
||||
None => {
|
||||
return Err(StoreInvariant::OnlyInStore {
|
||||
@@ -516,7 +526,7 @@ impl StoreDriverOn {
|
||||
}
|
||||
Some(x) => x,
|
||||
};
|
||||
let store_value = handle.get_value(&self.store).unwrap().into_boxed_slice();
|
||||
let store_value = handle.get_value(&self.store)?.into_boxed_slice();
|
||||
if store_value != model_value {
|
||||
return Err(StoreInvariant::DifferentValue {
|
||||
key: handle.get_key(),
|
||||
@@ -528,7 +538,7 @@ impl StoreDriverOn {
|
||||
if let Some(&key) = model_content.keys().next() {
|
||||
return Err(StoreInvariant::OnlyInModel { key });
|
||||
}
|
||||
let store_capacity = self.store.capacity().unwrap().remaining();
|
||||
let store_capacity = self.store.capacity()?.remaining();
|
||||
let model_capacity = self.model.capacity().remaining();
|
||||
if store_capacity != model_capacity {
|
||||
return Err(StoreInvariant::DifferentCapacity {
|
||||
@@ -544,8 +554,8 @@ impl StoreDriverOn {
|
||||
let format = self.model.format();
|
||||
let storage = self.store.storage();
|
||||
let num_words = format.page_size() / format.word_size();
|
||||
let head = self.store.head().unwrap();
|
||||
let tail = self.store.tail().unwrap();
|
||||
let head = self.store.head()?;
|
||||
let tail = self.store.tail()?;
|
||||
for page in 0..format.num_pages() {
|
||||
// Check the erase cycle of the page.
|
||||
let store_erase = head.cycle(format) + (page < head.page(format)) as Nat;
|
||||
|
||||
@@ -1257,7 +1257,7 @@ mod tests {
|
||||
page_size: self.page_size,
|
||||
max_word_writes: self.max_word_writes,
|
||||
max_page_erases: self.max_page_erases,
|
||||
strict_write: true,
|
||||
strict_mode: true,
|
||||
};
|
||||
StoreDriverOff::new(options, self.num_pages)
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -32,6 +32,7 @@ cd ../..
|
||||
|
||||
echo "Running Clippy lints..."
|
||||
cargo clippy --all-targets --features std -- -A clippy::new_without_default -D warnings
|
||||
cargo clippy --all-targets --features std,with_nfc -- -A clippy::new_without_default -D warnings
|
||||
|
||||
echo "Building sha256sum tool..."
|
||||
cargo build --manifest-path third_party/tock/tools/sha256sum/Cargo.toml
|
||||
@@ -54,12 +55,16 @@ cargo check --release --target=thumbv7em-none-eabi --features debug_ctap,with_ct
|
||||
|
||||
echo "Checking that examples build properly..."
|
||||
cargo check --release --target=thumbv7em-none-eabi --examples
|
||||
cargo check --release --target=thumbv7em-none-eabi --examples --features with_nfc
|
||||
|
||||
echo "Checking that fuzz targets build properly..."
|
||||
cargo fuzz build
|
||||
cd libraries/cbor
|
||||
cargo fuzz build
|
||||
cd ../..
|
||||
cd libraries/persistent_store
|
||||
cargo fuzz build
|
||||
cd ../..
|
||||
|
||||
echo "Checking that CTAP2 builds and links properly (1 set of features)..."
|
||||
cargo build --release --target=thumbv7em-none-eabi --features with_ctap1
|
||||
|
||||
+1
-1
@@ -57,8 +57,8 @@ impl Command {
|
||||
const AUTHENTICATOR_GET_INFO: u8 = 0x04;
|
||||
const AUTHENTICATOR_CLIENT_PIN: u8 = 0x06;
|
||||
const AUTHENTICATOR_RESET: u8 = 0x07;
|
||||
// TODO(kaczmarczyck) use or remove those constants
|
||||
const AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08;
|
||||
// TODO(kaczmarczyck) use or remove those constants
|
||||
const AUTHENTICATOR_BIO_ENROLLMENT: u8 = 0x09;
|
||||
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0;
|
||||
const AUTHENTICATOR_SELECTION: u8 = 0xB0;
|
||||
|
||||
+89
-39
@@ -13,7 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
use super::hid::ChannelID;
|
||||
use super::key_material::{ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
|
||||
use super::status_code::Ctap2StatusCode;
|
||||
use super::CtapState;
|
||||
use alloc::vec::Vec;
|
||||
@@ -36,6 +35,8 @@ pub enum Ctap1StatusCode {
|
||||
SW_WRONG_LENGTH = 0x6700,
|
||||
SW_CLA_NOT_SUPPORTED = 0x6E00,
|
||||
SW_INS_NOT_SUPPORTED = 0x6D00,
|
||||
SW_MEMERR = 0x6501,
|
||||
SW_COMMAND_ABORTED = 0x6F00,
|
||||
SW_VENDOR_KEY_HANDLE_TOO_LONG = 0xF000,
|
||||
}
|
||||
|
||||
@@ -50,6 +51,8 @@ impl TryFrom<u16> for Ctap1StatusCode {
|
||||
0x6700 => Ok(Ctap1StatusCode::SW_WRONG_LENGTH),
|
||||
0x6E00 => Ok(Ctap1StatusCode::SW_CLA_NOT_SUPPORTED),
|
||||
0x6D00 => Ok(Ctap1StatusCode::SW_INS_NOT_SUPPORTED),
|
||||
0x6501 => Ok(Ctap1StatusCode::SW_MEMERR),
|
||||
0x6F00 => Ok(Ctap1StatusCode::SW_COMMAND_ABORTED),
|
||||
0xF000 => Ok(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG),
|
||||
_ => Err(()),
|
||||
}
|
||||
@@ -288,21 +291,31 @@ impl Ctap1Command {
|
||||
let sk = crypto::ecdsa::SecKey::gensk(ctap_state.rng);
|
||||
let pk = sk.genpk();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application)
|
||||
.map_err(|_| Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG)?;
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.map_err(|_| Ctap1StatusCode::SW_COMMAND_ABORTED)?;
|
||||
if key_handle.len() > 0xFF {
|
||||
// This is just being defensive with unreachable code.
|
||||
return Err(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG);
|
||||
}
|
||||
|
||||
let mut response =
|
||||
Vec::with_capacity(105 + key_handle.len() + ATTESTATION_CERTIFICATE.len());
|
||||
let certificate = ctap_state
|
||||
.persistent_store
|
||||
.attestation_certificate()
|
||||
.map_err(|_| Ctap1StatusCode::SW_MEMERR)?
|
||||
.ok_or(Ctap1StatusCode::SW_COMMAND_ABORTED)?;
|
||||
let private_key = ctap_state
|
||||
.persistent_store
|
||||
.attestation_private_key()
|
||||
.map_err(|_| Ctap1StatusCode::SW_MEMERR)?
|
||||
.ok_or(Ctap1StatusCode::SW_COMMAND_ABORTED)?;
|
||||
|
||||
let mut response = Vec::with_capacity(105 + key_handle.len() + certificate.len());
|
||||
response.push(Ctap1Command::LEGACY_BYTE);
|
||||
let user_pk = pk.to_uncompressed();
|
||||
response.extend_from_slice(&user_pk);
|
||||
response.push(key_handle.len() as u8);
|
||||
response.extend(key_handle.clone());
|
||||
response.extend_from_slice(&ATTESTATION_CERTIFICATE);
|
||||
response.extend_from_slice(&certificate);
|
||||
|
||||
// The first byte is reserved.
|
||||
let mut signature_data = Vec::with_capacity(66 + key_handle.len());
|
||||
@@ -312,7 +325,7 @@ impl Ctap1Command {
|
||||
signature_data.extend(key_handle);
|
||||
signature_data.extend_from_slice(&user_pk);
|
||||
|
||||
let attestation_key = crypto::ecdsa::SecKey::from_bytes(ATTESTATION_PRIVATE_KEY).unwrap();
|
||||
let attestation_key = crypto::ecdsa::SecKey::from_bytes(private_key).unwrap();
|
||||
let signature = attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data);
|
||||
|
||||
response.extend(signature.to_asn1_der());
|
||||
@@ -373,7 +386,7 @@ impl Ctap1Command {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::{ENCRYPTED_CREDENTIAL_ID_SIZE, USE_SIGNATURE_COUNTER};
|
||||
use super::super::{key_material, CREDENTIAL_ID_BASE_SIZE, USE_SIGNATURE_COUNTER};
|
||||
use super::*;
|
||||
use crypto::rng256::ThreadRng256;
|
||||
use crypto::Hash256;
|
||||
@@ -413,12 +426,12 @@ mod test {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
65 + ENCRYPTED_CREDENTIAL_ID_SIZE as u8,
|
||||
65 + CREDENTIAL_ID_BASE_SIZE as u8,
|
||||
];
|
||||
let challenge = [0x0C; 32];
|
||||
message.extend(&challenge);
|
||||
message.extend(application);
|
||||
message.push(ENCRYPTED_CREDENTIAL_ID_SIZE as u8);
|
||||
message.push(CREDENTIAL_ID_BASE_SIZE as u8);
|
||||
message.extend(key_handle);
|
||||
message
|
||||
}
|
||||
@@ -427,28 +440,49 @@ mod test {
|
||||
fn test_process_register() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
let application = [0x0A; 32];
|
||||
let message = create_register_message(&application);
|
||||
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
|
||||
// Certificate and private key are missing
|
||||
assert_eq!(response, Err(Ctap1StatusCode::SW_COMMAND_ABORTED));
|
||||
|
||||
let fake_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
||||
assert!(ctap_state
|
||||
.persistent_store
|
||||
.set_attestation_private_key(&fake_key)
|
||||
.is_ok());
|
||||
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
|
||||
// Certificate is still missing
|
||||
assert_eq!(response, Err(Ctap1StatusCode::SW_COMMAND_ABORTED));
|
||||
|
||||
let fake_cert = [0x99u8; 100]; // Arbitrary length
|
||||
assert!(ctap_state
|
||||
.persistent_store
|
||||
.set_attestation_certificate(&fake_cert[..])
|
||||
.is_ok());
|
||||
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||
let response =
|
||||
Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap();
|
||||
|
||||
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
|
||||
assert_eq!(response[66], ENCRYPTED_CREDENTIAL_ID_SIZE as u8);
|
||||
assert_eq!(response[66], CREDENTIAL_ID_BASE_SIZE as u8);
|
||||
assert!(ctap_state
|
||||
.decrypt_credential_source(
|
||||
response[67..67 + ENCRYPTED_CREDENTIAL_ID_SIZE].to_vec(),
|
||||
response[67..67 + CREDENTIAL_ID_BASE_SIZE].to_vec(),
|
||||
&application
|
||||
)
|
||||
.unwrap()
|
||||
.is_some());
|
||||
const CERT_START: usize = 67 + ENCRYPTED_CREDENTIAL_ID_SIZE;
|
||||
const CERT_START: usize = 67 + CREDENTIAL_ID_BASE_SIZE;
|
||||
assert_eq!(
|
||||
&response[CERT_START..CERT_START + ATTESTATION_CERTIFICATE.len()],
|
||||
&ATTESTATION_CERTIFICATE[..]
|
||||
&response[CERT_START..CERT_START + fake_cert.len()],
|
||||
&fake_cert[..]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -456,7 +490,7 @@ mod test {
|
||||
fn test_process_register_bad_message() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
let application = [0x0A; 32];
|
||||
let message = create_register_message(&application);
|
||||
@@ -476,7 +510,7 @@ mod test {
|
||||
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||
@@ -490,11 +524,13 @@ mod test {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||
|
||||
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
|
||||
@@ -506,11 +542,13 @@ mod test {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let application = [0x55; 32];
|
||||
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||
|
||||
@@ -523,11 +561,13 @@ mod test {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let mut message =
|
||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||
|
||||
@@ -547,11 +587,13 @@ mod test {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let mut message =
|
||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||
message[0] = 0xEE;
|
||||
@@ -565,11 +607,13 @@ mod test {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let mut message =
|
||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||
message[1] = 0xEE;
|
||||
@@ -583,11 +627,13 @@ mod test {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let mut message =
|
||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||
message[2] = 0xEE;
|
||||
@@ -601,11 +647,13 @@ mod test {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let message =
|
||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||
|
||||
@@ -626,11 +674,13 @@ mod test {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let message = create_authenticate_message(
|
||||
&application,
|
||||
Ctap1Flags::DontEnforceUpAndSign,
|
||||
@@ -650,13 +700,13 @@ mod test {
|
||||
#[test]
|
||||
fn test_process_authenticate_bad_key_handle() {
|
||||
let application = [0x0A; 32];
|
||||
let key_handle = vec![0x00; ENCRYPTED_CREDENTIAL_ID_SIZE];
|
||||
let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE];
|
||||
let message =
|
||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||
@@ -667,13 +717,13 @@ mod test {
|
||||
#[test]
|
||||
fn test_process_authenticate_without_up() {
|
||||
let application = [0x0A; 32];
|
||||
let key_handle = vec![0x00; ENCRYPTED_CREDENTIAL_ID_SIZE];
|
||||
let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE];
|
||||
let message =
|
||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||
|
||||
let mut rng = ThreadRng256 {};
|
||||
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence);
|
||||
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
|
||||
|
||||
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
|
||||
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user