Skip to content

Commit 833660e

Browse files
committed
fix: impose some hard limits while parsing DEX files.
These are sanity checks against corrupted DEX files that can cause out-of-memory errors.
1 parent eccf374 commit 833660e

1 file changed

Lines changed: 41 additions & 18 deletions

File tree

lib/src/modules/dex/parser.rs

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,18 @@ pub struct Dex {
4141
}
4242

4343
impl Dex {
44-
// the type of endianness used in the file
4544
const ENDIAN_CONSTANT: u32 = 0x12345678;
4645
const REVERSE_ENDIAN_CONSTANT: u32 = 0x78563412;
47-
48-
// lack of information
46+
const DEX_HEADER_SIZE: u32 = 0x70;
4947
const NO_INDEX: u32 = 0xffffffff;
5048

49+
const MAX_STRINGS: usize = 1_000_000;
50+
const MAX_TYPES: usize = 1_000_000;
51+
const MAX_PROTOS: usize = 1_000_000;
52+
const MAX_CLASSES: usize = 1_000_000;
53+
const MAX_METHODS: usize = 1_000_000;
54+
const MAX_FIELDS: usize = 1_000_000;
55+
5156
pub fn parse<'a>(data: &'a [u8]) -> Result<Self, Err<Error<'a>>> {
5257
// Extract dex header with information about data location
5358
let (strings_offset, header) = Self::parse_dex_header(data)?;
@@ -60,14 +65,9 @@ impl Dex {
6065
let (proto_offset, types) =
6166
Self::parse_types(types_offset, &header, &strings)?;
6267

63-
// Exctract defined prototypes
64-
let (field_offset, proto_ids) = Self::parse_proto_ids(
65-
proto_offset,
66-
data,
67-
&header,
68-
&strings,
69-
&types,
70-
)?;
68+
// Extract defined prototypes
69+
let (field_offset, protos) =
70+
Self::parse_protos(proto_offset, data, &header, &strings, &types)?;
7171

7272
// Extract defined fields
7373
let (method_offset, fields) =
@@ -79,7 +79,7 @@ impl Dex {
7979
&header,
8080
&strings,
8181
&types,
82-
&proto_ids,
82+
&protos,
8383
)?;
8484

8585
// Extract defined classes
@@ -93,7 +93,7 @@ impl Dex {
9393
header,
9494
strings,
9595
types,
96-
protos: proto_ids,
96+
protos,
9797
fields,
9898
methods,
9999
class_defs,
@@ -216,6 +216,7 @@ impl Dex {
216216

217217
let string_offsets = it
218218
.by_ref()
219+
.take(Self::MAX_STRINGS)
219220
.take(header.string_ids_size as usize)
220221
.filter_map(|offset| Self::parse_string_from_offset(data, offset))
221222
.map(Rc::new)
@@ -230,12 +231,24 @@ impl Dex {
230231
///
231232
/// idx - is an index in the string_ids_off table
232233
/// strings_ids_off[idx] -> string_data_item
234+
///
235+
/// Strings larger than 64KB will be considered invalid and the result will
236+
/// be None.
233237
fn parse_string_from_offset(
234238
data: &[u8],
235239
string_data_offset: u32,
236240
) -> Option<String> {
241+
if string_data_offset < Self::DEX_HEADER_SIZE {
242+
return None;
243+
}
244+
237245
data.get(string_data_offset as usize..).and_then(|data| {
238246
let (data, utf16_size) = uleb128(data).ok()?;
247+
248+
if utf16_size > 65536 || (utf16_size as usize) > data.len() {
249+
return None;
250+
}
251+
239252
let (_, bytes) =
240253
take::<usize, &[u8], Error>(utf16_size as usize)(data).ok()?;
241254

@@ -268,6 +281,7 @@ impl Dex {
268281

269282
let type_indexes = it
270283
.by_ref()
284+
.take(Self::MAX_TYPES)
271285
.take(header.type_ids_size as usize)
272286
.filter_map(|idx| string_items.get(idx as usize).cloned())
273287
.collect();
@@ -281,7 +295,7 @@ impl Dex {
281295
///
282296
/// See: https://source.android.com/docs/core/runtime/dex-format#proto-id-item
283297
/// See: https://source.android.com/docs/core/runtime/dex-format#type-list
284-
fn parse_proto_ids<'a>(
298+
fn parse_protos<'a>(
285299
remainder: &'a [u8],
286300
data: &'a [u8],
287301
header: &DexHeader,
@@ -298,13 +312,15 @@ impl Dex {
298312

299313
let proto_entries = it
300314
.by_ref()
315+
.take(Self::MAX_PROTOS)
301316
.take(header.proto_ids_size as usize)
302317
.filter_map(|(shorty_idx, return_type_idx, parameters_off)| {
303318
let shorty = string_items.get(shorty_idx as usize)?.clone();
304319
let return_type =
305320
type_items.get(return_type_idx as usize)?.clone();
306321

307-
// According to the documentation, if parameters_off is 0, then the type has 0 parameters.
322+
// According to the documentation, if parameters_off is 0, then
323+
// the type has 0 parameters.
308324
let parameters = if parameters_off == 0 {
309325
Vec::new()
310326
} else {
@@ -335,10 +351,15 @@ impl Dex {
335351
offset: u32,
336352
) -> Option<Vec<Rc<String>>> {
337353
let remainder = data.get(offset as usize..)?;
354+
let (remainder, size) = le_u32::<&[u8], Error>(remainder).ok()?;
338355

339-
let (rem, size) = le_u32::<&[u8], Error>(remainder).ok()?;
356+
// The number of arguments can't be higher than 255 due to constraints
357+
// in the Dalvik bytecode instruction set itself.
358+
if size > 255 {
359+
return None;
360+
}
340361

341-
let mut it = iterator(rem, le_u16::<&[u8], Error>);
362+
let mut it = iterator(remainder, le_u16::<&[u8], Error>);
342363
let items = it
343364
.by_ref()
344365
.take(size as usize)
@@ -369,6 +390,7 @@ impl Dex {
369390

370391
let field_entries = it
371392
.by_ref()
393+
.take(Self::MAX_FIELDS)
372394
.take(header.field_ids_size as usize)
373395
.filter_map(|(class_idx, type_idx, name_idx)| {
374396
let class = type_items.get(class_idx as usize)?.clone();
@@ -404,6 +426,7 @@ impl Dex {
404426

405427
let method_entries = it
406428
.by_ref()
429+
.take(Self::MAX_METHODS)
407430
.take(header.method_ids_size as usize)
408431
.filter_map(|(class_idx, proto_idx, name_idx)| {
409432
let class = type_items.get(class_idx as usize)?.clone();
@@ -442,6 +465,7 @@ impl Dex {
442465

443466
let class_entries = it
444467
.by_ref()
468+
.take(Self::MAX_CLASSES)
445469
.take(header.class_defs_size as usize)
446470
.filter_map(
447471
|(
@@ -484,7 +508,6 @@ impl Dex {
484508
fn parse_map_items(data: &[u8], header: &DexHeader) -> Option<MapList> {
485509
data.get(header.map_off as usize..).and_then(|offset| {
486510
let (items_offset, size) = le_u32::<&[u8], Error>(offset).ok()?;
487-
488511
let mut it = iterator(items_offset, Self::parse_map_item);
489512
let items = it.by_ref().take(size as usize).collect();
490513
let _ = it.finish();

0 commit comments

Comments
 (0)