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