Skip to content

Commit

Permalink
Merge pull request jsonpickle#546 from davvid/invalid-id-data
Browse files Browse the repository at this point in the history
* davvid/invalid-id-data:
  unpickler: increase robustness to invalid py/repr data
  unpickler: increase robustness to invalid py/id data
  • Loading branch information
davvid committed Dec 26, 2024
2 parents 01d4e12 + 2113322 commit 7db7dd4
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 4 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Upcoming
========
* The unpickler is now more resilient to malformed "py/id" and "py/repr" data.
(+546)

v4.0.1
======
* The unpickler is now more resilient to malformed "py/reduce", "py/set",
Expand Down
22 changes: 18 additions & 4 deletions jsonpickle/unpickler.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,10 @@ def __init__(self, objs, index):
self._objs = objs

def get(self):
return self._objs[self._index]
try:
return self._objs[self._index]
except IndexError:
return None


def _obj_setattr(obj, attr, proxy):
Expand Down Expand Up @@ -310,8 +313,15 @@ def _loadmodule(module_str):
"""
module, identifier = module_str.split('/')
result = __import__(module)
for name in identifier.split('.')[1:]:
try:
result = __import__(module)
except ImportError:
return None
identifier_parts = identifier.split('.')
first_identifier = identifier_parts[0]
if first_identifier != module and not module.startswith(f'{first_identifier}.'):
return None
for name in identifier_parts[1:]:
try:
result = getattr(result, name)
except AttributeError:
Expand Down Expand Up @@ -591,6 +601,8 @@ def _restore_id(self, obj):
return self._objs[idx]
except IndexError:
return _IDProxy(self._objs, idx)
except TypeError:
return None

def _restore_type(self, obj):
typeref = loadclass(obj[tags.TYPE], classes=self._classes)
Expand Down Expand Up @@ -917,7 +929,9 @@ def _restore_dict(self, obj):
else:
str_k = k
self._namestack.append(str_k)
data[k] = self._restore(v)
data[k] = result = self._restore(v)
if isinstance(result, _Proxy):
self._proxies.append((data, k, result, _obj_setvalue))
self._namestack.pop()
return data

Expand Down
22 changes: 22 additions & 0 deletions tests/jsonpickle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,13 @@ def test_reduce_with_invalid_data(value, unpickler):
assert result == []


@pytest.mark.parametrize('value', ['', 'x', 1, True, [], {}])
def test_restore_id_with_invalid_data(value, unpickler):
"""Invalid serialized ID data results in None"""
result = unpickler.restore({'ref': {tags.ID: value}})
assert result['ref'] is None


def test_dict(pickler, unpickler):
"""Our custom keys are preserved when user dicts contain them"""
dict_a = {'key1': 1.0, 'key2': 20, 'key3': 'thirty', tags.JSON_KEY + '6': 6}
Expand Down Expand Up @@ -606,6 +613,21 @@ def test_restore_legacy_builtins():
assert cls is int


@pytest.mark.parametrize(
'value,expect',
[
('module_does_not_exist/ignored', None),
('builtins/int', None),
('builtins/invalid.int', None),
('builtins/builtinsx.int', None),
],
)
def test_restore_invalid_repr(value, expect, unpickler):
"""Test restoring invalid repr tags"""
result = unpickler.restore({tags.REPR: value})
assert result is expect


def test_unpickler_on_missing():
"""Emit warnings when decoding objects whose classes are missing"""
encoded = jsonpickle.encode(Outer.Middle.Inner())
Expand Down

0 comments on commit 7db7dd4

Please sign in to comment.