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