On Tuesday, March 3, 2020 11:41:26 AM EST Salvatore Bonaccorso wrote: > Hi Scott, > > On Tue, Mar 03, 2020 at 09:19:06AM -0500, Scott Kitterman wrote: > > On Tuesday, March 3, 2020 2:29:51 AM EST Salvatore Bonaccorso wrote: > > > Source: pyyaml > > > Version: 5.3-1 > > > Severity: important > > > Tags: security upstream > > > Forwarded: https://github.com/yaml/pyyaml/pull/386 > > > > > > Hi, > > > > > > The following vulnerability was published for pyyaml. > > > > > > CVE-2020-1747[0]: > > > |arbitrary command execution through python/object/new when FullLoader > > > |is used > > > > > > If you fix the vulnerability please also make sure to include the > > > CVE (Common Vulnerabilities & Exposures) id in your changelog entry. > > > > > > For further information see: > > > > > > [0] https://security-tracker.debian.org/tracker/CVE-2020-1747 > > > > > > https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1747 > > > > > > [1] https://github.com/yaml/pyyaml/pull/386 > > > > > > Please adjust the affected versions in the BTS as needed. > > > > Thank you for the report. > > > > I've reviewed the current security-tracker entry and I believe it's > > incorrect. While it's true that the unsafe loader was the default loader > > in releases prior to 5.0, the safe loader has always been part of pyyaml > > and recommended for cases where untrusted input needed to be parsed. > > > > Although I haven't actually tried it yet, I reviewed the relevant code > > from > > the earlier releases and I believe the same fix would work. Unfortunately > > I don't have a test case to verify that. > > > > Also, the upstream pull request to fix this is incomplete. It only > > provides a fix for python3-yaml, not python-yaml. > > > > I've updated the affected versions in the BTS and will take a shot at > > updating the security tracker shortly. > > I'm actually unsure how to handle that CVE. While I believe to > undrstand that the unsafe loader are present, my understanding of the > CVE-2020-1747 is directly associated with the FullLoader use and > looking at the Red Hat bug where the CVE originates, > https://bugzilla.redhat.com/show_bug.cgi?id=1807367 it looks to me > that the CVE's scope is only to cover FullLoader. > > Again I'm not really sure. But if that interpretation would be > correct, and given upstream recommended for for cases where untrusted > input needed to be parsed to use the safe loaders, then not-affected > would theoretically be correct, but maybe with changed/expanded > explanation on that FullLoader is not present until 5.1. > > I'm not going to touch the CVE entry in the security-tracker now, and > looking for feedback from you all (you, Emilio, Moritz said to look as > well a the CVE later).
I have investigated this issue further for buster. With the test now provided in the upstream pull request added to the buster pyyaml package, the new test fails (there are other failures, but those are known and can be ignored). The patch applies with only minimal adjustment. Once the patch is applied, the new test then passes. What I can't determine is a clean way to control if the new checks are enabled. They are enabled by default in the upstream patch (which is appropriate for FullLoader, which is supposed to be safe. The Loader class in the buster pyyaml is, however not. I don't see a way around the default. We can either make the default safe and break backward compatibility or default unsafe and no one will get the benefit of the improved behavior without changes to the calling code (what are the odds that would happen). This behavior is essentially what CVE-2017-18342 describes, which is open against stretch and buster, so the issue is documented. I reluctantly conclude that it's best left alone for stretch/buster and because of CVE-2017-18342, the problem is reported, so we don't have any additional disclosures we need to make. I've provided the debdiff for buster in case the Security Team feels differently and for anyone interested in rebuilding their pyyaml locally. Scott K
diff -Nru pyyaml-3.13/debian/changelog pyyaml-3.13/debian/changelog --- pyyaml-3.13/debian/changelog 2019-01-10 11:09:51.000000000 +0000 +++ pyyaml-3.13/debian/changelog 2020-03-07 05:43:00.000000000 +0000 @@ -1,3 +1,10 @@ +pyyaml (3.13-2+deb10u1~) buster; urgency=high + + * Disable arbitrary code execution (CVE-2017-18342) + - See Debian #953013 for details + + -- Scott Kitterman <sc...@kitterman.com> Sat, 07 Mar 2020 01:43:00 -0400 + pyyaml (3.13-2) unstable; urgency=medium * Team upload diff -Nru pyyaml-3.13/debian/patches/safety.patch pyyaml-3.13/debian/patches/safety.patch --- pyyaml-3.13/debian/patches/safety.patch 1970-01-01 00:00:00.000000000 +0000 +++ pyyaml-3.13/debian/patches/safety.patch 2020-03-07 05:43:00.000000000 +0000 @@ -0,0 +1,127 @@ +Description: Disable arbitrary code execution + * Disable arbitrary code execution (CVE-2017-18342) + - See Debian #953013 for details +Author: Scott Kitterman <sc...@kitterman.com> +Origin: vendor +Bug-Debian: https://bugs.debian.org/953013 +Forwarded: not-needed +Last-Update: 2020-03-07 + +--- pyyaml-3.13.orig/lib/yaml/constructor.py ++++ pyyaml-3.13/lib/yaml/constructor.py +@@ -27,6 +27,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.state_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(): +@@ -465,6 +473,12 @@ SafeConstructor.add_constructor(None, + SafeConstructor.construct_undefined) + + class Constructor(SafeConstructor): ++ # 'extend' is blacklisted because it is used by ++ # construct_python_object_apply to add `listitems` to a newly generate ++ # python instance ++ STATE_BLACKLIST_KEYS = ['^extend$', '^__.*__$'] ++ ++ state_blacklist_regexp = re.compile('(' + '|'.join(STATE_BLACKLIST_KEYS) + ')') + + def construct_python_str(self, node): + return self.construct_scalar(node).encode('utf-8') +@@ -548,7 +562,7 @@ class Constructor(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: +@@ -556,10 +570,15 @@ class Constructor(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): +--- pyyaml-3.13.orig/lib3/yaml/constructor.py ++++ pyyaml-3.13/lib3/yaml/constructor.py +@@ -25,6 +25,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.state_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(): +@@ -465,6 +473,12 @@ SafeConstructor.add_constructor(None, + SafeConstructor.construct_undefined) + + class Constructor(SafeConstructor): ++ # 'extend' is blacklisted because it is used by ++ # construct_python_object_apply to add `listitems` to a newly generate ++ # python instance ++ STATE_BLACKLIST_KEYS = ['^extend$', '^__.*__$'] ++ ++ state_blacklist_regexp = re.compile('(' + '|'.join(STATE_BLACKLIST_KEYS) + ')') + + def construct_python_str(self, node): + return self.construct_scalar(node) +@@ -555,7 +569,7 @@ class Constructor(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: +@@ -563,10 +577,15 @@ class Constructor(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): +--- /dev/null ++++ pyyaml-3.13/tests/data/overwrite-state-new-constructor.loader-error +@@ -0,0 +1,5 @@ ++- !!python/object/new:yaml.MappingNode ++ args: ++ state: ++ extend: test ++ __test__: test diff -Nru pyyaml-3.13/debian/patches/series pyyaml-3.13/debian/patches/series --- pyyaml-3.13/debian/patches/series 2019-01-10 11:09:51.000000000 +0000 +++ pyyaml-3.13/debian/patches/series 2020-03-07 05:43:00.000000000 +0000 @@ -1,2 +1,3 @@ 0001-support-high-codepoints.patch.patch 0002-Import-Hashable-from-collections.abc.patch +safety.patch
signature.asc
Description: This is a digitally signed message part.