From 9b68143e0b801a36ffbb38629dfeba07da481a6c Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Thu, 12 Dec 2024 20:31:23 -0800 Subject: [PATCH] jsonpickle: guard against invalid b85 and b64 data --- CHANGES.rst | 2 +- jsonpickle/unpickler.py | 10 ++++++++-- jsonpickle/util.py | 11 +++++++++-- tests/jsonpickle_test.py | 16 ++++++++++++++++ 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f9a43ab..81b125a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,7 @@ Upcoming ======== * The unpickler is now more resilient to malformed "py/reduce", "py/set", - "py/tuple", and "py/iterator" input data. (+544) + "py/tuple", "py/b64", "py/b85", and "py/iterator" input data. (+544) (+545) * The test suite was updated to leverage more pytest features. * The ``jsonpickle.compat`` module is no longer used. It is still provided for backwards compatibility but it may be removed in a future version. diff --git a/jsonpickle/unpickler.py b/jsonpickle/unpickler.py index ea74a20..3ea9ade 100644 --- a/jsonpickle/unpickler.py +++ b/jsonpickle/unpickler.py @@ -433,10 +433,16 @@ def register_classes(self, classes): self._classes[util.importable_name(classes)] = classes def _restore_base64(self, obj): - return util.b64decode(obj[tags.B64].encode('utf-8')) + try: + return util.b64decode(obj[tags.B64].encode('utf-8')) + except AttributeError: + return b'' def _restore_base85(self, obj): - return util.b85decode(obj[tags.B85].encode('utf-8')) + try: + return util.b85decode(obj[tags.B85].encode('utf-8')) + except AttributeError: + return b'' def _refname(self): """Calculates the name of the current location in the JSON stack. diff --git a/jsonpickle/util.py b/jsonpickle/util.py index 5e5699e..18d951b 100644 --- a/jsonpickle/util.py +++ b/jsonpickle/util.py @@ -9,6 +9,7 @@ determining the type of an object. """ import base64 +import binascii import collections import inspect import io @@ -557,7 +558,10 @@ def b64decode(payload): """ Decode payload - must be ascii text. """ - return base64.b64decode(payload) + try: + return base64.b64decode(payload) + except (TypeError, binascii.Error): + return b'' def b85encode(data): @@ -571,7 +575,10 @@ def b85decode(payload): """ Decode payload - must be ascii text. """ - return base64.b85decode(payload) + try: + return base64.b85decode(payload) + except (TypeError, ValueError): + return b'' def itemgetter(obj, getter=operator.itemgetter(0)): diff --git a/tests/jsonpickle_test.py b/tests/jsonpickle_test.py index befc536..d438827 100644 --- a/tests/jsonpickle_test.py +++ b/tests/jsonpickle_test.py @@ -217,6 +217,14 @@ def test_decode_base85(unpickler): assert unpickler.restore(pickled) == expected +@pytest.mark.parametrize('value', ['', '/', 1, True, False, None, [], {}]) +def test_decode_invalid_b85(value, unpickler): + """Invalid base85 data restores to an empty string""" + expected = b'' + pickled = {tags.B85: value} + assert unpickler.restore(pickled) == expected + + def test_base85_still_handles_base64(unpickler): """base64 must be restored even though base85 is the default""" expected = 'Pÿthöñ 3!'.encode() @@ -224,6 +232,14 @@ def test_base85_still_handles_base64(unpickler): assert unpickler.restore(pickled) == expected +@pytest.mark.parametrize('value', ['', 'x', '!', 0, 1, True, False, None, [], {}]) +def test_decode_invalid_b64(value, unpickler): + """Invalid base85 data restores to an empty string""" + expected = b'' + pickled = {tags.B64: value} + assert unpickler.restore(pickled) == expected + + def test_string(pickler, unpickler): """Strings must roundtrip""" assert pickler.flatten('a string') == 'a string'