Skip to content

Commit

Permalink
Merge pull request #165 from mingxguo27/mingxguo-fuzzing-mvp
Browse files Browse the repository at this point in the history
Fuzzing helper library and more fuzz targets
  • Loading branch information
gendx authored Oct 5, 2020
2 parents 079c0a4 + cc0c9ee commit f328971
Show file tree
Hide file tree
Showing 13 changed files with 335 additions and 79 deletions.
37 changes: 31 additions & 6 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,42 @@ cargo-fuzz = true

[dependencies]
libfuzzer-sys = { version = "0.3"}
arrayref = "0.3.6"
libtock_drivers = { path = "../third_party/libtock-drivers" }
crypto = { path = "../libraries/crypto", features = ['std'] }
cbor = { path = "../libraries/cbor", features = ['std'] }
ctap2 = { path = "..", features = ['std', 'ram_storage'] }
lang_items = { path = "../third_party/lang-items", features = ['std'] }
fuzz_helper = { path = "fuzz_helper" }

# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[[bin]]
name = "fuzz_target_process_ctap_command"
path = "fuzz_targets/fuzz_target_process_ctap_command.rs"
test = false
doc = false

[[bin]]
name = "fuzz_target_process_ctap1"
path = "fuzz_targets/fuzz_target_process_ctap1.rs"
test = false
doc = false

[[bin]]
name = "fuzz_target_process_ctap2_client_pin"
path = "fuzz_targets/fuzz_target_process_ctap2_client_pin.rs"
test = false
doc = false

[[bin]]
name = "fuzz_target_process_ctap2_get_assertion"
path = "fuzz_targets/fuzz_target_process_ctap2_get_assertion.rs"
test = false
doc = false

[[bin]]
name = "fuzz_target_process_ctap2_make_credential"
path = "fuzz_targets/fuzz_target_process_ctap2_make_credential.rs"
test = false
doc = false

[[bin]]
name = "fuzz_target_split_assemble"
path = "fuzz_targets/fuzz_target_split_assemble.rs"
Expand Down
14 changes: 14 additions & 0 deletions fuzz/fuzz_helper/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "fuzz_helper"
version = "0.1.0"
authors = ["Mingxiao Guo <mingxguo@google.com>"]
license = "Apache-2.0"
edition = "2018"

[dependencies]
arrayref = "0.3.6"
libtock_drivers = { path = "../../third_party/libtock-drivers" }
crypto = { path = "../../libraries/crypto", features = ['std'] }
cbor = { path = "../../libraries/cbor", features = ['std'] }
ctap2 = { path = "../..", features = ['std', 'ram_storage'] }
lang_items = { path = "../../third_party/lang-items", features = ['std'] }
208 changes: 208 additions & 0 deletions fuzz/fuzz_helper/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Copyright 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.

// This explicit "extern crate" is needed to make the linker aware of the
// `libtock_alloc_init` symbol.
extern crate lang_items;

use arrayref::array_ref;
use core::convert::TryFrom;
use crypto::rng256::ThreadRng256;
use ctap2::ctap::command::{
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
AuthenticatorMakeCredentialParameters,
};
use ctap2::ctap::hid::receive::MessageAssembler;
use ctap2::ctap::hid::send::HidPacketIterator;
use ctap2::ctap::hid::{ChannelID, CtapHid, HidPacket, Message};
use ctap2::ctap::status_code::Ctap2StatusCode;
use ctap2::ctap::CtapState;
use libtock_drivers::timer::{ClockValue, Timestamp};

const COMMAND_INIT: u8 = 0x06;
const CHANNEL_BROADCAST: ChannelID = [0xFF, 0xFF, 0xFF, 0xFF];
const PACKET_TYPE_MASK: u8 = 0x80;

const CLOCK_FREQUENCY_HZ: usize = 32768;
const DUMMY_TIMESTAMP: Timestamp<isize> = Timestamp::from_ms(0);
const DUMMY_CLOCK_VALUE: ClockValue = ClockValue::new(0, CLOCK_FREQUENCY_HZ);

#[derive(Clone, Copy, PartialEq)]
pub enum InputType {
CborMakeCredentialParameter,
CborGetAssertionParameter,
CborClientPinParameter,
Ctap1,
}

fn user_immediately_present(_: ChannelID) -> Result<(), Ctap2StatusCode> {
Ok(())
}

// Converts a byte slice into Message
fn raw_to_message(data: &[u8]) -> Message {
if data.len() <= 4 {
let mut cid = [0; 4];
cid[..data.len()].copy_from_slice(data);
Message {
cid,
cmd: 0,
payload: vec![],
}
} else {
Message {
cid: array_ref!(data, 0, 4).clone(),
cmd: data[4],
payload: data[5..].to_vec(),
}
}
}

// Returns an initialized ctap state, hid and the allocated cid
// after processing the init command.
fn initialize<CheckUserPresence>(
ctap_state: &mut CtapState<ThreadRng256, CheckUserPresence>,
ctap_hid: &mut CtapHid,
) -> ChannelID
where
CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>,
{
let nonce = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
let message = Message {
cid: CHANNEL_BROADCAST,
cmd: COMMAND_INIT,
payload: nonce,
};
let mut assembler_reply = MessageAssembler::new();
let mut result_cid: ChannelID = Default::default();
for pkt_request in HidPacketIterator::new(message).unwrap() {
for pkt_reply in ctap_hid.process_hid_packet(&pkt_request, DUMMY_CLOCK_VALUE, ctap_state) {
if let Ok(Some(result)) = assembler_reply.parse_packet(&pkt_reply, DUMMY_TIMESTAMP) {
result_cid.copy_from_slice(&result.payload[8..12]);
}
}
}
result_cid
}

// Checks whether the given data can be interpreted as the given type.
fn is_type(data: &[u8], input_type: InputType) -> bool {
if input_type == InputType::Ctap1 {
return true;
}
match cbor::read(data) {
Err(_) => false,
Ok(decoded_cbor) => match input_type {
InputType::CborMakeCredentialParameter => {
AuthenticatorMakeCredentialParameters::try_from(decoded_cbor).is_ok()
}
InputType::CborGetAssertionParameter => {
AuthenticatorGetAssertionParameters::try_from(decoded_cbor).is_ok()
}
InputType::CborClientPinParameter => {
AuthenticatorClientPinParameters::try_from(decoded_cbor).is_ok()
}
_ => true,
},
}
}

// Interprets the raw data as a complete message (with channel id, command type and payload) and
// invokes message splitting, packet processing at CTAP HID level and response assembling.
fn process_message<CheckUserPresence>(
data: &[u8],
ctap_state: &mut CtapState<ThreadRng256, CheckUserPresence>,
ctap_hid: &mut CtapHid,
) where
CheckUserPresence: Fn(ChannelID) -> Result<(), Ctap2StatusCode>,
{
let message = raw_to_message(data);
if let Some(hid_packet_iterator) = HidPacketIterator::new(message) {
let mut assembler_reply = MessageAssembler::new();
for pkt_request in hid_packet_iterator {
for pkt_reply in
ctap_hid.process_hid_packet(&pkt_request, DUMMY_CLOCK_VALUE, ctap_state)
{
// Only checks for assembling crashes, not for semantics.
let _ = assembler_reply.parse_packet(&pkt_reply, DUMMY_TIMESTAMP);
}
}
}
}

// Interprets the raw data as any ctap command (including the command byte) and
// invokes message splitting, packet processing at CTAP HID level and response assembling
// using an initialized and allocated channel.
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_hid = CtapHid::new();
let cid = initialize(&mut ctap_state, &mut ctap_hid);
// Wrap input as message with the allocated cid.
let mut command = cid.to_vec();
command.extend(data);
process_message(&command, &mut ctap_state, &mut ctap_hid);
}

// Interprets the raw data as of the given input type and
// invokes message splitting, packet processing at CTAP HID level and response assembling
// using an initialized and allocated channel.
pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) {
if !is_type(data, input_type) {
return;
}
// 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_hid = CtapHid::new();
let cid = initialize(&mut ctap_state, &mut ctap_hid);
// Wrap input as message with allocated cid and command type.
let mut command = cid.to_vec();
match input_type {
InputType::CborMakeCredentialParameter => {
command.extend(&[0x10, 0x01]);
}
InputType::CborGetAssertionParameter => {
command.extend(&[0x10, 0x02]);
}
InputType::CborClientPinParameter => {
command.extend(&[0x10, 0x06]);
}
InputType::Ctap1 => {
command.extend(&[0x03]);
}
}
command.extend(data);
process_message(&command, &mut ctap_state, &mut ctap_hid);
}

// Splits the given data as HID packets and reassembles it, verifying that the original input message is reconstructed.
pub fn split_assemble_hid_packets(data: &[u8]) {
let mut message = raw_to_message(data);
if let Some(hid_packet_iterator) = HidPacketIterator::new(message.clone()) {
let mut assembler = MessageAssembler::new();
let packets: Vec<HidPacket> = hid_packet_iterator.collect();
if let Some((last_packet, first_packets)) = packets.split_last() {
for packet in first_packets {
assert_eq!(assembler.parse_packet(packet, DUMMY_TIMESTAMP), Ok(None));
}
message.cmd &= !PACKET_TYPE_MASK;
assert_eq!(
assembler.parse_packet(last_packet, DUMMY_TIMESTAMP),
Ok(Some(message))
);
}
}
}
11 changes: 11 additions & 0 deletions fuzz/fuzz_targets/fuzz_target_process_ctap1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![no_main]

use fuzz_helper::{process_ctap_specific_type, InputType};
use libfuzzer_sys::fuzz_target;

// Fuzz inputs as CTAP1 U2F raw messages.
// For a more generic fuzz target including all CTAP commands, you can use
// fuzz_target_process_ctap_command.
fuzz_target!(|data: &[u8]| {
process_ctap_specific_type(data, InputType::Ctap1);
});
11 changes: 11 additions & 0 deletions fuzz/fuzz_targets/fuzz_target_process_ctap2_client_pin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![no_main]

use fuzz_helper::{process_ctap_specific_type, InputType};
use libfuzzer_sys::fuzz_target;

// Fuzz inputs as CTAP2 client pin command parameters encoded in cbor.
// For a more generic fuzz target including all CTAP commands, you can use
// fuzz_target_process_ctap_command.
fuzz_target!(|data: &[u8]| {
process_ctap_specific_type(data, InputType::CborClientPinParameter);
});
11 changes: 11 additions & 0 deletions fuzz/fuzz_targets/fuzz_target_process_ctap2_get_assertion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![no_main]

use fuzz_helper::{process_ctap_specific_type, InputType};
use libfuzzer_sys::fuzz_target;

// Fuzz inputs as CTAP2 get assertion command parameters encoded in cbor.
// For a more generic fuzz target including all CTAP commands, you can use
// fuzz_target_process_ctap_command.
fuzz_target!(|data: &[u8]| {
process_ctap_specific_type(data, InputType::CborGetAssertionParameter);
});
11 changes: 11 additions & 0 deletions fuzz/fuzz_targets/fuzz_target_process_ctap2_make_credential.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![no_main]

use fuzz_helper::{process_ctap_specific_type, InputType};
use libfuzzer_sys::fuzz_target;

// Fuzz inputs as CTAP2 make credential command parameters encoded in cbor.
// For a more generic fuzz target including all CTAP commands, you can use
// fuzz_target_process_ctap_command.
fuzz_target!(|data: &[u8]| {
process_ctap_specific_type(data, InputType::CborMakeCredentialParameter);
});
9 changes: 9 additions & 0 deletions fuzz/fuzz_targets/fuzz_target_process_ctap_command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#![no_main]

use fuzz_helper::process_ctap_any_type;
use libfuzzer_sys::fuzz_target;

// Generically fuzz inputs as CTAP commands.
fuzz_target!(|data: &[u8]| {
process_ctap_any_type(data);
});
50 changes: 3 additions & 47 deletions fuzz/fuzz_targets/fuzz_target_split_assemble.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,9 @@
#![no_main]

// This explicit "extern crate" is needed to make the linker aware of the
// `libtock_alloc_init` symbol.
extern crate lang_items;

use arrayref::array_ref;
use ctap2::ctap::hid::receive::MessageAssembler;
use ctap2::ctap::hid::send::HidPacketIterator;
use ctap2::ctap::hid::{HidPacket, Message};
use fuzz_helper::split_assemble_hid_packets;
use libfuzzer_sys::fuzz_target;
use libtock_drivers::timer::Timestamp;

const DUMMY_TIMESTAMP: Timestamp<isize> = Timestamp::from_ms(0);
const PACKET_TYPE_MASK: u8 = 0x80;

// Converts a byte slice into Message
fn raw_to_message(data: &[u8]) -> Message {
if data.len() <= 4 {
let mut cid = [0; 4];
cid[..data.len()].copy_from_slice(data);
Message {
cid,
cmd: 0,
payload: vec![],
}
} else {
Message {
cid: array_ref!(data, 0, 4).clone(),
cmd: data[4],
payload: data[5..].to_vec(),
}
}
}

/* Fuzzing HID packets splitting and assembling functions*/
// Fuzzing HID packets splitting and assembling functions.
fuzz_target!(|data: &[u8]| {
let mut message = raw_to_message(data);
if let Some(hid_packet_iterator) = HidPacketIterator::new(message.clone()) {
let mut assembler = MessageAssembler::new();
let packets: Vec<HidPacket> = hid_packet_iterator.collect();
if let Some((last_packet, first_packets)) = packets.split_last() {
for packet in first_packets {
assert_eq!(assembler.parse_packet(packet, DUMMY_TIMESTAMP), Ok(None));
}
message.cmd &= !PACKET_TYPE_MASK;
assert_eq!(
assembler.parse_packet(last_packet, DUMMY_TIMESTAMP),
Ok(Some(message))
);
}
}
split_assemble_hid_packets(data);
});
Loading

0 comments on commit f328971

Please sign in to comment.