Skip to content

Commit f087bb9

Browse files
iaincoulterterriko
andauthored
fix: support .arr, support ios/wasm binaries (#4521)
* fix: support .arr, support ios/wasm binaries * fix: aar file, apple/wasm binary * fix: add aar to spellings * fix: add tests for File.is_binary and VersionScanner.is_executable --------- Co-authored-by: Terri Oda <terri.oda@intel.com>
1 parent 8791957 commit f087bb9

File tree

9 files changed

+189
-25
lines changed

9 files changed

+189
-25
lines changed

.github/actions/spelling/expect.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ NOTKNOWN
1313
pyyaml
1414
skontar
1515
Svunknown
16-
urllib
16+
urllib
17+
aar

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ The following archive formats are currently supported by the auto-extractor:
313313
| Archive Format | File Extension |
314314
| -------------- | ---------------------------------------------- |
315315
| zip | .zip, .exe, .jar, .msi, .egg, .whl, .war, .ear |
316+
| | .aar |
316317
| tar | .tar, .tgz, .tar.gz, .tar.xz, .tar.bz2 |
317318
| deb | .deb, .ipk |
318319
| rpm | .rpm |
@@ -348,7 +349,7 @@ On windows systems, you may need:
348349

349350
Windows has `Expand` installed by default, but `ar` and `7z` might need to be installed.
350351
If you want to run our test-suite or scan a zstd compressed file, We recommend installing this [7-zip-zstd](https://github.com/mcmilk/7-Zip-zstd)
351-
fork of 7zip. We are currently using `7z` for extracting `jar`, `apk`, `msi`, `exe` and `rpm` files.
352+
fork of 7zip. We are currently using `7z` for extracting `jar`, `apk`, `aar`, `msi`, `exe` and `rpm` files.
352353
To install `ar` you can install MinGW (which has binutils as a part of it) from [here](https://www.mingw-w64.org/downloads/#msys2) and run the downloaded .exe file.
353354

354355
If you get an error about building libraries when you try to install from pip,

cve_bin_tool/extractor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def __init__(self, logger=None, error_mode=ErrorMode.TruncTrace):
9090
".whl",
9191
".war",
9292
".ear",
93+
".aar",
9394
],
9495
MIMES: [
9596
"application/x-msdownload",

cve_bin_tool/file.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,28 @@ def check_pe(_filename: str, signature: bytes) -> bool:
4747
def check_fake_test(_filename: str, signature: bytes) -> bool:
4848
"""check for fake tests under windows."""
4949
return signature == b"MZ\x90\x00"
50+
51+
52+
def check_mach_o_32(_filename: str, signature: bytes) -> bool:
53+
"""Check for Mach-O 32-bit signature."""
54+
return signature[:4] == b"\xFE\xED\xFA\xCE"
55+
56+
57+
def check_mach_o_64(_filename: str, signature: bytes) -> bool:
58+
"""Check for Mach-O 64-bit signature."""
59+
return signature[:4] == b"\xFE\xED\xFA\xCF"
60+
61+
62+
def check_mach_o_universal(_filename: str, signature: bytes) -> bool:
63+
"""Check for Mach-O Universal Binary signature."""
64+
return signature[:4] == b"\xCA\xFE\xBA\xBE"
65+
66+
67+
def check_ios_arm(_filename: str, signature: bytes) -> bool:
68+
"""Check for Mach-O Universal Binary signature."""
69+
return signature[:4] == b"\xCF\xFA\xED\xFE"
70+
71+
72+
def check_wasm(_filename: str, signature: bytes) -> bool:
73+
"""Check for WebAssembly (WASM) signature."""
74+
return signature[:4] == b"\x00\x61\x73\x6D"

cve_bin_tool/version_scanner.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,17 @@ def is_executable(self, filename: str) -> tuple[bool, str | None]:
173173
"PE32 executable",
174174
"PE32+ executable",
175175
"Mach-O",
176+
"WebAssembly",
176177
"YAFFS",
177178
": data",
178179
*list(valid_files.keys()),
179180
)
180181
):
181-
return False, None
182+
# Fallback as not all types are covered by the File command
183+
# On linux noticed Mach-O Universal Binary not recognized
184+
if not is_binary(filename):
185+
return False, None
186+
182187
# otherwise use python implementation of file
183188
elif not is_binary(filename):
184189
return False, None

test/assets/single-byte.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/assets/windows.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/test_executable.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Copyright (C) 2024 Iain Coulter
2+
# SPDX-License-Identifier: GPL-3.0-or-later
3+
4+
"""
5+
CVE-bin-tool version scanner file/is_binary tests
6+
"""
7+
8+
from os import remove
9+
from tempfile import NamedTemporaryFile
10+
11+
from cve_bin_tool.version_scanner import VersionScanner
12+
13+
14+
class TestFile:
15+
"""Tests the CVE Bin Tool file using 'file' command."""
16+
17+
def _write_magic_signature(self, f, signature):
18+
"""Helper function to write a magic signature to a file."""
19+
f.write(signature)
20+
f.seek(0)
21+
22+
def _check_file_type(self, file_type, signature, expected_result):
23+
"""Helper function to check if a file is binary based on its type."""
24+
with NamedTemporaryFile("w+b", suffix=file_type, delete=False) as f:
25+
self._write_magic_signature(f, signature)
26+
scanner = VersionScanner()
27+
result, *_ = scanner.is_executable(f.name)
28+
assert (
29+
result == expected_result
30+
), f"Expected {expected_result}, but got {result}"
31+
remove(f.name)
32+
33+
def _check_test(self, type):
34+
"""Helper function to parse a binary file and check whether
35+
the given string is in the parsed result"""
36+
file_signatures = {
37+
"elf": (b"\x7f\x45\x4c\x46\x02\x01\x01\x03\n", True, ".out"),
38+
"mach_o_32": (b"\xFE\xED\xFA\xCE\x00\x00\x00\x00", True, ".out"),
39+
"mach_o_64": (b"\xFE\xED\xFA\xCF\x00\x00\x00\x00", True, ".out"),
40+
"mach_o_universal": (b"\xCA\xFE\xBA\xBE\x00\x00\x00\x00", True, ".out"),
41+
"ios_arm": (b"\xCF\xFA\xED\xFE\x00\x00\x00\x00", True, ".out"),
42+
"wasm": (b"yoyo\x00\x61\x73\x6D\x01\x00\x00\x00", True, ".out"),
43+
"c": (b"#include <stdio.h>", False, ".c"),
44+
"single_byte": (b"1", False, ".txt"),
45+
"windows": (b"MZ\x90\x00", True, ".dll"),
46+
}
47+
signature, expected_result, file_type = file_signatures.get(
48+
type, (b"some other data\n", False, ".txt")
49+
)
50+
self._check_file_type(file_type, signature, expected_result)
51+
52+
def test_binary_elf_file(self):
53+
"""file *.out"""
54+
self._check_test("elf")
55+
56+
def test_binary_mach_o_32_file(self):
57+
"""file *.out"""
58+
self._check_test("mach_o_32")
59+
60+
def test_binary_mach_o_64_file(self):
61+
"""file *.out"""
62+
self._check_test("mach_o_64")
63+
64+
def test_binary_mach_o_universal_file(self):
65+
"""file *.out"""
66+
self._check_test("mach_o_universal")
67+
68+
def test_binary_ios_arm_file(self):
69+
"""file *.out"""
70+
self._check_test("ios_arm")
71+
72+
def test_binary_wasm_file(self):
73+
"""file *.out"""
74+
self._check_test("wasm")
75+
76+
def test_source_file(self):
77+
"""file *.c"""
78+
self._check_test("c")
79+
80+
def test_single_byte_file(self):
81+
"""file single-byte"""
82+
self._check_test("single_byte")
83+
84+
def test_windows(self):
85+
"""file *.txt"""
86+
self._check_test("windows")
87+
88+
def test_other_file(self):
89+
"""file *.txt"""
90+
self._check_test("other")

test/test_file.py

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,51 +4,94 @@
44
"""
55
CVE-bin-tool file tests
66
"""
7-
from pathlib import Path
87

98
import pytest
109

1110
from cve_bin_tool.async_utils import NamedTemporaryFile, aio_rmfile
1211
from cve_bin_tool.file import aio_is_binary
1312

14-
ASSETS_PATH = Path(__file__).parent.resolve() / "assets"
15-
1613

1714
class TestFile:
1815
"""Tests the CVE Bin Tool file binary checker."""
1916

17+
async def _write_magic_signature(self, f, signature):
18+
"""Helper function to write a magic signature to a file."""
19+
await f.write(signature)
20+
await f.seek(0)
21+
22+
async def _check_file_type(self, file_type, signature, expected_result):
23+
"""Helper function to check if a file is binary based on its type."""
24+
async with NamedTemporaryFile("w+b", suffix=file_type, delete=False) as f:
25+
await self._write_magic_signature(f, signature)
26+
assert await aio_is_binary(f.name) == expected_result
27+
await aio_rmfile(f.name)
28+
2029
@pytest.mark.asyncio
21-
async def _check_test(self, file_type):
30+
async def _check_test(self, type):
2231
"""Helper function to parse a binary file and check whether
2332
the given string is in the parsed result"""
24-
async with NamedTemporaryFile("w+b", suffix=file_type, delete=False) as f:
25-
if file_type == "out":
26-
# write magic signature
27-
await f.write(b"\x7f\x45\x4c\x46\x02\x01\x01\x03\n")
28-
await f.seek(0)
29-
assert await aio_is_binary(f.name)
30-
else:
31-
await f.write(b"some other data\n")
32-
await f.seek(0)
33-
assert not await aio_is_binary(f.name)
34-
await aio_rmfile(f.name)
33+
file_signatures = {
34+
"elf": (b"\x7f\x45\x4c\x46\x02\x01\x01\x03\n", True, ".out"),
35+
"mach_o_32": (b"\xFE\xED\xFA\xCE\x00\x00\x00\x00", True, ".out"),
36+
"mach_o_64": (b"\xFE\xED\xFA\xCF\x00\x00\x00\x00", True, ".out"),
37+
"mach_o_universal": (b"\xCA\xFE\xBA\xBE\x00\x00\x00\x00", True, ".out"),
38+
"ios_arm": (b"\xCF\xFA\xED\xFE\x00\x00\x00\x00", True, ".out"),
39+
"wasm": (b"\x00\x61\x73\x6D\x01\x00\x00\x00", True, ".out"),
40+
"c": (b"#include <stdio.h>", False, ".c"),
41+
"single_byte": (b"1", False, ".txt"),
42+
"windows": (b"MZ", True, ".txt"),
43+
}
44+
signature, expected_result, file_type = file_signatures.get(
45+
type, (b"some other data\n", False, ".txt")
46+
)
47+
await self._check_file_type(file_type, signature, expected_result)
48+
49+
@pytest.mark.asyncio
50+
async def test_binary_elf_file(self):
51+
"""file *.out"""
52+
await self._check_test("elf")
53+
54+
@pytest.mark.asyncio
55+
async def test_binary_mach_o_32_file(self):
56+
"""file *.out"""
57+
await self._check_test("mach_o_32")
3558

3659
@pytest.mark.asyncio
37-
async def test_binary_out_file(self):
60+
async def test_binary_mach_o_64_file(self):
3861
"""file *.out"""
39-
await self._check_test("out")
62+
await self._check_test("mach_o_64")
63+
64+
@pytest.mark.asyncio
65+
async def test_binary_mach_o_universal_file(self):
66+
"""file *.out"""
67+
await self._check_test("mach_o_universal")
68+
69+
@pytest.mark.asyncio
70+
async def test_binary_ios_arm_file(self):
71+
"""file *.out"""
72+
await self._check_test("ios_arm")
73+
74+
@pytest.mark.asyncio
75+
async def test_binary_wasm_file(self):
76+
"""file *.out"""
77+
await self._check_test("wasm")
4078

4179
@pytest.mark.asyncio
4280
async def test_source_file(self):
4381
"""file *.c"""
4482
await self._check_test("c")
4583

84+
@pytest.mark.asyncio
85+
async def test_text_file(self):
86+
"""file *.txt"""
87+
await self._check_test("other")
88+
4689
@pytest.mark.asyncio
4790
async def test_single_byte_file(self):
4891
"""file single-byte"""
49-
assert not await aio_is_binary(str(ASSETS_PATH / "single-byte.txt"))
92+
await self._check_test("single_byte")
5093

5194
@pytest.mark.asyncio
5295
async def test_windows(self):
53-
"""file single-byte"""
54-
assert await aio_is_binary(str(ASSETS_PATH / "windows.txt"))
96+
"""file *.txt"""
97+
await self._check_test("windows")

0 commit comments

Comments
 (0)