Files
capa/tests/test_dnfile_features.py
Willi Ballenthin 57be779ef1 fix: correct off-by-one in get_dotnet_table_row so row_index=1 is not rejected
`get_dotnet_table_row` used `if row_index - 1 <= 0` to guard against invalid
indices. Because .NET metadata tables are 1-indexed, row_index=1 is the first
valid row, but the condition is equivalent to `row_index <= 1`, silently
rejecting it and making the first row of every table unreachable.

Changed to `if row_index <= 0`, which correctly rejects only the zero/null
token and leaves all valid rows accessible. Added four unit tests against the
real dd9098ff91717f4906afe9dafdfa2f52.exe_ sample to verify the guard
boundary: row_index=1 returns the first row, row_index=0 returns None, all
row indices 1..N succeed, and an out-of-bounds index returns None.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:02:58 +03:00

76 lines
2.9 KiB
Python

# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pathlib import Path
import dnfile
import fixtures
from capa.features.extractors.dnfile.helpers import get_dotnet_table_row
DOTNET_DIR = Path(__file__).resolve().parent / "data" / "dotnet"
@fixtures.parametrize(
"sample,scope,feature,expected",
fixtures.FEATURE_PRESENCE_TESTS_DOTNET,
indirect=["sample", "scope"],
)
def test_dnfile_features(sample, scope, feature, expected):
fixtures.do_test_feature_presence(fixtures.get_dnfile_extractor, sample, scope, feature, expected)
@fixtures.parametrize(
"sample,scope,feature,expected",
fixtures.FEATURE_COUNT_TESTS_DOTNET,
indirect=["sample", "scope"],
)
def test_dnfile_feature_counts(sample, scope, feature, expected):
fixtures.do_test_feature_count(fixtures.get_dnfile_extractor, sample, scope, feature, expected)
def test_get_dotnet_table_row_first_row():
"""row_index=1 is the first valid .NET metadata row; it must not be rejected."""
pe = dnfile.dnPE(DOTNET_DIR / "dd9098ff91717f4906afe9dafdfa2f52.exe_")
row = get_dotnet_table_row(pe, dnfile.mdtable.TypeDef.number, 1)
assert row is not None
assert str(row.TypeName) == "<Module>"
def test_get_dotnet_table_row_invalid_zero():
"""row_index=0 is the null token; the function must return None."""
pe = dnfile.dnPE(DOTNET_DIR / "dd9098ff91717f4906afe9dafdfa2f52.exe_")
assert get_dotnet_table_row(pe, dnfile.mdtable.TypeDef.number, 0) is None
def test_get_dotnet_table_row_valid_rows():
"""All valid row indices 1..N return a row from the real PE."""
pe = dnfile.dnPE(DOTNET_DIR / "dd9098ff91717f4906afe9dafdfa2f52.exe_")
assert pe.net is not None
assert pe.net.mdtables is not None
table = pe.net.mdtables.tables.get(dnfile.mdtable.TypeDef.number)
assert table is not None
for row_index in range(1, len(table.rows) + 1):
assert get_dotnet_table_row(pe, dnfile.mdtable.TypeDef.number, row_index) is not None
def test_get_dotnet_table_row_out_of_bounds():
"""row_index beyond the table size returns None."""
pe = dnfile.dnPE(DOTNET_DIR / "dd9098ff91717f4906afe9dafdfa2f52.exe_")
assert pe.net is not None
assert pe.net.mdtables is not None
table = pe.net.mdtables.tables.get(dnfile.mdtable.TypeDef.number)
assert table is not None
assert get_dotnet_table_row(pe, dnfile.mdtable.TypeDef.number, len(table.rows) + 1) is None