diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py index 8ce39722..352bbd9e 100644 --- a/lib/yaml/constructor.py +++ b/lib/yaml/constructor.py @@ -56,6 +56,14 @@ def check_data(self): # 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(): @@ -495,6 +503,12 @@ def construct_undefined(self, node): 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 + 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') @@ -590,7 +604,7 @@ def make_python_instance(self, suffix, node, 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: @@ -598,10 +612,15 @@ def set_python_instance_state(self, instance, state): 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(instance, key, value) def construct_python_object(self, suffix, node): @@ -723,6 +742,10 @@ 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) + 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 cd9167ea..20b7221d 100644 --- a/lib3/yaml/constructor.py +++ b/lib3/yaml/constructor.py @@ -31,6 +31,14 @@ def check_data(self): # 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(): @@ -472,6 +480,12 @@ def construct_undefined(self, node): 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 + STATE_BLACKLIST_KEYS = ['^extend$', '^__.*__$'] + + state_blacklist_regexp = re.compile('(' + '|'.join(STATE_BLACKLIST_KEYS) + ')') def construct_python_str(self, node): return self.construct_scalar(node) @@ -574,7 +588,7 @@ def make_python_instance(self, suffix, node, 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: @@ -582,10 +596,15 @@ def set_python_instance_state(self, instance, state): 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(instance, key, value) def construct_python_object(self, suffix, node): @@ -711,6 +730,10 @@ 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) + 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)