xref: /sqlite-3.40.0/test/corrupt.test (revision b7c2cf0a)
1887d4b2bSdan# 2004 August 30 {}
2ee696e22Sdrh#
3ee696e22Sdrh# The author disclaims copyright to this source code.  In place of
4ee696e22Sdrh# a legal notice, here is a blessing:
5ee696e22Sdrh#
6ee696e22Sdrh#    May you do good and not evil.
7ee696e22Sdrh#    May you find forgiveness for yourself and forgive others.
8ee696e22Sdrh#    May you share freely, never taking more than you give.
9ee696e22Sdrh#
10ee696e22Sdrh#***********************************************************************
11ee696e22Sdrh# This file implements regression tests for SQLite library.
12ee696e22Sdrh#
13ee696e22Sdrh# This file implements tests to make sure SQLite does not crash or
14ee696e22Sdrh# segfault if it sees a corrupt database file.
15ee696e22Sdrh#
168f880a8cSdanielk1977# $Id: corrupt.test,v 1.12 2009/07/13 09:41:45 danielk1977 Exp $
175558a8a6Sdanielk1977
18fda06befSmistachkincatch {forcedelete test.db test.db-journal test.bu}
19ee696e22Sdrh
20ee696e22Sdrhset testdir [file dirname $argv0]
21ee696e22Sdrhsource $testdir/tester.tcl
22ee696e22Sdrh
2368928b6cSdan# Do not use a codec for tests in this file, as the database file is
2468928b6cSdan# manipulated directly using tcl scripts (using the [hexio_write] command).
2568928b6cSdan#
2668928b6cSdando_not_use_codec
2768928b6cSdan
2809fe6143Sdrh# These tests deal with corrupt database files
2909fe6143Sdrh#
3009fe6143Sdrhdatabase_may_be_corrupt
3109fe6143Sdrh
32ee696e22Sdrh# Construct a large database for testing.
33ee696e22Sdrh#
34ee696e22Sdrhdo_test corrupt-1.1 {
35ee696e22Sdrh  execsql {
36ee696e22Sdrh    BEGIN;
37ee696e22Sdrh    CREATE TABLE t1(x);
3836963fdcSdanielk1977    INSERT INTO t1 VALUES(randstr(100,100));
3936963fdcSdanielk1977    INSERT INTO t1 VALUES(randstr(90,90));
4036963fdcSdanielk1977    INSERT INTO t1 VALUES(randstr(80,80));
4136963fdcSdanielk1977    INSERT INTO t1 SELECT x || randstr(5,5) FROM t1;
4236963fdcSdanielk1977    INSERT INTO t1 SELECT x || randstr(6,6) FROM t1;
4336963fdcSdanielk1977    INSERT INTO t1 SELECT x || randstr(7,7) FROM t1;
4436963fdcSdanielk1977    INSERT INTO t1 SELECT x || randstr(8,8) FROM t1;
4536963fdcSdanielk1977    INSERT INTO t1 VALUES(randstr(3000,3000));
4636963fdcSdanielk1977    INSERT INTO t1 SELECT x || randstr(9,9) FROM t1;
4736963fdcSdanielk1977    INSERT INTO t1 SELECT x || randstr(10,10) FROM t1;
4836963fdcSdanielk1977    INSERT INTO t1 SELECT x || randstr(11,11) FROM t1;
4936963fdcSdanielk1977    INSERT INTO t1 SELECT x || randstr(12,12) FROM t1;
50ee696e22Sdrh    CREATE INDEX t1i1 ON t1(x);
51ee696e22Sdrh    CREATE TABLE t2 AS SELECT * FROM t1;
52ee696e22Sdrh    DELETE FROM t2 WHERE rowid%5!=0;
53ee696e22Sdrh    COMMIT;
54ee696e22Sdrh  }
554489f9bdSdanielk1977} {}
564489f9bdSdanielk1977integrity_check corrupt-1.2
57ee696e22Sdrh
58615ae553Sdrh# Setup for the tests.  Make a backup copy of the good database in test.bu.
59615ae553Sdrh# Create a string of garbage data that is 256 bytes long.
60615ae553Sdrh#
61fda06befSmistachkinforcecopy test.db test.bu
62ee696e22Sdrhset fsize [file size test.db]
63ee696e22Sdrhset junk "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
64ee696e22Sdrhwhile {[string length $junk]<256} {append junk $junk}
65ee696e22Sdrhset junk [string range $junk 0 255]
66ee696e22Sdrh
67615ae553Sdrh# Go through the database and write garbage data into each 256 segment
68615ae553Sdrh# of the file.  Then do various operations on the file to make sure that
69615ae553Sdrh# the database engine can recover gracefully from the corruption.
70615ae553Sdrh#
712ed11e7bSdanfor {set i [expr {1*256}]} {$i<$fsize-256} {incr i 256} {
72ee696e22Sdrh  set tn [expr {$i/256}]
73ee696e22Sdrh  db close
74fda06befSmistachkin  forcecopy test.bu test.db
75ee696e22Sdrh  set fd [open test.db r+]
76ee696e22Sdrh  fconfigure $fd -translation binary
77ee696e22Sdrh  seek $fd $i
78ee696e22Sdrh  puts -nonewline $fd $junk
79ee696e22Sdrh  close $fd
80ee696e22Sdrh  do_test corrupt-2.$tn.1 {
81ee696e22Sdrh    sqlite3 db test.db
82ee696e22Sdrh    catchsql {SELECT count(*) FROM sqlite_master}
83ee696e22Sdrh    set x {}
84ee696e22Sdrh  } {}
85ee696e22Sdrh  do_test corrupt-2.$tn.2 {
86ee696e22Sdrh    catchsql {SELECT count(*) FROM t1}
87ee696e22Sdrh    set x {}
88ee696e22Sdrh  } {}
89ee696e22Sdrh  do_test corrupt-2.$tn.3 {
90ee696e22Sdrh    catchsql {SELECT count(*) FROM t1 WHERE x>'abcdef'}
91ee696e22Sdrh    set x {}
92ee696e22Sdrh  } {}
93ee696e22Sdrh  do_test corrupt-2.$tn.4 {
94ee696e22Sdrh    catchsql {SELECT count(*) FROM t2}
95ee696e22Sdrh    set x {}
96ee696e22Sdrh  } {}
97ee696e22Sdrh  do_test corrupt-2.$tn.5 {
98ee696e22Sdrh    catchsql {CREATE TABLE t3 AS SELECT * FROM t1}
99ee696e22Sdrh    set x {}
100ee696e22Sdrh  } {}
101ee696e22Sdrh  do_test corrupt-2.$tn.6 {
102ee696e22Sdrh    catchsql {DROP TABLE t1}
103ee696e22Sdrh    set x {}
104ee696e22Sdrh  } {}
105ee696e22Sdrh  do_test corrupt-2.$tn.7 {
106ee696e22Sdrh    catchsql {PRAGMA integrity_check}
107ee696e22Sdrh    set x {}
108ee696e22Sdrh  } {}
10943e377afSdanielk1977
11043e377afSdanielk1977  # Check that no page references were leaked.
11143e377afSdanielk1977  do_test corrupt-2.$tn.8 {
11243e377afSdanielk1977    set bt [btree_from_db db]
11343e377afSdanielk1977    db_enter db
11443e377afSdanielk1977    array set stats [btree_pager_stats $bt]
11543e377afSdanielk1977    db_leave db
11643e377afSdanielk1977    set stats(ref)
11743e377afSdanielk1977  } {0}
118ee696e22Sdrh}
119ee696e22Sdrh
120ac171788Sdanielk1977#------------------------------------------------------------------------
121ac171788Sdanielk1977# For these tests, swap the rootpage entries of t1 (a table) and t1i1 (an
122ac171788Sdanielk1977# index on t1) in sqlite_master. Then perform a few different queries
123ac171788Sdanielk1977# and make sure this is detected as corruption.
124ac171788Sdanielk1977#
125ac171788Sdanielk1977do_test corrupt-3.1 {
126ac171788Sdanielk1977  db close
127fda06befSmistachkin  forcecopy test.bu test.db
128ac171788Sdanielk1977  sqlite3 db test.db
129a2dc3b1aSdanielk1977  list
130a2dc3b1aSdanielk1977} {}
131ac171788Sdanielk1977do_test corrupt-3.2 {
132ac171788Sdanielk1977  set t1_r [execsql {SELECT rootpage FROM sqlite_master WHERE name = 't1i1'}]
133ac171788Sdanielk1977  set t1i1_r [execsql {SELECT rootpage FROM sqlite_master WHERE name = 't1'}]
134ac171788Sdanielk1977  set cookie [expr [execsql {PRAGMA schema_version}] + 1]
135*b7c2cf0aSdrh  sqlite3_db_config db DEFENSIVE 0
136ac171788Sdanielk1977  execsql "
137ac171788Sdanielk1977    PRAGMA writable_schema = 1;
138ac171788Sdanielk1977    UPDATE sqlite_master SET rootpage = $t1_r WHERE name = 't1';
139ac171788Sdanielk1977    UPDATE sqlite_master SET rootpage = $t1i1_r WHERE name = 't1i1';
140ac171788Sdanielk1977    PRAGMA writable_schema = 0;
141ac171788Sdanielk1977    PRAGMA schema_version = $cookie;
142ac171788Sdanielk1977  "
143a2dc3b1aSdanielk1977} {}
144ac171788Sdanielk1977
145ac171788Sdanielk1977# This one tests the case caught by code in checkin [2313].
146ac171788Sdanielk1977do_test corrupt-3.3 {
147ac171788Sdanielk1977  db close
148ac171788Sdanielk1977  sqlite3 db test.db
149ac171788Sdanielk1977  catchsql {
150ac171788Sdanielk1977    INSERT INTO t1 VALUES('abc');
151ac171788Sdanielk1977  }
152ac171788Sdanielk1977} {1 {database disk image is malformed}}
153ac171788Sdanielk1977do_test corrupt-3.4 {
154ac171788Sdanielk1977  db close
155ac171788Sdanielk1977  sqlite3 db test.db
156ac171788Sdanielk1977  catchsql {
157ac171788Sdanielk1977    SELECT * FROM t1;
158ac171788Sdanielk1977  }
159ac171788Sdanielk1977} {1 {database disk image is malformed}}
160ac171788Sdanielk1977do_test corrupt-3.5 {
161ac171788Sdanielk1977  db close
162ac171788Sdanielk1977  sqlite3 db test.db
163ac171788Sdanielk1977  catchsql {
164ac171788Sdanielk1977    SELECT * FROM t1 WHERE oid = 10;
165ac171788Sdanielk1977  }
166ac171788Sdanielk1977} {1 {database disk image is malformed}}
167ac171788Sdanielk1977do_test corrupt-3.6 {
168ac171788Sdanielk1977  db close
169ac171788Sdanielk1977  sqlite3 db test.db
170ac171788Sdanielk1977  catchsql {
171ac171788Sdanielk1977    SELECT * FROM t1 WHERE x = 'abcde';
172ac171788Sdanielk1977  }
173ac171788Sdanielk1977} {1 {database disk image is malformed}}
174ac171788Sdanielk1977
175bd5969a2Sdanielk1977do_test corrupt-4.1 {
176bd5969a2Sdanielk1977  db close
177fda06befSmistachkin  forcedelete test.db test.db-journal
178bd5969a2Sdanielk1977  sqlite3 db test.db
179bd5969a2Sdanielk1977  execsql {
180bd5969a2Sdanielk1977    PRAGMA page_size = 1024;
181bd5969a2Sdanielk1977    CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
182bd5969a2Sdanielk1977  }
183bd5969a2Sdanielk1977  for {set i 0} {$i < 10} {incr i} {
184bd5969a2Sdanielk1977    set text [string repeat $i 220]
185bd5969a2Sdanielk1977    execsql { INSERT INTO t1 VALUES($i, $text) }
186bd5969a2Sdanielk1977  }
187bd5969a2Sdanielk1977  execsql { CREATE INDEX i1 ON t1(b) }
188bd5969a2Sdanielk1977} {}
189bd5969a2Sdanielk1977do_test corrupt-4.2 {
190bd5969a2Sdanielk1977  set iRoot [db one {SELECT rootpage FROM sqlite_master WHERE name = 'i1'}]
191bd5969a2Sdanielk1977  set iOffset [hexio_get_int [hexio_read test.db [expr 12+($iRoot-1)*1024] 2]]
192bd5969a2Sdanielk1977  set data [hexio_render_int32 [expr $iRoot - 1]]
193bd5969a2Sdanielk1977  hexio_write test.db [expr ($iRoot-1)*1024 + $iOffset] $data
194bd5969a2Sdanielk1977  db close
195bd5969a2Sdanielk1977  sqlite3 db test.db
196bd5969a2Sdanielk1977
197bd5969a2Sdanielk1977  # The following DELETE statement attempts to delete a cell stored on the
198bd5969a2Sdanielk1977  # root page of index i1. After this cell is deleted it must be replaced
199bd5969a2Sdanielk1977  # by a cell retrieved from the child page (a leaf) of the deleted cell.
200bd5969a2Sdanielk1977  # This will fail, as the block modified the database image so that the
201bd5969a2Sdanielk1977  # child page of the deleted cell is from a table (intkey) b-tree, not an
202bd5969a2Sdanielk1977  # index b-tree as expected. At one point this was causing an assert()
203bd5969a2Sdanielk1977  # to fail.
204bd5969a2Sdanielk1977  catchsql { DELETE FROM t1 WHERE rowid = 3 }
205bd5969a2Sdanielk1977} {1 {database disk image is malformed}}
206bd5969a2Sdanielk1977
2078f880a8cSdanielk1977do_test corrupt-5.1 {
2088f880a8cSdanielk1977  db close
209fda06befSmistachkin  forcedelete test.db test.db-journal
2108f880a8cSdanielk1977  sqlite3 db test.db
2118f880a8cSdanielk1977
2128f880a8cSdanielk1977  execsql { PRAGMA page_size = 1024 }
2138f880a8cSdanielk1977  set ct "CREATE TABLE t1(c0 "
2148f880a8cSdanielk1977  set i 0
2158f880a8cSdanielk1977  while {[string length $ct] < 950} { append ct ", c[incr i]" }
2168f880a8cSdanielk1977  append ct ")"
2178f880a8cSdanielk1977  execsql $ct
2188f880a8cSdanielk1977} {}
2198f880a8cSdanielk1977
2208f880a8cSdanielk1977do_test corrupt-5.2 {
2218f880a8cSdanielk1977  db close
2228f880a8cSdanielk1977  hexio_write test.db 108 00000000
2238f880a8cSdanielk1977  sqlite3 db test.db
2248f880a8cSdanielk1977  catchsql { SELECT * FROM sqlite_master }
2258f880a8cSdanielk1977} {1 {database disk image is malformed}}
2268f880a8cSdanielk1977
22759d60c2cSdan# At one point, the specific corruption caused by this test case was
22859d60c2cSdan# causing a buffer overwrite. Although a crash was never demonstrated,
22959d60c2cSdan# running this testcase under valgrind revealed the problem.
2304361e79fSdando_test corrupt-6.1 {
2314361e79fSdan  db close
232fda06befSmistachkin  forcedelete test.db test.db-journal
2334361e79fSdan  sqlite3 db test.db
2344361e79fSdan  execsql {
2354361e79fSdan    PRAGMA page_size = 1024; CREATE TABLE t1(x);
2364361e79fSdan  }
2374361e79fSdan
2384361e79fSdan  # The root page of t1 is 1024 bytes in size. The header is 8 bytes, and
2394361e79fSdan  # each of the cells inserted by the following INSERT statements consume
2404361e79fSdan  # 16 bytes (including the 2 byte cell-offset array entry). So the page
2414361e79fSdan  # can contain up to 63 cells.
2424361e79fSdan  for {set i 0} {$i < 63} {incr i} {
2434361e79fSdan    execsql { INSERT INTO t1 VALUES( randomblob(10) ) }
2444361e79fSdan  }
2454361e79fSdan
2464361e79fSdan  # Free the cell stored right at the end of the page (at offset pgsz-14).
2474361e79fSdan  execsql { DELETE FROM t1 WHERE rowid=1 }
2484361e79fSdan  set rootpage [db one {SELECT rootpage FROM sqlite_master WHERE name = 't1'}]
2494361e79fSdan  db close
2504361e79fSdan
2514361e79fSdan  set offset [expr ($rootpage * 1024)-14+2]
2524361e79fSdan  hexio_write test.db $offset 00FF
2534361e79fSdan  sqlite3 db test.db
2544361e79fSdan
2554361e79fSdan  catchsql { INSERT INTO t1 VALUES( randomblob(10) ) }
25659d60c2cSdan} {1 {database disk image is malformed}}
2574361e79fSdan
258d1f7e926Sdanifcapable oversize_cell_check {
259d1f7e926Sdan  db close
260fda06befSmistachkin  forcedelete test.db test.db-journal
261d1f7e926Sdan  sqlite3 db test.db
262d1f7e926Sdan  execsql {
263d1f7e926Sdan    PRAGMA page_size = 1024; CREATE TABLE t1(x);
264d1f7e926Sdan  }
265d1f7e926Sdan
266d1f7e926Sdan  do_test corrupt-7.1 {
267d1f7e926Sdan    for {set i 0} {$i < 39} {incr i} {
268d1f7e926Sdan      execsql {
269d1f7e926Sdan        INSERT INTO t1 VALUES(X'000100020003000400050006000700080009000A');
270d1f7e926Sdan      }
271d1f7e926Sdan    }
272d1f7e926Sdan  } {}
273d1f7e926Sdan  db close
274d1f7e926Sdan
275d1f7e926Sdan  # Corrupt the root page of table t1 so that the first offset in the
276d1f7e926Sdan  # cell-offset array points to the data for the SQL blob associated with
277d1f7e926Sdan  # record (rowid=10). The root page still passes the checks in btreeInitPage(),
278d1f7e926Sdan  # because the start of said blob looks like the start of a legitimate
279d1f7e926Sdan  # page cell.
280d1f7e926Sdan  #
281d1f7e926Sdan  # Test case cc-2 overwrites the blob so that it no longer looks like a
282d1f7e926Sdan  # real cell. But, by the time it is overwritten, btreeInitPage() has already
283d1f7e926Sdan  # initialized the root page, so no corruption is detected.
284d1f7e926Sdan  #
285d1f7e926Sdan  # Test case cc-3 inserts an extra record into t1, forcing balance-deeper
286d1f7e926Sdan  # to run. After copying the contents of the root page to the new child,
287d1f7e926Sdan  # btreeInitPage() is called on the child. This time, it detects corruption
288d1f7e926Sdan  # (because the start of the blob associated with the (rowid=10) record
289d1f7e926Sdan  # no longer looks like a real cell). At one point the code assumed that
290d1f7e926Sdan  # detecting corruption was not possible at that point, and an assert() failed.
291d1f7e926Sdan  #
292d1f7e926Sdan  set fd [open test.db r+]
293d1f7e926Sdan  fconfigure $fd -translation binary -encoding binary
294d1f7e926Sdan  seek $fd [expr 1024+8]
295d1f7e926Sdan  puts -nonewline $fd "\x03\x14"
296d1f7e926Sdan  close $fd
297d1f7e926Sdan
298d1f7e926Sdan  sqlite3 db test.db
299d1f7e926Sdan  do_test corrupt-7.2 {
300d1f7e926Sdan    execsql {
301d1f7e926Sdan      UPDATE t1 SET x = X'870400020003000400050006000700080009000A'
302d1f7e926Sdan      WHERE rowid = 10;
303d1f7e926Sdan    }
304d1f7e926Sdan  } {}
305d1f7e926Sdan  do_test corrupt-7.3 {
306d1f7e926Sdan    catchsql {
307d1f7e926Sdan      INSERT INTO t1 VALUES(X'000100020003000400050006000700080009000A');
308d1f7e926Sdan    }
309d1f7e926Sdan  } {1 {database disk image is malformed}}
310d1f7e926Sdan}
311d1f7e926Sdan
312887d4b2bSdandb close
313fda06befSmistachkinforcedelete test.db test.db-journal
314887d4b2bSdando_test corrupt-8.1 {
315887d4b2bSdan  sqlite3 db test.db
316887d4b2bSdan  execsql {
317887d4b2bSdan    PRAGMA page_size = 1024;
318887d4b2bSdan    PRAGMA secure_delete = on;
319887d4b2bSdan    PRAGMA auto_vacuum = 0;
320887d4b2bSdan    CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
321887d4b2bSdan    INSERT INTO t1 VALUES(5, randomblob(1900));
322887d4b2bSdan  }
323887d4b2bSdan
324887d4b2bSdan  hexio_write test.db 2044 [hexio_render_int32 2]
325887d4b2bSdan  hexio_write test.db 24   [hexio_render_int32 45]
326887d4b2bSdan
327887d4b2bSdan  catchsql { INSERT OR REPLACE INTO t1 VALUES(5, randomblob(1900)) }
328887d4b2bSdan} {1 {database disk image is malformed}}
329887d4b2bSdan
3302ed11e7bSdandb close
331fda06befSmistachkinforcedelete test.db test.db-journal
3322ed11e7bSdando_test corrupt-8.2 {
3332ed11e7bSdan  sqlite3 db test.db
3342ed11e7bSdan  execsql {
3352ed11e7bSdan    PRAGMA page_size = 1024;
3362ed11e7bSdan    PRAGMA secure_delete = on;
3372ed11e7bSdan    PRAGMA auto_vacuum = 0;
3382ed11e7bSdan    CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
3392ed11e7bSdan    INSERT INTO t1 VALUES(5, randomblob(900));
3402ed11e7bSdan    INSERT INTO t1 VALUES(6, randomblob(900));
3412ed11e7bSdan  }
3422ed11e7bSdan
3432ed11e7bSdan  hexio_write test.db 2047 FF
3442ed11e7bSdan  hexio_write test.db 24   [hexio_render_int32 45]
3452ed11e7bSdan
3462ed11e7bSdan  catchsql { INSERT INTO t1 VALUES(4, randomblob(1900)) }
3472ed11e7bSdan} {1 {database disk image is malformed}}
3482ed11e7bSdan
349ee696e22Sdrhfinish_test
350