Skip to content

Commit e4e8504

Browse files
committed
Properly handle integer under/overflow in libmagic
1 parent dfb393b commit e4e8504

File tree

3 files changed

+55
-27
lines changed

3 files changed

+55
-27
lines changed

polyfile/arithmetic.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from typing import Callable, Dict, Tuple
2+
3+
import cint
4+
5+
6+
CStyleInt = cint.Cint
7+
8+
9+
INT_TYPES: Dict[Tuple[int, bool], Callable[[int], CStyleInt]] = {
10+
(1, False): cint.U8,
11+
(1, True): cint.I8,
12+
(2, False): cint.U16,
13+
(2, True): cint.I16,
14+
(4, False): cint.U32,
15+
(4, True): cint.I32,
16+
(8, False): cint.U64,
17+
(8, True): cint.I64
18+
}
19+
20+
21+
def make_c_style_int(value: int, num_bytes: int, signed: bool):
22+
if (num_bytes, signed) not in INT_TYPES:
23+
raise NotImplementedError(f"{num_bytes*8}-bit {['un',''][signed]}signed integers are not yet supported")
24+
return INT_TYPES[(num_bytes, signed)](value)
25+
26+
27+
setattr(CStyleInt, "new", make_c_style_int)

polyfile/magic.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
)
2626
from uuid import UUID
2727

28+
from .arithmetic import CStyleInt, make_c_style_int
2829
from .iterators import LazyIterableSet
2930
from .logger import getStatusLogger, TRACE
3031

32+
3133
if sys.version_info < (3, 9):
3234
from typing import Pattern
3335
else:
@@ -1419,22 +1421,31 @@ class NumericOperator(Enum):
14191421
ALL_BITS_CLEAR = ("^", lambda a, b: not (a & b)) # value from the file (a) must have clear all bits set in b
14201422
NOT = ("!", lambda a, b: not (a == b))
14211423

1422-
def __init__(self, symbol: str, test: Union[Callable[[int, int], bool], Callable[[float, float], bool]]):
1424+
def __init__(self, symbol: str, test: Union[
1425+
Callable[[int, int], bool],
1426+
Callable[[float, float], bool],
1427+
Callable[[CStyleInt, CStyleInt], bool]
1428+
]):
14231429
self.symbol: str = symbol
1424-
self.test: Union[Callable[[int, int], bool], Callable[[float, float], bool]] = test
1430+
self.test: Union[
1431+
Callable[[int, int], bool], Callable[[float, float], bool], Callable[[CStyleInt, CStyleInt], bool]
1432+
] = test
14251433
NUMERIC_OPERATORS_BY_SYMBOL[symbol] = self
14261434

14271435
@staticmethod
14281436
def get(symbol: str) -> "NumericOperator":
14291437
return NUMERIC_OPERATORS_BY_SYMBOL[symbol]
14301438

1439+
def __str__(self):
1440+
return self.symbol
1441+
14311442

14321443
class NumericValue(Generic[T]):
14331444
def __init__(self, value: T, operator: NumericOperator = NumericOperator.EQUALS):
14341445
self.value: T = value
14351446
self.operator: NumericOperator = operator
14361447

1437-
def test(self, to_match: T, unsigned: bool, num_bytes: int, preprocess: Callable[[int], int] = lambda x: x) -> bool:
1448+
def test(self, to_match: T, unsigned: bool, num_bytes: int, preprocess: Callable[[T], T] = lambda x: x) -> bool:
14381449
return self.operator.test(preprocess(to_match), self.value)
14391450

14401451
@staticmethod
@@ -1450,6 +1461,9 @@ def parse(value: str, num_bytes: int) -> "NumericValue":
14501461
pass
14511462
raise ValueError(f"Could not parse numeric type {value!r}")
14521463

1464+
def __str__(self):
1465+
return f"{self.operator}{self.value!s}"
1466+
14531467

14541468
class NumericWildcard(NumericValue):
14551469
def __init__(self):
@@ -1460,30 +1474,16 @@ def test(self, to_match, unsigned, num_bytes, preprocess: Callable[[int], int] =
14601474

14611475

14621476
class IntegerValue(NumericValue[int]):
1463-
@staticmethod
1464-
def normalize_signedness(value: int, unsigned: bool, num_bytes: int) -> int:
1465-
bits = 8 * num_bytes
1466-
if unsigned:
1467-
max_value = (1 << bits) - 1
1468-
min_value = 0
1469-
if value < 0:
1470-
# convert the value to a bit-equivalent unsigned value
1471-
value += 2**bits
1472-
else:
1473-
max_value = (1 << bits) >> 1
1474-
min_value = ~max_value
1475-
if value > max_value:
1476-
# convert the value to a bit-equivalent signed value
1477-
value -= 2 ** bits
1478-
if not (min_value <= value <= max_value):
1479-
raise ValueError(f"Invalid integer constant {value} for comparing to a "
1480-
f"{['signed', 'n unsigned'][unsigned]} {num_bytes}-byte integer")
1481-
return value
1482-
1483-
def test(self, to_match: int, unsigned: bool, num_bytes: int, preprocess: Callable[[int], int] = lambda x: x) -> bool:
1484-
to_test = IntegerValue.normalize_signedness(self.value, unsigned, num_bytes)
1485-
to_match = IntegerValue.normalize_signedness(preprocess(to_match), unsigned, num_bytes)
1486-
return self.operator.test(to_match, to_test)
1477+
def test(
1478+
self,
1479+
to_match: int,
1480+
unsigned: bool,
1481+
num_bytes: int,
1482+
preprocess: Callable[[CStyleInt], CStyleInt] = lambda x: x
1483+
) -> bool:
1484+
to_test = make_c_style_int(value=self.value, num_bytes=num_bytes, signed=not unsigned)
1485+
to_match = make_c_style_int(value=to_match, num_bytes=num_bytes, signed=not unsigned)
1486+
return self.operator.test(preprocess(to_match), to_test)
14871487

14881488
@staticmethod
14891489
def parse(value: Union[str, bytes], num_bytes: int) -> "IntegerValue":

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ def update(self, n: int):
122122
python_requires='>=3.6',
123123
install_requires=[
124124
"dataclasses;python_version<'3.7'", # dataclasses were only added in Python 3.7
125+
'cint',
125126
'graphviz',
126127
'intervaltree',
127128
'jinja2',

0 commit comments

Comments
 (0)