Skip to content

Commit 75479d4

Browse files
authored
pe: Fix potential out-of-bounds read in unwind/POGO info parser (#498)
* Fix potential out-of-bounds read in unwind info parser * remove -1 bias in bound check * Add more offset check; preserve error semantics * Add test for malformed name
1 parent 2847f2d commit 75479d4

File tree

3 files changed

+62
-13
lines changed

3 files changed

+62
-13
lines changed

src/pe/debug.rs

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -810,35 +810,39 @@ impl<'a> Iterator for POGOEntryIterator<'a> {
810810
Err(error) => return Some(Err(error.into())),
811811
};
812812

813+
// Use >= to avoid empty slice, that we want to emit an error early here for
814+
// malformed name in a POGO entry.
813815
if offset >= self.data.len() {
814816
return Some(Err(error::Error::Malformed(format!(
815-
"Offset {:#x} is too big for containing name field of POGO entry (rva {:#x} and size {:#X})",
816-
offset, rva, size
817+
"Offset {offset:#x} is too big for containing name field of POGO entry (rva {rva:#x} and size {size:#X})",
817818
))));
818819
}
819820
let name = match self.data[offset..].iter().position(|&b| b == 0) {
820821
Some(pos) => {
821-
if offset + pos as usize >= self.data.len() {
822+
// + 1 nul
823+
let Some(name) = self.data.gread_with::<&[u8]>(&mut offset, pos + 1).ok() else {
822824
return Some(Err(error::Error::Malformed(format!(
823-
"Null-terminator for POGO entry (rva {:#x} and size {:#X}) found but exceeds iterator buffer",
824-
rva, size
825+
"Null-terminator for POGO entry (rva {rva:#x} and size {size:#X}) found but exceeds iterator buffer",
825826
))));
826-
}
827-
let name = &self.data[offset..offset + pos + 1];
828-
offset = offset + pos + 1;
829-
// Align to the next u32 boundary
830-
offset = (offset + 3) & !3; // Round up to the nearest multiple of 4
827+
};
828+
// Round up to the nearest multiple of 4
829+
offset = (offset + 3) & !3;
831830
name
832831
}
833832
None => {
834833
return Some(Err(error::Error::Malformed(format!(
835-
"Cannot find null-terimnator for POGO entry (rva {:#x} and size {:#X})",
836-
rva, size
834+
"Cannot find null-terminator for POGO entry (rva {rva:#x} and size {size:#X})",
837835
))
838836
.into()));
839837
}
840838
};
841839

840+
if offset > self.data.len() {
841+
return Some(Err(error::Error::Malformed(format!(
842+
"Offset {offset:#x} exceeds buffer length {:#x}",
843+
self.data.len()
844+
))));
845+
}
842846
self.data = &self.data[offset..];
843847
Some(Ok(POGOInfoEntry { rva, size, name }))
844848
}
@@ -853,7 +857,8 @@ mod tests {
853857
IMAGE_DEBUG_TYPE_CODEVIEW, IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS, IMAGE_DEBUG_TYPE_ILTCG,
854858
IMAGE_DEBUG_TYPE_POGO, IMAGE_DEBUG_TYPE_REPRO, IMAGE_DEBUG_TYPE_VC_FEATURE,
855859
IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT, IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT_STRICT_MODE,
856-
ImageDebugDirectory, POGO_SIGNATURE_SIZE, POGOInfoEntry, ReproInfo, VCFeatureInfo,
860+
ImageDebugDirectory, POGO_SIGNATURE_SIZE, POGOEntryIterator, POGOInfoEntry, ReproInfo,
861+
VCFeatureInfo,
857862
};
858863

859864
const NO_DEBUG_DIRECTORIES_BIN: &[u8] =
@@ -1100,4 +1105,42 @@ mod tests {
11001105
];
11011106
assert_eq!(entries, entries_expect);
11021107
}
1108+
1109+
#[rustfmt::skip]
1110+
const MALFORMED_POGO_NAME: &[u8; 83] = &[
1111+
// Entry 1: .text$mn
1112+
0x00, 0x10, 0x00, 0x00, // RVA: 0x00001000
1113+
0x03, 0x00, 0x00, 0x00, // Size: 0x00000003
1114+
0x2e, 0x74, 0x65, 0x78, // Name: ".text$mn\0"
1115+
0x74, 0x24, 0x6d, 0x6e,
1116+
0x00, 0x00, 0x00, 0x00,
1117+
// Entry 2: .rdata
1118+
0x00, 0x20, 0x00, 0x00, // RVA: 0x00002000
1119+
0xa8, 0x00, 0x00, 0x00, // Size: 0x000000A8
1120+
0x2e, 0x72, 0x64, 0x61, // Name: ".rdata\0"
1121+
0x74, 0x61, 0x00, 0x00,
1122+
// Entry 3: .rdata$voltmd
1123+
0xa8, 0x20, 0x00, 0x00, // RVA: 0x000020A8
1124+
0x18, 0x00, 0x00, 0x00, // Size: 0x00000018
1125+
0x2e, 0x72, 0x64, 0x61, // Name: ".rdata$voltmd\0"
1126+
0x74, 0x61, 0x24, 0x76,
1127+
0x6f, 0x6c, 0x74, 0x6d,
1128+
0x64, 0x00, 0x00, 0x00,
1129+
// Entry 4: .rdata$zzzdbg
1130+
0xc0, 0x20, 0x00, 0x00, // RVA: 0x000020C0
1131+
0xcc, 0x00, 0x00, 0x00, // Size: 0x000000CC
1132+
0x2e, 0x72, 0x64, 0x61, // Name: ".rdata$zzzdbg\0"
1133+
0x74, 0x61, 0x24, 0x7a,
1134+
0x7a, 0x7a, 0x64, 0x62,
1135+
0x67, 0x00, 0x00, /* truncated 0x00 */
1136+
];
1137+
1138+
#[test]
1139+
#[should_panic = "Malformed(\"Offset 0x18 exceeds buffer length 0x17\")"]
1140+
fn parse_debug_pogo_malformed_name() {
1141+
let entries = POGOEntryIterator {
1142+
data: MALFORMED_POGO_NAME,
1143+
};
1144+
let _ = entries.collect::<Result<Vec<_>, _>>().unwrap();
1145+
}
11031146
}

src/pe/exception.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,11 @@ impl<'a> UnwindInfo<'a> {
710710
// handler is called as part of the search for an exception handler or as part of an unwind.
711711
} else if flags & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER) != 0 {
712712
let address = bytes.gread_with::<u32>(&mut offset, scroll::LE)?;
713+
if offset > bytes.len() {
714+
return Err(error::Error::Malformed(format!(
715+
"Offset {offset:#x} is too big to contain unwind handlers",
716+
)));
717+
}
713718
let data = &bytes[offset..];
714719

715720
handler = Some(if flags & UNW_FLAG_EHANDLER != 0 {

src/pe/resource.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,7 @@ impl<'a> Iterator for ResourceStringIterator<'a> {
668668
"Parsed next resource string as size {:#x}: {:#x?}",
669669
offset, next?
670670
);
671+
// Using offset from `ResourceString::parse` so this is infailable.
671672
self.data = &self.data[offset..];
672673
Ok(next?)
673674
}

0 commit comments

Comments
 (0)