Add contributions section to README
[sqlcipher.git] / test / corrupt2.test
blob805a6148f0b4d8e48d0eb99b7475dde1bd140d55
1 # 2004 August 30
3 # The author disclaims copyright to this source code.  In place of
4 # a legal notice, here is a blessing:
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.
10 #***********************************************************************
11 # This file implements regression tests for SQLite library.
13 # This file implements tests to make sure SQLite does not crash or
14 # segfault if it sees a corrupt database file.
16 # $Id: corrupt2.test,v 1.20 2009/04/06 17:50:03 danielk1977 Exp $
18 set testdir [file dirname $argv0]
19 source $testdir/tester.tcl
21 # Do not use a codec for tests in this file, as the database file is
22 # manipulated directly using tcl scripts (using the [hexio_write] command).
24 do_not_use_codec
26 # These tests deal with corrupt database files
28 database_may_be_corrupt
30 set presql ""
31 catch { set presql "$::G(perm:presql);" }
32 unset -nocomplain ::G(perm:presql)
34 # The following tests - corrupt2-1.* - create some databases corrupted in
35 # specific ways and ensure that SQLite detects them as corrupt.
37 do_test corrupt2-1.1 {
38   execsql {
39     PRAGMA auto_vacuum=0;
40     PRAGMA page_size=1024;
41     CREATE TABLE abc(a, b, c);
42   }
43 } {}
45 do_test corrupt2-1.2 {
47   # Corrupt the 16 byte magic string at the start of the file
48   forcedelete corrupt.db
49   forcedelete corrupt.db-journal
50   forcecopy test.db corrupt.db
51   set f [open corrupt.db RDWR]
52   seek $f 8 start
53   puts $f blah
54   close $f
56   sqlite3 db2 corrupt.db
57   catchsql "
58     $::presql
59     SELECT * FROM sqlite_master;
60   " db2
61 } {1 {file is encrypted or is not a database}}
63 do_test corrupt2-1.3 {
64   db2 close
66   # Corrupt the page-size (bytes 16 and 17 of page 1).
67   forcedelete corrupt.db
68   forcedelete corrupt.db-journal
69   forcecopy test.db corrupt.db
70   set f [open corrupt.db RDWR]
71   fconfigure $f -encoding binary
72   seek $f 16 start
73   puts -nonewline $f "\x00\xFF"
74   close $f
76   sqlite3 db2 corrupt.db
77   catchsql "
78     $::presql
79     SELECT * FROM sqlite_master;
80   " db2
81 } {1 {file is encrypted or is not a database}}
83 do_test corrupt2-1.4 {
84   db2 close
86   # Corrupt the free-block list on page 1.
87   forcedelete corrupt.db
88   forcedelete corrupt.db-journal
89   forcecopy test.db corrupt.db
90   set f [open corrupt.db RDWR]
91   fconfigure $f -encoding binary
92   seek $f 101 start
93   puts -nonewline $f "\xFF\xFF"
94   close $f
96   sqlite3 db2 corrupt.db
97   catchsql "
98     $::presql
99     SELECT * FROM sqlite_master;
100   " db2
101 } {1 {database disk image is malformed}}
103 do_test corrupt2-1.5 {
104   db2 close
106   # Corrupt the free-block list on page 1.
107   forcedelete corrupt.db
108   forcedelete corrupt.db-journal
109   forcecopy test.db corrupt.db
110   set f [open corrupt.db RDWR]
111   fconfigure $f -encoding binary
112   seek $f 101 start
113   puts -nonewline $f "\x00\xC8"
114   seek $f 200 start
115   puts -nonewline $f "\x00\x00"
116   puts -nonewline $f "\x10\x00"
117   close $f
119   sqlite3 db2 corrupt.db
120   catchsql "
121     $::presql
122     SELECT * FROM sqlite_master;
123   " db2
124 } {1 {database disk image is malformed}}
125 db2 close
127 # Corrupt a database by having 2 indices of the same name:
128 do_test corrupt2-2.1 {
130   forcedelete corrupt.db
131   forcedelete corrupt.db-journal
132   forcecopy test.db corrupt.db
134   sqlite3 db2 corrupt.db 
135   execsql "
136     $::presql
137     CREATE INDEX a1 ON abc(a);
138     CREATE INDEX a2 ON abc(b);
139     PRAGMA writable_schema = 1;
140     UPDATE sqlite_master 
141       SET name = 'a3', sql = 'CREATE INDEX a3' || substr(sql, 16, 10000)
142       WHERE type = 'index';
143     PRAGMA writable_schema = 0;
144   " db2
146   db2 close
147   sqlite3 db2 corrupt.db 
148   catchsql "
149     $::presql
150     SELECT * FROM sqlite_master;
151   " db2
152 } {1 {malformed database schema (a3) - index a3 already exists}}
154 db2 close
156 do_test corrupt2-3.1 {
157   forcedelete corrupt.db
158   forcedelete corrupt.db-journal
159   sqlite3 db2 corrupt.db 
161   execsql "
162     $::presql
163     PRAGMA auto_vacuum = 1;
164     PRAGMA page_size = 1024;
165     CREATE TABLE t1(a, b, c);
166     CREATE TABLE t2(a, b, c);
167     INSERT INTO t2 VALUES(randomblob(100), randomblob(100), randomblob(100));
168     INSERT INTO t2 SELECT * FROM t2;
169     INSERT INTO t2 SELECT * FROM t2;
170     INSERT INTO t2 SELECT * FROM t2;
171     INSERT INTO t2 SELECT * FROM t2;
172   " db2
174   db2 close
176   # On the root page of table t2 (page 4), set one of the child page-numbers
177   # to 0. This corruption will be detected when SQLite attempts to update
178   # the pointer-map after moving the content of page 4 to page 3 as part
179   # of the DROP TABLE operation below.
180   #
181   set fd [open corrupt.db r+]
182   fconfigure $fd -encoding binary -translation binary
183   seek $fd [expr 1024*3 + 12]
184   set zCelloffset [read $fd 2]
185   binary scan $zCelloffset S iCelloffset
186   seek $fd [expr 1024*3 + $iCelloffset]
187   puts -nonewline $fd "\00\00\00\00" 
188   close $fd
190   sqlite3 db2 corrupt.db 
191   catchsql "
192     $::presql
193     DROP TABLE t1;
194   " db2
195 } {1 {database disk image is malformed}}
197 do_test corrupt2-4.1 {
198   catchsql {
199     SELECT * FROM t2;
200   } db2
201 } {1 {database disk image is malformed}}
203 db2 close
205 unset -nocomplain result
206 do_test corrupt2-5.1 {
207   forcedelete corrupt.db
208   forcedelete corrupt.db-journal
209   sqlite3 db2 corrupt.db 
211   execsql "
212     $::presql
213     PRAGMA auto_vacuum = 0;
214     PRAGMA page_size = 1024;
215     CREATE TABLE t1(a, b, c);
216     CREATE TABLE t2(a, b, c);
217     INSERT INTO t2 VALUES(randomblob(100), randomblob(100), randomblob(100));
218     INSERT INTO t2 SELECT * FROM t2;
219     INSERT INTO t2 SELECT * FROM t2;
220     INSERT INTO t2 SELECT * FROM t2;
221     INSERT INTO t2 SELECT * FROM t2;
222     INSERT INTO t1 SELECT * FROM t2;
223   " db2
225   db2 close
227   # This block links a page from table t2 into the t1 table structure.
228   #
229   set fd [open corrupt.db r+]
230   fconfigure $fd -encoding binary -translation binary
231   seek $fd [expr 1024 + 12]
232   set zCelloffset [read $fd 2]
233   binary scan $zCelloffset S iCelloffset
234   seek $fd [expr 1024 + $iCelloffset]
235   set zChildPage [read $fd 4]
236   seek $fd [expr 2*1024 + 12]
237   set zCelloffset [read $fd 2]
238   binary scan $zCelloffset S iCelloffset
239   seek $fd [expr 2*1024 + $iCelloffset]
240   puts -nonewline $fd $zChildPage
241   close $fd
243   sqlite3 db2 corrupt.db 
244   db2 eval $::presql
245   db2 eval {SELECT rowid FROM t1} {
246     set result [db2 eval {pragma integrity_check}]
247     break
248   }
249   set result
250 } {{*** in database main ***
251 On tree page 2 cell 0: 2nd reference to page 10
252 On tree page 2 cell 1: Child page depth differs
253 Page 4 is never used}}
255 db2 close
257 proc corruption_test {args} {
258   set A(-corrupt) {}
259   set A(-sqlprep) {}
260   set A(-tclprep) {}
261   array set A $args
263   catch {db close}
264   forcedelete corrupt.db
265   forcedelete corrupt.db-journal
267   sqlite3 db corrupt.db 
268   db eval $::presql
269   eval $A(-tclprep)
270   db eval $A(-sqlprep)
271   db close
273   eval $A(-corrupt)
275   sqlite3 db corrupt.db
276   eval $A(-test)
279 ifcapable autovacuum {
280   # The tests within this block - corrupt2-6.* - aim to test corruption
281   # detection within an incremental-vacuum. When an incremental-vacuum
282   # step is executed, the last non-free page of the database file is 
283   # moved into a free space in the body of the file. After doing so,
284   # the page reference in the parent page must be updated to refer
285   # to the new location. These tests test the outcome of corrupting
286   # that page reference before performing the incremental vacuum.
287   #
289   # The last page in the database page is the second page 
290   # in an overflow chain.
291   #
292   corruption_test -sqlprep {
293     PRAGMA auto_vacuum = incremental;
294     PRAGMA page_size = 1024;
295     CREATE TABLE t1(a, b);
296     INSERT INTO t1 VALUES(1, randomblob(2500));
297     INSERT INTO t1 VALUES(2, randomblob(2500));
298     DELETE FROM t1 WHERE a = 1;
299   } -corrupt {
300     hexio_write corrupt.db [expr 1024*5] 00000008
301   } -test {
302     do_test corrupt2-6.1 {
303       catchsql " $::presql pragma incremental_vacuum = 1 "
304     } {1 {database disk image is malformed}}
305   }
307   # The last page in the database page is a non-root b-tree page.
308   #
309   corruption_test -sqlprep {
310     PRAGMA auto_vacuum = incremental;
311     PRAGMA page_size = 1024;
312     CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
313     INSERT INTO t1 VALUES(1, randomblob(2500));
314     INSERT INTO t1 VALUES(2, randomblob(50));
315     INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
316     INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
317     INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
318     INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
319     DELETE FROM t1 WHERE a = 1;
320   } -corrupt {
321     hexio_write corrupt.db [expr 1024*2 + 8] 00000009
322   } -test {
323     do_test corrupt2-6.2 {
324       catchsql " $::presql pragma incremental_vacuum = 1 "
325     } {1 {database disk image is malformed}}
326   }
328   # Set up a pointer-map entry so that the last page of the database
329   # file appears to be a b-tree root page. This should be detected
330   # as corruption.
331   #
332   corruption_test -sqlprep {
333     PRAGMA auto_vacuum = incremental;
334     PRAGMA page_size = 1024;
335     CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
336     INSERT INTO t1 VALUES(1, randomblob(2500));
337     INSERT INTO t1 VALUES(2, randomblob(2500));
338     INSERT INTO t1 VALUES(3, randomblob(2500));
339     DELETE FROM t1 WHERE a = 1;
340   } -corrupt {
341     set nPage [expr [file size corrupt.db] / 1024]
342     hexio_write corrupt.db [expr 1024 + ($nPage-3)*5] 010000000
343   } -test {
344     do_test corrupt2-6.3 {
345       catchsql " $::presql pragma incremental_vacuum = 1 "
346     } {1 {database disk image is malformed}}
347   }
349   corruption_test -sqlprep {
350     PRAGMA auto_vacuum = 1;
351     PRAGMA page_size = 1024;
352     CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
353     INSERT INTO t1 VALUES(1, randomblob(2500));
354     DELETE FROM t1 WHERE a = 1;
355   } -corrupt {
356     set nAppend [expr 1024*207 - [file size corrupt.db]]
357     set fd [open corrupt.db r+]
358     seek $fd 0 end
359     puts -nonewline $fd [string repeat x $nAppend]
360     close $fd
361     hexio_write corrupt.db 28 00000000
362   } -test {
363     do_test corrupt2-6.4 {
364       catchsql " 
365         $::presql 
366         BEGIN EXCLUSIVE;
367         COMMIT;
368       "
369     } {1 {database disk image is malformed}}
370   }
374 set sqlprep {
375   PRAGMA auto_vacuum = 0;
376   PRAGMA page_size = 1024;
377   CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
378   CREATE INDEX i1 ON t1(b);
379   INSERT INTO t1 VALUES(1, randomblob(50));
380   INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
381   INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
382   INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
383   INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
384   INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
385   INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
388 corruption_test -sqlprep $sqlprep -corrupt {
389   # Set the page-flags of one of the leaf pages of the index B-Tree to
390   # 0x0D (interpreted by SQLite as "leaf page of a table B-Tree").
391   #
392   set fd [open corrupt.db r+]
393   fconfigure $fd -translation binary -encoding binary
394   seek $fd [expr 1024*2 + 8] 
395   set zRightChild [read $fd 4]
396   binary scan $zRightChild I iRightChild
397   seek $fd [expr 1024*($iRightChild-1)]
398   puts -nonewline $fd "\x0D"
399   close $fd
400 } -test {
401   do_test corrupt2-7.1 {
402     catchsql " $::presql SELECT b FROM t1 ORDER BY b ASC "
403   } {1 {database disk image is malformed}}
406 corruption_test -sqlprep $sqlprep -corrupt {
407   # Mess up the page-header of one of the leaf pages of the index B-Tree.
408   # The corruption is detected as part of an OP_Prev opcode.
409   #
410   set fd [open corrupt.db r+]
411   fconfigure $fd -translation binary -encoding binary
412   seek $fd [expr 1024*2 + 12] 
413   set zCellOffset [read $fd 2]
414   binary scan $zCellOffset S iCellOffset
415   seek $fd [expr 1024*2 + $iCellOffset]
416   set zChild [read $fd 4]
417   binary scan $zChild I iChild
418   seek $fd [expr 1024*($iChild-1)+3]
419   puts -nonewline $fd "\xFFFF"
420   close $fd
421 } -test {
422   do_test corrupt2-7.1 {
423     catchsql " $::presql SELECT b FROM t1 ORDER BY b DESC "
424   } {1 {database disk image is malformed}}
427 corruption_test -sqlprep $sqlprep -corrupt {
428   # Set the page-flags of one of the leaf pages of the table B-Tree to
429   # 0x0A (interpreted by SQLite as "leaf page of an index B-Tree").
430   #
431   set fd [open corrupt.db r+]
432   fconfigure $fd -translation binary -encoding binary
433   seek $fd [expr 1024*1 + 8] 
434   set zRightChild [read $fd 4]
435   binary scan $zRightChild I iRightChild
436   seek $fd [expr 1024*($iRightChild-1)]
437   puts -nonewline $fd "\x0A"
438   close $fd
439 } -test {
440   do_test corrupt2-8.1 {
441     catchsql " $::presql SELECT * FROM t1 WHERE rowid=1000 "
442   } {1 {database disk image is malformed}}
445 corruption_test -sqlprep {
446   CREATE TABLE t1(a, b, c); CREATE TABLE t8(a, b, c); CREATE TABLE tE(a, b, c);
447   CREATE TABLE t2(a, b, c); CREATE TABLE t9(a, b, c); CREATE TABLE tF(a, b, c);
448   CREATE TABLE t3(a, b, c); CREATE TABLE tA(a, b, c); CREATE TABLE tG(a, b, c);
449   CREATE TABLE t4(a, b, c); CREATE TABLE tB(a, b, c); CREATE TABLE tH(a, b, c);
450   CREATE TABLE t5(a, b, c); CREATE TABLE tC(a, b, c); CREATE TABLE tI(a, b, c);
451   CREATE TABLE t6(a, b, c); CREATE TABLE tD(a, b, c); CREATE TABLE tJ(a, b, c);
452   CREATE TABLE x1(a, b, c); CREATE TABLE x8(a, b, c); CREATE TABLE xE(a, b, c);
453   CREATE TABLE x2(a, b, c); CREATE TABLE x9(a, b, c); CREATE TABLE xF(a, b, c);
454   CREATE TABLE x3(a, b, c); CREATE TABLE xA(a, b, c); CREATE TABLE xG(a, b, c);
455   CREATE TABLE x4(a, b, c); CREATE TABLE xB(a, b, c); CREATE TABLE xH(a, b, c);
456   CREATE TABLE x5(a, b, c); CREATE TABLE xC(a, b, c); CREATE TABLE xI(a, b, c);
457   CREATE TABLE x6(a, b, c); CREATE TABLE xD(a, b, c); CREATE TABLE xJ(a, b, c);
458 } -corrupt {
459   set fd [open corrupt.db r+]
460   fconfigure $fd -translation binary -encoding binary
461   seek $fd 108
462   set zRightChild [read $fd 4]
463   binary scan $zRightChild I iRightChild
464   seek $fd [expr 1024*($iRightChild-1)+3]
465   puts -nonewline $fd "\x00\x00"
466   close $fd
467 } -test {
468   do_test corrupt2-9.1 {
469     catchsql " $::presql SELECT sql FROM sqlite_master "
470   } {1 {database disk image is malformed}}
473 corruption_test -sqlprep {
474   CREATE TABLE t1(a, b, c);
475   CREATE TABLE t2(a, b, c);
476   PRAGMA writable_schema = 1;
477   UPDATE sqlite_master SET rootpage = NULL WHERE name = 't2';
478 } -test {
479   do_test corrupt2-10.1 {
480     catchsql " $::presql SELECT * FROM t2 "
481   } {1 {malformed database schema (t2)}}
482   do_test corrupt2-10.2 {
483     sqlite3_errcode db
484   } {SQLITE_CORRUPT}
487 corruption_test -sqlprep {
488   PRAGMA auto_vacuum = incremental;
489   CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
490   CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
491   INSERT INTO t1 VALUES(1, randstr(100,100));
492   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
493   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
494   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
495   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
496   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
497   INSERT INTO t2 SELECT * FROM t1;
498   DELETE FROM t1;
499 } -corrupt {
500   set offset [expr [file size corrupt.db] - 1024]
501   hexio_write corrupt.db $offset FF 
502   hexio_write corrupt.db 24   12345678
503 } -test {
504   do_test corrupt2-11.1 {
505     catchsql " $::presql PRAGMA incremental_vacuum "
506   } {1 {database disk image is malformed}}
508 corruption_test -sqlprep {
509   PRAGMA auto_vacuum = incremental;
510   CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
511   CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
512   INSERT INTO t1 VALUES(1, randstr(100,100));
513   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
514   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
515   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
516   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
517   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
518   INSERT INTO t2 SELECT * FROM t1;
519   DELETE FROM t1;
520 } -corrupt {
521   set pgno [expr [file size corrupt.db] / 1024]
522   hexio_write corrupt.db [expr 1024+5*($pgno-3)] 03 
523   hexio_write corrupt.db 24   12345678
524 } -test {
525   do_test corrupt2-12.1 {
526     catchsql " $::presql PRAGMA incremental_vacuum "
527   } {1 {database disk image is malformed}}
530 ifcapable autovacuum {
531   # It is not possible for the last page in a database file to be the
532   # pending-byte page (AKA the locking page). This test verifies that if
533   # an attempt is made to commit a transaction to such an auto-vacuum 
534   # database SQLITE_CORRUPT is returned.
535   #
536   corruption_test -tclprep {
537     db eval { 
538       PRAGMA auto_vacuum = full;
539       PRAGMA page_size = 1024;
540       CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
541       INSERT INTO t1 VALUES(NULL, randstr(50,50));
542     }
543     for {set ii 0} {$ii < 10} {incr ii} {
544       db eval " $::presql INSERT INTO t1 SELECT NULL, randstr(50,50) FROM t1 "
545     }
546   } -corrupt {
547     do_test corrupt2-13.1 {
548       file size corrupt.db
549     } $::sqlite_pending_byte
550     hexio_write corrupt.db [expr $::sqlite_pending_byte+1023] 00
551     hexio_write corrupt.db 28 00000000
552   } -test {
553     do_test corrupt2-13.2 {
554       file size corrupt.db
555     } [expr $::sqlite_pending_byte + 1024]
556     do_test corrupt2-13.3 {
557       catchsql { DELETE FROM t1 WHERE rowid < 30; }
558     } {1 {database disk image is malformed}}
559   }
562 finish_test