xref: /sqlite-3.40.0/test/corrupt.test (revision fda06bef)
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: corrupt.test,v 1.12 2009/07/13 09:41:45 danielk1977 Exp $
17
18catch {forcedelete test.db test.db-journal test.bu}
19
20set testdir [file dirname $argv0]
21source $testdir/tester.tcl
22
23# Do not use a codec for tests in this file, as the database file is
24# manipulated directly using tcl scripts (using the [hexio_write] command).
25#
26do_not_use_codec
27
28# Construct a large database for testing.
29#
30do_test corrupt-1.1 {
31  execsql {
32    BEGIN;
33    CREATE TABLE t1(x);
34    INSERT INTO t1 VALUES(randstr(100,100));
35    INSERT INTO t1 VALUES(randstr(90,90));
36    INSERT INTO t1 VALUES(randstr(80,80));
37    INSERT INTO t1 SELECT x || randstr(5,5) FROM t1;
38    INSERT INTO t1 SELECT x || randstr(6,6) FROM t1;
39    INSERT INTO t1 SELECT x || randstr(7,7) FROM t1;
40    INSERT INTO t1 SELECT x || randstr(8,8) FROM t1;
41    INSERT INTO t1 VALUES(randstr(3000,3000));
42    INSERT INTO t1 SELECT x || randstr(9,9) FROM t1;
43    INSERT INTO t1 SELECT x || randstr(10,10) FROM t1;
44    INSERT INTO t1 SELECT x || randstr(11,11) FROM t1;
45    INSERT INTO t1 SELECT x || randstr(12,12) FROM t1;
46    CREATE INDEX t1i1 ON t1(x);
47    CREATE TABLE t2 AS SELECT * FROM t1;
48    DELETE FROM t2 WHERE rowid%5!=0;
49    COMMIT;
50  }
51} {}
52integrity_check corrupt-1.2
53
54# Setup for the tests.  Make a backup copy of the good database in test.bu.
55# Create a string of garbage data that is 256 bytes long.
56#
57forcecopy test.db test.bu
58set fsize [file size test.db]
59set junk "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
60while {[string length $junk]<256} {append junk $junk}
61set junk [string range $junk 0 255]
62
63# Go through the database and write garbage data into each 256 segment
64# of the file.  Then do various operations on the file to make sure that
65# the database engine can recover gracefully from the corruption.
66#
67for {set i [expr {1*256}]} {$i<$fsize-256} {incr i 256} {
68  set tn [expr {$i/256}]
69  db close
70  forcecopy test.bu test.db
71  set fd [open test.db r+]
72  fconfigure $fd -translation binary
73  seek $fd $i
74  puts -nonewline $fd $junk
75  close $fd
76  do_test corrupt-2.$tn.1 {
77    sqlite3 db test.db
78    catchsql {SELECT count(*) FROM sqlite_master}
79    set x {}
80  } {}
81  do_test corrupt-2.$tn.2 {
82    catchsql {SELECT count(*) FROM t1}
83    set x {}
84  } {}
85  do_test corrupt-2.$tn.3 {
86    catchsql {SELECT count(*) FROM t1 WHERE x>'abcdef'}
87    set x {}
88  } {}
89  do_test corrupt-2.$tn.4 {
90    catchsql {SELECT count(*) FROM t2}
91    set x {}
92  } {}
93  do_test corrupt-2.$tn.5 {
94    catchsql {CREATE TABLE t3 AS SELECT * FROM t1}
95    set x {}
96  } {}
97  do_test corrupt-2.$tn.6 {
98    catchsql {DROP TABLE t1}
99    set x {}
100  } {}
101  do_test corrupt-2.$tn.7 {
102    catchsql {PRAGMA integrity_check}
103    set x {}
104  } {}
105
106  # Check that no page references were leaked.
107  do_test corrupt-2.$tn.8 {
108    set bt [btree_from_db db]
109    db_enter db
110    array set stats [btree_pager_stats $bt]
111    db_leave db
112    set stats(ref)
113  } {0}
114}
115
116#------------------------------------------------------------------------
117# For these tests, swap the rootpage entries of t1 (a table) and t1i1 (an
118# index on t1) in sqlite_master. Then perform a few different queries
119# and make sure this is detected as corruption.
120#
121do_test corrupt-3.1 {
122  db close
123  forcecopy test.bu test.db
124  sqlite3 db test.db
125  list
126} {}
127do_test corrupt-3.2 {
128  set t1_r [execsql {SELECT rootpage FROM sqlite_master WHERE name = 't1i1'}]
129  set t1i1_r [execsql {SELECT rootpage FROM sqlite_master WHERE name = 't1'}]
130  set cookie [expr [execsql {PRAGMA schema_version}] + 1]
131  execsql "
132    PRAGMA writable_schema = 1;
133    UPDATE sqlite_master SET rootpage = $t1_r WHERE name = 't1';
134    UPDATE sqlite_master SET rootpage = $t1i1_r WHERE name = 't1i1';
135    PRAGMA writable_schema = 0;
136    PRAGMA schema_version = $cookie;
137  "
138} {}
139
140# This one tests the case caught by code in checkin [2313].
141do_test corrupt-3.3 {
142  db close
143  sqlite3 db test.db
144  catchsql {
145    INSERT INTO t1 VALUES('abc');
146  }
147} {1 {database disk image is malformed}}
148do_test corrupt-3.4 {
149  db close
150  sqlite3 db test.db
151  catchsql {
152    SELECT * FROM t1;
153  }
154} {1 {database disk image is malformed}}
155do_test corrupt-3.5 {
156  db close
157  sqlite3 db test.db
158  catchsql {
159    SELECT * FROM t1 WHERE oid = 10;
160  }
161} {1 {database disk image is malformed}}
162do_test corrupt-3.6 {
163  db close
164  sqlite3 db test.db
165  catchsql {
166    SELECT * FROM t1 WHERE x = 'abcde';
167  }
168} {1 {database disk image is malformed}}
169
170do_test corrupt-4.1 {
171  db close
172  forcedelete test.db test.db-journal
173  sqlite3 db test.db
174  execsql {
175    PRAGMA page_size = 1024;
176    CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT);
177  }
178  for {set i 0} {$i < 10} {incr i} {
179    set text [string repeat $i 220]
180    execsql { INSERT INTO t1 VALUES($i, $text) }
181  }
182  execsql { CREATE INDEX i1 ON t1(b) }
183} {}
184do_test corrupt-4.2 {
185  set iRoot [db one {SELECT rootpage FROM sqlite_master WHERE name = 'i1'}]
186  set iOffset [hexio_get_int [hexio_read test.db [expr 12+($iRoot-1)*1024] 2]]
187  set data [hexio_render_int32 [expr $iRoot - 1]]
188  hexio_write test.db [expr ($iRoot-1)*1024 + $iOffset] $data
189  db close
190  sqlite3 db test.db
191
192  # The following DELETE statement attempts to delete a cell stored on the
193  # root page of index i1. After this cell is deleted it must be replaced
194  # by a cell retrieved from the child page (a leaf) of the deleted cell.
195  # This will fail, as the block modified the database image so that the
196  # child page of the deleted cell is from a table (intkey) b-tree, not an
197  # index b-tree as expected. At one point this was causing an assert()
198  # to fail.
199  catchsql { DELETE FROM t1 WHERE rowid = 3 }
200} {1 {database disk image is malformed}}
201
202do_test corrupt-5.1 {
203  db close
204  forcedelete test.db test.db-journal
205  sqlite3 db test.db
206
207  execsql { PRAGMA page_size = 1024 }
208  set ct "CREATE TABLE t1(c0 "
209  set i 0
210  while {[string length $ct] < 950} { append ct ", c[incr i]" }
211  append ct ")"
212  execsql $ct
213} {}
214
215do_test corrupt-5.2 {
216  db close
217  hexio_write test.db 108 00000000
218  sqlite3 db test.db
219  catchsql { SELECT * FROM sqlite_master }
220} {1 {database disk image is malformed}}
221
222# At one point, the specific corruption caused by this test case was
223# causing a buffer overwrite. Although a crash was never demonstrated,
224# running this testcase under valgrind revealed the problem.
225do_test corrupt-6.1 {
226  db close
227  forcedelete test.db test.db-journal
228  sqlite3 db test.db
229  execsql {
230    PRAGMA page_size = 1024; CREATE TABLE t1(x);
231  }
232
233  # The root page of t1 is 1024 bytes in size. The header is 8 bytes, and
234  # each of the cells inserted by the following INSERT statements consume
235  # 16 bytes (including the 2 byte cell-offset array entry). So the page
236  # can contain up to 63 cells.
237  for {set i 0} {$i < 63} {incr i} {
238    execsql { INSERT INTO t1 VALUES( randomblob(10) ) }
239  }
240
241  # Free the cell stored right at the end of the page (at offset pgsz-14).
242  execsql { DELETE FROM t1 WHERE rowid=1 }
243  set rootpage [db one {SELECT rootpage FROM sqlite_master WHERE name = 't1'}]
244  db close
245
246  set offset [expr ($rootpage * 1024)-14+2]
247  hexio_write test.db $offset 00FF
248  sqlite3 db test.db
249
250  catchsql { INSERT INTO t1 VALUES( randomblob(10) ) }
251} {1 {database disk image is malformed}}
252
253ifcapable oversize_cell_check {
254  db close
255  forcedelete test.db test.db-journal
256  sqlite3 db test.db
257  execsql {
258    PRAGMA page_size = 1024; CREATE TABLE t1(x);
259  }
260
261  do_test corrupt-7.1 {
262    for {set i 0} {$i < 39} {incr i} {
263      execsql {
264        INSERT INTO t1 VALUES(X'000100020003000400050006000700080009000A');
265      }
266    }
267  } {}
268  db close
269
270  # Corrupt the root page of table t1 so that the first offset in the
271  # cell-offset array points to the data for the SQL blob associated with
272  # record (rowid=10). The root page still passes the checks in btreeInitPage(),
273  # because the start of said blob looks like the start of a legitimate
274  # page cell.
275  #
276  # Test case cc-2 overwrites the blob so that it no longer looks like a
277  # real cell. But, by the time it is overwritten, btreeInitPage() has already
278  # initialized the root page, so no corruption is detected.
279  #
280  # Test case cc-3 inserts an extra record into t1, forcing balance-deeper
281  # to run. After copying the contents of the root page to the new child,
282  # btreeInitPage() is called on the child. This time, it detects corruption
283  # (because the start of the blob associated with the (rowid=10) record
284  # no longer looks like a real cell). At one point the code assumed that
285  # detecting corruption was not possible at that point, and an assert() failed.
286  #
287  set fd [open test.db r+]
288  fconfigure $fd -translation binary -encoding binary
289  seek $fd [expr 1024+8]
290  puts -nonewline $fd "\x03\x14"
291  close $fd
292
293  sqlite3 db test.db
294  do_test corrupt-7.2 {
295    execsql {
296      UPDATE t1 SET x = X'870400020003000400050006000700080009000A'
297      WHERE rowid = 10;
298    }
299  } {}
300  do_test corrupt-7.3 {
301    catchsql {
302      INSERT INTO t1 VALUES(X'000100020003000400050006000700080009000A');
303    }
304  } {1 {database disk image is malformed}}
305}
306
307db close
308forcedelete test.db test.db-journal
309do_test corrupt-8.1 {
310  sqlite3 db test.db
311  execsql {
312    PRAGMA page_size = 1024;
313    PRAGMA secure_delete = on;
314    PRAGMA auto_vacuum = 0;
315    CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
316    INSERT INTO t1 VALUES(5, randomblob(1900));
317  }
318
319  hexio_write test.db 2044 [hexio_render_int32 2]
320  hexio_write test.db 24   [hexio_render_int32 45]
321
322  catchsql { INSERT OR REPLACE INTO t1 VALUES(5, randomblob(1900)) }
323} {1 {database disk image is malformed}}
324
325db close
326forcedelete test.db test.db-journal
327do_test corrupt-8.2 {
328  sqlite3 db test.db
329  execsql {
330    PRAGMA page_size = 1024;
331    PRAGMA secure_delete = on;
332    PRAGMA auto_vacuum = 0;
333    CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
334    INSERT INTO t1 VALUES(5, randomblob(900));
335    INSERT INTO t1 VALUES(6, randomblob(900));
336  }
337
338  hexio_write test.db 2047 FF
339  hexio_write test.db 24   [hexio_render_int32 45]
340
341  catchsql { INSERT INTO t1 VALUES(4, randomblob(1900)) }
342} {1 {database disk image is malformed}}
343
344finish_test
345