Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit be467e5

Browse files
committed
Fix Bug #114293:
Strings are unpickled by calling eval on the string's repr. This change makes pickle work like cPickle; it checks if the pickled string is safe to eval and raises ValueError if it is not. test suite modifications: Verify that pickle catches a variety of insecure string pickles Make test_pickle and test_cpickle use exactly the same test suite Add test for pickling recursive object
1 parent a647f57 commit be467e5

5 files changed

Lines changed: 131 additions & 112 deletions

File tree

Lib/pickle.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,10 +577,50 @@ def load_binfloat(self, unpack=struct.unpack):
577577
dispatch[BINFLOAT] = load_binfloat
578578

579579
def load_string(self):
580-
self.append(eval(self.readline()[:-1],
580+
rep = self.readline()[:-1]
581+
if not self._is_string_secure(rep):
582+
raise ValueError, "insecure string pickle"
583+
self.append(eval(rep,
581584
{'__builtins__': {}})) # Let's be careful
582585
dispatch[STRING] = load_string
583586

587+
def _is_string_secure(self, s):
588+
"""Return true if s contains a string that is safe to eval
589+
590+
The definition of secure string is based on the implementation
591+
in cPickle. s is secure as long as it only contains a quoted
592+
string and optional trailing whitespace.
593+
"""
594+
q = s[0]
595+
if q not in ("'", '"'):
596+
return 0
597+
# find the closing quote
598+
offset = 1
599+
i = None
600+
while 1:
601+
try:
602+
i = s.index(q, offset)
603+
except ValueError:
604+
# if there is an error the first time, there is no
605+
# close quote
606+
if offset == 1:
607+
return 0
608+
if s[i-1] != '\\':
609+
break
610+
# check to see if this one is escaped
611+
nslash = 0
612+
j = i - 1
613+
while j >= offset and s[j] == '\\':
614+
j = j - 1
615+
nslash = nslash + 1
616+
if nslash % 2 == 0:
617+
break
618+
offset = i + 1
619+
for c in s[i+1:]:
620+
if ord(c) > 32:
621+
return 0
622+
return 1
623+
584624
def load_binstring(self):
585625
len = mloads('i' + self.read(4))
586626
self.append(self.read(len))

Lib/test/output/test_cpickle

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,17 @@ loads() binary
99
ok
1010
loads() BINDATA
1111
ok
12+
dumps() RECURSIVE
13+
ok
14+
dumps()
15+
loads()
16+
ok
17+
loads() DATA
18+
ok
19+
dumps() binary
20+
loads() binary
21+
ok
22+
loads() BINDATA
23+
ok
24+
dumps() RECURSIVE
25+
ok

Lib/test/output/test_pickle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ loads() binary
99
ok
1010
loads() BINDATA
1111
ok
12+
dumps() RECURSIVE
13+
ok

Lib/test/test_cpickle.py

Lines changed: 2 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,5 @@
11
# Test the cPickle module
22

3-
DATA = """(lp0
4-
I0
5-
aL1L
6-
aF2.0
7-
ac__builtin__
8-
complex
9-
p1
10-
(F3.0
11-
F0.0
12-
tp2
13-
Rp3
14-
a(S'abc'
15-
p4
16-
g4
17-
(i__main__
18-
C
19-
p5
20-
(dp6
21-
S'foo'
22-
p7
23-
I1
24-
sS'bar'
25-
p8
26-
I2
27-
sbg5
28-
tp9
29-
ag9
30-
aI5
31-
a.
32-
"""
33-
34-
BINDATA = ']q\000(K\000L1L\012G@\000\000\000\000\000\000\000c__builtin__\012complex\012q\001(G@\010\000\000\000\000\000\000G\000\000\000\000\000\000\000\000tq\002Rq\003(U\003abcq\004h\004(c__main__\012C\012q\005oq\006}q\007(U\003fooq\010K\001U\003barq\011K\002ubh\006tq\012h\012K\005e.'
35-
363
import cPickle
37-
38-
class C:
39-
def __cmp__(self, other):
40-
return cmp(self.__dict__, other.__dict__)
41-
42-
import __main__
43-
__main__.C = C
44-
45-
def dotest():
46-
c = C()
47-
c.foo = 1
48-
c.bar = 2
49-
x = [0, 1L, 2.0, 3.0+0j]
50-
y = ('abc', 'abc', c, c)
51-
x.append(y)
52-
x.append(y)
53-
x.append(5)
54-
print "dumps()"
55-
s = cPickle.dumps(x)
56-
print "loads()"
57-
x2 = cPickle.loads(s)
58-
if x2 == x: print "ok"
59-
else: print "bad"
60-
print "loads() DATA"
61-
x2 = cPickle.loads(DATA)
62-
if x2 == x: print "ok"
63-
else: print "bad"
64-
print "dumps() binary"
65-
s = cPickle.dumps(x, 1)
66-
print "loads() binary"
67-
x2 = cPickle.loads(s)
68-
if x2 == x: print "ok"
69-
else: print "bad"
70-
print "loads() BINDATA"
71-
x2 = cPickle.loads(BINDATA)
72-
if x2 == x: print "ok"
73-
else: print "bad"
74-
75-
# Test protection against closed files
76-
import tempfile, os
77-
fn = tempfile.mktemp()
78-
f = open(fn, "w")
79-
f.close()
80-
try:
81-
cPickle.dump(123, f)
82-
except ValueError:
83-
pass
84-
else:
85-
print "dump to closed file should raise ValueError"
86-
f = open(fn, "r")
87-
f.close()
88-
try:
89-
cPickle.load(f)
90-
except ValueError:
91-
pass
92-
else:
93-
print "load from closed file should raise ValueError"
94-
os.remove(fn)
95-
96-
# Test specific bad cases
97-
for i in range(10):
98-
try:
99-
x = cPickle.loads('garyp')
100-
except cPickle.BadPickleGet, y:
101-
del y
102-
else:
103-
print "unexpected success!"
104-
break
105-
106-
107-
dotest()
4+
import test_pickle
5+
test_pickle.dotest(cPickle)

Lib/test/test_pickle.py

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
# Test the pickle module
22

3+
# break into multiple strings to please font-lock-mode
34
DATA = """(lp0
45
I0
56
aL1L
67
aF2.0
78
ac__builtin__
89
complex
910
p1
10-
(F3.0
11+
""" \
12+
"""(F3.0
1113
F0.0
1214
tp2
1315
Rp3
1416
a(S'abc'
1517
p4
1618
g4
17-
(i__main__
19+
""" \
20+
"""(i__main__
1821
C
1922
p5
20-
(dp6
23+
""" \
24+
"""(dp6
2125
S'foo'
2226
p7
2327
I1
@@ -33,16 +37,14 @@
3337

3438
BINDATA = ']q\000(K\000L1L\012G@\000\000\000\000\000\000\000c__builtin__\012complex\012q\001(G@\010\000\000\000\000\000\000G\000\000\000\000\000\000\000\000tq\002Rq\003(U\003abcq\004h\004(c__main__\012C\012q\005oq\006}q\007(U\003fooq\010K\001U\003barq\011K\002ubh\006tq\012h\012K\005e.'
3539

36-
import pickle
37-
3840
class C:
3941
def __cmp__(self, other):
4042
return cmp(self.__dict__, other.__dict__)
4143

4244
import __main__
4345
__main__.C = C
4446

45-
def dotest():
47+
def dotest(pickle):
4648
c = C()
4749
c.foo = 1
4850
c.bar = 2
@@ -51,6 +53,8 @@ def dotest():
5153
x.append(y)
5254
x.append(y)
5355
x.append(5)
56+
r = []
57+
r.append(r)
5458
print "dumps()"
5559
s = pickle.dumps(x)
5660
print "loads()"
@@ -71,5 +75,66 @@ def dotest():
7175
x2 = pickle.loads(BINDATA)
7276
if x2 == x: print "ok"
7377
else: print "bad"
78+
s = pickle.dumps(r)
79+
print "dumps() RECURSIVE"
80+
x2 = pickle.loads(s)
81+
if x2 == r: print "ok"
82+
else: print "bad"
7483

75-
dotest()
84+
# Test protection against closed files
85+
import tempfile, os
86+
fn = tempfile.mktemp()
87+
f = open(fn, "w")
88+
f.close()
89+
try:
90+
pickle.dump(123, f)
91+
except ValueError:
92+
pass
93+
else:
94+
print "dump to closed file should raise ValueError"
95+
f = open(fn, "r")
96+
f.close()
97+
try:
98+
pickle.load(f)
99+
except ValueError:
100+
pass
101+
else:
102+
print "load from closed file should raise ValueError"
103+
os.remove(fn)
104+
105+
# Test specific bad cases
106+
for i in range(10):
107+
try:
108+
x = pickle.loads('garyp')
109+
except KeyError, y:
110+
# pickle
111+
del y
112+
except pickle.BadPickleGet, y:
113+
# cPickle
114+
del y
115+
else:
116+
print "unexpected success!"
117+
break
118+
119+
# Test insecure strings
120+
insecure = ["abc", "2 + 2", # not quoted
121+
"'abc' + 'def'", # not a single quoted string
122+
"'abc", # quote is not closed
123+
"'abc\"", # open quote and close quote don't match
124+
"'abc' ?", # junk after close quote
125+
# some tests of the quoting rules
126+
"'abc\"\''",
127+
"'\\\\a\'\'\'\\\'\\\\\''",
128+
]
129+
for s in insecure:
130+
buf = "S" + s + "\012p0\012."
131+
try:
132+
x = pickle.loads(buf)
133+
except ValueError:
134+
pass
135+
else:
136+
print "accepted insecure string: %s" % repr(buf)
137+
138+
139+
import pickle
140+
dotest(pickle)

0 commit comments

Comments
 (0)