xref: /sqlite-3.40.0/test/corruptC.test (revision e589a67f)
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.11 2009/04/11 16:06:15 danielk1977 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
279for {set tn 0} {$tn<1024} {incr tn 1} {
280
281  # setup for test
282  db close
283  copy_file test.bu test.db
284  sqlite3 db test.db
285
286  # Seek to a random location in the file, and write a random single byte
287  # value.  Then do various operations on the file to make sure that
288  # the database engine can handle the corruption gracefully.
289  #
290  set last 0
291  for {set i 1} {$i<=512 && !$last} {incr i 1} {
292
293    # insert random byte at random location
294    db close
295    set roffset [random $fsize]
296    set rbyte [format %02x [random 255]]
297
298    # You can uncomment the following to have it trace
299    # exactly how it's corrupting the file.  This is
300    # useful for generating the "seed specific" tests
301    # above.
302    # set rline "$roffset $rbyte"
303    # puts stdout $rline
304
305    hexio_write test.db $roffset $rbyte
306    sqlite3 db test.db
307
308    # do a few random operations to make sure that if
309    # they error, they error gracefully instead of crashing.
310    do_test corruptC-3.$tn.($qseed).$i.1 {
311      catchsql {SELECT count(*) FROM sqlite_master}
312      set x {}
313    } {}
314    do_test corruptC-3.$tn.($qseed).$i.2 {
315      catchsql {SELECT count(*) FROM t1}
316      set x {}
317    } {}
318    do_test corruptC-3.$tn.($qseed).$i.3 {
319      catchsql {SELECT count(*) FROM t1 WHERE x>13}
320      set x {}
321    } {}
322    do_test corruptC-3.$tn.($qseed).$i.4 {
323      catchsql {SELECT count(*) FROM t2}
324      set x {}
325    } {}
326    do_test corruptC-3.$tn.($qseed).$i.5 {
327      catchsql {SELECT count(*) FROM t2 WHERE x<13}
328      set x {}
329    } {}
330    do_test corruptC-3.$tn.($qseed).$i.6 {
331      catchsql {BEGIN; UPDATE t1 SET y=1; ROLLBACK;}
332      set x {}
333    } {}
334    do_test corruptC-3.$tn.($qseed).$i.7 {
335      catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
336      set x {}
337    } {}
338    do_test corruptC-3.$tn.($qseed).$i.8 {
339      catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;}
340      set x {}
341    } {}
342    do_test corruptC-3.$tn.($qseed).$i.9 {
343      catchsql {BEGIN; DELETE FROM t2 WHERE x<13; ROLLBACK;}
344      set x {}
345    } {}
346    do_test corruptC-3.$tn.($qseed).$i.10 {
347      catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;}
348      set x {}
349    } {}
350
351    # check the integrity of the database.
352    # once the corruption is detected, we can stop.
353    ifcapable {integrityck} {
354      set res [ catchsql {PRAGMA integrity_check} ]
355      set ans [lindex $res 1]
356      if { [ string compare $ans "ok" ] != 0 } {
357        set last -1
358      }
359    }
360    # if we are not capable of doing an integrity check,
361    # stop after corrupting 5 bytes.
362    ifcapable {!integrityck} {
363      if { $i > 5 } {
364        set last -1
365      }
366    }
367
368    # Check that no page references were leaked.
369    # TBD:  need to figure out why this doesn't work
370    # work with ROLLBACKs...
371    if {0} {
372      do_test corruptC-3.$tn.($qseed).$i.11 {
373        set bt [btree_from_db db]
374        db_enter db
375        array set stats [btree_pager_stats $bt]
376        db_leave db
377        set stats(ref)
378      } {0}
379    }
380  }
381  # end for i
382
383}
384# end for tn
385
386finish_test
387