diff --git a/README.md b/README.md index 178cb609..48a01d80 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ See docs/clvm.org or https://chialisp.com/ for more info. Testing ======= + $ python3 -m venv venv + $ . ./venv/bin/activate $ pip install -e '.[dev]' - $ py.test tests + $ pytest tests diff --git a/clvm/more_ops.py b/clvm/more_ops.py index fd68d07b..fff5a303 100644 --- a/clvm/more_ops.py +++ b/clvm/more_ops.py @@ -242,7 +242,9 @@ def op_point_add(items: _T_SExp) -> typing.Tuple[int, _T_SExp]: if _.pair: raise EvalError("point_add on list", _) try: - p += G1Element.from_bytes(_.as_atom()) + atom = _.as_atom() + assert atom is not None + p += G1Element.from_bytes(atom) cost += POINT_ADD_COST_PER_ARG except Exception as ex: raise EvalError("point_add expects blob, got %s: %s" % (_, ex), items) diff --git a/clvm/serialize.py b/clvm/serialize.py index 9d0eb265..3d4a4bae 100644 --- a/clvm/serialize.py +++ b/clvm/serialize.py @@ -26,6 +26,8 @@ BACK_REFERENCE = 0xFE CONS_BOX_MARKER = 0xFF +MAX_SAFE_BYTES = 2_000_000 + T = typing.TypeVar("T") _T_CLVMStorage = typing.TypeVar("_T_CLVMStorage", bound=CLVMStorage) @@ -153,9 +155,12 @@ def atom_to_byte_iterator(as_atom: bytes) -> typing.Iterator[bytes]: def sexp_to_stream( - sexp: CLVMStorage, f: typing.BinaryIO, *, allow_backrefs: bool = False + sexp: CLVMStorage, f: typing.BinaryIO, *, allow_backrefs: bool = False, max_size: int = MAX_SAFE_BYTES ) -> None: for b in sexp_to_byte_iterator(sexp, allow_backrefs=allow_backrefs): + max_size -= len(b) + if max_size < 0: + raise ValueError("SExp exceeds maximum size") f.write(b) diff --git a/tests/serialize_test.py b/tests/serialize_test.py index 0f2e8521..06ccee8f 100644 --- a/tests/serialize_test.py +++ b/tests/serialize_test.py @@ -3,13 +3,17 @@ import unittest from typing import Optional +import pytest + from clvm import to_sexp_f from clvm.SExp import CastableType, SExp from clvm.serialize import ( + MAX_SAFE_BYTES, _atom_from_stream, sexp_from_stream, sexp_buffer_from_stream, atom_to_byte_iterator, + sexp_to_stream, ) @@ -54,6 +58,7 @@ class SerializeTest(unittest.TestCase): def check_serde(self, s: CastableType) -> bytes: v = to_sexp_f(s) b = v.as_bin() + f = io.BytesIO() v1 = sexp_from_stream(io.BytesIO(b), to_sexp_f) if v != v1: print("%s: %d %r %s" % (v, len(b), b, v1)) @@ -61,6 +66,9 @@ def check_serde(self, s: CastableType) -> bytes: b = v.as_bin() v1 = sexp_from_stream(io.BytesIO(b), to_sexp_f) self.assertEqual(v, v1) + sexp_to_stream(v1, f) + length = len(f.getvalue()) + assert f.getbuffer() == b # this copies the bytes that represent a single s-expression, just to # know where the message ends. It doesn't build a python representaion # of it @@ -85,6 +93,11 @@ def check_serde(self, s: CastableType) -> bytes: self.assertEqual(v2, s) b3 = v2.as_bin() self.assertEqual(b, b3) + with pytest.raises(ValueError, match="SExp exceeds maximum size"): + f = io.BytesIO() + sexp_to_stream(v1, f, max_size=length-1) + f = io.BytesIO() + sexp_to_stream(v1, f, max_size=length) return b2 def test_zero(self) -> None: @@ -134,10 +147,18 @@ def test_very_long_blobs(self) -> None: count = size // len(TEXT) text = TEXT * count assert len(text) < size - self.check_serde(text) + if len(text) >= MAX_SAFE_BYTES: + with pytest.raises(ValueError, match="SExp exceeds maximum size"): + self.check_serde(text) + else: + self.check_serde(text) text = TEXT * (count + 1) assert len(text) > size - self.check_serde(text) + if len(text) >= MAX_SAFE_BYTES: + with pytest.raises(ValueError, match="SExp exceeds maximum size"): + self.check_serde(text) + else: + self.check_serde(text) def test_very_deep_tree(self) -> None: blob = b"a" @@ -210,17 +231,16 @@ def make_bomb(depth: int) -> SExp: self.assertEqual(len(b10_2), 75) bomb_20 = make_bomb(20) - b20_1 = bomb_20.as_bin(allow_backrefs=False) + with pytest.raises(ValueError, match="SExp exceeds maximum size"): + bomb_20.as_bin(allow_backrefs=False) b20_2 = bomb_20.as_bin(allow_backrefs=True) - self.assertEqual(len(b20_1), 48234495) self.assertEqual(len(b20_2), 105) bomb_30 = make_bomb(30) - # do not uncomment the next line unless you want to run out of memory - # b30_1 = bomb_30.as_bin(allow_backrefs=False) + with pytest.raises(ValueError, match="SExp exceeds maximum size"): + bomb_30.as_bin(allow_backrefs=False) b30_2 = bomb_30.as_bin(allow_backrefs=True) - # self.assertEqual(len(b30_1), 1) self.assertEqual(len(b30_2), 135) def test_specific_tree(self) -> None: