adds recent updates to CHANGELOG.md
[sqlcipher.git] / test / corrupt2.test
blobeee7ed322c52ca6200fc235cfa07a63c162a0b24
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
20 set testprefix corrupt2
22 # Do not use a codec for tests in this file, as the database file is
23 # manipulated directly using tcl scripts (using the [hexio_write] command).
25 do_not_use_codec
27 # These tests deal with corrupt database files
29 database_may_be_corrupt
31 set presql ""
32 catch { set presql "$::G(perm:presql);" }
33 unset -nocomplain ::G(perm:presql)
35 # The following tests - corrupt2-1.* - create some databases corrupted in
36 # specific ways and ensure that SQLite detects them as corrupt.
38 do_test corrupt2-1.1 {
39   execsql {
40     PRAGMA auto_vacuum=0;
41     PRAGMA page_size=1024;
42     CREATE TABLE abc(a, b, c);
43   }
44 } {}
46 do_test corrupt2-1.2 {
48   # Corrupt the 16 byte magic string at the start of the file
49   forcedelete corrupt.db
50   forcedelete corrupt.db-journal
51   forcecopy test.db corrupt.db
52   set f [open corrupt.db RDWR]
53   seek $f 8 start
54   puts $f blah
55   close $f
57   sqlite3 db2 corrupt.db
58   catchsql "
59     $::presql
60     SELECT * FROM sqlite_master;
61   " db2
62 } {1 {file is not a database}}
64 do_test corrupt2-1.3 {
65   db2 close
67   # Corrupt the page-size (bytes 16 and 17 of page 1).
68   forcedelete corrupt.db
69   forcedelete corrupt.db-journal
70   forcecopy test.db corrupt.db
71   set f [open corrupt.db RDWR]
72   fconfigure $f -encoding binary
73   seek $f 16 start
74   puts -nonewline $f "\x00\xFF"
75   close $f
77   sqlite3 db2 corrupt.db
78   catchsql "
79     $::presql
80     SELECT * FROM sqlite_master;
81   " db2
82 } {1 {file is not a database}}
84 do_test corrupt2-1.4 {
85   db2 close
87   # Corrupt the free-block list on page 1.
88   forcedelete corrupt.db
89   forcedelete corrupt.db-journal
90   forcecopy test.db corrupt.db
91   set f [open corrupt.db RDWR]
92   fconfigure $f -encoding binary
93   seek $f 101 start
94   puts -nonewline $f "\xFF\xFF"
95   close $f
97   sqlite3 db2 corrupt.db
98   catchsql "
99     $::presql
100     SELECT * FROM sqlite_master;
101   " db2
102 } {1 {database disk image is malformed}}
104 do_test corrupt2-1.5 {
105   db2 close
107   # Corrupt the free-block list on page 1.
108   forcedelete corrupt.db
109   forcedelete corrupt.db-journal
110   forcecopy test.db corrupt.db
111   set f [open corrupt.db RDWR]
112   fconfigure $f -encoding binary
113   seek $f 101 start
114   puts -nonewline $f "\x00\xC8"
115   seek $f 200 start
116   puts -nonewline $f "\x00\x00"
117   puts -nonewline $f "\x10\x00"
118   close $f
120   sqlite3 db2 corrupt.db
121   catchsql "
122     $::presql
123     SELECT * FROM sqlite_master;
124   " db2
125 } {1 {database disk image is malformed}}
126 db2 close
128 # Corrupt a database by having 2 indices of the same name:
129 do_test corrupt2-2.1 {
131   forcedelete corrupt.db
132   forcedelete corrupt.db-journal
133   forcecopy test.db corrupt.db
135   sqlite3 db2 corrupt.db 
136   sqlite3_db_config db2 DEFENSIVE 0
137   execsql "
138     $::presql
139     CREATE INDEX a1 ON abc(a);
140     CREATE INDEX a2 ON abc(b);
141     PRAGMA writable_schema = 1;
142     UPDATE sqlite_master 
143       SET name = 'a3', sql = 'CREATE INDEX a3' || substr(sql, 16, 10000)
144       WHERE type = 'index';
145     PRAGMA writable_schema = 0;
146   " db2
148   db2 close
149   sqlite3 db2 corrupt.db 
150   catchsql "
151     $::presql
152     SELECT * FROM sqlite_master;
153   " db2
154 } {1 {malformed database schema (a3) - index a3 already exists}}
156 db2 close
158 do_test corrupt2-3.1 {
159   forcedelete corrupt.db
160   forcedelete corrupt.db-journal
161   sqlite3 db2 corrupt.db 
163   execsql "
164     $::presql
165     PRAGMA auto_vacuum = 1;
166     PRAGMA page_size = 1024;
167     CREATE TABLE t1(a, b, c);
168     CREATE TABLE t2(a, b, c);
169     INSERT INTO t2 VALUES(randomblob(100), randomblob(100), randomblob(100));
170     INSERT INTO t2 SELECT * FROM t2;
171     INSERT INTO t2 SELECT * FROM t2;
172     INSERT INTO t2 SELECT * FROM t2;
173     INSERT INTO t2 SELECT * FROM t2;
174   " db2
176   db2 close
178   # On the root page of table t2 (page 4), set one of the child page-numbers
179   # to 0. This corruption will be detected when SQLite attempts to update
180   # the pointer-map after moving the content of page 4 to page 3 as part
181   # of the DROP TABLE operation below.
182   #
183   set fd [open corrupt.db r+]
184   fconfigure $fd -encoding binary -translation binary
185   seek $fd [expr 1024*3 + 12]
186   set zCelloffset [read $fd 2]
187   binary scan $zCelloffset S iCelloffset
188   seek $fd [expr 1024*3 + $iCelloffset]
189   puts -nonewline $fd "\00\00\00\00" 
190   close $fd
192   sqlite3 db2 corrupt.db 
193   catchsql "
194     $::presql
195     DROP TABLE t1;
196   " db2
197 } {1 {database disk image is malformed}}
199 do_test corrupt2-4.1 {
200   catchsql {
201     SELECT * FROM t2;
202   } db2
203 } {1 {database disk image is malformed}}
205 db2 close
207 unset -nocomplain result
208 do_test corrupt2-5.1 {
209   forcedelete corrupt.db
210   forcedelete corrupt.db-journal
211   sqlite3 db2 corrupt.db 
213   execsql "
214     $::presql
215     PRAGMA auto_vacuum = 0;
216     PRAGMA page_size = 1024;
217     CREATE TABLE t1(a, b, c);
218     CREATE TABLE t2(a, b, c);
219     INSERT INTO t2 VALUES(randomblob(100), randomblob(100), randomblob(100));
220     INSERT INTO t2 SELECT * FROM t2;
221     INSERT INTO t2 SELECT * FROM t2;
222     INSERT INTO t2 SELECT * FROM t2;
223     INSERT INTO t2 SELECT * FROM t2;
224     INSERT INTO t1 SELECT * FROM t2;
225   " db2
227   db2 close
229   # This block links a page from table t2 into the t1 table structure.
230   #
231   set fd [open corrupt.db r+]
232   fconfigure $fd -encoding binary -translation binary
233   seek $fd [expr 1024 + 12]
234   set zCelloffset [read $fd 2]
235   binary scan $zCelloffset S iCelloffset
236   seek $fd [expr 1024 + $iCelloffset]
237   set zChildPage [read $fd 4]
238   seek $fd [expr 2*1024 + 12]
239   set zCelloffset [read $fd 2]
240   binary scan $zCelloffset S iCelloffset
241   seek $fd [expr 2*1024 + $iCelloffset]
242   puts -nonewline $fd $zChildPage
243   close $fd
245   sqlite3 db2 corrupt.db 
246   db2 eval $::presql
247   db2 eval {SELECT rowid FROM t1} {
248     set result [db2 eval {pragma integrity_check}]
249     break
250   }
251   set result
252 } {{*** in database main ***
253 On tree page 2 cell 0: 2nd reference to page 10
254 Page 4 is never used}}
256 db2 close
258 proc corruption_test {args} {
259   set A(-corrupt) {}
260   set A(-sqlprep) {}
261   set A(-tclprep) {}
262   array set A $args
264   catch {db close}
265   forcedelete corrupt.db
266   forcedelete corrupt.db-journal
268   sqlite3 db corrupt.db 
269   sqlite3_db_config db DEFENSIVE 0
270   db eval $::presql
271   eval $A(-tclprep)
272   db eval $A(-sqlprep)
273   db close
275   eval $A(-corrupt)
277   sqlite3 db corrupt.db
278   eval $A(-test)
281 ifcapable autovacuum {
282   # The tests within this block - corrupt2-6.* - aim to test corruption
283   # detection within an incremental-vacuum. When an incremental-vacuum
284   # step is executed, the last non-free page of the database file is 
285   # moved into a free space in the body of the file. After doing so,
286   # the page reference in the parent page must be updated to refer
287   # to the new location. These tests test the outcome of corrupting
288   # that page reference before performing the incremental vacuum.
289   #
291   # The last page in the database page is the second page 
292   # in an overflow chain.
293   #
294   corruption_test -sqlprep {
295     PRAGMA auto_vacuum = incremental;
296     PRAGMA page_size = 1024;
297     CREATE TABLE t1(a, b);
298     INSERT INTO t1 VALUES(1, randomblob(2500));
299     INSERT INTO t1 VALUES(2, randomblob(2500));
300     DELETE FROM t1 WHERE a = 1;
301   } -corrupt {
302     hexio_write corrupt.db [expr 1024*5] 00000008
303   } -test {
304     do_test corrupt2-6.1 {
305       catchsql " $::presql pragma incremental_vacuum = 1 "
306     } {1 {database disk image is malformed}}
307   }
309   # The last page in the database page is a non-root b-tree page.
310   #
311   corruption_test -sqlprep {
312     PRAGMA auto_vacuum = incremental;
313     PRAGMA page_size = 1024;
314     CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
315     INSERT INTO t1 VALUES(1, randomblob(2500));
316     INSERT INTO t1 VALUES(2, randomblob(50));
317     INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
318     INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
319     INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
320     INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
321     DELETE FROM t1 WHERE a = 1;
322   } -corrupt {
323     hexio_write corrupt.db [expr 1024*2 + 8] 00000009
324   } -test {
325     do_test corrupt2-6.2 {
326       catchsql " $::presql pragma incremental_vacuum = 1 "
327     } {1 {database disk image is malformed}}
328   }
330   # Set up a pointer-map entry so that the last page of the database
331   # file appears to be a b-tree root page. This should be detected
332   # as corruption.
333   #
334   corruption_test -sqlprep {
335     PRAGMA auto_vacuum = incremental;
336     PRAGMA page_size = 1024;
337     CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
338     INSERT INTO t1 VALUES(1, randomblob(2500));
339     INSERT INTO t1 VALUES(2, randomblob(2500));
340     INSERT INTO t1 VALUES(3, randomblob(2500));
341     DELETE FROM t1 WHERE a = 1;
342   } -corrupt {
343     set nPage [expr [file size corrupt.db] / 1024]
344     hexio_write corrupt.db [expr 1024 + ($nPage-3)*5] 010000000
345   } -test {
346     do_test corrupt2-6.3 {
347       catchsql " $::presql pragma incremental_vacuum = 1 "
348     } {1 {database disk image is malformed}}
349   }
351   if {![nonzero_reserved_bytes]} {
352     corruption_test -sqlprep {
353       PRAGMA auto_vacuum = 1;
354       PRAGMA page_size = 1024;
355       CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
356       INSERT INTO t1 VALUES(1, randomblob(2500));
357       DELETE FROM t1 WHERE a = 1;
358     } -corrupt {
359       set nAppend [expr 1024*207 - [file size corrupt.db]]
360       set fd [open corrupt.db r+]
361       seek $fd 0 end
362       puts -nonewline $fd [string repeat x $nAppend]
363       close $fd
364       hexio_write corrupt.db 28 00000000
365     } -test {
366       do_test corrupt2-6.4 {
367         catchsql " 
368           $::presql 
369           BEGIN EXCLUSIVE;
370           COMMIT;
371         "
372       } {1 {database disk image is malformed}}
373     }
374   }
378 set sqlprep {
379   PRAGMA auto_vacuum = 0;
380   PRAGMA page_size = 1024;
381   CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
382   CREATE INDEX i1 ON t1(b);
383   INSERT INTO t1 VALUES(1, randomblob(50));
384   INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
385   INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
386   INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
387   INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
388   INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
389   INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1;
392 corruption_test -sqlprep $sqlprep -corrupt {
393   # Set the page-flags of one of the leaf pages of the index B-Tree to
394   # 0x0D (interpreted by SQLite as "leaf page of a table B-Tree").
395   #
396   set fd [open corrupt.db r+]
397   fconfigure $fd -translation binary -encoding binary
398   seek $fd [expr 1024*2 + 8] 
399   set zRightChild [read $fd 4]
400   binary scan $zRightChild I iRightChild
401   seek $fd [expr 1024*($iRightChild-1)]
402   puts -nonewline $fd "\x0D"
403   close $fd
404 } -test {
405   do_test corrupt2-7.1 {
406     catchsql " $::presql SELECT b FROM t1 ORDER BY b ASC "
407   } {1 {database disk image is malformed}}
410 corruption_test -sqlprep $sqlprep -corrupt {
411   # Mess up the page-header of one of the leaf pages of the index B-Tree.
412   # The corruption is detected as part of an OP_Prev opcode.
413   #
414   set fd [open corrupt.db r+]
415   fconfigure $fd -translation binary -encoding binary
416   seek $fd [expr 1024*2 + 12] 
417   set zCellOffset [read $fd 2]
418   binary scan $zCellOffset S iCellOffset
419   seek $fd [expr 1024*2 + $iCellOffset]
420   set zChild [read $fd 4]
421   binary scan $zChild I iChild
422   seek $fd [expr 1024*($iChild-1)+3]
423   puts -nonewline $fd "\xFFFF"
424   close $fd
425 } -test {
426   do_test corrupt2-7.1 {
427     catchsql " $::presql SELECT b FROM t1 ORDER BY b DESC "
428   } {1 {database disk image is malformed}}
431 corruption_test -sqlprep $sqlprep -corrupt {
432   # Set the page-flags of one of the leaf pages of the table B-Tree to
433   # 0x0A (interpreted by SQLite as "leaf page of an index B-Tree").
434   #
435   set fd [open corrupt.db r+]
436   fconfigure $fd -translation binary -encoding binary
437   seek $fd [expr 1024*1 + 8] 
438   set zRightChild [read $fd 4]
439   binary scan $zRightChild I iRightChild
440   seek $fd [expr 1024*($iRightChild-1)]
441   puts -nonewline $fd "\x0A"
442   close $fd
443 } -test {
444   do_test corrupt2-8.1 {
445     catchsql " $::presql SELECT * FROM t1 WHERE rowid=1000 "
446   } {1 {database disk image is malformed}}
449 corruption_test -sqlprep {
450   CREATE TABLE t1(a, b, c); CREATE TABLE t8(a, b, c); CREATE TABLE tE(a, b, c);
451   CREATE TABLE t2(a, b, c); CREATE TABLE t9(a, b, c); CREATE TABLE tF(a, b, c);
452   CREATE TABLE t3(a, b, c); CREATE TABLE tA(a, b, c); CREATE TABLE tG(a, b, c);
453   CREATE TABLE t4(a, b, c); CREATE TABLE tB(a, b, c); CREATE TABLE tH(a, b, c);
454   CREATE TABLE t5(a, b, c); CREATE TABLE tC(a, b, c); CREATE TABLE tI(a, b, c);
455   CREATE TABLE t6(a, b, c); CREATE TABLE tD(a, b, c); CREATE TABLE tJ(a, b, c);
456   CREATE TABLE x1(a, b, c); CREATE TABLE x8(a, b, c); CREATE TABLE xE(a, b, c);
457   CREATE TABLE x2(a, b, c); CREATE TABLE x9(a, b, c); CREATE TABLE xF(a, b, c);
458   CREATE TABLE x3(a, b, c); CREATE TABLE xA(a, b, c); CREATE TABLE xG(a, b, c);
459   CREATE TABLE x4(a, b, c); CREATE TABLE xB(a, b, c); CREATE TABLE xH(a, b, c);
460   CREATE TABLE x5(a, b, c); CREATE TABLE xC(a, b, c); CREATE TABLE xI(a, b, c);
461   CREATE TABLE x6(a, b, c); CREATE TABLE xD(a, b, c); CREATE TABLE xJ(a, b, c);
462 } -corrupt {
463   set fd [open corrupt.db r+]
464   fconfigure $fd -translation binary -encoding binary
465   seek $fd 108
466   set zRightChild [read $fd 4]
467   binary scan $zRightChild I iRightChild
468   seek $fd [expr 1024*($iRightChild-1)+3]
469   puts -nonewline $fd "\x00\x00"
470   close $fd
471 } -test {
472   do_test corrupt2-9.1 {
473     catchsql " $::presql SELECT sql FROM sqlite_master "
474   } {1 {database disk image is malformed}}
477 corruption_test -sqlprep {
478   CREATE TABLE t1(a, b, c);
479   CREATE TABLE t2(a, b, c);
480   PRAGMA writable_schema = 1;
481   UPDATE sqlite_master SET rootpage = NULL WHERE name = 't2';
482 } -test {
483   do_test corrupt2-10.1 {
484     catchsql " $::presql SELECT * FROM t2 "
485   } {1 {malformed database schema (t2)}}
486   do_test corrupt2-10.2 {
487     sqlite3_errcode db
488   } {SQLITE_CORRUPT}
491 corruption_test -sqlprep {
492   PRAGMA auto_vacuum = incremental;
493   CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
494   CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
495   INSERT INTO t1 VALUES(1, randstr(100,100));
496   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
497   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
498   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
499   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
500   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
501   INSERT INTO t2 SELECT * FROM t1;
502   DELETE FROM t1;
503 } -corrupt {
504   set offset [expr [file size corrupt.db] - 1024]
505   hexio_write corrupt.db $offset FF 
506   hexio_write corrupt.db 24   12345678
507 } -test {
508   do_test corrupt2-11.1 {
509     catchsql " $::presql PRAGMA incremental_vacuum "
510   } {1 {database disk image is malformed}}
512 corruption_test -sqlprep {
513   PRAGMA auto_vacuum = incremental;
514   CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
515   CREATE TABLE t2(a INTEGER PRIMARY KEY, b);
516   INSERT INTO t1 VALUES(1, randstr(100,100));
517   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
518   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
519   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
520   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
521   INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1;
522   INSERT INTO t2 SELECT * FROM t1;
523   DELETE FROM t1;
524 } -corrupt {
525   set pgno [expr [file size corrupt.db] / 1024]
526   hexio_write corrupt.db [expr 1024+5*($pgno-3)] 03 
527   hexio_write corrupt.db 24   12345678
528 } -test {
529   do_test corrupt2-12.1 {
530     catchsql " $::presql PRAGMA incremental_vacuum "
531   } {1 {database disk image is malformed}}
534 ifcapable autovacuum {
535   # It is not possible for the last page in a database file to be the
536   # pending-byte page (AKA the locking page). This test verifies that if
537   # an attempt is made to commit a transaction to such an auto-vacuum 
538   # database SQLITE_CORRUPT is returned.
539   #
540   corruption_test -tclprep {
541     db eval { 
542       PRAGMA auto_vacuum = full;
543       PRAGMA page_size = 1024;
544       CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
545       INSERT INTO t1 VALUES(NULL, randstr(50,50));
546     }
547     for {set ii 0} {$ii < 10} {incr ii} {
548       db eval " $::presql INSERT INTO t1 SELECT NULL, randstr(50,50) FROM t1 "
549     }
550   } -corrupt {
551     do_test corrupt2-13.1 {
552       file size corrupt.db
553     } $::sqlite_pending_byte
554     hexio_write corrupt.db [expr $::sqlite_pending_byte+1023] 00
555     hexio_write corrupt.db 28 00000000
556   } -test {
557     do_test corrupt2-13.2 {
558       file size corrupt.db
559     } [expr $::sqlite_pending_byte + 1024]
560     do_test corrupt2-13.3 {
561       catchsql { DELETE FROM t1 WHERE rowid < 30; }
562     } {1 {database disk image is malformed}}
563   }
566 #-------------------------------------------------------------------------
567 # Test that PRAGMA integrity_check detects cases where the freelist-count
568 # header field is smaller than the actual number of pages on the freelist.
571 reset_db
572 do_execsql_test 14.0 {
573   PRAGMA auto_vacuum = 0;
574   CREATE TABLE t1(x);
575   INSERT INTO t1 VALUES(randomblob(3500));
576   DELETE FROM t1;
579 do_execsql_test 14.1 {
580   PRAGMA integrity_check;
581   PRAGMA freelist_count;
582 } {ok 3}
584 # There are now 3 free pages. Modify the header-field so that it 
585 # (incorrectly) says that just 2 are free.
586 do_test 14.2 {
587   db close
588   hexio_write test.db 36 [hexio_render_int32 2]
589   sqlite3 db test.db
590   execsql { PRAGMA freelist_count }
591 } {2}
593 do_execsql_test 14.3 {
594   PRAGMA integrity_check;
595 } {{*** in database main ***
596 Main freelist: size is 3 but should be 2}}
598 # Use 2 of the free pages on the free-list.
600 do_execsql_test 14.4 {
601   INSERT INTO t1 VALUES(randomblob(2500));
602   PRAGMA freelist_count;
603 } {0}
605 do_execsql_test 14.5 {
606   PRAGMA integrity_check;
607 } {{*** in database main ***
608 Main freelist: size is 1 but should be 0}}
611 finish_test
613 finish_test