diff --git a/deluge/bencode.py b/deluge/bencode.py index df8cc85c28..c42ba85194 100644 --- a/deluge/bencode.py +++ b/deluge/bencode.py @@ -22,8 +22,11 @@ class BTFailure(Exception): LIST_DELIM = b'l' BYTE_SEP = b':' +# The max depth permitted for recursive functions. Depths above this will throw a BTFailure. +MAX_DEPTH: int = 100 -def decode_int(x, f): + +def decode_int(x, f, depth=0): f += 1 newf = x.index(END_DELIM, f) n = int(x[f:newf]) @@ -34,28 +37,36 @@ def decode_int(x, f): return (n, newf + 1) -def decode_string(x, f): +def decode_string(x, f, depth=0): colon = x.index(BYTE_SEP, f) n = int(x[f:colon]) + # The length must be numeric digits only + if not x[f:colon].isdigit(): + raise ValueError + n = int(x[f:colon]) if x[f : f + 1] == b'0' and colon != f + 1: raise ValueError colon += 1 return (x[colon : colon + n], colon + n) -def decode_list(x, f): +def decode_list(x, f, depth): + if depth > MAX_DEPTH: + raise BTFailure('Too much nesting in list') r, f = [], f + 1 while x[f : f + 1] != END_DELIM: - v, f = decode_func[x[f : f + 1]](x, f) + v, f = decode_func[x[f : f + 1]](x, f, depth + 1) r.append(v) return (r, f + 1) -def decode_dict(x, f): +def decode_dict(x, f, depth): + if depth > MAX_DEPTH: + raise BTFailure('Too much nesting in dict') r, f = {}, f + 1 while x[f : f + 1] != END_DELIM: - k, f = decode_string(x, f) - r[k], f = decode_func[x[f : f + 1]](x, f) + k, f = decode_string(x, f, depth + 1) + r[k], f = decode_func[x[f : f + 1]](x, f, depth + 1) return (r, f + 1) @@ -77,7 +88,7 @@ def decode_dict(x, f): def bdecode(x): try: - r, __ = decode_func[x[0:1]](x, 0) + r, __ = decode_func[x[0:1]](x, 0, 0) except (LookupError, TypeError, ValueError): raise BTFailure('Not a valid bencoded string') else: @@ -111,14 +122,18 @@ def encode_bytes(x, r): r.extend((str(len(x)).encode('utf8'), BYTE_SEP, x)) -def encode_list(x, r): +def encode_list(x, r, depth): + if depth > MAX_DEPTH: + raise BTFailure('Too much nesting in list') r.append(LIST_DELIM) for i in x: - encode_func[type(i)](i, r) + encode_func[type(i)](i, r, depth + 1) r.append(END_DELIM) -def encode_dict(x, r): +def encode_dict(x, r, depth): + if depth > MAX_DEPTH: + raise BTFailure('Too much nesting in dict') r.append(DICT_DELIM) for k, v in sorted(x.items()): try: @@ -126,22 +141,22 @@ def encode_dict(x, r): except AttributeError: pass r.extend((str(len(k)).encode('utf8'), BYTE_SEP, k)) - encode_func[type(v)](v, r) + encode_func[type(v)](v, r, depth + 1) r.append(END_DELIM) encode_func = {} -encode_func[Bencached] = encode_bencached -encode_func[int] = encode_int +encode_func[Bencached] = lambda x, r, d: encode_bencached(x, r) +encode_func[int] = lambda x, r, d: encode_int(x, r) encode_func[list] = encode_list encode_func[tuple] = encode_list encode_func[dict] = encode_dict -encode_func[bool] = encode_bool -encode_func[str] = encode_string -encode_func[bytes] = encode_bytes +encode_func[bool] = lambda x, r, d: encode_bool(x, r) +encode_func[str] = lambda x, r, d: encode_string(x, r) +encode_func[bytes] = lambda x, r, d: encode_bytes(x, r) def bencode(x): r = [] - encode_func[type(x)](x, r) + encode_func[type(x)](x, r, 0) return b''.join(r) diff --git a/deluge/tests/test_bencode.py b/deluge/tests/test_bencode.py index a4a76818ff..af8040d436 100644 --- a/deluge/tests/test_bencode.py +++ b/deluge/tests/test_bencode.py @@ -30,3 +30,28 @@ def test_bdecode(self): bencode.bdecode(b'dEf') with pytest.raises(bencode.BTFailure): bencode.bdecode({'dEf': 123}) + + def test_bdecode_negative(self): + with pytest.raises(bencode.BTFailure): + bencode.bdecode(b'd-4:e') + with pytest.raises(bencode.BTFailure): + bencode.bdecode(b'd-0:i1ee') + with pytest.raises(bencode.BTFailure): + bencode.bdecode(b'd-1:i1ee') + with pytest.raises(bencode.BTFailure): + bencode.bdecode(b' 3:abc') + with pytest.raises(bencode.BTFailure): + bencode.bdecode(b' +3:abc') + + def test_bencode_recursion_decode(self): + nesting_level = 200 + data = b'l' * nesting_level + b'i1e' + b'e' * nesting_level + with pytest.raises(bencode.BTFailure): + bencode.bdecode(data) + + def test_bencode_recursion_encode(self): + obj = 'a' + for _ in range(200): + obj = [obj] + with pytest.raises(bencode.BTFailure): + bencode.bencode(obj)