From 243e6408b0f0866570a0a6b58ccfc5247ffa40ce Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Wed, 22 Apr 2026 17:38:45 +0300 Subject: [PATCH] fix: close file handle in get_file_taste using a with statement `get_file_taste` opened a file handle with `sample_path.open("rb").read(8)`, discarding the file object without explicitly closing it. CPython reference- counting closes it promptly in practice, but other implementations (PyPy, Jython) and CPython under GC pressure may defer closure. Use a `with` statement to guarantee the handle is released immediately after reading. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 1 + capa/helpers.py | 4 ++-- tests/test_helpers.py | 22 +++++++++++++++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b77a44f..2989b47b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - ### Bug Fixes +- fix: close file handle in get_file_taste using a with statement @williballenthin - fix: correct off-by-one in dynamic analysis call_count debug log (enumerate -> explicit counter) @williballenthin - fix: correct capa/subscope-rule key in RuleMetadata so is_subscope_rule is no longer always False @williballenthin - fix: remove unreachable backports.functools_lru_cache fallback and dead dependency @williballenthin diff --git a/capa/helpers.py b/capa/helpers.py index 49539db1..625f638c 100644 --- a/capa/helpers.py +++ b/capa/helpers.py @@ -88,8 +88,8 @@ def hex(n: int) -> str: def get_file_taste(sample_path: Path) -> bytes: if not sample_path.exists(): raise IOError(f"sample path {sample_path} does not exist or cannot be accessed") - taste = sample_path.open("rb").read(8) - return taste + with sample_path.open("rb") as f: + return f.read(8) def is_runtime_ida(): diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 6c394717..2b40a369 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -14,10 +14,13 @@ import codecs +import tempfile from pathlib import Path +import pytest + import capa.helpers -from capa.helpers import get_format_from_extension +from capa.helpers import get_file_taste, get_format_from_extension from capa.features.common import ( FORMAT_ELF, FORMAT_SC32, @@ -101,3 +104,20 @@ def test_get_format_from_extension(): assert get_format_from_extension(Path("sample.BinExport2")) == FORMAT_BINEXPORT2 assert get_format_from_extension(Path("sample.bndb")) == FORMAT_BINJA_DB assert get_format_from_extension(Path("sample.exe")) == FORMAT_UNKNOWN + + +def test_get_file_taste_reads_first_bytes(): + with tempfile.NamedTemporaryFile(delete=False) as tmp: + tmp.write(b"\x4d\x5a\x90\x00\x01\x02\x03\x04\xff\xfe") + tmp_path = Path(tmp.name) + try: + taste = get_file_taste(tmp_path) + assert taste == b"\x4d\x5a\x90\x00\x01\x02\x03\x04" + assert len(taste) == 8 + finally: + tmp_path.unlink() + + +def test_get_file_taste_missing_file_raises(): + with pytest.raises(IOError): + get_file_taste(Path("/nonexistent/path/sample.exe"))