xref: /sqlite-3.40.0/test/corrupt2.test (revision e89feee5)
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.
15#
16# $Id: corrupt2.test,v 1.20 2009/04/06 17:50:03 danielk1977 Exp $
17
18set testdir [file dirname $argv0]
19source $testdir/tester.tcl
20set testprefix corrupt2
21
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).
24#
25do_not_use_codec
26
27# These tests deal with corrupt database files
28#
29database_may_be_corrupt
30
31set presql ""
32catch { set presql "$::G(perm:presql);" }
33unset -nocomplain ::G(perm:presql)
34
35# The following tests - corrupt2-1.* - create some databases corrupted in
36# specific ways and ensure that SQLite detects them as corrupt.
37#
38do_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} {}
45
46do_test corrupt2-1.2 {
47
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
56
57  sqlite3 db2 corrupt.db
58  catchsql "
59    $::presql
60    SELECT * FROM sqlite_master;
61  " db2
62} {1 {file is not a database}}
63
64do_test corrupt2-1.3 {
65  db2 close
66
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
76
77  sqlite3 db2 corrupt.db
78  catchsql "
79    $::presql
80    SELECT * FROM sqlite_master;
81  " db2
82} {1 {file is not a database}}
83
84do_test corrupt2-1.4 {
85  db2 close
86
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
96
97  sqlite3 db2 corrupt.db
98  catchsql "
99    $::presql
100    SELECT * FROM sqlite_master;
101  " db2
102} {1 {database disk image is malformed}}
103
104do_test corrupt2-1.5 {
105  db2 close
106
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
119
120  sqlite3 db2 corrupt.db
121  catchsql "
122    $::presql
123    SELECT * FROM sqlite_master;
124  " db2
125} {1 {database disk image is malformed}}
126db2 close
127
128# Corrupt a database by having 2 indices of the same name:
129do_test corrupt2-2.1 {
130
131  forcedelete corrupt.db
132  forcedelete corrupt.db-journal
133  forcecopy test.db corrupt.db
134
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
147
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}}
155
156db2 close
157
158do_test corrupt2-3.1 {
159  forcedelete corrupt.db
160  forcedelete corrupt.db-journal
161  sqlite3 db2 corrupt.db
162
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
175
176  db2 close
177
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
191
192  sqlite3 db2 corrupt.db
193  catchsql "
194    $::presql
195    DROP TABLE t1;
196  " db2
197} {1 {database disk image is malformed}}
198
199do_test corrupt2-4.1 {
200  catchsql {
201    SELECT * FROM t2;
202  } db2
203} {1 {database disk image is malformed}}
204
205db2 close
206
207unset -nocomplain result
208do_test corrupt2-5.1 {
209  forcedelete corrupt.db
210  forcedelete corrupt.db-journal
211  sqlite3 db2 corrupt.db
212
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
226
227  db2 close
228
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
244
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 ***
253On tree page 2 cell 0: 2nd reference to page 10
254Page 4 is never used}}
255
256db2 close
257
258proc corruption_test {args} {
259  set A(-corrupt) {}
260  set A(-sqlprep) {}
261  set A(-tclprep) {}
262  array set A $args
263
264  catch {db close}
265  forcedelete corrupt.db
266  forcedelete corrupt.db-journal
267
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
274
275  eval $A(-corrupt)
276
277  sqlite3 db corrupt.db
278  eval $A(-test)
279}
280
281ifcapable 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  #
290
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  }
308
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  }
329
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  }
350
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  }
375}
376
377
378set 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;
390}
391
392corruption_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}}
408}
409
410corruption_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}}
429}
430
431corruption_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}}
447}
448
449corruption_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}}
475}
476
477corruption_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}
489}
490
491corruption_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}}
511}
512corruption_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}}
532}
533
534ifcapable 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  }
564}
565
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.
569#
570
571reset_db
572do_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;
577}
578
579do_execsql_test 14.1 {
580  PRAGMA integrity_check;
581  PRAGMA freelist_count;
582} {ok 3}
583
584# There are now 3 free pages. Modify the header-field so that it
585# (incorrectly) says that just 2 are free.
586do_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}
592
593do_execsql_test 14.3 {
594  PRAGMA integrity_check;
595} {{*** in database main ***
596Main freelist: size is 3 but should be 2}}
597
598# Use 2 of the free pages on the free-list.
599#
600do_execsql_test 14.4 {
601  INSERT INTO t1 VALUES(randomblob(2500));
602  PRAGMA freelist_count;
603} {0}
604
605do_execsql_test 14.5 {
606  PRAGMA integrity_check;
607} {{*** in database main ***
608Main freelist: size is 1 but should be 0}}
609
610
611finish_test
612
613finish_test
614