Skip to content

Commit 1e0203b

Browse files
committed
sanity check on mdtable num_rows value
1 parent cab1530 commit 1e0203b

File tree

4 files changed

+65
-16
lines changed

4 files changed

+65
-16
lines changed

src/dnfile/base.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,7 @@ def __init__(
752752
strings_heap: Optional["stream.StringsHeap"],
753753
guid_heap: Optional["stream.GuidHeap"],
754754
blob_heap: Optional["stream.BlobHeap"],
755+
mdtables: "stream.MetaDataTables",
755756
lazy_load=False
756757
):
757758
"""
@@ -793,6 +794,32 @@ def init_row():
793794
)
794795

795796
self.rows: List[RowType]
797+
# initialized table data to an empty byte sequence
798+
self._table_data: bytes = b""
799+
800+
# store heap info
801+
self._strings_heap: Optional["stream.StringsHeap"] = strings_heap
802+
self._guid_heap: Optional["stream.GuidHeap"] = guid_heap
803+
self._blob_heap: Optional["stream.BlobHeap"] = blob_heap
804+
self._strings_offset_size = strings_offset_size
805+
self._guid_offset_size = guid_offset_size
806+
self._blob_offset_size = blob_offset_size
807+
self._tables_rowcounts = tables_rowcounts
808+
809+
# calculate the row size and table size in bytes
810+
fake_row = init_row()
811+
self.row_size = fake_row.row_size
812+
table_size = self.row_size * self.num_rows
813+
814+
# sanity check: if table size is larger than the containing stream
815+
if self.rva + table_size > mdtables.rva + mdtables.sizeof():
816+
# initialize an empty row list
817+
self.rows = []
818+
# indicate error
819+
logger.warning(f"Metadata table {self.name} with row_size {self.row_size} and num_rows {self.num_rows} does not fit in MD stream size {mdtables.sizeof()}")
820+
# do not try to parse rows in this table
821+
return
822+
796823
if lazy_load and num_rows > 0:
797824
self.rows = _LazyList(self._lazy_parse_rows, num_rows)
798825
try:
@@ -812,18 +839,6 @@ def init_row():
812839
# this probably means invalid data.
813840
logger.warning("failed to construct %s row %d", self.name, e)
814841

815-
# store heap info
816-
self._strings_heap: Optional["stream.StringsHeap"] = strings_heap
817-
self._guid_heap: Optional["stream.GuidHeap"] = guid_heap
818-
self._blob_heap: Optional["stream.BlobHeap"] = blob_heap
819-
self._strings_offset_size = strings_offset_size
820-
self._guid_offset_size = guid_offset_size
821-
self._blob_offset_size = blob_offset_size
822-
self._tables_rowcounts = tables_rowcounts
823-
824-
self._table_data: bytes = b""
825-
self.row_size: int = self._get_row_size()
826-
827842
def _get_row_size(self):
828843
if not self.rows:
829844
return 0

src/dnfile/mdtable.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2155,6 +2155,7 @@ def createTable(
21552155
strings_heap: Optional["stream.StringsHeap"],
21562156
guid_heap: Optional["stream.GuidHeap"],
21572157
blob_heap: Optional["stream.BlobHeap"],
2158+
mdtables: "stream.MetaDataTables",
21582159
lazy_load=False
21592160
) -> ClrMetaDataTable:
21602161
if number not in cls._table_number_map:
@@ -2169,6 +2170,7 @@ def createTable(
21692170
strings_heap,
21702171
guid_heap,
21712172
blob_heap,
2173+
mdtables,
21722174
lazy_load,
21732175
)
21742176
return table

src/dnfile/stream.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,11 @@ def parse(self, streams: List[base.ClrStream], lazy_load=False):
550550
# if table bit is set
551551
if header_struct.MaskValid & 2 ** i != 0:
552552
# read the row count
553-
table_rowcounts.append(self.get_dword_at_rva(cur_rva))
553+
row_count = self.get_dword_at_rva(cur_rva)
554+
# sanity check
555+
if row_count > self.sizeof():
556+
logger.warning(f"invalid table {i} row_count {row_count} larger than stream size {self.sizeof()}")
557+
table_rowcounts.append(row_count)
554558
# increment to next dword
555559
cur_rva += 4
556560
else:
@@ -576,6 +580,7 @@ def parse(self, streams: List[base.ClrStream], lazy_load=False):
576580
strings_heap,
577581
guid_heap,
578582
blob_heap,
583+
self,
579584
lazy_load,
580585
)
581586
except errors.dnFormatError as e:
@@ -618,22 +623,47 @@ def full_loader():
618623
# Setup lazy loading for all tables
619624
for table in self.tables_list:
620625
if table.row_size > 0 and table.num_rows > 0:
626+
table.rva = cur_rva
627+
table.file_offset = self.get_file_offset(table.rva)
628+
# calculate the table size
629+
table_size = table.row_size * table.num_rows
630+
# sanity check: if table size is more than data in stream
631+
if cur_rva + table_size > self.rva + self.sizeof():
632+
# the table is too large
633+
err_msg = f"Metadata table {table.name} with row_size {table.row_size} and num_rows {table.num_rows} is larger than stream size {self.sizeof()}"
634+
deferred_exceptions.append(
635+
errors.dnFormatError(err_msg)
636+
)
637+
logging.warning(err_msg)
638+
# stop processing tables
639+
break
621640
table_data = self.get_data_at_rva(
622641
cur_rva, table.row_size * table.num_rows
623642
)
624643
table.setup_lazy_load(cur_rva, table_data, full_loader)
625-
table.file_offset = self.get_file_offset(cur_rva)
626644
cur_rva += table.row_size * table.num_rows
627645
else:
628646
#### parse each table
629647
# here, cur_rva points to start of table rows
630648
for table in self.tables_list:
631649
if table.row_size > 0 and table.num_rows > 0:
650+
table.rva = cur_rva
651+
table.file_offset = self.get_file_offset(cur_rva)
652+
# calculate the table size
653+
table_size = table.row_size * table.num_rows
654+
# sanity check: if table size is more than data in stream
655+
if cur_rva + table_size > self.rva + self.sizeof():
656+
# the table is too large
657+
err_msg = f"Metadata table {table.name} with row_size {table.row_size} and num_rows {table.num_rows} is larger than stream size {self.sizeof()}"
658+
deferred_exceptions.append(
659+
errors.dnFormatError(err_msg)
660+
)
661+
logging.warning(err_msg)
662+
# stop processing tables
663+
break
632664
table_data = self.get_data_at_rva(
633665
cur_rva, table.row_size * table.num_rows
634666
)
635-
table.rva = cur_rva
636-
table.file_offset = self.get_file_offset(cur_rva)
637667
# parse structures (populates .struct for each row)
638668
table.parse_rows(cur_rva, table_data)
639669
# move to next set of rows

tests/test_parse.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ def test_tables():
8888
dn = dnfile.dnPE(path)
8989
assert dn.net is not None
9090

91+
assert len(dn.net.mdtables.tables_list) == 9
92+
9193
for table in ["Module", "TypeRef", "TypeDef", "MethodDef", "Param", "MemberRef", "CustomAttribute", "Assembly", "AssemblyRef"]:
9294
assert hasattr(dn.net.mdtables, table)
9395

0 commit comments

Comments
 (0)