Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/firecracker/examples/seccomp/jailer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fn main() {
let bpf_path = &args[2];

let filter_file = File::open(bpf_path).unwrap();
let map = deserialize_binary(&filter_file, None).unwrap();
let map = deserialize_binary(&filter_file).unwrap();

// Loads filters.
apply_filter(map.get("main").unwrap()).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion src/firecracker/examples/seccomp/panic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fn main() {
let filter_thread = &args[2];

let filter_file = File::open(bpf_path).unwrap();
let map = deserialize_binary(&filter_file, None).unwrap();
let map = deserialize_binary(&filter_file).unwrap();
apply_filter(map.get(filter_thread).unwrap()).unwrap();
panic!("Expected panic.");
}
12 changes: 2 additions & 10 deletions src/firecracker/src/seccomp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@

const THREAD_CATEGORIES: [&str; 3] = ["vmm", "api", "vcpu"];

// This byte limit is passed to `bincode` to guard against a potential memory
// allocation DOS caused by binary filters that are too large.
// This limit can be safely determined since the maximum length of a BPF
// filter is 4096 instructions and Firecracker has a finite number of threads.
const DESERIALIZATION_BYTES_LIMIT: Option<u64> = Some(100_000);

/// Error retrieving seccomp filters.
#[derive(Debug, thiserror::Error, displaydoc::Display)]
pub enum FilterError {
Expand Down Expand Up @@ -72,15 +66,13 @@
fn get_default_filters() -> Result<BpfThreadMap, FilterError> {
// Retrieve, at compile-time, the serialized binary filter generated with seccompiler.
let bytes: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/seccomp_filter.bpf"));
let map = deserialize_binary(bytes, DESERIALIZATION_BYTES_LIMIT)
.map_err(FilterError::Deserialization)?;
let map = deserialize_binary(bytes).map_err(FilterError::Deserialization)?;

Check warning on line 69 in src/firecracker/src/seccomp.rs

View check run for this annotation

Codecov / codecov/patch

src/firecracker/src/seccomp.rs#L69

Added line #L69 was not covered by tests
filter_thread_categories(map)
}

/// Retrieve custom seccomp filters.
fn get_custom_filters<R: Read + Debug>(reader: R) -> Result<BpfThreadMap, FilterError> {
let map = deserialize_binary(BufReader::new(reader), DESERIALIZATION_BYTES_LIMIT)
.map_err(FilterError::Deserialization)?;
let map = deserialize_binary(BufReader::new(reader)).map_err(FilterError::Deserialization)?;
filter_thread_categories(map)
}

Expand Down
2 changes: 1 addition & 1 deletion src/seccompiler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ path = "src/bin.rs"
bench = false

[dependencies]
bincode = "1.2.1"
bincode = { version = "2.0.1", features = ["serde"] }
clap = { version = "4.5.32", features = ["derive", "string"] }
displaydoc = "0.2.5"
libc = "0.2.171"
Expand Down
21 changes: 18 additions & 3 deletions src/seccompiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
use std::os::unix::fs::MetadataExt;
use std::str::FromStr;

use bincode::Error as BincodeError;
use bincode::config;
use bincode::config::{Configuration, Fixint, Limit, LittleEndian};
use bincode::error::EncodeError as BincodeError;

mod bindings;
use bindings::*;
Expand All @@ -17,6 +19,18 @@
pub use types::*;
use zerocopy::IntoBytes;

// This byte limit is passed to `bincode` to guard against a potential memory
// allocation DOS caused by binary filters that are too large.
// This limit can be safely determined since the maximum length of a BPF
// filter is 4096 instructions and Firecracker has a finite number of threads.
const DESERIALIZATION_BYTES_LIMIT: usize = 100_000;

pub const BINCODE_CONFIG: Configuration<LittleEndian, Fixint, Limit<DESERIALIZATION_BYTES_LIMIT>> =
config::standard()
.with_fixed_int_encoding()
.with_limit::<DESERIALIZATION_BYTES_LIMIT>()
.with_little_endian();

/// Binary filter compilation errors.
#[derive(Debug, thiserror::Error, displaydoc::Display)]
pub enum CompilationError {
Expand Down Expand Up @@ -174,8 +188,9 @@
bpf_map.insert(name.clone(), bpf);
}

let output_file = File::create(out_path).map_err(CompilationError::OutputCreate)?;
let mut output_file = File::create(out_path).map_err(CompilationError::OutputCreate)?;

Check warning on line 191 in src/seccompiler/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

src/seccompiler/src/lib.rs#L191

Added line #L191 was not covered by tests

bincode::serialize_into(output_file, &bpf_map).map_err(CompilationError::BincodeSerialize)?;
bincode::encode_into_std_write(&bpf_map, &mut output_file, BINCODE_CONFIG)
.map_err(CompilationError::BincodeSerialize)?;

Check warning on line 194 in src/seccompiler/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

src/seccompiler/src/lib.rs#L193-L194

Added lines #L193 - L194 were not covered by tests
Ok(())
}
3 changes: 1 addition & 2 deletions src/vmm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ aes-gcm = { version = "0.10.1", default-features = false, features = ["aes"] }
arrayvec = { version = "0.7.6", optional = true }
aws-lc-rs = { version = "1.12.6", features = ["bindgen"] }
base64 = "0.22.1"
bincode = "1.2.1"
bincode = { version = "2.0.1", features = ["serde"] }
bitflags = "2.9.0"
crc64 = "2.0.0"
derive_more = { version = "2.0.1", default-features = false, features = ["from", "display"] }
Expand All @@ -30,7 +30,6 @@ log = { version = "0.4.27", features = ["std", "serde"] }
log-instrument = { path = "../log-instrument", optional = true }
memfd = "0.6.3"
micro_http = { git = "https://github.com/firecracker-microvm/micro-http" }

semver = { version = "1.0.26", features = ["serde"] }
serde = { version = "1.0.219", features = ["derive", "rc"] }
serde_json = "1.0.140"
Expand Down
63 changes: 13 additions & 50 deletions src/vmm/src/mmds/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use std::{fmt, io};

use aes_gcm::{AeadInPlace, Aes256Gcm, Key, KeyInit, Nonce};
use base64::Engine;
use bincode::{DefaultOptions, Error as BincodeError, Options};
use bincode::config;
use bincode::config::{Configuration, Fixint, Limit, LittleEndian};
use serde::{Deserialize, Serialize};
use utils::time::{ClockType, get_time_ms};

Expand Down Expand Up @@ -45,6 +46,12 @@ const TOKEN_LENGTH_LIMIT: usize = 70;
/// too much memory when deserializing tokens.
const DESERIALIZATION_BYTES_LIMIT: usize = std::mem::size_of::<Token>();

const BINCODE_CONFIG: Configuration<LittleEndian, Fixint, Limit<DESERIALIZATION_BYTES_LIMIT>> =
config::standard()
.with_fixed_int_encoding()
.with_limit::<DESERIALIZATION_BYTES_LIMIT>()
.with_little_endian();

#[rustfmt::skip]
#[derive(Debug, thiserror::Error, displaydoc::Display)]
pub enum MmdsTokenError {
Expand All @@ -57,7 +64,7 @@ pub enum MmdsTokenError {
/// Invalid time to live value provided for token: {0}. Please provide a value between {MIN_TOKEN_TTL_SECONDS:} and {MAX_TOKEN_TTL_SECONDS:}.
InvalidTtlValue(u32),
/// Bincode serialization failed: {0}.
Serialization(#[from] BincodeError),
Serialization(#[from] bincode::error::EncodeError),
/// Failed to encrypt token.
TokenEncryption,
}
Expand Down Expand Up @@ -287,7 +294,7 @@ impl Token {

/// Encode token structure into a string using base64 encoding.
fn base64_encode(&self) -> Result<String, MmdsTokenError> {
let token_bytes: Vec<u8> = bincode::serialize(self)?;
let token_bytes: Vec<u8> = bincode::serde::encode_to_vec(self, BINCODE_CONFIG)?;

// Encode token structure bytes into base64.
Ok(base64::engine::general_purpose::STANDARD.encode(token_bytes))
Expand All @@ -299,12 +306,9 @@ impl Token {
.decode(encoded_token)
.map_err(|_| MmdsTokenError::ExpiryExtraction)?;

let token: Token = DefaultOptions::new()
.with_fixint_encoding()
.allow_trailing_bytes()
.with_limit(DESERIALIZATION_BYTES_LIMIT as u64)
.deserialize(&token_bytes)
.map_err(|_| MmdsTokenError::ExpiryExtraction)?;
let token: Token = bincode::serde::decode_from_slice(&token_bytes, BINCODE_CONFIG)
.map_err(|_| MmdsTokenError::ExpiryExtraction)?
.0;
Ok(token)
}
}
Expand Down Expand Up @@ -518,45 +522,4 @@ mod tests {
assert!(!token_authority.is_valid(&token0));
assert!(!token_authority.is_valid(&token1));
}

#[test]
fn test_error_display() {
assert_eq!(
MmdsTokenError::EntropyPool(io::Error::from_raw_os_error(0)).to_string(),
format!(
"Failed to extract entropy from /dev/urandom entropy pool: {}.",
io::Error::from_raw_os_error(0)
)
);

assert_eq!(
MmdsTokenError::ExpiryExtraction.to_string(),
"Failed to extract expiry value from token."
);

assert_eq!(
MmdsTokenError::InvalidState.to_string(),
"Invalid token authority state."
);

assert_eq!(
MmdsTokenError::InvalidTtlValue(0).to_string(),
format!(
"Invalid time to live value provided for token: 0. Please provide a value between \
{} and {}.",
MIN_TOKEN_TTL_SECONDS, MAX_TOKEN_TTL_SECONDS
)
);

assert_eq!(
MmdsTokenError::Serialization(BincodeError::new(bincode::ErrorKind::SizeLimit))
.to_string(),
"Bincode serialization failed: the size limit has been reached."
);

assert_eq!(
MmdsTokenError::TokenEncryption.to_string(),
"Failed to encrypt token."
);
}
}
91 changes: 36 additions & 55 deletions src/vmm/src/seccomp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@ use std::collections::HashMap;
use std::io::Read;
use std::sync::Arc;

use bincode::{DefaultOptions, Options};
use bincode::config;
use bincode::config::{Configuration, Fixint, Limit, LittleEndian};

// This byte limit is passed to `bincode` to guard against a potential memory
// allocation DOS caused by binary filters that are too large.
// This limit can be safely determined since the maximum length of a BPF
// filter is 4096 instructions and Firecracker has a finite number of threads.
const DESERIALIZATION_BYTES_LIMIT: usize = 100_000;

const BINCODE_CONFIG: Configuration<LittleEndian, Fixint, Limit<DESERIALIZATION_BYTES_LIMIT>> =
config::standard()
.with_fixed_int_encoding()
.with_limit::<DESERIALIZATION_BYTES_LIMIT>()
.with_little_endian();

/// Each BPF instruction is 8 bytes long and 4 byte aligned.
/// This alignment needs to be satisfied in order for a BPF code to be accepted
Expand All @@ -22,7 +35,7 @@ pub type BpfProgramRef<'a> = &'a [BpfInstruction];
pub type BpfThreadMap = HashMap<String, Arc<BpfProgram>>;

/// Binary filter deserialization errors.
pub type DeserializationError = bincode::Error;
pub type DeserializationError = bincode::error::DecodeError;

/// Retrieve empty seccomp filters.
pub fn get_empty_filters() -> BpfThreadMap {
Expand All @@ -34,19 +47,8 @@ pub fn get_empty_filters() -> BpfThreadMap {
}

/// Deserialize binary with bpf filters
pub fn deserialize_binary<R: Read>(
reader: R,
bytes_limit: Option<u64>,
) -> Result<BpfThreadMap, DeserializationError> {
let result = match bytes_limit {
Some(limit) => DefaultOptions::new()
.with_fixint_encoding()
.allow_trailing_bytes()
.with_limit(limit)
.deserialize_from::<R, HashMap<String, BpfProgram>>(reader),
// No limit is the default.
None => bincode::deserialize_from::<R, HashMap<String, BpfProgram>>(reader),
}?;
pub fn deserialize_binary<R: Read>(mut reader: R) -> Result<BpfThreadMap, DeserializationError> {
let result: HashMap<String, _> = bincode::decode_from_std_read(&mut reader, BINCODE_CONFIG)?;

Ok(result
.into_iter()
Expand Down Expand Up @@ -134,49 +136,28 @@ mod tests {
#[test]
fn test_deserialize_binary() {
// Malformed bincode binary.
{
let data = "adassafvc".to_string();
deserialize_binary(data.as_bytes(), None).unwrap_err();
}
let data = "adassafvc".to_string();
deserialize_binary(data.as_bytes()).unwrap_err();

// Test that the binary deserialization is correct, and that the thread keys
// have been lowercased.
{
let bpf_prog = vec![0; 2];
let mut filter_map: HashMap<String, BpfProgram> = HashMap::new();
filter_map.insert("VcpU".to_string(), bpf_prog.clone());
let bytes = bincode::serialize(&filter_map).unwrap();

let mut expected_res = BpfThreadMap::new();
expected_res.insert("vcpu".to_string(), Arc::new(bpf_prog));
assert_eq!(deserialize_binary(&bytes[..], None).unwrap(), expected_res);
}

// Test deserialization with binary_limit.
{
let bpf_prog = vec![0; 2];

let mut filter_map: HashMap<String, BpfProgram> = HashMap::new();
filter_map.insert("t1".to_string(), bpf_prog.clone());

let bytes = bincode::serialize(&filter_map).unwrap();

// Binary limit too low.
assert!(matches!(
deserialize_binary(&bytes[..], Some(20)).unwrap_err(),
error
if error.to_string() == "the size limit has been reached"
));

let mut expected_res = BpfThreadMap::new();
expected_res.insert("t1".to_string(), Arc::new(bpf_prog));

// Correct binary limit.
assert_eq!(
deserialize_binary(&bytes[..], Some(50)).unwrap(),
expected_res
);
}
let bpf_prog = vec![0; 2];
let mut filter_map: HashMap<String, BpfProgram> = HashMap::new();
filter_map.insert("VcpU".to_string(), bpf_prog.clone());
let bytes = bincode::serde::encode_to_vec(&filter_map, BINCODE_CONFIG).unwrap();

let mut expected_res = BpfThreadMap::new();
expected_res.insert("vcpu".to_string(), Arc::new(bpf_prog));
assert_eq!(deserialize_binary(&bytes[..]).unwrap(), expected_res);

let bpf_prog = vec![0; DESERIALIZATION_BYTES_LIMIT + 1];
let mut filter_map: HashMap<String, BpfProgram> = HashMap::new();
filter_map.insert("VcpU".to_string(), bpf_prog.clone());
let bytes = bincode::serde::encode_to_vec(&filter_map, BINCODE_CONFIG).unwrap();
assert!(matches!(
deserialize_binary(&bytes[..]).unwrap_err(),
bincode::error::DecodeError::LimitExceeded
));
}

#[test]
Expand Down
Loading
Loading