summaryrefslogtreecommitdiff
path: root/test/fuzz3.test
blob: 2b21404f43740846a17c18adebdb6b601fd4cc1a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# 2007 May 10
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library.  The focus
# of this file is checking the libraries response to subtly corrupting
# the database file by changing the values of pseudo-randomly selected
# bytes.
#
# $Id: fuzz3.test,v 1.3 2009/01/05 17:19:03 drh Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl

# These tests deal with corrupt database files
#
database_may_be_corrupt

expr srand(123)

proc rstring {n} {
  set str s
  while {[string length $str] < $n} {
    append str [expr rand()]
  }
  return [string range $str 0 $n]
}

# Return a randomly generated SQL literal.
#
proc rvalue {} {
  switch -- [expr int(rand()*5)] {
    0 { # SQL NULL value.
      return NULL 
    }
    1 { # Integer value.
      return [expr int(rand()*1024)] 
    }
    2 { # Real value.
      return [expr rand()] 
    }
    3 { # String value.
      set n [expr int(rand()*2500)]
      return "'[rstring $n]'"
    }
    4 { # Blob value.
      set n [expr int(rand()*2500)]
      return "CAST('[rstring $n]' AS BLOB)"
    }
  }
}

proc db_checksum {} {
  set    cksum [execsql { SELECT md5sum(a, b, c) FROM t1 }]
  append cksum [execsql { SELECT md5sum(d, e, f) FROM t2 }]
  set cksum
}

# Modify a single byte in the file 'test.db' using tcl IO commands. The
# argument value, which must be an integer, determines both the offset of
# the byte that is modified, and the value that it is set to. The lower
# 8 bits of iMod determine the new byte value. The offset of the byte
# modified is the value of ($iMod >> 8).
#
# The return value is the iMod value required to restore the file
# to its original state. The command:
#
#   modify_database [modify_database $x]
#
# leaves the file in the same state as it was in at the start of the
# command (assuming that the file is at least ($x>>8) bytes in size).
#
proc modify_database {iMod} {
  set blob [binary format c [expr {$iMod&0xFF}]]
  set offset [expr {$iMod>>8}]

  set fd [open test.db r+]
  fconfigure $fd -encoding binary -translation binary
  seek $fd $offset
  set old_blob [read $fd 1]
  seek $fd $offset
  puts -nonewline $fd $blob
  close $fd

  binary scan $old_blob c iOld
  return [expr {($offset<<8) + ($iOld&0xFF)}]
}

proc purge_pcache {} {
  ifcapable !memorymanage {
    db close
    sqlite3 db test.db
  } else {
    sqlite3_release_memory 10000000
  }
  if {[lindex [pcache_stats] 1] != 0} {
    error "purge_pcache failed: [pcache_stats]"
  }
}

# This block creates a database to work with. 
#
do_test fuzz3-1 {
  execsql {
    BEGIN;
    CREATE TABLE t1(a, b, c);
    CREATE TABLE t2(d, e, f);
    CREATE INDEX i1 ON t1(a, b, c);
    CREATE INDEX i2 ON t2(d, e, f);
  }
  for {set i 0} {$i < 50} {incr i} {
    execsql "INSERT INTO t1 VALUES([rvalue], [rvalue], [rvalue])"
    execsql "INSERT INTO t2 VALUES([rvalue], [rvalue], [rvalue])"
  }
  execsql COMMIT
} {}

set ::cksum [db_checksum]
do_test fuzz3-2 {
  db_checksum
} $::cksum

for {set ii 0} {$ii < 5000} {incr ii} {
  purge_pcache

  # Randomly modify a single byte of the database file somewhere within
  # the first 100KB of the file.
  set iNew [expr int(rand()*5*1024*256)]
  set iOld [modify_database $iNew]

  set iTest 0
  foreach sql {
    {SELECT * FROM t2 ORDER BY d}      
    {SELECT * FROM t1}                 
    {SELECT * FROM t2}                 
    {SELECT * FROM t1 ORDER BY a}      
    {SELECT * FROM t1 WHERE a = (SELECT a FROM t1 WHERE rowid=25)} 
    {SELECT * FROM t2 WHERE d = (SELECT d FROM t2 WHERE rowid=1)}  
    {SELECT * FROM t2 WHERE d = (SELECT d FROM t2 WHERE rowid=50)} 
    {PRAGMA integrity_check}           
  } {
    do_test fuzz3-$ii.$iNew.[incr iTest] {
      foreach {rc msg} [catchsql $sql] {}
      if {$rc == 0 
       || $msg eq "database or disk is full"
       || $msg eq "database disk image is malformed"
       || $msg eq "file is encrypted or is not a database"
       || [string match "malformed database schema*" $msg]
      } {
        set msg ok
      }
      set msg
    } {ok}
  }

  # Restore the original database file content. Test that the correct 
  # checksum is now returned.
  #
  purge_pcache
  modify_database $iOld
  do_test fuzz3-$ii.$iNew.[incr iTest] {
    db_checksum
  } $::cksum
}

finish_test