Skip to content

Commit 32fca56

Browse files
committed
fix: fail closed on unsupported DWARF expressions
Stop DWARF expression parsing from returning values when a valid prefix is followed by an invalid or unsupported operation. Make CFI expression parsing reject unsupported opcodes instead of skipping them, so traces fail rather than using partial compute steps. Add unit coverage for both DWARF and CFI fail-open regressions.
1 parent e013075 commit 32fca56

2 files changed

Lines changed: 129 additions & 26 deletions

File tree

ghostscope-dwarf/src/index/cfi_index.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ impl CfiIndex {
154154
use gimli::Reader;
155155
let temp = expression.0.to_slice().ok();
156156
let expr_bytes = temp.as_deref().unwrap_or(&[]);
157-
let steps = self.parse_dwarf_expression(expr_bytes)?;
157+
let steps = Self::parse_dwarf_expression(expr_bytes)?;
158158
CfaResult::Expression { steps }
159159
}
160160
};
@@ -355,7 +355,7 @@ impl CfiIndex {
355355
let expression = expr.get(&self.eh_frame)?;
356356
let temp = expression.0.to_slice().ok();
357357
let expr_bytes = temp.as_deref().unwrap_or(&[]);
358-
self.parse_dwarf_expression(expr_bytes)
358+
Self::parse_dwarf_expression(expr_bytes)
359359
}
360360

361361
fn default_register_rule(register: u16) -> Option<RegisterRule<usize>> {
@@ -368,7 +368,7 @@ impl CfiIndex {
368368
}
369369

370370
/// Parse DWARF expression bytes into ComputeStep sequence
371-
fn parse_dwarf_expression(&self, expr_bytes: &[u8]) -> Result<Vec<ComputeStep>> {
371+
fn parse_dwarf_expression(expr_bytes: &[u8]) -> Result<Vec<ComputeStep>> {
372372
let mut steps = Vec::new();
373373
let mut pc = 0;
374374

@@ -381,7 +381,7 @@ impl CfiIndex {
381381
0x70..=0x8f => {
382382
let register = (opcode - 0x70) as u16;
383383
// Read SLEB128 offset
384-
let (offset, bytes_read) = self.read_sleb128(&expr_bytes[pc..])?;
384+
let (offset, bytes_read) = Self::read_sleb128(&expr_bytes[pc..])?;
385385
pc += bytes_read;
386386

387387
steps.push(ComputeStep::LoadRegister(register));
@@ -392,7 +392,7 @@ impl CfiIndex {
392392
}
393393
// DW_OP_plus_uconst
394394
0x23 => {
395-
let (value, bytes_read) = self.read_uleb128(&expr_bytes[pc..])?;
395+
let (value, bytes_read) = Self::read_uleb128(&expr_bytes[pc..])?;
396396
pc += bytes_read;
397397
steps.push(ComputeStep::PushConstant(value as i64));
398398
steps.push(ComputeStep::Add);
@@ -414,10 +414,15 @@ impl CfiIndex {
414414
0x21 => steps.push(ComputeStep::Or),
415415
// DW_OP_xor
416416
0x27 => steps.push(ComputeStep::Xor),
417+
// DW_OP_nop
418+
0x96 => {}
417419

418420
_ => {
419-
debug!("Unhandled DWARF opcode 0x{:02x} in CFA expression", opcode);
420-
// For now, skip unknown opcodes
421+
return Err(anyhow!(
422+
"unsupported DWARF opcode 0x{:02x} in CFA expression at byte offset {}",
423+
opcode,
424+
pc - 1
425+
));
421426
}
422427
}
423428
}
@@ -426,7 +431,7 @@ impl CfiIndex {
426431
}
427432

428433
/// Read ULEB128 from byte slice
429-
fn read_uleb128(&self, data: &[u8]) -> Result<(u64, usize)> {
434+
fn read_uleb128(data: &[u8]) -> Result<(u64, usize)> {
430435
let mut result = 0u64;
431436
let mut shift = 0;
432437
let mut bytes_read = 0;
@@ -444,7 +449,7 @@ impl CfiIndex {
444449
}
445450

446451
/// Read SLEB128 from byte slice
447-
fn read_sleb128(&self, data: &[u8]) -> Result<(i64, usize)> {
452+
fn read_sleb128(data: &[u8]) -> Result<(i64, usize)> {
448453
let mut result = 0i64;
449454
let mut shift = 0;
450455
let mut bytes_read = 0;
@@ -491,9 +496,22 @@ pub struct CfiStats {
491496

492497
#[cfg(test)]
493498
mod tests {
499+
use super::CfiIndex;
500+
494501
#[test]
495502
fn test_cfi_index_creation() {
496503
// This would need a real ELF file for testing
497504
// For now, just ensure the module compiles
498505
}
506+
507+
#[test]
508+
fn cfa_expression_rejects_unknown_opcode_after_valid_prefix() {
509+
let error = CfiIndex::parse_dwarf_expression(&[0x70, 0x00, 0xff])
510+
.expect_err("unknown CFI expression opcode must not be skipped");
511+
512+
assert!(
513+
error.to_string().contains("unsupported"),
514+
"unexpected error: {error}"
515+
);
516+
}
499517
}

ghostscope-dwarf/src/parser/expression_evaluator.rs

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,15 @@ impl ExpressionEvaluator {
341341
let mut has_stack_value = false;
342342

343343
// Parse all operations in the expression
344-
while let Ok(op) = Operation::parse(&mut expression.0, encoding) {
344+
while !expression.0.is_empty() {
345+
let offset = expr_bytes.len() - expression.0.len();
346+
let op = Operation::parse(&mut expression.0, encoding).map_err(|error| {
347+
anyhow::anyhow!(
348+
"failed to parse DWARF expression operation at byte offset {}: {}",
349+
offset,
350+
error
351+
)
352+
})?;
345353
if matches!(op, Operation::StackValue) {
346354
has_stack_value = true;
347355
debug!("Found DW_OP_stack_value - this is a computed value");
@@ -353,7 +361,16 @@ impl ExpressionEvaluator {
353361
Operation::EntryValue { expression } => {
354362
let mut inner = *expression;
355363
let mut inner_ops: Vec<Operation<_>> = Vec::new();
356-
while let Ok(iop) = Operation::parse(&mut inner, encoding) {
364+
let inner_len = inner.len();
365+
while !inner.is_empty() {
366+
let offset = inner_len - inner.len();
367+
let iop = Operation::parse(&mut inner, encoding).map_err(|error| {
368+
anyhow::anyhow!(
369+
"failed to parse DW_OP_entry_value inner expression operation at byte offset {}: {}",
370+
offset,
371+
error
372+
)
373+
})?;
357374
inner_ops.push(iop);
358375
}
359376
if inner_ops.len() == 1 {
@@ -524,18 +541,34 @@ impl ExpressionEvaluator {
524541
// This marks the result as a computed value, not a memory location
525542
// Already handled by has_stack_value flag
526543
}
527-
ParsedOperation::Operation(Operation::Deref { size, .. }) => {
544+
ParsedOperation::Operation(Operation::Deref { size, space, .. }) => {
545+
if *space {
546+
return Err(anyhow::anyhow!(
547+
"unsupported DWARF expression operation: {:?}",
548+
op
549+
));
550+
}
528551
let mem_size = match size {
529552
1 => MemoryAccessSize::U8,
530553
2 => MemoryAccessSize::U16,
531554
4 => MemoryAccessSize::U32,
532555
8 => MemoryAccessSize::U64,
533-
_ => MemoryAccessSize::U64, // Default
556+
_ => {
557+
return Err(anyhow::anyhow!(
558+
"unsupported DWARF dereference size {} in operation: {:?}",
559+
size,
560+
op
561+
))
562+
}
534563
};
535564
steps.push(ComputeStep::Dereference { size: mem_size });
536565
}
566+
ParsedOperation::Operation(Operation::Nop) => {}
537567
_ => {
538-
debug!("Unhandled operation in expression: {:?}", op);
568+
return Err(anyhow::anyhow!(
569+
"unsupported DWARF expression operation: {:?}",
570+
op
571+
));
539572
}
540573
}
541574
}
@@ -1600,6 +1633,14 @@ mod tests {
16001633
use gimli::{Format, LittleEndian, Register, RunTimeEndian};
16011634
use std::sync::Arc;
16021635

1636+
fn test_encoding() -> gimli::Encoding {
1637+
gimli::Encoding {
1638+
format: gimli::Format::Dwarf32,
1639+
version: 5,
1640+
address_size: 8,
1641+
}
1642+
}
1643+
16031644
fn build_scanned_incoming_entry_value_fixture(
16041645
register: u16,
16051646
caller_value: u64,
@@ -1946,13 +1987,62 @@ mod tests {
19461987
);
19471988
}
19481989

1990+
#[test]
1991+
fn multi_op_expression_rejects_invalid_opcode_after_valid_prefix() {
1992+
let expr_bytes = [
1993+
constants::DW_OP_lit1.0,
1994+
0xff,
1995+
constants::DW_OP_stack_value.0,
1996+
];
1997+
1998+
let error = ExpressionEvaluator::parse_expression_with_context(
1999+
&expr_bytes,
2000+
RunTimeEndian::Little,
2001+
test_encoding(),
2002+
None,
2003+
0,
2004+
None,
2005+
None,
2006+
None,
2007+
0,
2008+
)
2009+
.expect_err("invalid opcode after a valid prefix must not return a value");
2010+
2011+
assert!(
2012+
error.to_string().contains("DWARF expression"),
2013+
"unexpected error: {error}"
2014+
);
2015+
}
2016+
2017+
#[test]
2018+
fn multi_op_expression_rejects_unsupported_operation_after_valid_prefix() {
2019+
let expr_bytes = [
2020+
constants::DW_OP_lit1.0,
2021+
constants::DW_OP_drop.0,
2022+
constants::DW_OP_stack_value.0,
2023+
];
2024+
2025+
let error = ExpressionEvaluator::parse_expression_with_context(
2026+
&expr_bytes,
2027+
RunTimeEndian::Little,
2028+
test_encoding(),
2029+
None,
2030+
0,
2031+
None,
2032+
None,
2033+
None,
2034+
0,
2035+
)
2036+
.expect_err("unsupported operation after a valid prefix must not return a value");
2037+
2038+
assert!(
2039+
error.to_string().contains("unsupported"),
2040+
"unexpected error: {error}"
2041+
);
2042+
}
2043+
19492044
#[test]
19502045
fn single_fbreg_fast_path_saturates_cfa_offset_addition() {
1951-
let encoding = gimli::Encoding {
1952-
format: gimli::Format::Dwarf32,
1953-
version: 5,
1954-
address_size: 8,
1955-
};
19562046
let get_cfa = |_address| {
19572047
Ok(Some(CfaResult::RegisterPlusOffset {
19582048
register: 7,
@@ -1963,7 +2053,7 @@ mod tests {
19632053
let result = ExpressionEvaluator::parse_expression_with_context(
19642054
&[0x91, 0x0a],
19652055
RunTimeEndian::Little,
1966-
encoding,
2056+
test_encoding(),
19672057
None,
19682058
0,
19692059
Some(&get_cfa),
@@ -1985,16 +2075,11 @@ mod tests {
19852075

19862076
#[test]
19872077
fn big_endian_dw_op_addr_preserves_absolute_address() {
1988-
let encoding = gimli::Encoding {
1989-
format: gimli::Format::Dwarf32,
1990-
version: 5,
1991-
address_size: 8,
1992-
};
19932078
let expr_bytes = [0x03, 0, 0, 0, 0, 0, 0, 0x12, 0x34];
19942079
let result = ExpressionEvaluator::parse_expression_with_context(
19952080
&expr_bytes,
19962081
gimli::RunTimeEndian::Big,
1997-
encoding,
2082+
test_encoding(),
19982083
None,
19992084
0,
20002085
None,

0 commit comments

Comments
 (0)