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