From fce1bb28b38f5cf8a91bd0fb70e2ce6b3254f552 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ingy=20d=C3=B6t=20Net?= <ingy@ingy.net>
Date: Sun, 17 Feb 2019 19:22:58 -0800
Subject: [PATCH 1/5] Deprecate/warn usage of yaml.load(input)

Original commit: 0cedb2a0697b2bc49e4f3841b8d4590b6b15657e

The `load` and `load_all` methods will issue a warning when they are
called without the 'Loader=' parameter. The warning will point to a URL
that is always up to date with the latest information on the usage of
`load`.

There are several ways to stop the warning:

* Use `full_load(input)` - sugar for `yaml.load(input, FullLoader)`
  * FullLoader is the new safe but complete loader class
* Use `safe_load(input)` - sugar for `yaml.load(input, SafeLoader)`
  * Make sure your input YAML consists of the 'safe' subset
* Use `unsafe_load(input)` - sugar for `yaml.load(input, UnsafeLoader)`
  * Make sure your input YAML consists of the 'safe' subset
* Use `yaml.load(input, Loader=yaml.<loader>)`
  * Or shorter `yaml.load(input, yaml.<loader>)`
  * Where '<loader>' can be:
    * FullLoader - safe, complete Python YAML loading
    * SafeLoader - safe, partial Python YAML loading
    * UnsafeLoader - more explicit name for the old, unsafe 'Loader' class
* yaml.warnings({'YAMLLoadWarning': False})
  * Use this when you use third party modules that use `yaml.load(input)`
  * Only do this if input is trusted

The above `load()` expressions all have `load_all()` counterparts.

You can get the original unsafe behavior with:
* `yaml.unsafe_load(input)`
* `yaml.load(input, Loader=yaml.UnsafeLoader)`

In a future release, `yaml.load(input)` will raise an exception.

The new loader called FullLoader is almost entirely complete as
Loader/UnsafeLoader but it does it avoids all known code execution
paths. It is the preferred YAML loader, and the current default for
`yaml.load(input)` when you get the warning.

Here are some of the exploits that can be triggered with UnsafeLoader
but not with FullLoader:
```
python -c 'import os, yaml; yaml.full_load("!!python/object/new:os.system [echo EXPLOIT!]")'`
python -c 'import yaml; print yaml.full_load("!!python/object/new:abs [-5]")'
python -c 'import yaml; yaml.full_load("!!python/object/new:eval [exit(5)]")' ; echo $?
python -c 'import yaml; yaml.full_load("!!python/object/new:exit [5]")' ; echo $?
---
 lib/yaml/__init__.py           |  99 ++++++++++++++++++++++++--
 lib/yaml/constructor.py        | 126 +++++++++++++++++++++------------
 lib/yaml/cyaml.py              |  20 +++++-
 lib/yaml/loader.py             |  25 ++++++-
 tests/lib/test_errors.py       |   6 +-
 tests/lib/test_input_output.py |  10 +--
 tests/lib/test_recursive.py    |   2 +-
 7 files changed, 226 insertions(+), 62 deletions(-)

diff --git a/lib/yaml/__init__.py b/lib/yaml/__init__.py
index d414be8..20c5a1d 100644
--- a/lib/yaml/__init__.py
+++ b/lib/yaml/__init__.py
@@ -16,6 +16,45 @@ try:
 except ImportError:
     __with_libyaml__ = False
 
+
+#------------------------------------------------------------------------------
+# Warnings control
+#------------------------------------------------------------------------------
+
+# 'Global' warnings state:
+_warnings_enabled = {
+    'YAMLLoadWarning': True,
+}
+
+# Get or set global warnings' state
+def warnings(settings=None):
+    if settings is None:
+        return _warnings_enabled
+
+    if type(settings) is dict:
+        for key in settings:
+            if key in _warnings_enabled:
+                _warnings_enabled[key] = settings[key]
+
+# Warn when load() is called without Loader=...
+class YAMLLoadWarning(RuntimeWarning):
+    pass
+
+def load_warning(method):
+    if _warnings_enabled['YAMLLoadWarning'] is False:
+        return
+
+    import warnings
+
+    message = (
+        "calling yaml.%s() without Loader=... is deprecated, as the "
+        "default Loader is unsafe. Please read "
+        "https://msg.pyyaml.org/load for full details."
+    ) % method
+
+    warnings.warn(message, YAMLLoadWarning, stacklevel=3)
+
+#------------------------------------------------------------------------------
 def scan(stream, Loader=Loader):
     """
     Scan a YAML stream and produce scanning tokens.
@@ -61,22 +100,30 @@ def compose_all(stream, Loader=Loader):
     finally:
         loader.dispose()
 
-def load(stream, Loader=Loader):
+def load(stream, Loader=None):
     """
     Parse the first YAML document in a stream
     and produce the corresponding Python object.
     """
+    if Loader is None:
+        load_warning('load')
+        Loader = FullLoader
+
     loader = Loader(stream)
     try:
         return loader.get_single_data()
     finally:
         loader.dispose()
 
-def load_all(stream, Loader=Loader):
+def load_all(stream, Loader=None):
     """
     Parse all YAML documents in a stream
     and produce corresponding Python objects.
     """
+    if Loader is None:
+        load_warning('load_all')
+        Loader = FullLoader
+
     loader = Loader(stream)
     try:
         while loader.check_data():
@@ -84,11 +131,33 @@ def load_all(stream, Loader=Loader):
     finally:
         loader.dispose()
 
+def full_load(stream):
+    """
+    Parse the first YAML document in a stream
+    and produce the corresponding Python object.
+
+    Resolve all tags except those known to be
+    unsafe on untrusted input.
+    """
+    return load(stream, FullLoader)
+
+def full_load_all(stream):
+    """
+    Parse all YAML documents in a stream
+    and produce corresponding Python objects.
+
+    Resolve all tags except those known to be
+    unsafe on untrusted input.
+    """
+    return load_all(stream, FullLoader)
+
 def safe_load(stream):
     """
     Parse the first YAML document in a stream
     and produce the corresponding Python object.
-    Resolve only basic YAML tags.
+
+    Resolve only basic YAML tags. This is known
+    to be safe for untrusted input.
     """
     return load(stream, SafeLoader)
 
@@ -96,10 +165,32 @@ def safe_load_all(stream):
     """
     Parse all YAML documents in a stream
     and produce corresponding Python objects.
-    Resolve only basic YAML tags.
+
+    Resolve only basic YAML tags. This is known
+    to be safe for untrusted input.
     """
     return load_all(stream, SafeLoader)
 
+def unsafe_load(stream):
+    """
+    Parse the first YAML document in a stream
+    and produce the corresponding Python object.
+
+    Resolve all tags, even those known to be
+    unsafe on untrusted input.
+    """
+    return load(stream, UnsafeLoader)
+
+def unsafe_load_all(stream):
+    """
+    Parse all YAML documents in a stream
+    and produce corresponding Python objects.
+
+    Resolve all tags, even those known to be
+    unsafe on untrusted input.
+    """
+    return load_all(stream, UnsafeLoader)
+
 def emit(events, stream=None, Dumper=Dumper,
         canonical=None, indent=None, width=None,
         allow_unicode=None, line_break=None):
diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py
index 081d78a..427aeaf 100644
--- a/lib/yaml/constructor.py
+++ b/lib/yaml/constructor.py
@@ -1,6 +1,12 @@
 
-__all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor',
-    'ConstructorError']
+__all__ = [
+    'BaseConstructor',
+    'SafeConstructor',
+    'FullConstructor',
+    'UnsafeConstructor',
+    'Constructor',
+    'ConstructorError'
+]
 
 from error import *
 from nodes import *
@@ -469,7 +475,7 @@ SafeConstructor.add_constructor(
 SafeConstructor.add_constructor(None,
         SafeConstructor.construct_undefined)
 
-class Constructor(SafeConstructor):
+class FullConstructor(SafeConstructor):
 
     def construct_python_str(self, node):
         return self.construct_scalar(node).encode('utf-8')
@@ -486,18 +492,22 @@ class Constructor(SafeConstructor):
     def construct_python_tuple(self, node):
         return tuple(self.construct_sequence(node))
 
-    def find_python_module(self, name, mark):
+    def find_python_module(self, name, mark, unsafe=False):
         if not name:
             raise ConstructorError("while constructing a Python module", mark,
                     "expected non-empty name appended to the tag", mark)
-        try:
-            __import__(name)
-        except ImportError, exc:
+        if unsafe:
+            try:
+                __import__(name)
+            except ImportError, exc:
+                raise ConstructorError("while constructing a Python module", mark,
+                        "cannot find module %r (%s)" % (name.encode('utf-8'), exc), mark)
+        if not name in sys.modules:
             raise ConstructorError("while constructing a Python module", mark,
-                    "cannot find module %r (%s)" % (name.encode('utf-8'), exc), mark)
+                    "module %r is not imported" % name.encode('utf-8'), mark)
         return sys.modules[name]
 
-    def find_python_name(self, name, mark):
+    def find_python_name(self, name, mark, unsafe=False):
         if not name:
             raise ConstructorError("while constructing a Python object", mark,
                     "expected non-empty name appended to the tag", mark)
@@ -510,11 +520,15 @@ class Constructor(SafeConstructor):
         else:
             module_name = '__builtin__'
             object_name = name
-        try:
-            __import__(module_name)
-        except ImportError, exc:
+        if unsafe:
+            try:
+                __import__(module_name)
+            except ImportError, exc:
+                raise ConstructorError("while constructing a Python object", mark,
+                        "cannot find module %r (%s)" % (module_name.encode('utf-8'), exc), mark)
+        if not module_name in sys.modules:
             raise ConstructorError("while constructing a Python object", mark,
-                    "cannot find module %r (%s)" % (module_name.encode('utf-8'), exc), mark)
+                    "module %r is not imported" % module_name.encode('utf-8'), mark)
         module = sys.modules[module_name]
         if not hasattr(module, object_name):
             raise ConstructorError("while constructing a Python object", mark,
@@ -541,12 +555,16 @@ class Constructor(SafeConstructor):
     class classobj: pass
 
     def make_python_instance(self, suffix, node,
-            args=None, kwds=None, newobj=False):
+            args=None, kwds=None, newobj=False, unsafe=False):
         if not args:
             args = []
         if not kwds:
             kwds = {}
         cls = self.find_python_name(suffix, node.start_mark)
+        if not (unsafe or isinstance(cls, type) or isinstance(cls, type(self.classobj))):
+            raise ConstructorError("while constructing a Python instance", node.start_mark,
+                    "expected a class, but found %r" % type(cls),
+                    node.start_mark)
         if newobj and isinstance(cls, type(self.classobj))  \
                 and not args and not kwds:
             instance = self.classobj()
@@ -618,67 +636,83 @@ class Constructor(SafeConstructor):
     def construct_python_object_new(self, suffix, node):
         return self.construct_python_object_apply(suffix, node, newobj=True)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     u'tag:yaml.org,2002:python/none',
-    Constructor.construct_yaml_null)
+    FullConstructor.construct_yaml_null)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     u'tag:yaml.org,2002:python/bool',
-    Constructor.construct_yaml_bool)
+    FullConstructor.construct_yaml_bool)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     u'tag:yaml.org,2002:python/str',
-    Constructor.construct_python_str)
+    FullConstructor.construct_python_str)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     u'tag:yaml.org,2002:python/unicode',
-    Constructor.construct_python_unicode)
+    FullConstructor.construct_python_unicode)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     u'tag:yaml.org,2002:python/int',
-    Constructor.construct_yaml_int)
+    FullConstructor.construct_yaml_int)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     u'tag:yaml.org,2002:python/long',
-    Constructor.construct_python_long)
+    FullConstructor.construct_python_long)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     u'tag:yaml.org,2002:python/float',
-    Constructor.construct_yaml_float)
+    FullConstructor.construct_yaml_float)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     u'tag:yaml.org,2002:python/complex',
-    Constructor.construct_python_complex)
+    FullConstructor.construct_python_complex)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     u'tag:yaml.org,2002:python/list',
-    Constructor.construct_yaml_seq)
+    FullConstructor.construct_yaml_seq)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     u'tag:yaml.org,2002:python/tuple',
-    Constructor.construct_python_tuple)
+    FullConstructor.construct_python_tuple)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     u'tag:yaml.org,2002:python/dict',
-    Constructor.construct_yaml_map)
+    FullConstructor.construct_yaml_map)
 
-Constructor.add_multi_constructor(
+FullConstructor.add_multi_constructor(
     u'tag:yaml.org,2002:python/name:',
-    Constructor.construct_python_name)
+    FullConstructor.construct_python_name)
 
-Constructor.add_multi_constructor(
+FullConstructor.add_multi_constructor(
     u'tag:yaml.org,2002:python/module:',
-    Constructor.construct_python_module)
+    FullConstructor.construct_python_module)
 
-Constructor.add_multi_constructor(
+FullConstructor.add_multi_constructor(
     u'tag:yaml.org,2002:python/object:',
-    Constructor.construct_python_object)
+    FullConstructor.construct_python_object)
 
-Constructor.add_multi_constructor(
+FullConstructor.add_multi_constructor(
     u'tag:yaml.org,2002:python/object/apply:',
-    Constructor.construct_python_object_apply)
+    FullConstructor.construct_python_object_apply)
 
-Constructor.add_multi_constructor(
+FullConstructor.add_multi_constructor(
     u'tag:yaml.org,2002:python/object/new:',
-    Constructor.construct_python_object_new)
+    FullConstructor.construct_python_object_new)
 
+class UnsafeConstructor(FullConstructor):
+
+    def find_python_module(self, name, mark):
+        return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True)
+
+    def find_python_name(self, name, mark):
+        return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True)
+
+    def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False):
+        return super(UnsafeConstructor, self).make_python_instance(
+            suffix, node, args, kwds, newobj, unsafe=True)
+
+# Constructor is same as UnsafeConstructor. Need to leave this in place in case
+# people have extended it directly.
+class Constructor(UnsafeConstructor):
+    pass
diff --git a/lib/yaml/cyaml.py b/lib/yaml/cyaml.py
index 68dcd75..d7a467c 100644
--- a/lib/yaml/cyaml.py
+++ b/lib/yaml/cyaml.py
@@ -1,6 +1,8 @@
 
-__all__ = ['CBaseLoader', 'CSafeLoader', 'CLoader',
-        'CBaseDumper', 'CSafeDumper', 'CDumper']
+__all__ = [
+    'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader',
+    'CBaseDumper', 'CSafeDumper', 'CDumper'
+]
 
 from _yaml import CParser, CEmitter
 
@@ -25,6 +27,20 @@ class CSafeLoader(CParser, SafeConstructor, Resolver):
         SafeConstructor.__init__(self)
         Resolver.__init__(self)
 
+class CFullLoader(CParser, FullConstructor, Resolver):
+
+    def __init__(self, stream):
+        CParser.__init__(self, stream)
+        FullConstructor.__init__(self)
+        Resolver.__init__(self)
+
+class CUnsafeLoader(CParser, UnsafeConstructor, Resolver):
+
+    def __init__(self, stream):
+        CParser.__init__(self, stream)
+        UnsafeConstructor.__init__(self)
+        Resolver.__init__(self)
+
 class CLoader(CParser, Constructor, Resolver):
 
     def __init__(self, stream):
diff --git a/lib/yaml/loader.py b/lib/yaml/loader.py
index 293ff46..a79182e 100644
--- a/lib/yaml/loader.py
+++ b/lib/yaml/loader.py
@@ -1,5 +1,5 @@
 
-__all__ = ['BaseLoader', 'SafeLoader', 'Loader']
+__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader']
 
 from reader import *
 from scanner import *
@@ -18,6 +18,16 @@ class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolve
         BaseConstructor.__init__(self)
         BaseResolver.__init__(self)
 
+class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver):
+
+    def __init__(self, stream):
+        Reader.__init__(self, stream)
+        Scanner.__init__(self)
+        Parser.__init__(self)
+        Composer.__init__(self)
+        FullConstructor.__init__(self)
+        Resolver.__init__(self)
+
 class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver):
 
     def __init__(self, stream):
@@ -38,3 +48,16 @@ class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver):
         Constructor.__init__(self)
         Resolver.__init__(self)
 
+# UnsafeLoader is the same as Loader (which is and was always unsafe on
+# untrusted input). Use of either Loader or UnsafeLoader should be rare, since
+# FullLoad should be able to load almost all YAML safely. Loader is left intact
+# to ensure backwards compatability.
+class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver):
+
+    def __init__(self, stream):
+        Reader.__init__(self, stream)
+        Scanner.__init__(self)
+        Parser.__init__(self)
+        Composer.__init__(self)
+        Constructor.__init__(self)
+        Resolver.__init__(self)
diff --git a/tests/lib/test_errors.py b/tests/lib/test_errors.py
index 7dc9388..32423c1 100644
--- a/tests/lib/test_errors.py
+++ b/tests/lib/test_errors.py
@@ -3,7 +3,7 @@ import yaml, test_emitter
 
 def test_loader_error(error_filename, verbose=False):
     try:
-        list(yaml.load_all(open(error_filename, 'rb')))
+        list(yaml.load_all(open(error_filename, 'rb'), yaml.FullLoader))
     except yaml.YAMLError, exc:
         if verbose:
             print "%s:" % exc.__class__.__name__, exc
@@ -14,7 +14,7 @@ test_loader_error.unittest = ['.loader-error']
 
 def test_loader_error_string(error_filename, verbose=False):
     try:
-        list(yaml.load_all(open(error_filename, 'rb').read()))
+        list(yaml.load_all(open(error_filename, 'rb').read(), yaml.FullLoader))
     except yaml.YAMLError, exc:
         if verbose:
             print "%s:" % exc.__class__.__name__, exc
@@ -25,7 +25,7 @@ test_loader_error_string.unittest = ['.loader-error']
 
 def test_loader_error_single(error_filename, verbose=False):
     try:
-        yaml.load(open(error_filename, 'rb').read())
+        yaml.load(open(error_filename, 'rb').read(), yaml.FullLoader)
     except yaml.YAMLError, exc:
         if verbose:
             print "%s:" % exc.__class__.__name__, exc
diff --git a/tests/lib/test_input_output.py b/tests/lib/test_input_output.py
index 9ccc8fc..8da5fa9 100644
--- a/tests/lib/test_input_output.py
+++ b/tests/lib/test_input_output.py
@@ -17,7 +17,7 @@ def _unicode_open(file, encoding, errors='strict'):
 def test_unicode_input(unicode_filename, verbose=False):
     data = open(unicode_filename, 'rb').read().decode('utf-8')
     value = ' '.join(data.split())
-    output = yaml.load(_unicode_open(StringIO.StringIO(data.encode('utf-8')), 'utf-8'))
+    output = yaml.full_load(_unicode_open(StringIO.StringIO(data.encode('utf-8')), 'utf-8'))
     assert output == value, (output, value)
     for input in [data, data.encode('utf-8'),
                     codecs.BOM_UTF8+data.encode('utf-8'),
@@ -25,9 +25,9 @@ def test_unicode_input(unicode_filename, verbose=False):
                     codecs.BOM_UTF16_LE+data.encode('utf-16-le')]:
         if verbose:
             print "INPUT:", repr(input[:10]), "..."
-        output = yaml.load(input)
+        output = yaml.full_load(input)
         assert output == value, (output, value)
-        output = yaml.load(StringIO.StringIO(input))
+        output = yaml.full_load(StringIO.StringIO(input))
         assert output == value, (output, value)
 
 test_unicode_input.unittest = ['.unicode']
@@ -40,14 +40,14 @@ def test_unicode_input_errors(unicode_filename, verbose=False):
                     codecs.BOM_UTF16_BE+data.encode('utf-16-le'),
                     codecs.BOM_UTF16_LE+data.encode('utf-8')+'!']:
         try:
-            yaml.load(input)
+            yaml.full_load(input)
         except yaml.YAMLError, exc:
             if verbose:
                 print exc
         else:
             raise AssertionError("expected an exception")
         try:
-            yaml.load(StringIO.StringIO(input))
+            yaml.full_load(StringIO.StringIO(input))
         except yaml.YAMLError, exc:
             if verbose:
                 print exc
diff --git a/tests/lib/test_recursive.py b/tests/lib/test_recursive.py
index 6707fd4..312204e 100644
--- a/tests/lib/test_recursive.py
+++ b/tests/lib/test_recursive.py
@@ -30,7 +30,7 @@ def test_recursive(recursive_filename, verbose=False):
     output2 = None
     try:
         output1 = yaml.dump(value1)
-        value2 = yaml.load(output1)
+        value2 = yaml.load(output1, yaml.FullLoader)
         output2 = yaml.dump(value2)
         assert output1 == output2, (output1, output2)
     finally:
-- 
2.35.3


From d20cff1c206b4e02e19d9510ca74582708e34f69 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tina=20M=C3=BCller?= <cpan2@tinita.de>
Date: Sat, 23 Feb 2019 21:12:43 +0100
Subject: [PATCH 2/5] Apply FullLoader/UnsafeLoader changes to lib3

Original commit: 857dff153d2c9f238d3a68821262b7764aeb44ea
---
 lib3/yaml/__init__.py           |  98 +++++++++++++++++++++++-
 lib3/yaml/constructor.py        | 130 ++++++++++++++++++++------------
 lib3/yaml/cyaml.py              |  20 ++++-
 lib3/yaml/loader.py             |  25 +++++-
 tests/lib3/test_errors.py       |   6 +-
 tests/lib3/test_input_output.py |  12 +--
 tests/lib3/test_recursive.py    |   2 +-
 7 files changed, 228 insertions(+), 65 deletions(-)

diff --git a/lib3/yaml/__init__.py b/lib3/yaml/__init__.py
index 2d49ac8..d7081a1 100644
--- a/lib3/yaml/__init__.py
+++ b/lib3/yaml/__init__.py
@@ -17,6 +17,44 @@ except ImportError:
 
 import io
 
+#------------------------------------------------------------------------------
+# Warnings control
+#------------------------------------------------------------------------------
+
+# 'Global' warnings state:
+_warnings_enabled = {
+    'YAMLLoadWarning': True,
+}
+
+# Get or set global warnings' state
+def warnings(settings=None):
+    if settings is None:
+        return _warnings_enabled
+
+    if type(settings) is dict:
+        for key in settings:
+            if key in _warnings_enabled:
+                _warnings_enabled[key] = settings[key]
+
+# Warn when load() is called without Loader=...
+class YAMLLoadWarning(RuntimeWarning):
+    pass
+
+def load_warning(method):
+    if _warnings_enabled['YAMLLoadWarning'] is False:
+        return
+
+    import warnings
+
+    message = (
+        "calling yaml.%s() without Loader=... is deprecated, as the "
+        "default Loader is unsafe. Please read "
+        "https://msg.pyyaml.org/load for full details."
+    ) % method
+
+    warnings.warn(message, YAMLLoadWarning, stacklevel=3)
+
+#------------------------------------------------------------------------------
 def scan(stream, Loader=Loader):
     """
     Scan a YAML stream and produce scanning tokens.
@@ -62,22 +100,30 @@ def compose_all(stream, Loader=Loader):
     finally:
         loader.dispose()
 
-def load(stream, Loader=Loader):
+def load(stream, Loader=None):
     """
     Parse the first YAML document in a stream
     and produce the corresponding Python object.
     """
+    if Loader is None:
+        load_warning('load')
+        Loader = FullLoader
+
     loader = Loader(stream)
     try:
         return loader.get_single_data()
     finally:
         loader.dispose()
 
-def load_all(stream, Loader=Loader):
+def load_all(stream, Loader=None):
     """
     Parse all YAML documents in a stream
     and produce corresponding Python objects.
     """
+    if Loader is None:
+        load_warning('load_all')
+        Loader = FullLoader
+
     loader = Loader(stream)
     try:
         while loader.check_data():
@@ -85,11 +131,33 @@ def load_all(stream, Loader=Loader):
     finally:
         loader.dispose()
 
+def full_load(stream):
+    """
+    Parse the first YAML document in a stream
+    and produce the corresponding Python object.
+
+    Resolve all tags except those known to be
+    unsafe on untrusted input.
+    """
+    return load(stream, FullLoader)
+
+def full_load_all(stream):
+    """
+    Parse all YAML documents in a stream
+    and produce corresponding Python objects.
+
+    Resolve all tags except those known to be
+    unsafe on untrusted input.
+    """
+    return load_all(stream, FullLoader)
+
 def safe_load(stream):
     """
     Parse the first YAML document in a stream
     and produce the corresponding Python object.
-    Resolve only basic YAML tags.
+
+    Resolve only basic YAML tags. This is known
+    to be safe for untrusted input.
     """
     return load(stream, SafeLoader)
 
@@ -97,10 +165,32 @@ def safe_load_all(stream):
     """
     Parse all YAML documents in a stream
     and produce corresponding Python objects.
-    Resolve only basic YAML tags.
+
+    Resolve only basic YAML tags. This is known
+    to be safe for untrusted input.
     """
     return load_all(stream, SafeLoader)
 
+def unsafe_load(stream):
+    """
+    Parse the first YAML document in a stream
+    and produce the corresponding Python object.
+
+    Resolve all tags, even those known to be
+    unsafe on untrusted input.
+    """
+    return load(stream, UnsafeLoader)
+
+def unsafe_load_all(stream):
+    """
+    Parse all YAML documents in a stream
+    and produce corresponding Python objects.
+
+    Resolve all tags, even those known to be
+    unsafe on untrusted input.
+    """
+    return load_all(stream, UnsafeLoader)
+
 def emit(events, stream=None, Dumper=Dumper,
         canonical=None, indent=None, width=None,
         allow_unicode=None, line_break=None):
diff --git a/lib3/yaml/constructor.py b/lib3/yaml/constructor.py
index 981543a..61853dd 100644
--- a/lib3/yaml/constructor.py
+++ b/lib3/yaml/constructor.py
@@ -1,6 +1,12 @@
 
-__all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor',
-    'ConstructorError']
+__all__ = [
+    'BaseConstructor',
+    'SafeConstructor',
+    'FullConstructor',
+    'UnsafeConstructor',
+    'Constructor',
+    'ConstructorError'
+]
 
 from .error import *
 from .nodes import *
@@ -464,7 +470,7 @@ SafeConstructor.add_constructor(
 SafeConstructor.add_constructor(None,
         SafeConstructor.construct_undefined)
 
-class Constructor(SafeConstructor):
+class FullConstructor(SafeConstructor):
 
     def construct_python_str(self, node):
         return self.construct_scalar(node)
@@ -497,18 +503,22 @@ class Constructor(SafeConstructor):
     def construct_python_tuple(self, node):
         return tuple(self.construct_sequence(node))
 
-    def find_python_module(self, name, mark):
+    def find_python_module(self, name, mark, unsafe=False):
         if not name:
             raise ConstructorError("while constructing a Python module", mark,
                     "expected non-empty name appended to the tag", mark)
-        try:
-            __import__(name)
-        except ImportError as exc:
+        if unsafe:
+            try:
+                __import__(name)
+            except ImportError as exc:
+                raise ConstructorError("while constructing a Python module", mark,
+                        "cannot find module %r (%s)" % (name, exc), mark)
+        if not name in sys.modules:
             raise ConstructorError("while constructing a Python module", mark,
-                    "cannot find module %r (%s)" % (name, exc), mark)
+                    "module %r is not imported" % name, mark)
         return sys.modules[name]
 
-    def find_python_name(self, name, mark):
+    def find_python_name(self, name, mark, unsafe=False):
         if not name:
             raise ConstructorError("while constructing a Python object", mark,
                     "expected non-empty name appended to the tag", mark)
@@ -517,11 +527,15 @@ class Constructor(SafeConstructor):
         else:
             module_name = 'builtins'
             object_name = name
-        try:
-            __import__(module_name)
-        except ImportError as exc:
+        if unsafe:
+            try:
+                __import__(module_name)
+            except ImportError as exc:
+                raise ConstructorError("while constructing a Python object", mark,
+                        "cannot find module %r (%s)" % (module_name, exc), mark)
+        if not module_name in sys.modules:
             raise ConstructorError("while constructing a Python object", mark,
-                    "cannot find module %r (%s)" % (module_name, exc), mark)
+                    "module %r is not imported" % module_name, mark)
         module = sys.modules[module_name]
         if not hasattr(module, object_name):
             raise ConstructorError("while constructing a Python object", mark,
@@ -544,12 +558,16 @@ class Constructor(SafeConstructor):
         return self.find_python_module(suffix, node.start_mark)
 
     def make_python_instance(self, suffix, node,
-            args=None, kwds=None, newobj=False):
+            args=None, kwds=None, newobj=False, unsafe=False):
         if not args:
             args = []
         if not kwds:
             kwds = {}
         cls = self.find_python_name(suffix, node.start_mark)
+        if not (unsafe or isinstance(cls, type)):
+            raise ConstructorError("while constructing a Python instance", node.start_mark,
+                    "expected a class, but found %r" % type(cls),
+                    node.start_mark)
         if newobj and isinstance(cls, type):
             return cls.__new__(cls, *args, **kwds)
         else:
@@ -616,71 +634,87 @@ class Constructor(SafeConstructor):
     def construct_python_object_new(self, suffix, node):
         return self.construct_python_object_apply(suffix, node, newobj=True)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     'tag:yaml.org,2002:python/none',
-    Constructor.construct_yaml_null)
+    FullConstructor.construct_yaml_null)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     'tag:yaml.org,2002:python/bool',
-    Constructor.construct_yaml_bool)
+    FullConstructor.construct_yaml_bool)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     'tag:yaml.org,2002:python/str',
-    Constructor.construct_python_str)
+    FullConstructor.construct_python_str)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     'tag:yaml.org,2002:python/unicode',
-    Constructor.construct_python_unicode)
+    FullConstructor.construct_python_unicode)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     'tag:yaml.org,2002:python/bytes',
-    Constructor.construct_python_bytes)
+    FullConstructor.construct_python_bytes)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     'tag:yaml.org,2002:python/int',
-    Constructor.construct_yaml_int)
+    FullConstructor.construct_yaml_int)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     'tag:yaml.org,2002:python/long',
-    Constructor.construct_python_long)
+    FullConstructor.construct_python_long)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     'tag:yaml.org,2002:python/float',
-    Constructor.construct_yaml_float)
+    FullConstructor.construct_yaml_float)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     'tag:yaml.org,2002:python/complex',
-    Constructor.construct_python_complex)
+    FullConstructor.construct_python_complex)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     'tag:yaml.org,2002:python/list',
-    Constructor.construct_yaml_seq)
+    FullConstructor.construct_yaml_seq)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     'tag:yaml.org,2002:python/tuple',
-    Constructor.construct_python_tuple)
+    FullConstructor.construct_python_tuple)
 
-Constructor.add_constructor(
+FullConstructor.add_constructor(
     'tag:yaml.org,2002:python/dict',
-    Constructor.construct_yaml_map)
+    FullConstructor.construct_yaml_map)
 
-Constructor.add_multi_constructor(
+FullConstructor.add_multi_constructor(
     'tag:yaml.org,2002:python/name:',
-    Constructor.construct_python_name)
+    FullConstructor.construct_python_name)
 
-Constructor.add_multi_constructor(
+FullConstructor.add_multi_constructor(
     'tag:yaml.org,2002:python/module:',
-    Constructor.construct_python_module)
+    FullConstructor.construct_python_module)
 
-Constructor.add_multi_constructor(
+FullConstructor.add_multi_constructor(
     'tag:yaml.org,2002:python/object:',
-    Constructor.construct_python_object)
+    FullConstructor.construct_python_object)
 
-Constructor.add_multi_constructor(
+FullConstructor.add_multi_constructor(
     'tag:yaml.org,2002:python/object/apply:',
-    Constructor.construct_python_object_apply)
+    FullConstructor.construct_python_object_apply)
 
-Constructor.add_multi_constructor(
+FullConstructor.add_multi_constructor(
     'tag:yaml.org,2002:python/object/new:',
-    Constructor.construct_python_object_new)
+    FullConstructor.construct_python_object_new)
+
+class UnsafeConstructor(FullConstructor):
 
+    def find_python_module(self, name, mark):
+        return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True)
+
+    def find_python_name(self, name, mark):
+        return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True)
+
+    def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False):
+        return super(UnsafeConstructor, self).make_python_instance(
+            suffix, node, args, kwds, newobj, unsafe=True)
+
+# Constructor is same as UnsafeConstructor. Need to leave this in place in case
+# people have extended it directly.
+class Constructor(UnsafeConstructor):
+    pass
diff --git a/lib3/yaml/cyaml.py b/lib3/yaml/cyaml.py
index d5cb87e..86f1d3d 100644
--- a/lib3/yaml/cyaml.py
+++ b/lib3/yaml/cyaml.py
@@ -1,6 +1,8 @@
 
-__all__ = ['CBaseLoader', 'CSafeLoader', 'CLoader',
-        'CBaseDumper', 'CSafeDumper', 'CDumper']
+__all__ = [
+    'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader',
+    'CBaseDumper', 'CSafeDumper', 'CDumper'
+]
 
 from _yaml import CParser, CEmitter
 
@@ -25,6 +27,20 @@ class CSafeLoader(CParser, SafeConstructor, Resolver):
         SafeConstructor.__init__(self)
         Resolver.__init__(self)
 
+class CFullLoader(CParser, FullConstructor, Resolver):
+
+    def __init__(self, stream):
+        CParser.__init__(self, stream)
+        FullConstructor.__init__(self)
+        Resolver.__init__(self)
+
+class CUnsafeLoader(CParser, UnsafeConstructor, Resolver):
+
+    def __init__(self, stream):
+        CParser.__init__(self, stream)
+        UnsafeConstructor.__init__(self)
+        Resolver.__init__(self)
+
 class CLoader(CParser, Constructor, Resolver):
 
     def __init__(self, stream):
diff --git a/lib3/yaml/loader.py b/lib3/yaml/loader.py
index 08c8f01..414cb2c 100644
--- a/lib3/yaml/loader.py
+++ b/lib3/yaml/loader.py
@@ -1,5 +1,5 @@
 
-__all__ = ['BaseLoader', 'SafeLoader', 'Loader']
+__all__ = ['BaseLoader', 'FullLoader', 'SafeLoader', 'Loader', 'UnsafeLoader']
 
 from .reader import *
 from .scanner import *
@@ -18,6 +18,16 @@ class BaseLoader(Reader, Scanner, Parser, Composer, BaseConstructor, BaseResolve
         BaseConstructor.__init__(self)
         BaseResolver.__init__(self)
 
+class FullLoader(Reader, Scanner, Parser, Composer, FullConstructor, Resolver):
+
+    def __init__(self, stream):
+        Reader.__init__(self, stream)
+        Scanner.__init__(self)
+        Parser.__init__(self)
+        Composer.__init__(self)
+        FullConstructor.__init__(self)
+        Resolver.__init__(self)
+
 class SafeLoader(Reader, Scanner, Parser, Composer, SafeConstructor, Resolver):
 
     def __init__(self, stream):
@@ -38,3 +48,16 @@ class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver):
         Constructor.__init__(self)
         Resolver.__init__(self)
 
+# UnsafeLoader is the same as Loader (which is and was always unsafe on
+# untrusted input). Use of either Loader or UnsafeLoader should be rare, since
+# FullLoad should be able to load almost all YAML safely. Loader is left intact
+# to ensure backwards compatability.
+class UnsafeLoader(Reader, Scanner, Parser, Composer, Constructor, Resolver):
+
+    def __init__(self, stream):
+        Reader.__init__(self, stream)
+        Scanner.__init__(self)
+        Parser.__init__(self)
+        Composer.__init__(self)
+        Constructor.__init__(self)
+        Resolver.__init__(self)
diff --git a/tests/lib3/test_errors.py b/tests/lib3/test_errors.py
index a3f86af..9ef9bbe 100644
--- a/tests/lib3/test_errors.py
+++ b/tests/lib3/test_errors.py
@@ -3,7 +3,7 @@ import yaml, test_emitter
 
 def test_loader_error(error_filename, verbose=False):
     try:
-        list(yaml.load_all(open(error_filename, 'rb')))
+        list(yaml.load_all(open(error_filename, 'rb'), yaml.FullLoader))
     except yaml.YAMLError as exc:
         if verbose:
             print("%s:" % exc.__class__.__name__, exc)
@@ -14,7 +14,7 @@ test_loader_error.unittest = ['.loader-error']
 
 def test_loader_error_string(error_filename, verbose=False):
     try:
-        list(yaml.load_all(open(error_filename, 'rb').read()))
+        list(yaml.load_all(open(error_filename, 'rb').read(), yaml.FullLoader))
     except yaml.YAMLError as exc:
         if verbose:
             print("%s:" % exc.__class__.__name__, exc)
@@ -25,7 +25,7 @@ test_loader_error_string.unittest = ['.loader-error']
 
 def test_loader_error_single(error_filename, verbose=False):
     try:
-        yaml.load(open(error_filename, 'rb').read())
+        yaml.load(open(error_filename, 'rb').read(), yaml.FullLoader)
     except yaml.YAMLError as exc:
         if verbose:
             print("%s:" % exc.__class__.__name__, exc)
diff --git a/tests/lib3/test_input_output.py b/tests/lib3/test_input_output.py
index 70a945a..3559691 100644
--- a/tests/lib3/test_input_output.py
+++ b/tests/lib3/test_input_output.py
@@ -5,9 +5,9 @@ import codecs, io, tempfile, os, os.path
 def test_unicode_input(unicode_filename, verbose=False):
     data = open(unicode_filename, 'rb').read().decode('utf-8')
     value = ' '.join(data.split())
-    output = yaml.load(data)
+    output = yaml.full_load(data)
     assert output == value, (output, value)
-    output = yaml.load(io.StringIO(data))
+    output = yaml.full_load(io.StringIO(data))
     assert output == value, (output, value)
     for input in [data.encode('utf-8'),
                     codecs.BOM_UTF8+data.encode('utf-8'),
@@ -15,9 +15,9 @@ def test_unicode_input(unicode_filename, verbose=False):
                     codecs.BOM_UTF16_LE+data.encode('utf-16-le')]:
         if verbose:
             print("INPUT:", repr(input[:10]), "...")
-        output = yaml.load(input)
+        output = yaml.full_load(input)
         assert output == value, (output, value)
-        output = yaml.load(io.BytesIO(input))
+        output = yaml.full_load(io.BytesIO(input))
         assert output == value, (output, value)
 
 test_unicode_input.unittest = ['.unicode']
@@ -30,14 +30,14 @@ def test_unicode_input_errors(unicode_filename, verbose=False):
                     codecs.BOM_UTF16_BE+data.encode('utf-16-le'),
                     codecs.BOM_UTF16_LE+data.encode('utf-8')+b'!']:
         try:
-            yaml.load(input)
+            yaml.full_load(input)
         except yaml.YAMLError as exc:
             if verbose:
                 print(exc)
         else:
             raise AssertionError("expected an exception")
         try:
-            yaml.load(io.BytesIO(input))
+            yaml.full_load(io.BytesIO(input))
         except yaml.YAMLError as exc:
             if verbose:
                 print(exc)
diff --git a/tests/lib3/test_recursive.py b/tests/lib3/test_recursive.py
index 321a75f..74c2ee6 100644
--- a/tests/lib3/test_recursive.py
+++ b/tests/lib3/test_recursive.py
@@ -31,7 +31,7 @@ def test_recursive(recursive_filename, verbose=False):
     output2 = None
     try:
         output1 = yaml.dump(value1)
-        value2 = yaml.load(output1)
+        value2 = yaml.full_load(output1)
         output2 = yaml.dump(value2)
         assert output1 == output2, (output1, output2)
     finally:
-- 
2.35.3


From 06a0108471a5a8ded1005ffd2eb077736bb64645 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tina=20M=C3=BCller?= <cpan2@tinita.de>
Date: Mon, 18 Nov 2019 15:55:27 +0100
Subject: [PATCH 3/5] Move constructor for object/apply to Unsafe

Original commit: 8c5e47fe62d7b9e0282a176a4b79b8b2980dc704
---
 lib/yaml/constructor.py  | 8 ++++----
 lib3/yaml/constructor.py | 8 ++++----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py
index 427aeaf..87252c7 100644
--- a/lib/yaml/constructor.py
+++ b/lib/yaml/constructor.py
@@ -692,10 +692,6 @@ FullConstructor.add_multi_constructor(
     u'tag:yaml.org,2002:python/object:',
     FullConstructor.construct_python_object)
 
-FullConstructor.add_multi_constructor(
-    u'tag:yaml.org,2002:python/object/apply:',
-    FullConstructor.construct_python_object_apply)
-
 FullConstructor.add_multi_constructor(
     u'tag:yaml.org,2002:python/object/new:',
     FullConstructor.construct_python_object_new)
@@ -712,6 +708,10 @@ class UnsafeConstructor(FullConstructor):
         return super(UnsafeConstructor, self).make_python_instance(
             suffix, node, args, kwds, newobj, unsafe=True)
 
+UnsafeConstructor.add_multi_constructor(
+    u'tag:yaml.org,2002:python/object/apply:',
+    UnsafeConstructor.construct_python_object_apply)
+
 # Constructor is same as UnsafeConstructor. Need to leave this in place in case
 # people have extended it directly.
 class Constructor(UnsafeConstructor):
diff --git a/lib3/yaml/constructor.py b/lib3/yaml/constructor.py
index 61853dd..e5b6005 100644
--- a/lib3/yaml/constructor.py
+++ b/lib3/yaml/constructor.py
@@ -694,10 +694,6 @@ FullConstructor.add_multi_constructor(
     'tag:yaml.org,2002:python/object:',
     FullConstructor.construct_python_object)
 
-FullConstructor.add_multi_constructor(
-    'tag:yaml.org,2002:python/object/apply:',
-    FullConstructor.construct_python_object_apply)
-
 FullConstructor.add_multi_constructor(
     'tag:yaml.org,2002:python/object/new:',
     FullConstructor.construct_python_object_new)
@@ -714,6 +710,10 @@ class UnsafeConstructor(FullConstructor):
         return super(UnsafeConstructor, self).make_python_instance(
             suffix, node, args, kwds, newobj, unsafe=True)
 
+UnsafeConstructor.add_multi_constructor(
+    'tag:yaml.org,2002:python/object/apply:',
+    UnsafeConstructor.construct_python_object_apply)
+
 # Constructor is same as UnsafeConstructor. Need to leave this in place in case
 # people have extended it directly.
 class Constructor(UnsafeConstructor):
-- 
2.35.3


From 9d850e3a683281afa4c4f237bb0d7279e9654fa3 Mon Sep 17 00:00:00 2001
From: Riccardo Schirone <ret2libc@users.noreply.github.com>
Date: Tue, 17 Mar 2020 19:09:55 +0100
Subject: [PATCH 4/5] Prevents arbitrary code execution during
 python/object/new constructor (#386)

Original commit: 5080ba513377b6355a0502104846ee804656f1e0

* Prevents arbitrary code execution during python/object/new constructor

In FullLoader python/object/new constructor, implemented by
construct_python_object_apply, has support for setting the state of a
deserialized instance through the set_python_instance_state method.
After setting the state, some operations are performed on the instance
to complete its initialization, however it is possible for an attacker
to set the instance' state in such a way that arbitrary code is executed
by the FullLoader.

This patch tries to block such attacks in FullLoader by preventing
set_python_instance_state from setting arbitrary properties. It
implements a blacklist that includes `extend` method (called by
construct_python_object_apply) and all special methods (e.g. __set__,
__setitem__, etc.).

Users who need special attributes being set in the state of a
deserialized object can still do it through the UnsafeLoader, which
however should not be used on untrusted input. Additionally, they can
subclass FullLoader and redefine `get_state_keys_blacklist()` to
extend/replace the list of blacklisted keys, passing the subclassed
loader to yaml.load.

* Make sure python/object/new constructor does not set some properties

* Add test to show how to subclass FullLoader with new blacklist
---
 lib/yaml/constructor.py                       | 29 ++++++++++++++++++-
 lib3/yaml/constructor.py                      | 29 ++++++++++++++++++-
 tests/data/myfullloader.subclass_blacklist    |  5 ++++
 ...erwrite-state-new-constructor.loader-error |  5 ++++
 tests/lib/test_constructor.py                 | 18 +++++++++++-
 tests/lib3/test_constructor.py                | 18 +++++++++++-
 6 files changed, 100 insertions(+), 4 deletions(-)
 create mode 100644 tests/data/myfullloader.subclass_blacklist
 create mode 100644 tests/data/overwrite-state-new-constructor.loader-error

diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py
index 87252c7..e99f019 100644
--- a/lib/yaml/constructor.py
+++ b/lib/yaml/constructor.py
@@ -38,6 +38,14 @@ class BaseConstructor(object):
         # If there are more documents available?
         return self.check_node()
 
+    def check_state_key(self, key):
+        """Block special attributes/methods from being set in a newly created
+        object, to prevent user-controlled methods from being called during
+        deserialization"""
+        if self.get_state_keys_blacklist_regexp().match(key):
+            raise ConstructorError(None, None,
+                "blacklisted key '%s' in instance state found" % (key,), None)
+
     def get_data(self):
         # Construct and return the next document.
         if self.check_node():
@@ -476,6 +484,16 @@ SafeConstructor.add_constructor(None,
         SafeConstructor.construct_undefined)
 
 class FullConstructor(SafeConstructor):
+    # 'extend' is blacklisted because it is used by
+    # construct_python_object_apply to add `listitems` to a newly generate
+    # python instance
+    def get_state_keys_blacklist(self):
+        return ['^extend$', '^__.*__$']
+
+    def get_state_keys_blacklist_regexp(self):
+        if not hasattr(self, 'state_keys_blacklist_regexp'):
+            self.state_keys_blacklist_regexp = re.compile('(' + '|'.join(self.get_state_keys_blacklist()) + ')')
+        return self.state_keys_blacklist_regexp
 
     def construct_python_str(self, node):
         return self.construct_scalar(node).encode('utf-8')
@@ -575,7 +593,7 @@ class FullConstructor(SafeConstructor):
         else:
             return cls(*args, **kwds)
 
-    def set_python_instance_state(self, instance, state):
+    def set_python_instance_state(self, instance, state, unsafe=False):
         if hasattr(instance, '__setstate__'):
             instance.__setstate__(state)
         else:
@@ -583,10 +601,15 @@ class FullConstructor(SafeConstructor):
             if isinstance(state, tuple) and len(state) == 2:
                 state, slotstate = state
             if hasattr(instance, '__dict__'):
+                if not unsafe and state:
+                    for key in state.keys():
+                        self.check_state_key(key)
                 instance.__dict__.update(state)
             elif state:
                 slotstate.update(state)
             for key, value in slotstate.items():
+                if not unsafe:
+                    self.check_state_key(key)
                 setattr(object, key, value)
 
     def construct_python_object(self, suffix, node):
@@ -708,6 +731,10 @@ class UnsafeConstructor(FullConstructor):
         return super(UnsafeConstructor, self).make_python_instance(
             suffix, node, args, kwds, newobj, unsafe=True)
 
+    def set_python_instance_state(self, instance, state):
+        return super(UnsafeConstructor, self).set_python_instance_state(
+            instance, state, unsafe=True)
+
 UnsafeConstructor.add_multi_constructor(
     u'tag:yaml.org,2002:python/object/apply:',
     UnsafeConstructor.construct_python_object_apply)
diff --git a/lib3/yaml/constructor.py b/lib3/yaml/constructor.py
index e5b6005..6929edc 100644
--- a/lib3/yaml/constructor.py
+++ b/lib3/yaml/constructor.py
@@ -31,6 +31,14 @@ class BaseConstructor:
         # If there are more documents available?
         return self.check_node()
 
+    def check_state_key(self, key):
+        """Block special attributes/methods from being set in a newly created
+        object, to prevent user-controlled methods from being called during
+        deserialization"""
+        if self.get_state_keys_blacklist_regexp().match(key):
+            raise ConstructorError(None, None,
+                "blacklisted key '%s' in instance state found" % (key,), None)
+
     def get_data(self):
         # Construct and return the next document.
         if self.check_node():
@@ -471,6 +479,16 @@ SafeConstructor.add_constructor(None,
         SafeConstructor.construct_undefined)
 
 class FullConstructor(SafeConstructor):
+    # 'extend' is blacklisted because it is used by
+    # construct_python_object_apply to add `listitems` to a newly generate
+    # python instance
+    def get_state_keys_blacklist(self):
+        return ['^extend$', '^__.*__$']
+
+    def get_state_keys_blacklist_regexp(self):
+        if not hasattr(self, 'state_keys_blacklist_regexp'):
+            self.state_keys_blacklist_regexp = re.compile('(' + '|'.join(self.get_state_keys_blacklist()) + ')')
+        return self.state_keys_blacklist_regexp
 
     def construct_python_str(self, node):
         return self.construct_scalar(node)
@@ -573,7 +591,7 @@ class FullConstructor(SafeConstructor):
         else:
             return cls(*args, **kwds)
 
-    def set_python_instance_state(self, instance, state):
+    def set_python_instance_state(self, instance, state, unsafe=False):
         if hasattr(instance, '__setstate__'):
             instance.__setstate__(state)
         else:
@@ -581,10 +599,15 @@ class FullConstructor(SafeConstructor):
             if isinstance(state, tuple) and len(state) == 2:
                 state, slotstate = state
             if hasattr(instance, '__dict__'):
+                if not unsafe and state:
+                    for key in state.keys():
+                        self.check_state_key(key)
                 instance.__dict__.update(state)
             elif state:
                 slotstate.update(state)
             for key, value in slotstate.items():
+                if not unsafe:
+                    self.check_state_key(key)
                 setattr(object, key, value)
 
     def construct_python_object(self, suffix, node):
@@ -710,6 +733,10 @@ class UnsafeConstructor(FullConstructor):
         return super(UnsafeConstructor, self).make_python_instance(
             suffix, node, args, kwds, newobj, unsafe=True)
 
+    def set_python_instance_state(self, instance, state):
+        return super(UnsafeConstructor, self).set_python_instance_state(
+            instance, state, unsafe=True)
+
 UnsafeConstructor.add_multi_constructor(
     'tag:yaml.org,2002:python/object/apply:',
     UnsafeConstructor.construct_python_object_apply)
diff --git a/tests/data/myfullloader.subclass_blacklist b/tests/data/myfullloader.subclass_blacklist
new file mode 100644
index 0000000..555a2b3
--- /dev/null
+++ b/tests/data/myfullloader.subclass_blacklist
@@ -0,0 +1,5 @@
+- !!python/object/new:yaml.MappingNode
+  args:
+  state:
+    mymethod: test
+    wrong_method: test2
diff --git a/tests/data/overwrite-state-new-constructor.loader-error b/tests/data/overwrite-state-new-constructor.loader-error
new file mode 100644
index 0000000..8d224f1
--- /dev/null
+++ b/tests/data/overwrite-state-new-constructor.loader-error
@@ -0,0 +1,5 @@
+- !!python/object/new:yaml.MappingNode
+  args:
+  state:
+    extend: test
+    __test__: test
diff --git a/tests/lib/test_constructor.py b/tests/lib/test_constructor.py
index beee7b0..a18d13d 100644
--- a/tests/lib/test_constructor.py
+++ b/tests/lib/test_constructor.py
@@ -17,7 +17,7 @@ def _make_objects():
     global MyLoader, MyDumper, MyTestClass1, MyTestClass2, MyTestClass3, YAMLObject1, YAMLObject2,  \
             AnObject, AnInstance, AState, ACustomState, InitArgs, InitArgsWithState,    \
             NewArgs, NewArgsWithState, Reduce, ReduceWithState, MyInt, MyList, MyDict,  \
-            FixedOffset, today, execute
+            FixedOffset, today, execute, MyFullLoader
 
     class MyLoader(yaml.Loader):
         pass
@@ -213,6 +213,10 @@ def _make_objects():
         def dst(self, dt):
             return datetime.timedelta(0)
 
+    class MyFullLoader(yaml.FullLoader):
+        def get_state_keys_blacklist(self):
+            return super(MyFullLoader, self).get_state_keys_blacklist() + ['^mymethod$', '^wrong_.*$']
+
     today = datetime.date.today()
 
 def _load_code(expression):
@@ -267,6 +271,18 @@ def test_constructor_types(data_filename, code_filename, verbose=False):
 
 test_constructor_types.unittest = ['.data', '.code']
 
+def test_subclass_blacklist_types(data_filename, verbose=False):
+    _make_objects()
+    try:
+        yaml.load(open(data_filename, 'rb').read(), MyFullLoader)
+    except yaml.YAMLError as exc:
+        if verbose:
+            print("%s:" % exc.__class__.__name__, exc)
+    else:
+        raise AssertionError("expected an exception")
+
+test_subclass_blacklist_types.unittest = ['.subclass_blacklist']
+
 if __name__ == '__main__':
     import sys, test_constructor
     sys.modules['test_constructor'] = sys.modules['__main__']
diff --git a/tests/lib3/test_constructor.py b/tests/lib3/test_constructor.py
index 427f53c..fb4509e 100644
--- a/tests/lib3/test_constructor.py
+++ b/tests/lib3/test_constructor.py
@@ -14,7 +14,7 @@ def _make_objects():
     global MyLoader, MyDumper, MyTestClass1, MyTestClass2, MyTestClass3, YAMLObject1, YAMLObject2,  \
             AnObject, AnInstance, AState, ACustomState, InitArgs, InitArgsWithState,    \
             NewArgs, NewArgsWithState, Reduce, ReduceWithState, MyInt, MyList, MyDict,  \
-            FixedOffset, today, execute
+            FixedOffset, today, execute, MyFullLoader
 
     class MyLoader(yaml.Loader):
         pass
@@ -200,6 +200,10 @@ def _make_objects():
         def dst(self, dt):
             return datetime.timedelta(0)
 
+    class MyFullLoader(yaml.FullLoader):
+        def get_state_keys_blacklist(self):
+            return super().get_state_keys_blacklist() + ['^mymethod$', '^wrong_.*$']
+
     today = datetime.date.today()
 
 def _load_code(expression):
@@ -252,6 +256,18 @@ def test_constructor_types(data_filename, code_filename, verbose=False):
 
 test_constructor_types.unittest = ['.data', '.code']
 
+def test_subclass_blacklist_types(data_filename, verbose=False):
+    _make_objects()
+    try:
+        yaml.load(open(data_filename, 'rb').read(), MyFullLoader)
+    except yaml.YAMLError as exc:
+        if verbose:
+            print("%s:" % exc.__class__.__name__, exc)
+    else:
+        raise AssertionError("expected an exception")
+
+test_subclass_blacklist_types.unittest = ['.subclass_blacklist']
+
 if __name__ == '__main__':
     import sys, test_constructor
     sys.modules['test_constructor'] = sys.modules['__main__']
-- 
2.35.3


From 66a7619e0500b48e356699ec20c48e3e3c8edc2e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ingy=20d=C3=B6t=20Net?= <ingy@ingy.net>
Date: Sat, 9 Jan 2021 10:53:23 -0500
Subject: [PATCH 5/5] Fix for CVE-2020-14343

Original commit: a001f2782501ad2d24986959f0239a354675f9dc

Per suggestion https://github.com/yaml/pyyaml/issues/420#issuecomment-663888344
move a few constructors from full_load to unsafe_load.
---
 lib/yaml/constructor.py      | 24 ++++++++++++------------
 lib3/yaml/constructor.py     | 24 ++++++++++++------------
 tests/lib/test_recursive.py  |  2 +-
 tests/lib3/test_recursive.py |  2 +-
 4 files changed, 26 insertions(+), 26 deletions(-)

diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py
index e99f019..8ef0907 100644
--- a/lib/yaml/constructor.py
+++ b/lib/yaml/constructor.py
@@ -707,18 +707,6 @@ FullConstructor.add_multi_constructor(
     u'tag:yaml.org,2002:python/name:',
     FullConstructor.construct_python_name)
 
-FullConstructor.add_multi_constructor(
-    u'tag:yaml.org,2002:python/module:',
-    FullConstructor.construct_python_module)
-
-FullConstructor.add_multi_constructor(
-    u'tag:yaml.org,2002:python/object:',
-    FullConstructor.construct_python_object)
-
-FullConstructor.add_multi_constructor(
-    u'tag:yaml.org,2002:python/object/new:',
-    FullConstructor.construct_python_object_new)
-
 class UnsafeConstructor(FullConstructor):
 
     def find_python_module(self, name, mark):
@@ -735,6 +723,18 @@ class UnsafeConstructor(FullConstructor):
         return super(UnsafeConstructor, self).set_python_instance_state(
             instance, state, unsafe=True)
 
+UnsafeConstructor.add_multi_constructor(
+    u'tag:yaml.org,2002:python/module:',
+    UnsafeConstructor.construct_python_module)
+
+UnsafeConstructor.add_multi_constructor(
+    u'tag:yaml.org,2002:python/object:',
+    UnsafeConstructor.construct_python_object)
+
+UnsafeConstructor.add_multi_constructor(
+    u'tag:yaml.org,2002:python/object/new:',
+    UnsafeConstructor.construct_python_object_new)
+
 UnsafeConstructor.add_multi_constructor(
     u'tag:yaml.org,2002:python/object/apply:',
     UnsafeConstructor.construct_python_object_apply)
diff --git a/lib3/yaml/constructor.py b/lib3/yaml/constructor.py
index 6929edc..2a0d812 100644
--- a/lib3/yaml/constructor.py
+++ b/lib3/yaml/constructor.py
@@ -709,18 +709,6 @@ FullConstructor.add_multi_constructor(
     'tag:yaml.org,2002:python/name:',
     FullConstructor.construct_python_name)
 
-FullConstructor.add_multi_constructor(
-    'tag:yaml.org,2002:python/module:',
-    FullConstructor.construct_python_module)
-
-FullConstructor.add_multi_constructor(
-    'tag:yaml.org,2002:python/object:',
-    FullConstructor.construct_python_object)
-
-FullConstructor.add_multi_constructor(
-    'tag:yaml.org,2002:python/object/new:',
-    FullConstructor.construct_python_object_new)
-
 class UnsafeConstructor(FullConstructor):
 
     def find_python_module(self, name, mark):
@@ -737,6 +725,18 @@ class UnsafeConstructor(FullConstructor):
         return super(UnsafeConstructor, self).set_python_instance_state(
             instance, state, unsafe=True)
 
+UnsafeConstructor.add_multi_constructor(
+    'tag:yaml.org,2002:python/module:',
+    UnsafeConstructor.construct_python_module)
+
+UnsafeConstructor.add_multi_constructor(
+    'tag:yaml.org,2002:python/object:',
+    UnsafeConstructor.construct_python_object)
+
+UnsafeConstructor.add_multi_constructor(
+    'tag:yaml.org,2002:python/object/new:',
+    UnsafeConstructor.construct_python_object_new)
+
 UnsafeConstructor.add_multi_constructor(
     'tag:yaml.org,2002:python/object/apply:',
     UnsafeConstructor.construct_python_object_apply)
diff --git a/tests/lib/test_recursive.py b/tests/lib/test_recursive.py
index 312204e..04c5798 100644
--- a/tests/lib/test_recursive.py
+++ b/tests/lib/test_recursive.py
@@ -30,7 +30,7 @@ def test_recursive(recursive_filename, verbose=False):
     output2 = None
     try:
         output1 = yaml.dump(value1)
-        value2 = yaml.load(output1, yaml.FullLoader)
+        value2 = yaml.load(output1, yaml.UnsafeLoader)
         output2 = yaml.dump(value2)
         assert output1 == output2, (output1, output2)
     finally:
diff --git a/tests/lib3/test_recursive.py b/tests/lib3/test_recursive.py
index 74c2ee6..08042c8 100644
--- a/tests/lib3/test_recursive.py
+++ b/tests/lib3/test_recursive.py
@@ -31,7 +31,7 @@ def test_recursive(recursive_filename, verbose=False):
     output2 = None
     try:
         output1 = yaml.dump(value1)
-        value2 = yaml.full_load(output1)
+        value2 = yaml.unsafe_load(output1)
         output2 = yaml.dump(value2)
         assert output1 == output2, (output1, output2)
     finally:
-- 
2.35.3

