use std::io::Write;
use crate::{
metadata::method::{
encode_exception_handlers, ExceptionHandler, ExceptionHandlerFlags, MethodBodyFlags,
SectionFlags,
},
utils::{read_le, read_le_at},
Result,
};
const MAX_EXCEPTION_HANDLERS: usize = 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum EhValidationMode {
Strict,
Filter,
Raw,
}
fn validate_exception_handler_bounds(
handler: &ExceptionHandler,
code_size: u32,
handler_index: usize,
) -> Result<()> {
let try_end = handler
.try_offset
.checked_add(handler.try_length)
.ok_or_else(|| {
malformed_error!(
"Exception handler {} try region overflow: offset {} + length {} overflows u32",
handler_index,
handler.try_offset,
handler.try_length
)
})?;
if try_end > code_size {
return Err(malformed_error!(
"Exception handler {} try region [{}, {}) exceeds method code size {}",
handler_index,
handler.try_offset,
try_end,
code_size
));
}
let handler_end = handler
.handler_offset
.checked_add(handler.handler_length)
.ok_or_else(|| {
malformed_error!(
"Exception handler {} handler region overflow: offset {} + length {} overflows u32",
handler_index,
handler.handler_offset,
handler.handler_length
)
})?;
if handler_end > code_size {
return Err(malformed_error!(
"Exception handler {} handler region [{}, {}) exceeds method code size {}",
handler_index,
handler.handler_offset,
handler_end,
code_size
));
}
if handler.flags.contains(ExceptionHandlerFlags::FILTER) && handler.filter_offset >= code_size {
return Err(malformed_error!(
"Exception handler {} filter offset {} exceeds method code size {}",
handler_index,
handler.filter_offset,
code_size
));
}
Ok(())
}
pub struct MethodBody {
pub size_code: usize,
pub size_header: usize,
pub local_var_sig_token: u32,
pub max_stack: usize,
pub is_fat: bool,
pub is_init_local: bool,
pub is_exception_data: bool,
pub exception_handlers: Vec<ExceptionHandler>,
}
impl MethodBody {
pub fn from(data: &[u8]) -> Result<MethodBody> {
let (body, _) = Self::parse(data, EhValidationMode::Strict)?;
Ok(body)
}
pub fn from_lenient(data: &[u8]) -> Result<(MethodBody, usize)> {
let (body, filtered) = Self::parse(data, EhValidationMode::Filter)?;
if filtered > 0 {
log::warn!(
"Filtered {filtered} invalid exception handler(s), {} valid handler(s) kept",
body.exception_handlers.len()
);
}
Ok((body, filtered))
}
pub fn from_raw(data: &[u8]) -> Result<MethodBody> {
let (body, _) = Self::parse(data, EhValidationMode::Raw)?;
Ok(body)
}
fn check_handler_count(handler_count: u32, eh_mode: EhValidationMode) -> Result<bool> {
if eh_mode == EhValidationMode::Filter || (handler_count as usize) <= MAX_EXCEPTION_HANDLERS
{
return Ok(true);
}
match eh_mode {
EhValidationMode::Strict => Err(malformed_error!(
"Method has too many exception handlers: {} (max: {})",
handler_count,
MAX_EXCEPTION_HANDLERS
)),
_ => {
log::warn!(
"Method has too many exception handlers: {} (max: {}), skipping",
handler_count,
MAX_EXCEPTION_HANDLERS
);
Ok(false)
}
}
}
fn push_handler(
handler: ExceptionHandler,
handler_idx: usize,
code_size: u32,
eh_mode: EhValidationMode,
handlers: &mut Vec<ExceptionHandler>,
filtered_count: &mut usize,
) -> Result<bool> {
match eh_mode {
EhValidationMode::Strict => {
validate_exception_handler_bounds(&handler, code_size, handler_idx)?;
handlers.push(handler);
}
EhValidationMode::Filter => {
if validate_exception_handler_bounds(&handler, code_size, handler_idx).is_ok() {
handlers.push(handler);
if handlers.len() >= MAX_EXCEPTION_HANDLERS {
return Ok(false);
}
} else {
*filtered_count += 1;
}
}
EhValidationMode::Raw => {
handlers.push(handler);
}
}
Ok(true)
}
fn parse(data: &[u8], eh_mode: EhValidationMode) -> Result<(MethodBody, usize)> {
if data.is_empty() {
return Err(malformed_error!("Provided data for body parsing is empty"));
}
let first_byte = read_le::<u8>(data)?;
match MethodBodyFlags::new(u16::from(first_byte & 0b_00000011_u8)) {
MethodBodyFlags::TINY_FORMAT => {
let size_code = (first_byte >> 2) as usize;
if size_code + 1 > data.len() {
return Err(out_of_bounds_error!());
}
Ok((
MethodBody {
size_code,
size_header: 1,
local_var_sig_token: 0,
max_stack: 8,
is_fat: false,
is_init_local: false,
is_exception_data: false,
exception_handlers: Vec::new(),
},
0,
))
}
MethodBodyFlags::FAT_FORMAT => {
if data.len() < 12 {
return Err(out_of_bounds_error!());
}
let first_duo = read_le::<u16>(data)?;
let size_header = (first_duo >> 12) * 4;
let size_code = read_le::<u32>(&data[4..])?;
if data.len() < (size_code as usize + size_header as usize) {
return Err(out_of_bounds_error!());
}
let local_var_sig_token = read_le::<u32>(&data[8..])?;
let flags_header = MethodBodyFlags::new(first_duo & 0b_0000111111111111_u16);
let max_stack = read_le::<u16>(&data[2..])? as usize;
let is_init_local = flags_header.contains(MethodBodyFlags::INIT_LOCALS);
let mut exception_handlers = Vec::new();
let mut filtered_count: usize = 0;
if flags_header.contains(MethodBodyFlags::MORE_SECTS) {
let mut cursor = size_header as usize + size_code as usize;
cursor = (cursor + 3) & !3;
while data.len() > (cursor + 4) {
let method_data_section_flags =
SectionFlags::new(read_le::<u8>(&data[cursor..])?);
if !method_data_section_flags.contains(SectionFlags::EHTABLE) {
break;
}
if method_data_section_flags.contains(SectionFlags::FAT_FORMAT) {
let method_data_section_size =
read_le::<u32>(&data[cursor + 1..])? & 0x00FF_FFFF;
let handler_count = method_data_section_size / 24;
let needed = 4 + handler_count as usize * 24;
if handler_count == 0 || data.len() < cursor + needed {
break;
}
if !Self::check_handler_count(handler_count, eh_mode)? {
break;
}
cursor += 4;
for handler_idx in 0..handler_count {
let flags_u32 = read_le_at::<u32>(data, &mut cursor)?;
if flags_u32 > 0xFFFF && eh_mode == EhValidationMode::Strict {
return Err(malformed_error!(
"Exception handler {} has invalid flags with upper bits set: 0x{:08X}",
handler_idx,
flags_u32
));
}
#[allow(clippy::cast_possible_truncation)]
let flags = ExceptionHandlerFlags::new(flags_u32 as u16);
let handler = ExceptionHandler {
flags,
try_offset: read_le_at::<u32>(data, &mut cursor)?,
try_length: read_le_at::<u32>(data, &mut cursor)?,
handler_offset: read_le_at::<u32>(data, &mut cursor)?,
handler_length: read_le_at::<u32>(data, &mut cursor)?,
filter_offset: read_le_at::<u32>(data, &mut cursor)?,
handler: None,
};
if !Self::push_handler(
handler,
handler_idx as usize,
size_code,
eh_mode,
&mut exception_handlers,
&mut filtered_count,
)? {
break;
}
}
} else {
let method_data_section_size =
u32::from(read_le::<u8>(&data[cursor + 1..])?);
let handler_count = method_data_section_size / 12;
let needed = 4 + handler_count as usize * 12;
if handler_count == 0 || data.len() < cursor + needed {
break;
}
if !Self::check_handler_count(handler_count, eh_mode)? {
break;
}
cursor += 4;
for handler_idx in 0..handler_count {
let handler = ExceptionHandler {
flags: ExceptionHandlerFlags::new(read_le_at::<u16>(
data,
&mut cursor,
)?),
try_offset: u32::from(read_le_at::<u16>(data, &mut cursor)?),
try_length: u32::from(read_le_at::<u8>(data, &mut cursor)?),
handler_offset: u32::from(read_le_at::<u16>(
data,
&mut cursor,
)?),
handler_length: u32::from(read_le_at::<u8>(data, &mut cursor)?),
filter_offset: read_le_at::<u32>(data, &mut cursor)?,
handler: None,
};
if !Self::push_handler(
handler,
handler_idx as usize,
size_code,
eh_mode,
&mut exception_handlers,
&mut filtered_count,
)? {
break;
}
}
}
if !method_data_section_flags.contains(SectionFlags::MORE_SECTS) {
break;
}
}
}
Ok((
MethodBody {
size_code: size_code as usize,
size_header: size_header as usize,
local_var_sig_token,
max_stack,
is_fat: true,
is_init_local,
is_exception_data: !exception_handlers.is_empty(),
exception_handlers,
},
filtered_count,
))
}
_ => Err(malformed_error!(
"MethodHeader is neither FAT nor TINY - {}",
first_byte
)),
}
}
#[must_use]
pub fn size(&self) -> usize {
let base_size = self.size_header + self.size_code;
if self.exception_handlers.is_empty() {
return base_size;
}
let aligned_base = (base_size + 3) & !3;
let needs_fat_format = self.exception_handlers.iter().any(|h| {
h.try_offset > 0xFFFF
|| h.try_length > 0xFF
|| h.handler_offset > 0xFFFF
|| h.handler_length > 0xFF
});
let section_size = if needs_fat_format {
4 + (self.exception_handlers.len() * 24)
} else {
4 + (self.exception_handlers.len() * 12)
};
aligned_base + section_size
}
pub fn write_to<W: Write>(&self, writer: &mut W, il_code: &[u8]) -> Result<u64> {
let code_size = il_code.len();
let has_exceptions = !self.exception_handlers.is_empty();
let use_tiny = code_size <= 63
&& self.max_stack <= 8
&& self.local_var_sig_token == 0
&& !has_exceptions;
let mut bytes_written: u64 = 0;
if use_tiny {
let code_size_u8 = u8::try_from(code_size).map_err(|_| {
malformed_error!("Code size {} exceeds tiny format limit", code_size)
})?;
let header_byte = (code_size_u8 << 2) | 0x02;
writer.write_all(&[header_byte])?;
bytes_written += 1;
} else {
let code_size_u32 = u32::try_from(code_size)
.map_err(|_| malformed_error!("Code size {} exceeds u32 range", code_size))?;
let max_stack_u16 = u16::try_from(self.max_stack)
.map_err(|_| malformed_error!("Max stack {} exceeds u16 range", self.max_stack))?;
let mut flags: u16 = 0x3003; if has_exceptions {
flags |= 0x0008; }
if self.is_init_local {
flags |= 0x0010; }
writer.write_all(&flags.to_le_bytes())?;
writer.write_all(&max_stack_u16.to_le_bytes())?;
writer.write_all(&code_size_u32.to_le_bytes())?;
writer.write_all(&self.local_var_sig_token.to_le_bytes())?;
bytes_written += 12;
}
writer.write_all(il_code)?;
bytes_written += code_size as u64;
if has_exceptions {
let padding = (4 - (bytes_written % 4)) % 4;
if padding > 0 {
writer.write_all(&vec![0u8; padding as usize])?;
bytes_written += padding;
}
let exception_bytes = encode_exception_handlers(&self.exception_handlers)?;
writer.write_all(&exception_bytes)?;
bytes_written += exception_bytes.len() as u64;
}
Ok(bytes_written)
}
}
#[cfg(test)]
mod tests {
use crate::metadata::method::ExceptionHandlerFlags;
use super::*;
#[test]
fn tiny() {
let data = include_bytes!("../../../tests/samples/WB_METHOD_TINY_0600032D.bin");
let method_header = MethodBody::from(data).unwrap();
assert!(!method_header.is_fat);
assert!(!method_header.is_exception_data);
assert!(!method_header.is_init_local);
assert_eq!(method_header.max_stack, 8);
assert_eq!(method_header.size_code, 18);
assert_eq!(method_header.size_header, 1);
assert_eq!(method_header.size(), 19);
assert_eq!(method_header.local_var_sig_token, 0);
}
#[test]
fn fat() {
let data = include_bytes!("../../../tests/samples/WB_METHOD_FAT_0600033E.bin");
let method_header = MethodBody::from(data).unwrap();
assert!(method_header.is_fat);
assert!(!method_header.is_exception_data);
assert!(method_header.is_init_local);
assert_eq!(method_header.max_stack, 5);
assert_eq!(method_header.size_code, 0x9B);
assert_eq!(method_header.size_header, 12);
assert_eq!(method_header.size(), 167);
assert_eq!(method_header.local_var_sig_token, 0x11000059);
}
#[test]
fn fat_exceptions_1() {
let data = include_bytes!("../../../tests/samples/WB_METHOD_FAT_EXCEPTION_06000341.bin");
let method_header = MethodBody::from(data).unwrap();
assert!(method_header.is_fat);
assert!(method_header.is_exception_data);
assert!(method_header.is_init_local);
assert_eq!(method_header.max_stack, 1);
assert_eq!(method_header.size_code, 30);
assert_eq!(method_header.size_header, 12);
assert_eq!(method_header.size(), 60); assert_eq!(method_header.local_var_sig_token, 0x11000003);
assert_eq!(method_header.exception_handlers.len(), 1);
assert!(method_header.exception_handlers[0]
.flags
.contains(ExceptionHandlerFlags::EXCEPTION));
assert_eq!(method_header.exception_handlers[0].try_offset, 0);
assert_eq!(method_header.exception_handlers[0].try_length, 0xF);
assert_eq!(method_header.exception_handlers[0].handler_offset, 0xF);
assert_eq!(method_header.exception_handlers[0].handler_length, 0xD);
assert_eq!(method_header.exception_handlers[0].filter_offset, 0x100003F);
}
#[test]
fn fat_exceptions_tiny_section_2() {
let data = include_bytes!(
"../../../tests/samples/WB_METHOD_FAT_EXCEPTION_N1_2LOCALS_060001AA.bin"
);
let method_header = MethodBody::from(data).unwrap();
assert!(method_header.is_fat);
assert!(method_header.is_exception_data);
assert!(method_header.is_init_local);
assert_eq!(method_header.max_stack, 3);
assert_eq!(method_header.size_code, 0x2E);
assert_eq!(method_header.size_header, 12);
assert_eq!(method_header.size(), 76); assert_eq!(method_header.local_var_sig_token, 0x1100001A);
assert_eq!(method_header.exception_handlers.len(), 1);
assert!(method_header.exception_handlers[0]
.flags
.contains(ExceptionHandlerFlags::FINALLY));
assert_eq!(method_header.exception_handlers[0].try_offset, 0x8);
assert_eq!(method_header.exception_handlers[0].try_length, 0x1B);
assert_eq!(method_header.exception_handlers[0].handler_offset, 0x23);
assert_eq!(method_header.exception_handlers[0].handler_length, 0xA);
assert_eq!(method_header.exception_handlers[0].filter_offset, 0);
}
#[test]
fn fat_exceptions_fat_section_3() {
let data = include_bytes!("../../../tests/samples/WB_METHOD_FAT_EXCEPTION_N2_06000421.bin");
let method_header = MethodBody::from(data).unwrap();
assert!(method_header.is_fat);
assert!(method_header.is_exception_data);
assert!(method_header.is_init_local);
assert_eq!(method_header.max_stack, 5);
assert_eq!(method_header.size_code, 0x19F);
assert_eq!(method_header.size_header, 12);
assert_eq!(method_header.size(), 480); assert_eq!(method_header.local_var_sig_token, 0x11000070);
assert_eq!(method_header.exception_handlers.len(), 2);
assert!(method_header.exception_handlers[0]
.flags
.contains(ExceptionHandlerFlags::FINALLY));
assert_eq!(method_header.exception_handlers[0].try_offset, 0x145);
assert_eq!(method_header.exception_handlers[0].try_length, 0x28);
assert_eq!(method_header.exception_handlers[0].handler_offset, 0x16D);
assert_eq!(method_header.exception_handlers[0].handler_length, 0xE);
assert_eq!(method_header.exception_handlers[0].filter_offset, 0);
assert!(method_header.exception_handlers[1]
.flags
.contains(ExceptionHandlerFlags::FINALLY));
assert_eq!(method_header.exception_handlers[1].try_offset, 0x9);
assert_eq!(method_header.exception_handlers[1].try_length, 0x18A);
assert_eq!(method_header.exception_handlers[1].handler_offset, 0x193);
assert_eq!(method_header.exception_handlers[1].handler_length, 0xA);
assert_eq!(method_header.exception_handlers[1].filter_offset, 0);
}
#[test]
fn fat_exceptions_multiple() {
let data = include_bytes!("../../../tests/samples/WB_METHOD_FAT_EXCEPTION_N2_06000D54.bin");
let method_header = MethodBody::from(data).unwrap();
assert!(method_header.is_fat);
assert!(method_header.is_exception_data);
assert!(method_header.is_init_local);
assert_eq!(method_header.max_stack, 3);
assert_eq!(method_header.size_code, 81);
assert_eq!(method_header.size_header, 12);
assert_eq!(method_header.size(), 124); assert_eq!(method_header.local_var_sig_token, 0x1100007C);
assert_eq!(method_header.exception_handlers.len(), 2);
assert!(method_header.exception_handlers[0]
.flags
.contains(ExceptionHandlerFlags::FINALLY));
assert_eq!(method_header.exception_handlers[0].try_offset, 17);
assert_eq!(method_header.exception_handlers[0].try_length, 48);
assert_eq!(method_header.exception_handlers[0].handler_offset, 65);
assert_eq!(method_header.exception_handlers[0].handler_length, 10);
assert_eq!(method_header.exception_handlers[0].filter_offset, 0);
assert!(method_header.exception_handlers[1]
.flags
.contains(ExceptionHandlerFlags::EXCEPTION));
assert_eq!(method_header.exception_handlers[1].try_offset, 0);
assert_eq!(method_header.exception_handlers[1].try_length, 77);
assert_eq!(method_header.exception_handlers[1].handler_offset, 77);
assert_eq!(method_header.exception_handlers[1].handler_length, 3);
assert_eq!(method_header.exception_handlers[1].filter_offset, 0x100001D);
}
fn build_fat_body_with_small_eh(
code_size: u32,
clauses: &[(u16, u16, u8, u16, u8, u32)],
) -> Vec<u8> {
let il_code = vec![0x00u8; code_size as usize];
let flags: u16 = 0x3003 | 0x0008 | 0x0010;
let max_stack: u16 = 8;
let local_var_sig: u32 = 0;
let mut data = Vec::new();
data.extend_from_slice(&flags.to_le_bytes());
data.extend_from_slice(&max_stack.to_le_bytes());
data.extend_from_slice(&code_size.to_le_bytes());
data.extend_from_slice(&local_var_sig.to_le_bytes());
data.extend_from_slice(&il_code);
let unaligned = 12 + code_size as usize;
let aligned = (unaligned + 3) & !3;
data.extend(std::iter::repeat_n(0x00, aligned - unaligned));
let section_size = (4 + clauses.len() * 12) as u8;
data.push(0x01); data.push(section_size);
data.push(0x00); data.push(0x00);
for &(flags, try_off, try_len, handler_off, handler_len, filter) in clauses {
data.extend_from_slice(&flags.to_le_bytes());
data.extend_from_slice(&try_off.to_le_bytes());
data.push(try_len);
data.extend_from_slice(&handler_off.to_le_bytes());
data.push(handler_len);
data.extend_from_slice(&filter.to_le_bytes());
}
data
}
#[test]
fn from_lenient_filters_invalid_handlers() {
let clauses = [
(0x0000u16, 0u16, 10u8, 10u16, 10u8, 0x01000001u32),
(0x0000, 0xFFFF, 0xFF, 0xFFFF, 0xFF, 0x01000001),
(0x0000, 0, 5, 0xAAAA, 0xBB, 0x01000001),
];
let data = build_fat_body_with_small_eh(100, &clauses);
assert!(MethodBody::from(&data).is_err());
let (body, filtered) = MethodBody::from_lenient(&data).unwrap();
assert!(body.is_fat);
assert_eq!(body.size_code, 100);
assert_eq!(body.exception_handlers.len(), 1);
assert_eq!(filtered, 2);
assert_eq!(body.exception_handlers[0].try_offset, 0);
assert_eq!(body.exception_handlers[0].try_length, 10);
assert_eq!(body.exception_handlers[0].handler_offset, 10);
assert_eq!(body.exception_handlers[0].handler_length, 10);
}
#[test]
fn from_raw_keeps_all_handlers() {
let clauses = [
(0x0000u16, 0u16, 10u8, 10u16, 10u8, 0x01000001u32),
(0x0000, 0xFFFF, 0xFF, 0xFFFF, 0xFF, 0x01000001),
(0x0000, 0, 5, 0xAAAA, 0xBB, 0x01000001),
];
let data = build_fat_body_with_small_eh(100, &clauses);
let body = MethodBody::from_raw(&data).unwrap();
assert_eq!(body.exception_handlers.len(), 3);
assert_eq!(body.exception_handlers[1].try_offset, 0xFFFF);
}
#[test]
fn from_strict_errors_on_max_exception_handlers() {
let code_size: u32 = 100;
let il_code = vec![0x00u8; code_size as usize];
let flags: u16 = 0x3003 | 0x0008 | 0x0010;
let max_stack: u16 = 8;
let local_var_sig: u32 = 0;
let mut data = Vec::new();
data.extend_from_slice(&flags.to_le_bytes());
data.extend_from_slice(&max_stack.to_le_bytes());
data.extend_from_slice(&code_size.to_le_bytes());
data.extend_from_slice(&local_var_sig.to_le_bytes());
data.extend_from_slice(&il_code);
let unaligned = 12 + code_size as usize;
let aligned = (unaligned + 3) & !3;
data.extend(std::iter::repeat_n(0x00, aligned - unaligned));
let handler_count: u32 = 1025; let section_size: u32 = 4 + handler_count * 24;
data.push(0x41); let size_bytes = section_size.to_le_bytes();
data.push(size_bytes[0]);
data.push(size_bytes[1]);
data.push(size_bytes[2]);
for _ in 0..handler_count {
data.extend_from_slice(&[0u8; 24]);
}
assert!(MethodBody::from(&data).is_err());
}
#[test]
fn from_lenient_accepts_malformed_eh() {
let clauses = [
(
0x0000u16,
0xFFFFu16,
0xFFu8,
0xFFFFu16,
0xFFu8,
0x01000001u32,
),
];
let data = build_fat_body_with_small_eh(10, &clauses);
assert!(MethodBody::from(&data).is_err());
let (body, filtered) = MethodBody::from_lenient(&data).unwrap();
assert!(body.is_fat);
assert_eq!(body.size_code, 10);
assert_eq!(filtered, 1);
assert_eq!(body.exception_handlers.len(), 0);
let raw_body = MethodBody::from_raw(&data).unwrap();
assert_eq!(raw_body.exception_handlers.len(), 1);
assert_eq!(raw_body.exception_handlers[0].try_offset, 0xFFFF);
}
}