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