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