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