gitlab.com/CoiaPrant/sqlite3@v1.19.1/testdata/tcl/corruptC.test (about)

     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.  It creates a base
    15  # data base file, then tests that single byte corruptions in 
    16  # increasingly larger quantities are handled gracefully.
    17  #
    18  # $Id: corruptC.test,v 1.14 2009/07/11 06:55:34 danielk1977 Exp $
    19  
    20  catch {forcedelete test.db test.db-journal test.bu}
    21  
    22  set testdir [file dirname $argv0]
    23  source $testdir/tester.tcl
    24  
    25  # Do not use a codec for tests in this file, as the database file is
    26  # manipulated directly using tcl scripts (using the [hexio_write] command).
    27  #
    28  do_not_use_codec
    29  
    30  # These tests deal with corrupt database files
    31  #
    32  database_may_be_corrupt
    33  
    34  # Construct a compact, dense database for testing.
    35  #
    36  do_test corruptC-1.1 {
    37    sqlite3_db_config db LEGACY_FILE_FORMAT 1
    38    execsql {
    39      PRAGMA auto_vacuum = 0;
    40      BEGIN;
    41      CREATE TABLE t1(x,y);
    42      INSERT INTO t1 VALUES(1,1);
    43      INSERT OR IGNORE INTO t1 SELECT x*2,y FROM t1;
    44      INSERT OR IGNORE INTO t1 SELECT x*3,y FROM t1;
    45      INSERT OR IGNORE INTO t1 SELECT x*5,y FROM t1;
    46      INSERT OR IGNORE INTO t1 SELECT x*7,y FROM t1;
    47      INSERT OR IGNORE INTO t1 SELECT x*11,y FROM t1;
    48      INSERT OR IGNORE INTO t1 SELECT x*13,y FROM t1;
    49      CREATE INDEX t1i1 ON t1(x);
    50      CREATE TABLE t2 AS SELECT x,2 as y FROM t1 WHERE rowid%5!=0;
    51      COMMIT;
    52    }
    53  } {}
    54  
    55  ifcapable {integrityck} {
    56    integrity_check corruptC-1.2
    57  }
    58  
    59  # Generate random integer
    60  #
    61  proc random {range} {
    62    return [expr {round(rand()*$range)}]
    63  }
    64  
    65  # Setup for the tests.  Make a backup copy of the good database in test.bu.
    66  #
    67  db close
    68  forcecopy test.db test.bu
    69  sqlite3 db test.db
    70  set fsize [file size test.db]
    71  
    72  # Set a quasi-random random seed. 
    73  if {[info exists ::G(issoak)]} {
    74    # If we are doing SOAK tests, we want a different
    75    # random seed for each run.  Ideally we would like 
    76    # to use [clock clicks] or something like that here.
    77    set qseed [file mtime test.db]
    78  } else {
    79    # If we are not doing soak tests,
    80    # make it repeatable.
    81    set qseed 0
    82  }
    83  expr srand($qseed)
    84  
    85  #
    86  # First test some specific corruption tests found from earlier runs
    87  # with specific seeds.
    88  #
    89  
    90  # test that a corrupt content offset size is handled (seed 5577)
    91  do_test corruptC-2.1 {
    92    db close
    93    forcecopy test.bu test.db
    94  
    95    # insert corrupt byte(s)
    96    hexio_write test.db 2053 [format %02x 0x04]
    97  
    98    sqlite3 db test.db
    99    catchsql {PRAGMA integrity_check}
   100  } {0 {{*** in database main ***
   101  Page 3: free space corruption}}}
   102  
   103  # test that a corrupt content offset size is handled (seed 5649)
   104  #
   105  # Update 2016-12-27:  As of check-in [0b86fbca66] "In sqlite3BtreeInsert() when
   106  # replacing a re-existing row, try to overwrite the cell directly rather than
   107  # deallocate and reallocate the cell" on 2016-12-09, this test case no longer
   108  # detects the offset size problem during the UPDATE.  We have to run a subsequent
   109  # integrity_check to see it.
   110  do_test corruptC-2.2 {
   111    db close
   112    forcecopy test.bu test.db
   113  
   114    # insert corrupt byte(s)
   115    hexio_write test.db 27   [format %02x 0x08]
   116    hexio_write test.db 233  [format %02x 0x6a]
   117    hexio_write test.db 328  [format %02x 0x67]
   118    hexio_write test.db 750  [format %02x 0x1f]
   119    hexio_write test.db 1132 [format %02x 0x52]
   120    hexio_write test.db 1133 [format %02x 0x84]
   121    hexio_write test.db 1220 [format %02x 0x01]
   122    hexio_write test.db 3688 [format %02x 0xc1]
   123    hexio_write test.db 3714 [format %02x 0x58]
   124    hexio_write test.db 3746 [format %02x 0x9a]
   125  
   126    sqlite3 db test.db
   127    db eval {UPDATE t1 SET y=1}
   128    db eval {PRAGMA integrity_check}
   129  } {/Offset .* out of range/}
   130  
   131  # test that a corrupt free cell size is handled (seed 13329)
   132  do_test corruptC-2.3 {
   133    db close
   134    forcecopy test.bu test.db
   135  
   136    # insert corrupt byte(s)
   137    hexio_write test.db 1094 [format %02x 0x76]
   138  
   139    sqlite3 db test.db
   140    catchsql {UPDATE t1 SET y=1}
   141  } {1 {database disk image is malformed}}
   142  
   143  # test that a corrupt free cell size is handled (seed 169571)
   144  do_test corruptC-2.4 {
   145    db close
   146    forcecopy test.bu test.db
   147  
   148    # insert corrupt byte(s)
   149    hexio_write test.db 3119 [format %02x 0xdf]
   150  
   151    sqlite3 db test.db
   152    catchsql {UPDATE t2 SET y='abcdef-uvwxyz'}
   153  } {1 {database disk image is malformed}}
   154  
   155  # test that a corrupt free cell size is handled (seed 169571)
   156  do_test corruptC-2.5 {
   157    db close
   158    forcecopy test.bu test.db
   159  
   160    # insert corrupt byte(s)
   161    hexio_write test.db 3119 [format %02x 0xdf]
   162    hexio_write test.db 4073 [format %02x 0xbf]
   163  
   164    sqlite3 db test.db
   165    catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
   166    catchsql {PRAGMA integrity_check}
   167  } {0 {{*** in database main ***
   168  On tree page 4 cell 19: Extends off end of page} {database disk image is malformed}}}
   169  
   170  # {0 {{*** in database main ***
   171  # Corruption detected in cell 710 on page 4
   172  # Multiple uses for byte 661 of page 4
   173  # Fragmented space is 249 byte reported as 21 on page 4}}}
   174  
   175  # test that a corrupt free cell size is handled (seed 169595)
   176  do_test corruptC-2.6 {
   177    db close
   178    forcecopy test.bu test.db
   179  
   180    # insert corrupt byte(s)
   181    hexio_write test.db 619 [format %02x 0xe2]
   182    hexio_write test.db 3150 [format %02x 0xa8]
   183  
   184    sqlite3 db test.db
   185    catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
   186  } {1 {database disk image is malformed}}
   187  
   188  # corruption (seed 178692)
   189  do_test corruptC-2.7 {
   190    db close
   191    forcecopy test.bu test.db
   192  
   193    # insert corrupt byte(s)
   194    hexio_write test.db 3074 [format %02x 0xa0]
   195  
   196    sqlite3 db test.db
   197    catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
   198  } {1 {database disk image is malformed}}
   199  
   200  
   201  # corruption (seed 179069)
   202  # Obsolete.  With single-pass DELETE the corruption in the
   203  # main database is not detected.
   204  if 0 {
   205  do_test corruptC-2.8 {
   206    db close
   207    forcecopy test.bu test.db
   208  
   209    # insert corrupt byte(s)
   210    hexio_write test.db 1393 [format %02x 0x7d]
   211    hexio_write test.db 84 [format %02x 0x19]
   212    hexio_write test.db 3287 [format %02x 0x3b]
   213    hexio_write test.db 2564 [format %02x 0xed]
   214    hexio_write test.db 2139 [format %02x 0x55]
   215  
   216    sqlite3 db test.db
   217    catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;}
   218  } {1 {database disk image is malformed}}
   219  }
   220  
   221  # corruption (seed 170434)
   222  #
   223  # UPDATE: Prior to 3.8.2, this used to return SQLITE_CORRUPT. It no longer
   224  # does. That is Ok, the point of these tests is to verify that no buffer
   225  # overruns or overreads can be caused by corrupt databases.
   226  do_test corruptC-2.9 {
   227    db close
   228    forcecopy test.bu test.db
   229  
   230    # insert corrupt byte(s)
   231    hexio_write test.db 2095 [format %02x 0xd6]
   232  
   233    sqlite3 db test.db
   234    catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;}
   235  } {0 {}}
   236  
   237  # corruption (seed 186504)
   238  do_test corruptC-2.10 {
   239    db close
   240    forcecopy test.bu test.db
   241  
   242    # insert corrupt byte(s)
   243    hexio_write test.db 3130 [format %02x 0x02]
   244    
   245    sqlite3 db test.db
   246    catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
   247  } {1 {database disk image is malformed}}
   248  
   249  # corruption (seed 1589)
   250  do_test corruptC-2.11 {
   251    db close
   252    forcecopy test.bu test.db
   253  
   254    # insert corrupt byte(s)
   255    hexio_write test.db 55 [format %02x 0xa7]
   256    
   257    sqlite3 db test.db
   258    catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;}
   259  } {1 {database disk image is malformed}}
   260  
   261  # corruption (seed 14166)
   262  do_test corruptC-2.12 {
   263    db close
   264    forcecopy test.bu test.db
   265  
   266    # insert corrupt byte(s)
   267    hexio_write test.db 974 [format %02x 0x2e]
   268    
   269    sqlite3 db test.db
   270    catchsql {SELECT count(*) FROM sqlite_master;}
   271  } {1 {malformed database schema (t1i1) - corrupt database}}
   272  
   273  # corruption (seed 218803)
   274  do_test corruptC-2.13 {
   275    db close
   276    forcecopy test.bu test.db
   277  
   278    # insert corrupt byte(s)
   279    hexio_write test.db 102 [format %02x 0x12]
   280    
   281    sqlite3 db test.db
   282    catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;}
   283  } {1 {database disk image is malformed}}
   284  
   285  do_test corruptC-2.14 {
   286    db close
   287    forcecopy test.bu test.db
   288  
   289    sqlite3 db test.db
   290    set blob [string repeat abcdefghij 10000]
   291    execsql { INSERT INTO t1 VALUES (1, $blob) }
   292  
   293    sqlite3 db test.db
   294    set filesize [file size test.db]
   295    hexio_write test.db [expr $filesize-2048] 00000001
   296    catchsql {DELETE FROM t1 WHERE rowid = (SELECT max(rowid) FROM t1)}
   297  } {1 {database disk image is malformed}}
   298  
   299  # At one point this particular corrupt database was causing a buffer
   300  # overread. Which caused a crash in a run of all.test once.
   301  #
   302  do_test corruptC-2.15 {
   303    db close
   304    forcecopy test.bu test.db
   305    hexio_write test.db 986 b9
   306    sqlite3 db test.db
   307    catchsql {SELECT count(*) FROM sqlite_master;}
   308  } {1 {database disk image is malformed}}
   309  
   310  #
   311  # Now test for a series of quasi-random seeds.
   312  # We loop over the entire file size and touch
   313  # each byte at least once.
   314  for {set tn 0} {$tn<$fsize} {incr tn 1} {
   315  
   316    # setup for test
   317    db close
   318    forcecopy test.bu test.db
   319    sqlite3 db test.db
   320  
   321    # Seek to a random location in the file, and write a random single byte
   322    # value.  Then do various operations on the file to make sure that
   323    # the database engine can handle the corruption gracefully.
   324    #
   325    set last 0
   326    for {set i 1} {$i<=512 && !$last} {incr i 1} {
   327  
   328      db close
   329      if {$i==1} {
   330        # on the first corrupt value, use location $tn
   331        # this ensures that we touch each location in the 
   332        # file at least once.
   333        set roffset $tn
   334      } else { 
   335        # insert random byte at random location
   336        set roffset [random $fsize]
   337      }
   338      set rbyte [format %02x [random 255]]
   339  
   340      # You can uncomment the following to have it trace
   341      # exactly how it's corrupting the file.  This is 
   342      # useful for generating the "seed specific" tests
   343      # above.
   344      # set rline "$roffset $rbyte"
   345      # puts stdout $rline
   346  
   347      hexio_write test.db $roffset $rbyte
   348      sqlite3 db test.db
   349  
   350      # do a few random operations to make sure that if 
   351      # they error, they error gracefully instead of crashing.
   352      do_test corruptC-3.$tn.($qseed).$i.1 {
   353        catchsql {SELECT count(*) FROM sqlite_master}
   354        set x {}
   355      } {}
   356      do_test corruptC-3.$tn.($qseed).$i.2 {
   357        catchsql {SELECT count(*) FROM t1}
   358        set x {}
   359      } {}
   360      do_test corruptC-3.$tn.($qseed).$i.3 {
   361        catchsql {SELECT count(*) FROM t1 WHERE x>13}
   362        set x {}
   363      } {}
   364      do_test corruptC-3.$tn.($qseed).$i.4 {
   365        catchsql {SELECT count(*) FROM t2}
   366        set x {}
   367      } {}
   368      do_test corruptC-3.$tn.($qseed).$i.5 {
   369        catchsql {SELECT count(*) FROM t2 WHERE x<13}
   370        set x {}
   371      } {}
   372      do_test corruptC-3.$tn.($qseed).$i.6 {
   373        catchsql {BEGIN; UPDATE t1 SET y=1; ROLLBACK;}
   374        set x {}
   375      } {}
   376      do_test corruptC-3.$tn.($qseed).$i.7 {
   377        catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
   378        set x {}
   379      } {}
   380      do_test corruptC-3.$tn.($qseed).$i.8 {
   381        catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;}
   382        set x {}
   383      } {}
   384      do_test corruptC-3.$tn.($qseed).$i.9 {
   385        catchsql {BEGIN; DELETE FROM t2 WHERE x<13; ROLLBACK;}
   386        set x {}
   387      } {}
   388      do_test corruptC-3.$tn.($qseed).$i.10 {
   389        catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;}
   390        set x {}
   391      } {}
   392  
   393      # check the integrity of the database.
   394      # once the corruption is detected, we can stop.
   395      ifcapable {integrityck} {
   396        set res [ catchsql {PRAGMA integrity_check} ]
   397        set ans [lindex $res 1]
   398        if { [ string compare $ans "ok" ] != 0 } {
   399          set last -1
   400        }
   401      }
   402      # if we are not capable of doing an integrity check,
   403      # stop after corrupting 5 bytes.
   404      ifcapable {!integrityck} {
   405        if { $i > 5 } {
   406          set last -1
   407        }
   408      }
   409  
   410      # Check that no page references were leaked.
   411      # TBD:  need to figure out why this doesn't work
   412      # work with ROLLBACKs...
   413      if {0} {
   414        do_test corruptC-3.$tn.($qseed).$i.11 {
   415          set bt [btree_from_db db]
   416          db_enter db
   417          array set stats [btree_pager_stats $bt]
   418          db_leave db
   419          set stats(ref)
   420        } {0}
   421      }
   422    }
   423    # end for i
   424  
   425  }
   426  # end for tn
   427  
   428  finish_test