mitogen: Raise TypeError on `mitogen.utils.cast(custom_str)` failures
If casting a string fails then raise a TypeError. This is potentially an API breaking change; chosen as the lesser evil vs. allowing silent errors. `cast()` relies on `bytes(obj)` & `str(obj)` returning the respective supertype. That's no longer the case for `AnsibleUnsafeBytes` & `AnsibleUnsafeText`; since fixes/mitigations for CVE-2023-5764. fixes #1046, refs #977 See also - https://github.com/advisories/GHSA-7j69-qfc3-2fq9 - https://github.com/ansible/ansible/pull/82293
This commit is contained in:
parent
dfc3c7d516
commit
d7979c3597
|
@ -21,6 +21,9 @@ Unreleased
|
|||
----------
|
||||
|
||||
* :gh:issue:`974` Support Ansible 7
|
||||
* :gh:issue:`1046` Raise :py:exc:`TypeError` in :func:`<mitogen.util.cast()>`
|
||||
when casting a string subtype to `bytes()` or `str()` fails. This is
|
||||
potentially an API breaking change. Failures previously passed silently.
|
||||
|
||||
|
||||
v0.3.5 (2024-03-17)
|
||||
|
|
|
@ -190,10 +190,13 @@ PASSTHROUGH = (
|
|||
|
||||
def cast(obj):
|
||||
"""
|
||||
Return obj (or a copy) with subtypes of builtins cast to their supertype.
|
||||
Subtypes of those in :data:`PASSTHROUGH` are not modified.
|
||||
|
||||
Many tools love to subclass built-in types in order to implement useful
|
||||
functionality, such as annotating the safety of a Unicode string, or adding
|
||||
additional methods to a dict. However, cPickle loves to preserve those
|
||||
subtypes during serialization, resulting in CallError during :meth:`call
|
||||
additional methods to a dict. However :py:mod:`pickle` serializes these
|
||||
exactly, leading to :exc:`mitogen.CallError` during :meth:`Context.call
|
||||
<mitogen.parent.Context.call>` in the target when it tries to deserialize
|
||||
the data.
|
||||
|
||||
|
@ -201,6 +204,9 @@ def cast(obj):
|
|||
custom sub-types removed. The functionality is not default since the
|
||||
resulting walk may be computationally expensive given a large enough graph.
|
||||
|
||||
Raises :py:exc:`TypeError` if an unknown subtype is encountered, or
|
||||
casting does not return the desired supertype.
|
||||
|
||||
See :ref:`serialization-rules` for a list of supported types.
|
||||
|
||||
:param obj:
|
||||
|
@ -215,8 +221,16 @@ def cast(obj):
|
|||
if isinstance(obj, PASSTHROUGH):
|
||||
return obj
|
||||
if isinstance(obj, mitogen.core.UnicodeType):
|
||||
return mitogen.core.UnicodeType(obj)
|
||||
return _cast(obj, mitogen.core.UnicodeType)
|
||||
if isinstance(obj, mitogen.core.BytesType):
|
||||
return mitogen.core.BytesType(obj)
|
||||
return _cast(obj, mitogen.core.BytesType)
|
||||
|
||||
raise TypeError("Cannot serialize: %r: %r" % (type(obj), obj))
|
||||
|
||||
|
||||
def _cast(obj, desired_type):
|
||||
result = desired_type(obj)
|
||||
if type(result) is not desired_type:
|
||||
raise TypeError("Cast of %r to %r failed, got %r"
|
||||
% (type(obj), desired_type, type(result)))
|
||||
return result
|
||||
|
|
|
@ -44,6 +44,44 @@ class Unicode(mitogen.core.UnicodeType): pass
|
|||
class Bytes(mitogen.core.BytesType): pass
|
||||
|
||||
|
||||
class StubbornBytes(mitogen.core.BytesType):
|
||||
"""
|
||||
A binary string type that persists through `bytes(...)`.
|
||||
|
||||
Stand-in for `AnsibleUnsafeBytes()` in Ansible 7-9 (core 2.14-2.16), after
|
||||
fixes/mitigations for CVE-2023-5764.
|
||||
"""
|
||||
if mitogen.core.PY3:
|
||||
def __bytes__(self): return self
|
||||
def __str__(self): return self.decode()
|
||||
else:
|
||||
def __str__(self): return self
|
||||
def __unicode__(self): return self.decode()
|
||||
|
||||
def decode(self, encoding='utf-8', errors='strict'):
|
||||
s = super(StubbornBytes).encode(encoding=encoding, errors=errors)
|
||||
return StubbornText(s)
|
||||
|
||||
|
||||
class StubbornText(mitogen.core.UnicodeType):
|
||||
"""
|
||||
A text string type that persists through `unicode(...)` or `str(...)`.
|
||||
|
||||
Stand-in for `AnsibleUnsafeText()` in Ansible 7-9 (core 2.14-2.16), after
|
||||
following fixes/mitigations for CVE-2023-5764.
|
||||
"""
|
||||
if mitogen.core.PY3:
|
||||
def __bytes__(self): return self.encode()
|
||||
def __str__(self): return self
|
||||
else:
|
||||
def __str__(self): return self.encode()
|
||||
def __unicode__(self): return self
|
||||
|
||||
def encode(self, encoding='utf-8', errors='strict'):
|
||||
s = super(StubbornText).encode(encoding=encoding, errors=errors)
|
||||
return StubbornBytes(s)
|
||||
|
||||
|
||||
class CastTest(testlib.TestCase):
|
||||
def test_dict(self):
|
||||
self.assertEqual(type(mitogen.utils.cast({})), dict)
|
||||
|
@ -91,6 +129,15 @@ class CastTest(testlib.TestCase):
|
|||
self.assertEqual(type(mitogen.utils.cast(b(''))), mitogen.core.BytesType)
|
||||
self.assertEqual(type(mitogen.utils.cast(Bytes())), mitogen.core.BytesType)
|
||||
|
||||
def test_stubborn_types_raise(self):
|
||||
stubborn_bytes = StubbornBytes(b('abc'))
|
||||
self.assertIs(stubborn_bytes, mitogen.core.BytesType(stubborn_bytes))
|
||||
self.assertRaises(TypeError, mitogen.utils.cast, stubborn_bytes)
|
||||
|
||||
stubborn_text = StubbornText(u'abc')
|
||||
self.assertIs(stubborn_text, mitogen.core.UnicodeType(stubborn_text))
|
||||
self.assertRaises(TypeError, mitogen.utils.cast, stubborn_text)
|
||||
|
||||
def test_unknown(self):
|
||||
self.assertRaises(TypeError, mitogen.utils.cast, set())
|
||||
self.assertRaises(TypeError, mitogen.utils.cast, 4j)
|
||||
|
|
Loading…
Reference in New Issue