xref: /sqlite-3.40.0/test/fuzz3.test (revision bd41d566)
1# 2007 May 10
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.  The focus
12# of this file is checking the libraries response to subtly corrupting
13# the database file by changing the values of pseudo-randomly selected
14# bytes.
15#
16# $Id: fuzz3.test,v 1.3 2009/01/05 17:19:03 drh Exp $
17
18set testdir [file dirname $argv0]
19source $testdir/tester.tcl
20
21# These tests deal with corrupt database files
22#
23database_may_be_corrupt
24
25expr srand(123)
26
27proc rstring {n} {
28  set str s
29  while {[string length $str] < $n} {
30    append str [expr rand()]
31  }
32  return [string range $str 0 $n]
33}
34
35# Return a randomly generated SQL literal.
36#
37proc rvalue {} {
38  switch -- [expr int(rand()*5)] {
39    0 { # SQL NULL value.
40      return NULL
41    }
42    1 { # Integer value.
43      return [expr int(rand()*1024)]
44    }
45    2 { # Real value.
46      return [expr rand()]
47    }
48    3 { # String value.
49      set n [expr int(rand()*2500)]
50      return "'[rstring $n]'"
51    }
52    4 { # Blob value.
53      set n [expr int(rand()*2500)]
54      return "CAST('[rstring $n]' AS BLOB)"
55    }
56  }
57}
58
59proc db_checksum {} {
60  set    cksum [execsql { SELECT md5sum(a, b, c) FROM t1 }]
61  append cksum [execsql { SELECT md5sum(d, e, f) FROM t2 }]
62  set cksum
63}
64
65# Modify a single byte in the file 'test.db' using tcl IO commands. The
66# argument value, which must be an integer, determines both the offset of
67# the byte that is modified, and the value that it is set to. The lower
68# 8 bits of iMod determine the new byte value. The offset of the byte
69# modified is the value of ($iMod >> 8).
70#
71# The return value is the iMod value required to restore the file
72# to its original state. The command:
73#
74#   modify_database [modify_database $x]
75#
76# leaves the file in the same state as it was in at the start of the
77# command (assuming that the file is at least ($x>>8) bytes in size).
78#
79proc modify_database {iMod} {
80  set blob [binary format c [expr {$iMod&0xFF}]]
81  set offset [expr {$iMod>>8}]
82
83  set fd [open test.db r+]
84  fconfigure $fd -encoding binary -translation binary
85  seek $fd $offset
86  set old_blob [read $fd 1]
87  seek $fd $offset
88  puts -nonewline $fd $blob
89  close $fd
90
91  binary scan $old_blob c iOld
92  return [expr {($offset<<8) + ($iOld&0xFF)}]
93}
94
95proc purge_pcache {} {
96  ifcapable !memorymanage {
97    db close
98    sqlite3 db test.db
99  } else {
100    sqlite3_release_memory 10000000
101  }
102  if {[lindex [pcache_stats] 1] != 0} {
103    error "purge_pcache failed: [pcache_stats]"
104  }
105}
106
107# This block creates a database to work with.
108#
109do_test fuzz3-1 {
110  execsql {
111    BEGIN;
112    CREATE TABLE t1(a, b, c);
113    CREATE TABLE t2(d, e, f);
114    CREATE INDEX i1 ON t1(a, b, c);
115    CREATE INDEX i2 ON t2(d, e, f);
116  }
117  for {set i 0} {$i < 50} {incr i} {
118    execsql "INSERT INTO t1 VALUES([rvalue], [rvalue], [rvalue])"
119    execsql "INSERT INTO t2 VALUES([rvalue], [rvalue], [rvalue])"
120  }
121  execsql COMMIT
122} {}
123
124set ::cksum [db_checksum]
125do_test fuzz3-2 {
126  db_checksum
127} $::cksum
128
129for {set ii 0} {$ii < 5000} {incr ii} {
130  purge_pcache
131
132  # Randomly modify a single byte of the database file somewhere within
133  # the first 100KB of the file.
134  set iNew [expr int(rand()*5*1024*256)]
135  set iOld [modify_database $iNew]
136
137  set iTest 0
138  foreach sql {
139    {SELECT * FROM t2 ORDER BY d}
140    {SELECT * FROM t1}
141    {SELECT * FROM t2}
142    {SELECT * FROM t1 ORDER BY a}
143    {SELECT * FROM t1 WHERE a = (SELECT a FROM t1 WHERE rowid=25)}
144    {SELECT * FROM t2 WHERE d = (SELECT d FROM t2 WHERE rowid=1)}
145    {SELECT * FROM t2 WHERE d = (SELECT d FROM t2 WHERE rowid=50)}
146    {PRAGMA integrity_check}
147  } {
148    do_test fuzz3-$ii.$iNew.[incr iTest] {
149      foreach {rc msg} [catchsql $sql] {}
150      if {$rc == 0
151       || $msg eq "database or disk is full"
152       || $msg eq "database disk image is malformed"
153       || $msg eq "file is encrypted or is not a database"
154       || [string match "malformed database schema*" $msg]
155      } {
156        set msg ok
157      }
158      set msg
159    } {ok}
160  }
161
162  # Restore the original database file content. Test that the correct
163  # checksum is now returned.
164  #
165  purge_pcache
166  modify_database $iOld
167  do_test fuzz3-$ii.$iNew.[incr iTest] {
168    db_checksum
169  } $::cksum
170}
171
172finish_test
173