xref: /sqlite-3.40.0/test/corruptC.test (revision bbc795fd)
1# 2004 August 30
2#
3# The author disclaims copyright to this source code.  In place of
4# a legal notice, here is a blessing:
5#
6#    May you do good and not evil.
7#    May you find forgiveness for yourself and forgive others.
8#    May you share freely, never taking more than you give.
9#
10#***********************************************************************
11# This file implements regression tests for SQLite library.
12#
13# This file implements tests to make sure SQLite does not crash or
14# segfault if it sees a corrupt database file.  It creates a base
15# data base file, then tests that single byte corruptions in
16# increasingly larger quantities are handled gracefully.
17#
18# $Id: corruptC.test,v 1.12 2009/06/04 02:46:20 shane Exp $
19
20catch {file delete -force test.db test.db-journal test.bu}
21
22set testdir [file dirname $argv0]
23source $testdir/tester.tcl
24
25# Construct a compact, dense database for testing.
26#
27do_test corruptC-1.1 {
28  execsql {
29    PRAGMA auto_vacuum = 0;
30    PRAGMA legacy_file_format=1;
31    BEGIN;
32    CREATE TABLE t1(x,y);
33    INSERT INTO t1 VALUES(1,1);
34    INSERT OR IGNORE INTO t1 SELECT x*2,y FROM t1;
35    INSERT OR IGNORE INTO t1 SELECT x*3,y FROM t1;
36    INSERT OR IGNORE INTO t1 SELECT x*5,y FROM t1;
37    INSERT OR IGNORE INTO t1 SELECT x*7,y FROM t1;
38    INSERT OR IGNORE INTO t1 SELECT x*11,y FROM t1;
39    INSERT OR IGNORE INTO t1 SELECT x*13,y FROM t1;
40    CREATE INDEX t1i1 ON t1(x);
41    CREATE TABLE t2 AS SELECT x,2 as y FROM t1 WHERE rowid%5!=0;
42    COMMIT;
43  }
44} {}
45
46ifcapable {integrityck} {
47  integrity_check corruptC-1.2
48}
49
50# Generate random integer
51#
52proc random {range} {
53  return [expr {round(rand()*$range)}]
54}
55
56# Copy file $from into $to
57#
58proc copy_file {from to} {
59  file copy -force $from $to
60}
61
62# Setup for the tests.  Make a backup copy of the good database in test.bu.
63#
64db close
65copy_file test.db test.bu
66sqlite3 db test.db
67set fsize [file size test.db]
68
69# Set a quasi-random random seed.
70if {[info exists SOAKTEST]} {
71  # If we are doing SOAK tests, we want a different
72  # random seed for each run.  Ideally we would like
73  # to use [clock clicks] or something like that here.
74  set qseed [file mtime test.db]
75} else {
76  # If we are not doing soak tests,
77  # make it repeatable.
78  set qseed 0
79}
80expr srand($qseed)
81
82#
83# First test some specific corruption tests found from earlier runs
84# with specific seeds.
85#
86
87# test that a corrupt content offset size is handled (seed 5577)
88do_test corruptC-2.1 {
89  db close
90  copy_file test.bu test.db
91
92  # insert corrupt byte(s)
93  hexio_write test.db 2053 [format %02x 0x04]
94
95  sqlite3 db test.db
96  catchsql {PRAGMA integrity_check}
97} {0 {{*** in database main ***
98Corruption detected in header on page 3}}}
99
100# test that a corrupt content offset size is handled (seed 5649)
101do_test corruptC-2.2 {
102  db close
103  copy_file test.bu test.db
104
105  # insert corrupt byte(s)
106  hexio_write test.db 27   [format %02x 0x08]
107  hexio_write test.db 233  [format %02x 0x6a]
108  hexio_write test.db 328  [format %02x 0x67]
109  hexio_write test.db 750  [format %02x 0x1f]
110  hexio_write test.db 1132 [format %02x 0x52]
111  hexio_write test.db 1133 [format %02x 0x84]
112  hexio_write test.db 1220 [format %02x 0x01]
113  hexio_write test.db 3688 [format %02x 0xc1]
114  hexio_write test.db 3714 [format %02x 0x58]
115  hexio_write test.db 3746 [format %02x 0x9a]
116
117  sqlite3 db test.db
118  catchsql {UPDATE t1 SET y=1}
119} {1 {database disk image is malformed}}
120
121# test that a corrupt free cell size is handled (seed 13329)
122do_test corruptC-2.3 {
123  db close
124  copy_file test.bu test.db
125
126  # insert corrupt byte(s)
127  hexio_write test.db 1094 [format %02x 0x76]
128
129  sqlite3 db test.db
130  catchsql {UPDATE t1 SET y=1}
131} {1 {database disk image is malformed}}
132
133# test that a corrupt free cell size is handled (seed 169571)
134do_test corruptC-2.4 {
135  db close
136  copy_file test.bu test.db
137
138  # insert corrupt byte(s)
139  hexio_write test.db 3119 [format %02x 0xdf]
140
141  sqlite3 db test.db
142  catchsql {UPDATE t2 SET y='abcdef-uvwxyz'}
143} {1 {database disk image is malformed}}
144
145# test that a corrupt free cell size is handled (seed 169571)
146do_test corruptC-2.5 {
147  db close
148  copy_file test.bu test.db
149
150  # insert corrupt byte(s)
151  hexio_write test.db 3119 [format %02x 0xdf]
152  hexio_write test.db 4073 [format %02x 0xbf]
153
154  sqlite3 db test.db
155  catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
156  catchsql {PRAGMA integrity_check}
157} {0 {{*** in database main ***
158Corruption detected in cell 710 on page 4
159Multiple uses for byte 661 of page 4
160Fragmented space is 249 byte reported as 21 on page 4}}}
161
162# test that a corrupt free cell size is handled (seed 169595)
163do_test corruptC-2.6 {
164  db close
165  copy_file test.bu test.db
166
167  # insert corrupt byte(s)
168  hexio_write test.db 619 [format %02x 0xe2]
169  hexio_write test.db 3150 [format %02x 0xa8]
170
171  sqlite3 db test.db
172  catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
173} {1 {database disk image is malformed}}
174
175# corruption (seed 178692)
176do_test corruptC-2.7 {
177  db close
178  copy_file test.bu test.db
179
180  # insert corrupt byte(s)
181  hexio_write test.db 3074 [format %02x 0xa0]
182
183  sqlite3 db test.db
184  catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
185} {1 {database disk image is malformed}}
186
187# corruption (seed 179069)
188do_test corruptC-2.8 {
189  db close
190  copy_file test.bu test.db
191
192  # insert corrupt byte(s)
193  hexio_write test.db 1393 [format %02x 0x7d]
194  hexio_write test.db 84 [format %02x 0x19]
195  hexio_write test.db 3287 [format %02x 0x3b]
196  hexio_write test.db 2564 [format %02x 0xed]
197  hexio_write test.db 2139 [format %02x 0x55]
198
199  sqlite3 db test.db
200  catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;}
201} {1 {database disk image is malformed}}
202
203# corruption (seed 170434)
204do_test corruptC-2.9 {
205  db close
206  copy_file test.bu test.db
207
208  # insert corrupt byte(s)
209  hexio_write test.db 2095 [format %02x 0xd6]
210
211  sqlite3 db test.db
212  catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;}
213} {1 {database disk image is malformed}}
214
215# corruption (seed 186504)
216do_test corruptC-2.10 {
217  db close
218  copy_file test.bu test.db
219
220  # insert corrupt byte(s)
221  hexio_write test.db 3130 [format %02x 0x02]
222
223  sqlite3 db test.db
224  catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
225} {1 {database disk image is malformed}}
226
227# corruption (seed 1589)
228do_test corruptC-2.11 {
229  db close
230  copy_file test.bu test.db
231
232  # insert corrupt byte(s)
233  hexio_write test.db 55 [format %02x 0xa7]
234
235  sqlite3 db test.db
236  catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;}
237} {1 {database disk image is malformed}}
238
239# corruption (seed 14166)
240do_test corruptC-2.12 {
241  db close
242  copy_file test.bu test.db
243
244  # insert corrupt byte(s)
245  hexio_write test.db 974 [format %02x 0x2e]
246
247  sqlite3 db test.db
248  catchsql {SELECT count(*) FROM sqlite_master;}
249} {1 {malformed database schema (t1i1) - corrupt database}}
250
251# corruption (seed 218803)
252do_test corruptC-2.13 {
253  db close
254  copy_file test.bu test.db
255
256  # insert corrupt byte(s)
257  hexio_write test.db 102 [format %02x 0x12]
258
259  sqlite3 db test.db
260  catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;}
261} {1 {database disk image is malformed}}
262
263do_test corruptC-2.14 {
264  db close
265  copy_file test.bu test.db
266
267  sqlite3 db test.db
268  set blob [string repeat abcdefghij 10000]
269  execsql { INSERT INTO t1 VALUES (1, $blob) }
270
271  sqlite3 db test.db
272  set filesize [file size test.db]
273  hexio_write test.db [expr $filesize-2048] 00000001
274  catchsql {DELETE FROM t1 WHERE rowid = (SELECT max(rowid) FROM t1)}
275} {1 {database disk image is malformed}}
276
277#
278# Now test for a series of quasi-random seeds.
279# We loop over the entire file size and touch
280# each byte at least once.
281for {set tn 0} {$tn<$fsize} {incr tn 1} {
282
283  # setup for test
284  db close
285  copy_file test.bu test.db
286  sqlite3 db test.db
287
288  # Seek to a random location in the file, and write a random single byte
289  # value.  Then do various operations on the file to make sure that
290  # the database engine can handle the corruption gracefully.
291  #
292  set last 0
293  for {set i 1} {$i<=512 && !$last} {incr i 1} {
294
295    db close
296    if {$i==1} {
297      # on the first corrupt value, use location $tn
298      # this ensures that we touch each location in the
299      # file at least once.
300      set roffset $tn
301    } else {
302      # insert random byte at random location
303      set roffset [random $fsize]
304    }
305    set rbyte [format %02x [random 255]]
306
307    # You can uncomment the following to have it trace
308    # exactly how it's corrupting the file.  This is
309    # useful for generating the "seed specific" tests
310    # above.
311    # set rline "$roffset $rbyte"
312    # puts stdout $rline
313
314    hexio_write test.db $roffset $rbyte
315    sqlite3 db test.db
316
317    # do a few random operations to make sure that if
318    # they error, they error gracefully instead of crashing.
319    do_test corruptC-3.$tn.($qseed).$i.1 {
320      catchsql {SELECT count(*) FROM sqlite_master}
321      set x {}
322    } {}
323    do_test corruptC-3.$tn.($qseed).$i.2 {
324      catchsql {SELECT count(*) FROM t1}
325      set x {}
326    } {}
327    do_test corruptC-3.$tn.($qseed).$i.3 {
328      catchsql {SELECT count(*) FROM t1 WHERE x>13}
329      set x {}
330    } {}
331    do_test corruptC-3.$tn.($qseed).$i.4 {
332      catchsql {SELECT count(*) FROM t2}
333      set x {}
334    } {}
335    do_test corruptC-3.$tn.($qseed).$i.5 {
336      catchsql {SELECT count(*) FROM t2 WHERE x<13}
337      set x {}
338    } {}
339    do_test corruptC-3.$tn.($qseed).$i.6 {
340      catchsql {BEGIN; UPDATE t1 SET y=1; ROLLBACK;}
341      set x {}
342    } {}
343    do_test corruptC-3.$tn.($qseed).$i.7 {
344      catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
345      set x {}
346    } {}
347    do_test corruptC-3.$tn.($qseed).$i.8 {
348      catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;}
349      set x {}
350    } {}
351    do_test corruptC-3.$tn.($qseed).$i.9 {
352      catchsql {BEGIN; DELETE FROM t2 WHERE x<13; ROLLBACK;}
353      set x {}
354    } {}
355    do_test corruptC-3.$tn.($qseed).$i.10 {
356      catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;}
357      set x {}
358    } {}
359
360    # check the integrity of the database.
361    # once the corruption is detected, we can stop.
362    ifcapable {integrityck} {
363      set res [ catchsql {PRAGMA integrity_check} ]
364      set ans [lindex $res 1]
365      if { [ string compare $ans "ok" ] != 0 } {
366        set last -1
367      }
368    }
369    # if we are not capable of doing an integrity check,
370    # stop after corrupting 5 bytes.
371    ifcapable {!integrityck} {
372      if { $i > 5 } {
373        set last -1
374      }
375    }
376
377    # Check that no page references were leaked.
378    # TBD:  need to figure out why this doesn't work
379    # work with ROLLBACKs...
380    if {0} {
381      do_test corruptC-3.$tn.($qseed).$i.11 {
382        set bt [btree_from_db db]
383        db_enter db
384        array set stats [btree_pager_stats $bt]
385        db_leave db
386        set stats(ref)
387      } {0}
388    }
389  }
390  # end for i
391
392}
393# end for tn
394
395finish_test
396