Remove use of caller() in strict.pm, and tighten Safe compartment
[p5sagit/p5-mst-13.2.git] / ext / Storable / t / malice.t
CommitLineData
b8778c7c 1#!./perl -w
b8778c7c 2#
3# Copyright 2002, Larry Wall.
4#
5# You may redistribute only under the same terms as Perl 5, as specified
6# in the README file that comes with the distribution.
7#
8
9# I'm trying to keep this test easily backwards compatible to 5.004, so no
10# qr//;
b8778c7c 11
12# This test tries to craft malicious data to test out as many different
13# error traps in Storable as possible
14# It also acts as a test for read_header
15
16sub BEGIN {
17 if ($ENV{PERL_CORE}){
18 chdir('t') if -d 't';
372cb964 19 @INC = ('.', '../lib');
b8778c7c 20 }
21 require Config; import Config;
22 if ($ENV{PERL_CORE} and $Config{'extensions'} !~ /\bStorable\b/) {
23 print "1..0 # Skip: Storable was not built\n";
24 exit 0;
25 }
197b90bc 26 if ($] < 5.005) {
27 print "1..0 # Skip: Config{ptrsize} not defined\n";
28 exit 0;
29 }
b8778c7c 30}
31
32use strict;
677a847b 33use vars qw($file_magic_str $other_magic $network_magic $byteorder
34 $major $minor $minor_write $fancy);
35
36$byteorder = $Config{byteorder};
37
96ef0061 38$file_magic_str = 'pst0';
677a847b 39$other_magic = 7 + length $byteorder;
96ef0061 40$network_magic = 2;
41$major = 2;
464b080a 42$minor = 6;
43$minor_write = $] > 5.007 ? 6 : 4;
b8778c7c 44
372cb964 45use Test::More;
46
47# If it's 5.7.3 or later the hash will be stored with flags, which is
48# 2 extra bytes. There are 2 * 2 * 2 tests per byte in the body and header
49# common to normal and network order serialised objects (hence the 8)
50# There are only 2 * 2 tests per byte in the parts of the header not present
51# for network order, and 2 tests per byte on the 'pst0' "magic number" only
52# present in files, but not in things store()ed to memory
53$fancy = ($] > 5.007 ? 2 : 0);
54
677a847b 55plan tests => 368 + length ($byteorder) * 4 + $fancy * 8;
b8778c7c 56
57use Storable qw (store retrieve freeze thaw nstore nfreeze);
58
59my $file = "malice.$$";
60die "Temporary file 'malice.$$' already exists" if -e $file;
61
62END { while (-f $file) {unlink $file or die "Can't unlink '$file': $!" }}
63
9d80fab7 64# The chr 256 is a hack to force the hash to always have the utf8 keys flag
65# set on 5.7.3 and later. Otherwise the test fails if run with -Mutf8 because
66# only there does the hash has the flag on, and hence only there is it stored
67# as a flagged hash, which is 2 bytes longer
68my %hash = (perl => 'rules', chr 256, '');
69delete $hash{chr 256};
b8778c7c 70
71sub test_hash {
72 my $clone = shift;
372cb964 73 is (ref $clone, "HASH", "Get hash back");
74 is (scalar keys %$clone, 1, "with 1 key");
75 is ((keys %$clone)[0], "perl", "which is correct");
76 is ($clone->{perl}, "rules");
b8778c7c 77}
78
79sub test_header {
80 my ($header, $isfile, $isnetorder) = @_;
372cb964 81 is (!!$header->{file}, !!$isfile, "is file");
82 is ($header->{major}, $major, "major number");
83 is ($header->{minor}, $minor_write, "minor number");
84 is (!!$header->{netorder}, !!$isnetorder, "is network order");
677a847b 85 if ($isnetorder) {
86 # Network order header has no sizes
87 } else {
88 is ($header->{byteorder}, $byteorder, "byte order");
372cb964 89 is ($header->{intsize}, $Config{intsize}, "int size");
90 is ($header->{longsize}, $Config{longsize}, "long size");
91 is ($header->{ptrsize}, $Config{ptrsize}, "long size");
92 is ($header->{nvsize}, $Config{nvsize} || $Config{doublesize} || 8,
b8778c7c 93 "nv size"); # 5.00405 doesn't even have doublesize in config.
94 }
95}
96
97sub store_and_retrieve {
98 my $data = shift;
99 unlink $file or die "Can't unlink '$file': $!";
100 open FH, ">$file" or die "Can't open '$file': $!";
101 binmode FH;
102 print FH $data or die "Can't print to '$file': $!";
103 close FH or die "Can't close '$file': $!";
104
105 return eval {retrieve $file};
106}
107
108sub freeze_and_thaw {
109 my $data = shift;
110 return eval {thaw $data};
111}
112
113sub test_truncated {
114 my ($data, $sub, $magic_len, $what) = @_;
115 for my $i (0 .. length ($data) - 1) {
116 my $short = substr $data, 0, $i;
117
118 my $clone = &$sub($short);
372cb964 119 is (defined ($clone), '', "truncated $what to $i should fail");
b8778c7c 120 if ($i < $magic_len) {
372cb964 121 like ($@, "/^Magic number checking on storable $what failed/",
b8778c7c 122 "Should croak with magic number warning");
123 } else {
372cb964 124 is ($@, "", "Should not set \$\@");
b8778c7c 125 }
126 }
127}
128
129sub test_corrupt {
130 my ($data, $sub, $what, $name) = @_;
131
132 my $clone = &$sub($data);
372cb964 133 is (defined ($clone), '', "$name $what should fail");
134 like ($@, $what, $name);
b8778c7c 135}
136
137sub test_things {
138 my ($contents, $sub, $what, $isnetwork) = @_;
139 my $isfile = $what eq 'file';
140 my $file_magic = $isfile ? length $file_magic_str : 0;
141
142 my $header = Storable::read_magic ($contents);
143 test_header ($header, $isfile, $isnetwork);
144
145 # Test that if we re-write it, everything still works:
146 my $clone = &$sub ($contents);
147
372cb964 148 is ($@, "", "There should be no error");
b8778c7c 149
150 test_hash ($clone);
151
152 # Now lets check the short version:
153 test_truncated ($contents, $sub, $file_magic
154 + ($isnetwork ? $network_magic : $other_magic), $what);
155
156 my $copy;
157 if ($isfile) {
158 $copy = $contents;
159 substr ($copy, 0, 4) = 'iron';
160 test_corrupt ($copy, $sub, "/^File is not a perl storable/",
161 "magic number");
162 }
163
164 $copy = $contents;
530b72ba 165 # Needs to be more than 1, as we're already coding a spread of 1 minor version
166 # number on writes (2.5, 2.4). May increase to 2 if we figure we can do 2.3
167 # on 5.005_03 (No utf8).
168 # 4 allows for a small safety margin
169 # (Joke:
170 # Question: What is the value of pi?
171 # Mathematician answers "It's pi, isn't it"
172 # Physicist answers "3.1, within experimental error"
173 # Engineer answers "Well, allowing for a small safety margin, 18"
174 # )
175 my $minor4 = $header->{minor} + 4;
176 substr ($copy, $file_magic + 1, 1) = chr $minor4;
e8189732 177 {
178 # Now by default newer minor version numbers are not a pain.
179 $clone = &$sub($copy);
372cb964 180 is ($@, "", "by default no error on higher minor");
e8189732 181 test_hash ($clone);
182
183 local $Storable::accept_future_minor = 0;
184 test_corrupt ($copy, $sub,
185 "/^Storable binary image v$header->{major}\.$minor4 more recent than I am \\(v$header->{major}\.$minor\\)/",
186 "higher minor");
187 }
b8778c7c 188
189 $copy = $contents;
190 my $major1 = $header->{major} + 1;
191 substr ($copy, $file_magic, 1) = chr 2*$major1;
192 test_corrupt ($copy, $sub,
530b72ba 193 "/^Storable binary image v$major1\.$header->{minor} more recent than I am \\(v$header->{major}\.$minor\\)/",
b8778c7c 194 "higher major");
195
196 # Continue messing with the previous copy
530b72ba 197 my $minor1 = $header->{minor} - 1;
b8778c7c 198 substr ($copy, $file_magic + 1, 1) = chr $minor1;
199 test_corrupt ($copy, $sub,
530b72ba 200 "/^Storable binary image v$major1\.$minor1 more recent than I am \\(v$header->{major}\.$minor\\)/",
b8778c7c 201 "higher major, lower minor");
202
203 my $where;
204 if (!$isnetwork) {
205 # All these are omitted from the network order header.
206 # I'm not sure if it's correct to omit the byte size stuff.
207 $copy = $contents;
208 substr ($copy, $file_magic + 3, length $header->{byteorder})
209 = reverse $header->{byteorder};
210
211 test_corrupt ($copy, $sub, "/^Byte order is not compatible/",
212 "byte order");
213 $where = $file_magic + 3 + length $header->{byteorder};
214 foreach (['intsize', "Integer"],
291cf09c 215 ['longsize', "Long integer"],
b8778c7c 216 ['ptrsize', "Pointer integer"],
217 ['nvsize', "Double"]) {
218 my ($key, $name) = @$_;
219 $copy = $contents;
220 substr ($copy, $where++, 1) = chr 0;
221 test_corrupt ($copy, $sub, "/^$name size is not compatible/",
222 "$name size");
223 }
224 } else {
225 $where = $file_magic + $network_magic;
226 }
227
228 # Just the header and a tag 255. As 26 is currently the highest tag, this
229 # is "unexpected"
230 $copy = substr ($contents, 0, $where) . chr 255;
231
232 test_corrupt ($copy, $sub,
233 "/^Corrupted storable $what \\(binary v$header->{major}.$header->{minor}\\)/",
234 "bogus tag");
e8189732 235
236 # Now drop the minor version number
237 substr ($copy, $file_magic + 1, 1) = chr $minor1;
238
239 test_corrupt ($copy, $sub,
240 "/^Corrupted storable $what \\(binary v$header->{major}.$minor1\\)/",
241 "bogus tag, minor less 1");
242 # Now increase the minor version number
243 substr ($copy, $file_magic + 1, 1) = chr $minor4;
244
245 # local $Storable::DEBUGME = 1;
246 # This is the delayed croak
247 test_corrupt ($copy, $sub,
464b080a 248 "/^Storable binary image v$header->{major}.$minor4 contains data of type 255. This Storable is v$header->{major}.$minor and can only handle data types up to 26/",
e8189732 249 "bogus tag, minor plus 4");
250 # And check again that this croak is not delayed:
251 {
252 # local $Storable::DEBUGME = 1;
253 local $Storable::accept_future_minor = 0;
254 test_corrupt ($copy, $sub,
255 "/^Storable binary image v$header->{major}\.$minor4 more recent than I am \\(v$header->{major}\.$minor\\)/",
256 "higher minor");
257 }
b8778c7c 258}
259
260sub slurp {
261 my $file = shift;
262 local (*FH, $/);
263 open FH, "<$file" or die "Can't open '$file': $!";
264 binmode FH;
265 my $contents = <FH>;
266 die "Can't read $file: $!" unless defined $contents;
267 return $contents;
268}
269
270
271ok (defined store(\%hash, $file));
272
9d80fab7 273my $expected = 20 + length ($file_magic_str) + $other_magic + $fancy;
b8778c7c 274my $length = -s $file;
275
276die "Don't seem to have written file '$file' as I can't get its length: $!"
277 unless defined $file;
278
291cf09c 279die "Expected file to be $expected bytes (sizeof long is $Config{longsize}) but it is $length"
b8778c7c 280 unless $length == $expected;
281
282# Read the contents into memory:
283my $contents = slurp $file;
284
285# Test the original direct from disk
286my $clone = retrieve $file;
287test_hash ($clone);
288
289# Then test it.
290test_things($contents, \&store_and_retrieve, 'file');
291
292# And now try almost everything again with a Storable string
293my $stored = freeze \%hash;
294test_things($stored, \&freeze_and_thaw, 'string');
295
296# Network order.
297unlink $file or die "Can't unlink '$file': $!";
298
299ok (defined nstore(\%hash, $file));
300
9d80fab7 301$expected = 20 + length ($file_magic_str) + $network_magic + $fancy;
b8778c7c 302$length = -s $file;
303
304die "Don't seem to have written file '$file' as I can't get its length: $!"
305 unless defined $file;
306
291cf09c 307die "Expected file to be $expected bytes (sizeof long is $Config{longsize}) but it is $length"
b8778c7c 308 unless $length == $expected;
309
310# Read the contents into memory:
311$contents = slurp $file;
312
313# Test the original direct from disk
314$clone = retrieve $file;
315test_hash ($clone);
316
317# Then test it.
318test_things($contents, \&store_and_retrieve, 'file', 1);
319
320# And now try almost everything again with a Storable string
321$stored = nfreeze \%hash;
322test_things($stored, \&freeze_and_thaw, 'string', 1);