Skip to content

Commit 0cedb2a

Browse files
committed
Deprecate/warn usage of yaml.load(input)
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 $?
1 parent d13a3d0 commit 0cedb2a

8 files changed

+228
-64
lines changed

lib/yaml/__init__.py

+96-5
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,53 @@
88
from loader import *
99
from dumper import *
1010

11-
__version__ = '4.1'
11+
__version__ = '3.13'
1212

1313
try:
1414
from cyaml import *
1515
__with_libyaml__ = True
1616
except ImportError:
1717
__with_libyaml__ = False
1818

19+
20+
#------------------------------------------------------------------------------
21+
# Warnings control
22+
#------------------------------------------------------------------------------
23+
24+
# 'Global' warnings state:
25+
_warnings_enabled = {
26+
'YAMLLoadWarning': True,
27+
}
28+
29+
# Get or set global warnings' state
30+
def warnings(settings=None):
31+
if settings is None:
32+
return _warnings_enabled
33+
34+
if type(settings) is dict:
35+
for key in settings:
36+
if key in _warnings_enabled:
37+
_warnings_enabled[key] = settings[key]
38+
39+
# Warn when load() is called without Loader=...
40+
class YAMLLoadWarning(RuntimeWarning):
41+
pass
42+
43+
def load_warning(method):
44+
if _warnings_enabled['YAMLLoadWarning'] is False:
45+
return
46+
47+
import warnings
48+
49+
message = (
50+
"calling yaml.%s() without Loader=... is deprecated, as the "
51+
"default Loader is unsafe. Please read "
52+
"https://msg.pyyaml.org/load for full details."
53+
) % method
54+
55+
warnings.warn(message, YAMLLoadWarning, stacklevel=3)
56+
57+
#------------------------------------------------------------------------------
1958
def scan(stream, Loader=Loader):
2059
"""
2160
Scan a YAML stream and produce scanning tokens.
@@ -61,45 +100,97 @@ def compose_all(stream, Loader=Loader):
61100
finally:
62101
loader.dispose()
63102

64-
def load(stream, Loader=Loader):
103+
def load(stream, Loader=None):
65104
"""
66105
Parse the first YAML document in a stream
67106
and produce the corresponding Python object.
68107
"""
108+
if Loader is None:
109+
load_warning('load')
110+
Loader = FullLoader
111+
69112
loader = Loader(stream)
70113
try:
71114
return loader.get_single_data()
72115
finally:
73116
loader.dispose()
74117

75-
def load_all(stream, Loader=Loader):
118+
def load_all(stream, Loader=None):
76119
"""
77120
Parse all YAML documents in a stream
78121
and produce corresponding Python objects.
79122
"""
123+
if Loader is None:
124+
load_warning('load_all')
125+
Loader = FullLoader
126+
80127
loader = Loader(stream)
81128
try:
82129
while loader.check_data():
83130
yield loader.get_data()
84131
finally:
85132
loader.dispose()
86133

134+
def full_load(stream):
135+
"""
136+
Parse the first YAML document in a stream
137+
and produce the corresponding Python object.
138+
139+
Resolve all tags except those known to be
140+
unsafe on untrusted input.
141+
"""
142+
return load(stream, FullLoader)
143+
144+
def full_load_all(stream):
145+
"""
146+
Parse all YAML documents in a stream
147+
and produce corresponding Python objects.
148+
149+
Resolve all tags except those known to be
150+
unsafe on untrusted input.
151+
"""
152+
return load_all(stream, FullLoader)
153+
87154
def safe_load(stream):
88155
"""
89156
Parse the first YAML document in a stream
90157
and produce the corresponding Python object.
91-
Resolve only basic YAML tags.
158+
159+
Resolve only basic YAML tags. This is known
160+
to be safe for untrusted input.
92161
"""
93162
return load(stream, SafeLoader)
94163

95164
def safe_load_all(stream):
96165
"""
97166
Parse all YAML documents in a stream
98167
and produce corresponding Python objects.
99-
Resolve only basic YAML tags.
168+
169+
Resolve only basic YAML tags. This is known
170+
to be safe for untrusted input.
100171
"""
101172
return load_all(stream, SafeLoader)
102173

174+
def unsafe_load(stream):
175+
"""
176+
Parse the first YAML document in a stream
177+
and produce the corresponding Python object.
178+
179+
Resolve all tags, even those known to be
180+
unsafe on untrusted input.
181+
"""
182+
return load(stream, UnsafeLoader)
183+
184+
def unsafe_load_all(stream):
185+
"""
186+
Parse all YAML documents in a stream
187+
and produce corresponding Python objects.
188+
189+
Resolve all tags, even those known to be
190+
unsafe on untrusted input.
191+
"""
192+
return load_all(stream, UnsafeLoader)
193+
103194
def emit(events, stream=None, Dumper=Dumper,
104195
canonical=None, indent=None, width=None,
105196
allow_unicode=None, line_break=None):

lib/yaml/constructor.py

+80-46
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11

2-
__all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor',
3-
'ConstructorError']
2+
__all__ = [
3+
'BaseConstructor',
4+
'SafeConstructor',
5+
'FullConstructor',
6+
'UnsafeConstructor',
7+
'Constructor',
8+
'ConstructorError'
9+
]
410

511
from error import *
612
from nodes import *
@@ -464,7 +470,7 @@ def construct_undefined(self, node):
464470
SafeConstructor.add_constructor(None,
465471
SafeConstructor.construct_undefined)
466472

467-
class Constructor(SafeConstructor):
473+
class FullConstructor(SafeConstructor):
468474

469475
def construct_python_str(self, node):
470476
return self.construct_scalar(node).encode('utf-8')
@@ -481,18 +487,22 @@ def construct_python_complex(self, node):
481487
def construct_python_tuple(self, node):
482488
return tuple(self.construct_sequence(node))
483489

484-
def find_python_module(self, name, mark):
490+
def find_python_module(self, name, mark, unsafe=False):
485491
if not name:
486492
raise ConstructorError("while constructing a Python module", mark,
487493
"expected non-empty name appended to the tag", mark)
488-
try:
489-
__import__(name)
490-
except ImportError, exc:
494+
if unsafe:
495+
try:
496+
__import__(name)
497+
except ImportError, exc:
498+
raise ConstructorError("while constructing a Python module", mark,
499+
"cannot find module %r (%s)" % (name.encode('utf-8'), exc), mark)
500+
if not name in sys.modules:
491501
raise ConstructorError("while constructing a Python module", mark,
492-
"cannot find module %r (%s)" % (name.encode('utf-8'), exc), mark)
502+
"module %r is not imported" % name.encode('utf-8'), mark)
493503
return sys.modules[name]
494504

495-
def find_python_name(self, name, mark):
505+
def find_python_name(self, name, mark, unsafe=False):
496506
if not name:
497507
raise ConstructorError("while constructing a Python object", mark,
498508
"expected non-empty name appended to the tag", mark)
@@ -501,11 +511,15 @@ def find_python_name(self, name, mark):
501511
else:
502512
module_name = '__builtin__'
503513
object_name = name
504-
try:
505-
__import__(module_name)
506-
except ImportError, exc:
514+
if unsafe:
515+
try:
516+
__import__(module_name)
517+
except ImportError, exc:
518+
raise ConstructorError("while constructing a Python object", mark,
519+
"cannot find module %r (%s)" % (module_name.encode('utf-8'), exc), mark)
520+
if not module_name in sys.modules:
507521
raise ConstructorError("while constructing a Python object", mark,
508-
"cannot find module %r (%s)" % (module_name.encode('utf-8'), exc), mark)
522+
"module %r is not imported" % module_name.encode('utf-8'), mark)
509523
module = sys.modules[module_name]
510524
if not hasattr(module, object_name):
511525
raise ConstructorError("while constructing a Python object", mark,
@@ -532,12 +546,16 @@ def construct_python_module(self, suffix, node):
532546
class classobj: pass
533547

534548
def make_python_instance(self, suffix, node,
535-
args=None, kwds=None, newobj=False):
549+
args=None, kwds=None, newobj=False, unsafe=False):
536550
if not args:
537551
args = []
538552
if not kwds:
539553
kwds = {}
540554
cls = self.find_python_name(suffix, node.start_mark)
555+
if not (unsafe or isinstance(cls, type) or isinstance(cls, type(self.classobj))):
556+
raise ConstructorError("while constructing a Python instance", node.start_mark,
557+
"expected a class, but found %r" % type(cls),
558+
node.start_mark)
541559
if newobj and isinstance(cls, type(self.classobj)) \
542560
and not args and not kwds:
543561
instance = self.classobj()
@@ -609,67 +627,83 @@ def construct_python_object_apply(self, suffix, node, newobj=False):
609627
def construct_python_object_new(self, suffix, node):
610628
return self.construct_python_object_apply(suffix, node, newobj=True)
611629

612-
Constructor.add_constructor(
630+
FullConstructor.add_constructor(
613631
u'tag:yaml.org,2002:python/none',
614-
Constructor.construct_yaml_null)
632+
FullConstructor.construct_yaml_null)
615633

616-
Constructor.add_constructor(
634+
FullConstructor.add_constructor(
617635
u'tag:yaml.org,2002:python/bool',
618-
Constructor.construct_yaml_bool)
636+
FullConstructor.construct_yaml_bool)
619637

620-
Constructor.add_constructor(
638+
FullConstructor.add_constructor(
621639
u'tag:yaml.org,2002:python/str',
622-
Constructor.construct_python_str)
640+
FullConstructor.construct_python_str)
623641

624-
Constructor.add_constructor(
642+
FullConstructor.add_constructor(
625643
u'tag:yaml.org,2002:python/unicode',
626-
Constructor.construct_python_unicode)
644+
FullConstructor.construct_python_unicode)
627645

628-
Constructor.add_constructor(
646+
FullConstructor.add_constructor(
629647
u'tag:yaml.org,2002:python/int',
630-
Constructor.construct_yaml_int)
648+
FullConstructor.construct_yaml_int)
631649

632-
Constructor.add_constructor(
650+
FullConstructor.add_constructor(
633651
u'tag:yaml.org,2002:python/long',
634-
Constructor.construct_python_long)
652+
FullConstructor.construct_python_long)
635653

636-
Constructor.add_constructor(
654+
FullConstructor.add_constructor(
637655
u'tag:yaml.org,2002:python/float',
638-
Constructor.construct_yaml_float)
656+
FullConstructor.construct_yaml_float)
639657

640-
Constructor.add_constructor(
658+
FullConstructor.add_constructor(
641659
u'tag:yaml.org,2002:python/complex',
642-
Constructor.construct_python_complex)
660+
FullConstructor.construct_python_complex)
643661

644-
Constructor.add_constructor(
662+
FullConstructor.add_constructor(
645663
u'tag:yaml.org,2002:python/list',
646-
Constructor.construct_yaml_seq)
664+
FullConstructor.construct_yaml_seq)
647665

648-
Constructor.add_constructor(
666+
FullConstructor.add_constructor(
649667
u'tag:yaml.org,2002:python/tuple',
650-
Constructor.construct_python_tuple)
668+
FullConstructor.construct_python_tuple)
651669

652-
Constructor.add_constructor(
670+
FullConstructor.add_constructor(
653671
u'tag:yaml.org,2002:python/dict',
654-
Constructor.construct_yaml_map)
672+
FullConstructor.construct_yaml_map)
655673

656-
Constructor.add_multi_constructor(
674+
FullConstructor.add_multi_constructor(
657675
u'tag:yaml.org,2002:python/name:',
658-
Constructor.construct_python_name)
676+
FullConstructor.construct_python_name)
659677

660-
Constructor.add_multi_constructor(
678+
FullConstructor.add_multi_constructor(
661679
u'tag:yaml.org,2002:python/module:',
662-
Constructor.construct_python_module)
680+
FullConstructor.construct_python_module)
663681

664-
Constructor.add_multi_constructor(
682+
FullConstructor.add_multi_constructor(
665683
u'tag:yaml.org,2002:python/object:',
666-
Constructor.construct_python_object)
684+
FullConstructor.construct_python_object)
667685

668-
Constructor.add_multi_constructor(
686+
FullConstructor.add_multi_constructor(
669687
u'tag:yaml.org,2002:python/object/apply:',
670-
Constructor.construct_python_object_apply)
688+
FullConstructor.construct_python_object_apply)
671689

672-
Constructor.add_multi_constructor(
690+
FullConstructor.add_multi_constructor(
673691
u'tag:yaml.org,2002:python/object/new:',
674-
Constructor.construct_python_object_new)
692+
FullConstructor.construct_python_object_new)
675693

694+
class UnsafeConstructor(FullConstructor):
695+
696+
def find_python_module(self, name, mark):
697+
return super(UnsafeConstructor, self).find_python_module(name, mark, unsafe=True)
698+
699+
def find_python_name(self, name, mark):
700+
return super(UnsafeConstructor, self).find_python_name(name, mark, unsafe=True)
701+
702+
def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False):
703+
return super(UnsafeConstructor, self).make_python_instance(
704+
suffix, node, args, kwds, newobj, unsafe=True)
705+
706+
# Constructor is same as UnsafeConstructor. Need to leave this in place in case
707+
# people have extended it directly.
708+
class Constructor(UnsafeConstructor):
709+
pass

lib/yaml/cyaml.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

2-
__all__ = ['CBaseLoader', 'CSafeLoader', 'CLoader',
3-
'CBaseDumper', 'CSafeDumper', 'CDumper']
2+
__all__ = [
3+
'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader',
4+
'CBaseDumper', 'CSafeDumper', 'CDumper'
5+
]
46

57
from _yaml import CParser, CEmitter
68

@@ -25,6 +27,20 @@ def __init__(self, stream):
2527
SafeConstructor.__init__(self)
2628
Resolver.__init__(self)
2729

30+
class CFullLoader(CParser, FullConstructor, Resolver):
31+
32+
def __init__(self, stream):
33+
CParser.__init__(self, stream)
34+
FullConstructor.__init__(self)
35+
Resolver.__init__(self)
36+
37+
class CUnsafeLoader(CParser, UnsafeConstructor, Resolver):
38+
39+
def __init__(self, stream):
40+
CParser.__init__(self, stream)
41+
UnsafeConstructor.__init__(self)
42+
Resolver.__init__(self)
43+
2844
class CLoader(CParser, Constructor, Resolver):
2945

3046
def __init__(self, stream):

0 commit comments

Comments
 (0)