Skip to content
Open
28 changes: 28 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ name = "fuzz_tbor_request_round_trip"
path = "fuzz_targets/ddi/tbor/fuzz_tbor_request_round_trip.rs"
test = false

[[bin]]
bench = false
doc = false
name = "fuzz_tbor_cmd_reqs"
path = "fuzz_targets/ddi/tbor/fuzz_tbor_cmd_reqs.rs"
test = false

[[bin]]
bench = false
doc = false
Expand All @@ -58,6 +65,13 @@ name = "fuzz_tbor_response_view"
path = "fuzz_targets/ddi/tbor/fuzz_tbor_response_view.rs"
test = false

[[bin]]
bench = false
doc = false
name = "fuzz_tbor_dispatcher"
path = "fuzz_targets/ddi/tbor/fuzz_tbor_dispatcher.rs"
test = false

[[bin]]
bench = false
doc = false
Expand Down Expand Up @@ -93,6 +107,13 @@ name = "fuzz_tbor_aes_encrypt_decrypt"
path = "fuzz_targets/ddi/tbor/fuzz_tbor_aes_encrypt_decrypt.rs"
test = false

[[bin]]
bench = false
doc = false
name = "fuzz_tbor_dispatcher_aes_fp"
path = "fuzz_targets/ddi/tbor/fuzz_tbor_dispatcher_aes_fp.rs"
test = false

[[bin]]
bench = false
doc = false
Expand Down Expand Up @@ -121,6 +142,13 @@ name = "fuzz_tbor_ecdh_key_exchange"
path = "fuzz_targets/ddi/tbor/fuzz_tbor_ecdh_key_exchange.rs"
test = false

[[bin]]
bench = false
doc = false
name = "fuzz_tbor_app_ops"
path = "fuzz_targets/ddi/tbor/fuzz_tbor_app_ops.rs"
test = false

[[bin]]
bench = false
doc = false
Expand Down
8 changes: 8 additions & 0 deletions fuzz/fuzz_targets/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,10 +553,17 @@ pub fn create_test_security_domain(ctx: &TestCtx, session: &SessionHandshake) {
})
.expect("sealing-key report generation should succeed");

let receiver_key = CaKey::generate();
let receiver_pub = receiver_key.raw_pub();
let receiver = make_chain(&sata, &receiver_pub);
let mfgr = make_chain(&CaKey::generate(), &pid_pub);
let owner = make_chain(&CaKey::generate(), &pid_pub);
let part_owner = make_chain(&sata, &pid_pub);
let mut oob_items = Vec::new();
let receiver_chain = vec![
add_evidence_item(&mut oob_items, &receiver.root_der),
add_evidence_item(&mut oob_items, &receiver.leaf_der),
];
let mfgr_chain = vec![
add_evidence_item(&mut oob_items, &mfgr.root_der),
add_evidence_item(&mut oob_items, &mfgr.leaf_der),
Expand All @@ -574,6 +581,7 @@ pub fn create_test_security_domain(ctx: &TestCtx, session: &SessionHandshake) {
let req = TborSdCreateRemoteBackupReq {
session_id: session.session_id,
masked_sealing_key: sealing_key.masked_key,
receiver_cert_chain: receiver_chain,
receiver_mfgr_cert_chain: mfgr_chain,
receiver_owner_cert_chain: owner_chain,
receiver_part_owner_cert_chain: part_owner_chain,
Expand Down
134 changes: 134 additions & 0 deletions fuzz/fuzz_targets/ddi/tbor/fuzz_tbor_app_ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#![no_main]

#[path = "../../common.rs"]
mod common;

use azihsm_ddi_interface::DdiError;
use azihsm_ddi_tbor_test_harness::CO_PSK_ID;
use azihsm_ddi_tbor_test_harness::CU_PSK_ID;
use azihsm_ddi_tbor_test_harness::ROTATED_CO_PSK;
use azihsm_ddi_tbor_test_harness::TestCtx;
use azihsm_ddi_tbor_test_harness::bootstrap_rotated_co;
use azihsm_ddi_tbor_test_harness::build_mac_fin;
use azihsm_ddi_tbor_types::MAC_FIN_LEN;
use azihsm_ddi_tbor_types::SessionType;
use azihsm_ddi_tbor_types::TborStatus;
use libfuzzer_sys::arbitrary;
use libfuzzer_sys::arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;

#[derive(Arbitrary, Debug)]
enum TestAppOps {
OpenSession([u8; 16], [u8; 16]),
CloseSession(u16),
}

fuzz_target!(|ops: Vec<TestAppOps>| {
common::common_fuzz_test(&|ctx: &TestCtx, path: &str| {
Comment thread
zimmy87 marked this conversation as resolved.
let session = bootstrap_rotated_co(ctx, &ROTATED_CO_PSK);
let mut file_handles = Vec::new();

for op in &ops {
match op {
TestAppOps::OpenSession(user_id, pin) => {
// The existing TBOR session occupies this file handle, so
// another SessionOpenInit must hit the per-handle limit.
let result = ctx.session_open_init(CO_PSK_ID, SessionType::Authenticated);
assert!(
matches!(
result,
Err(DdiError::TborStatus(status))
if status == TborStatus::FileHandleSessionLimitReached
),
"SessionOpenInit on an occupied file handle should hit the session limit"
);

// The bootstrap occupies the single CO slot. Open the
// secondary session as CU so Phase 1 can reach Phase 2.
let secondary = TestCtx::new_with_path(path);
let pending =
match secondary.session_open_init(CU_PSK_ID, SessionType::PlainText) {
Ok(pending) => pending,
Err(DdiError::TborStatus(status))
if status == TborStatus::VaultSessionLimitReached =>
{
continue;
}
Err(_error) => {
panic!("CU SessionOpenInit with available capacity failed")
}
};
let pending_session_id = pending.session_id;
let expected_mac = build_mac_fin(&pending)
.expect("building a valid SessionOpenFinish MAC should succeed");

if user_id[0] & 1 == 0 {
let opened = secondary
.session_open_finish_with_mac(pending, expected_mac)
.expect("SessionOpenFinish with the valid MAC should succeed");
file_handles.push((secondary, opened.session_id));
} else {
let mut invalid_mac = expected_mac;
for (index, byte) in user_id.iter().chain(pin.iter()).enumerate() {
invalid_mac[index % MAC_FIN_LEN] ^= *byte;
}
invalid_mac[MAC_FIN_LEN - 1] ^= 1;

let finish_result =
secondary.session_open_finish_with_mac(pending, invalid_mac);
assert!(
matches!(
finish_result,
Err(DdiError::TborStatus(status))
if status == TborStatus::SessionAuthFailure
),
"SessionOpenFinish with a mutated MAC should fail authentication"
);

let close_result = secondary.session_close(pending_session_id);
assert!(
matches!(
close_result,
Err(DdiError::TborStatus(status))
if status == TborStatus::SessionNotFound
),
"SessionClose on a failed handshake should report SessionNotFound"
);
}
}
TestAppOps::CloseSession(session_id) => {
if let Some((file_handle, opened_session_id)) = file_handles.pop() {
let result = file_handle.session_close(*session_id);
if *session_id == opened_session_id {
result.expect("SessionClose for the matching session should succeed");
} else {
assert!(
matches!(
result,
Err(DdiError::TborStatus(status))
if status == TborStatus::FileHandleSessionIdDoesNotMatch
),
"SessionClose for a different session ID should be rejected"
);
file_handle
.session_close(opened_session_id)
.expect("closing the tracked session should succeed");
}
}
}
}
}

for (file_handle, session_id) in file_handles {
file_handle
.session_close(session_id)
.expect("closing the tracked session should succeed");
}

ctx.session_close(session.session_id)
.expect("closing the bootstrap session should succeed");
});
});
179 changes: 179 additions & 0 deletions fuzz/fuzz_targets/ddi/tbor/fuzz_tbor_cmd_reqs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#![no_main]

#[path = "../../common.rs"]
mod common;

use azihsm_ddi_tbor_test_harness::ROTATED_CO_PSK;
use azihsm_ddi_tbor_test_harness::TestCtx;
use azihsm_ddi_tbor_test_harness::bootstrap_rotated_co;
use azihsm_ddi_tbor_types::*;
use common::KeyScope;
use libfuzzer_sys::arbitrary;
use libfuzzer_sys::arbitrary::Arbitrary;
use libfuzzer_sys::fuzz_target;

const MIN_NUMBER_OF_REQS: usize = 1;
const MAX_NUMBER_OF_REQS: usize = 32;

#[derive(Debug, Arbitrary)]
struct FuzzInput {
/// Seed used to choose a deterministic sequence of TBOR requests.
rand_seed: u64,
/// Whether to bind requests to the open session or use an invalid id.
use_valid_header: bool,
}

#[derive(Clone, Copy)]
enum Command {
AesGenerateKey,
EccGenerateKey,
HmacGenerateKey,
}

fn next_random(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E3779B97F4A7C15);
let mut value = *state;
value = (value ^ (value >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
value = (value ^ (value >> 27)).wrapping_mul(0x94D049BB133111EB);
value ^ (value >> 31)
}

fuzz_target!(|input: FuzzInput| {
common::common_fuzz_test(&|ctx: &TestCtx, _path: &str| {
Comment thread
zimmy87 marked this conversation as resolved.
let session = bootstrap_rotated_co(ctx, &ROTATED_CO_PSK);
let number_of_reqs = (input.rand_seed as usize
% (MAX_NUMBER_OF_REQS - MIN_NUMBER_OF_REQS + 1))
+ MIN_NUMBER_OF_REQS;
let mut state = input.rand_seed;
let mut requests = Vec::with_capacity(number_of_reqs);

for _ in 0..number_of_reqs {
let command = match next_random(&mut state) % 3 {
0 => Command::AesGenerateKey,
1 => Command::EccGenerateKey,
_ => Command::HmacGenerateKey,
};
let key_scope = match next_random(&mut state) % 6 {
0 => KeyScope::Unspecified,
1 => KeyScope::Session,
2 => KeyScope::Ephemeral,
3 => KeyScope::Local,
4 => KeyScope::SecurityDomain,
_ => KeyScope::Internal,
};
requests.push((command, key_scope));
}

if requests
.iter()
.any(|(_, scope)| matches!(scope, KeyScope::SecurityDomain))
{
common::create_test_security_domain(ctx, &session);
} else if requests
.iter()
.any(|(_, scope)| matches!(scope, KeyScope::Ephemeral | KeyScope::Local))
{
common::finalize_partition(ctx, &session);
}

for (command, key_scope) in requests {
let session_id = if input.use_valid_header {
session.session_id
} else {
session.session_id ^ 0x8000
};
let scope = key_scope.to_tbor();

let succeeded = match command {
Command::AesGenerateKey => {
let req = TborAesGenerateKeyReq {
session_id,
scope,
key_size: AES_KEY_SIZE_128,
key_usage: KEY_USAGE_ENCRYPT | KEY_USAGE_DECRYPT,
key_label: Vec::new(),
};
match ctx.tbor(&req) {
Ok(resp) => {
assert!(
(MASKED_AES_KEY_MIN_LEN..=MASKED_AES_KEY_MAX_LEN)
.contains(&resp.masked_key.len()),
"AES key generation returned an invalid masked-key length"
);
true
}
Err(err @ azihsm_ddi_interface::DdiError::DriverError(_)) => {
panic!("Crash Detected: {err}")
}
Err(_) => false,
Comment thread
zimmy87 marked this conversation as resolved.
}
}
Command::EccGenerateKey => {
let req = TborEccGenerateKeyReq {
session_id,
scope,
curve: ECC_CURVE_P256,
key_usage: KEY_USAGE_SIGN,
key_label: Vec::new(),
};
match ctx.tbor(&req) {
Ok(resp) => {
assert_eq!(resp.pub_key.len(), 64);
assert!(
(MASKED_ECC_KEY_MIN_LEN..=MASKED_ECC_KEY_MAX_LEN)
.contains(&resp.masked_key.len()),
"ECC key generation returned an invalid masked-key length"
);
true
}
Err(err @ azihsm_ddi_interface::DdiError::DriverError(_)) => {
panic!("Crash Detected: {err}")
}
Err(_) => false,
}
}
Command::HmacGenerateKey => {
let req = TborHmacGenerateKeyReq {
session_id,
scope,
hash_algo: HMAC_HASH_SHA256,
key_length: 32,
key_label: Vec::new(),
};
match ctx.tbor(&req) {
Ok(resp) => {
assert_eq!(resp.masked_key.len(), MASKED_HMAC_KEY_MIN_LEN);
true
}
Err(err @ azihsm_ddi_interface::DdiError::DriverError(_)) => {
panic!("Crash Detected: {err}")
}
Err(_) => false,
}
}
};

if input.use_valid_header {
let scope_is_supported = matches!(
key_scope,
KeyScope::Session
| KeyScope::Ephemeral
| KeyScope::Local
| KeyScope::SecurityDomain
);
assert_eq!(
succeeded, scope_is_supported,
"TBOR request success did not match the key-scope support"
);
} else {
assert!(!succeeded, "request with an invalid session id succeeded");
}
}

ctx.session_close(session.session_id)
.expect("session close should succeed");
});
});
Loading
Loading