/ Check-in [9b7a52e9]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fixed several more crashes due to corrupt db files. Added corruptC.test to soak.test. (CVS 5905)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 9b7a52e952c81e50611e04d2d79003b0ddc57ee5
User & Date: shane 2008-11-13 18:29:51
Context
2008-11-13
19:12
Fix a couple of memory leaks that may follow malloc failures. (CVS 5906) check-in: 4cf8a8e1 user: danielk1977 tags: trunk
18:29
Fixed several more crashes due to corrupt db files. Added corruptC.test to soak.test. (CVS 5905) check-in: 9b7a52e9 user: shane tags: trunk
18:20
Added support for -DSQLITE_NO_SYNC to os_win.c. (CVS 5904) check-in: 26493379 user: shane tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/btree.c.

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
...
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
...
853
854
855
856
857
858
859
860


861
862
863


864
865
866
867
868
869
870
...
873
874
875
876
877
878
879
880


881
882
883
884
885
886
887
...
890
891
892
893
894
895
896

897
898
899
900
901
902
903
....
4575
4576
4577
4578
4579
4580
4581

4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593



4594
4595
4596
4597
4598
4599
4600
....
6047
6048
6049
6050
6051
6052
6053
6054

6055

6056
6057
6058
6059
6060
6061
6062
** 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.
**
*************************************************************************
** $Id: btree.c,v 1.535 2008/11/13 14:28:29 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** See the header comment on "btreeInt.h" for additional information.
** Including a description of file format and an overview of operation.
*/
#include "btreeInt.h"

................................................................................
  cbrk = get2byte(&data[hdr+5]);
  memcpy(&temp[cbrk], &data[cbrk], usableSize - cbrk);
  cbrk = usableSize;
  for(i=0; i<nCell; i++){
    u8 *pAddr;     /* The i-th cell pointer */
    pAddr = &data[cellOffset + i*2];
    pc = get2byte(pAddr);
    if( pc>=pPage->pBt->usableSize ){
      return SQLITE_CORRUPT_BKPT;
    }
    size = cellSizePtr(pPage, &temp[pc]);
    cbrk -= size;
    if( cbrk<cellOffset+2*nCell || pc+size>usableSize ){
      return SQLITE_CORRUPT_BKPT;
    }
................................................................................
** Return a section of the pPage->aData to the freelist.
** The first byte of the new free block is pPage->aDisk[start]
** and the size of the block is "size" bytes.
**
** Most of the effort here is involved in coalesing adjacent
** free blocks into a single big free block.
*/
static void freeSpace(MemPage *pPage, int start, int size){
  int addr, pbegin, hdr;
  unsigned char *data = pPage->aData;

  assert( pPage->pBt!=0 );
  assert( sqlite3PagerIswriteable(pPage->pDbPage) );
  assert( start>=pPage->hdrOffset+6+(pPage->leaf?0:4) );
  assert( (start + size)<=pPage->pBt->usableSize );
................................................................................
#endif

  /* Add the space back into the linked list of freeblocks */
  hdr = pPage->hdrOffset;
  addr = hdr + 1;
  while( (pbegin = get2byte(&data[addr]))<start && pbegin>0 ){
    assert( pbegin<=pPage->pBt->usableSize-4 );
    assert( pbegin>addr );


    addr = pbegin;
  }
  assert( pbegin<=pPage->pBt->usableSize-4 );


  assert( pbegin>addr || pbegin==0 );
  put2byte(&data[addr], start);
  put2byte(&data[start], pbegin);
  put2byte(&data[start+2], size);
  pPage->nFree += size;

  /* Coalesce adjacent free blocks */
................................................................................
    int pnext, psize;
    assert( pbegin>addr );
    assert( pbegin<=pPage->pBt->usableSize-4 );
    pnext = get2byte(&data[pbegin]);
    psize = get2byte(&data[pbegin+2]);
    if( pbegin + psize + 3 >= pnext && pnext>0 ){
      int frag = pnext - (pbegin+psize);
      assert( frag<=data[pPage->hdrOffset+7] );


      data[pPage->hdrOffset+7] -= frag;
      put2byte(&data[pbegin], get2byte(&data[pnext]));
      put2byte(&data[pbegin+2], pnext+get2byte(&data[pnext+2])-pbegin);
    }else{
      addr = pbegin;
    }
  }
................................................................................
  if( data[hdr+1]==data[hdr+5] && data[hdr+2]==data[hdr+6] ){
    int top;
    pbegin = get2byte(&data[hdr+1]);
    memcpy(&data[hdr+1], &data[pbegin], 2);
    top = get2byte(&data[hdr+5]);
    put2byte(&data[hdr+5], top + get2byte(&data[pbegin+2]));
  }

}

/*
** Decode the flags byte (the first byte of the header) for a page
** and initialize fields of the MemPage structure accordingly.
**
** Only the following combinations are supported.  Anything different
................................................................................
** "sz" must be the number of bytes in the cell.
*/
static int dropCell(MemPage *pPage, int idx, int sz){
  int i;          /* Loop counter */
  int pc;         /* Offset to cell content of cell being deleted */
  u8 *data;       /* pPage->aData */
  u8 *ptr;        /* Used to move bytes around within data[] */


  assert( idx>=0 && idx<pPage->nCell );
  assert( sz==cellSize(pPage, idx) );
  assert( sqlite3PagerIswriteable(pPage->pDbPage) );
  assert( sqlite3_mutex_held(pPage->pBt->mutex) );
  data = pPage->aData;
  ptr = &data[pPage->cellOffset + 2*idx];
  pc = get2byte(ptr);
  if ( pc<=10 || pc+sz>pPage->pBt->usableSize ) {
    return SQLITE_CORRUPT_BKPT;
  }
  freeSpace(pPage, pc, sz);



  for(i=idx+1; i<pPage->nCell; i++, ptr+=2){
    ptr[0] = ptr[2];
    ptr[1] = ptr[3];
  }
  pPage->nCell--;
  put2byte(&data[pPage->hdrOffset+3], pPage->nCell);
  pPage->nFree += 2;
................................................................................
                                   || !pCur->pagesShuffled );
      }
    }
    sqlite3BtreeReleaseTempCursor(&leafCur);
  }else{
    TRACE(("DELETE: table=%d delete from leaf %d\n",
       pCur->pgnoRoot, pPage->pgno));
    dropCell(pPage, idx, cellSizePtr(pPage, pCell));

    rc = balance(pCur, 0);

  }
  if( rc==SQLITE_OK ){
    moveToRoot(pCur);
  }
  return rc;
}








|







 







|







 







|







 







|
>
>


|
>
>







 







|
>
>







 







>







 







>








|


|
>
>
>







 







|
>
|
>







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
...
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
...
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
...
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
...
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
....
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
....
6058
6059
6060
6061
6062
6063
6064
6065
6066
6067
6068
6069
6070
6071
6072
6073
6074
6075
** 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.
**
*************************************************************************
** $Id: btree.c,v 1.536 2008/11/13 18:29:51 shane Exp $
**
** This file implements a external (disk-based) database using BTrees.
** See the header comment on "btreeInt.h" for additional information.
** Including a description of file format and an overview of operation.
*/
#include "btreeInt.h"

................................................................................
  cbrk = get2byte(&data[hdr+5]);
  memcpy(&temp[cbrk], &data[cbrk], usableSize - cbrk);
  cbrk = usableSize;
  for(i=0; i<nCell; i++){
    u8 *pAddr;     /* The i-th cell pointer */
    pAddr = &data[cellOffset + i*2];
    pc = get2byte(pAddr);
    if( pc>=usableSize ){
      return SQLITE_CORRUPT_BKPT;
    }
    size = cellSizePtr(pPage, &temp[pc]);
    cbrk -= size;
    if( cbrk<cellOffset+2*nCell || pc+size>usableSize ){
      return SQLITE_CORRUPT_BKPT;
    }
................................................................................
** Return a section of the pPage->aData to the freelist.
** The first byte of the new free block is pPage->aDisk[start]
** and the size of the block is "size" bytes.
**
** Most of the effort here is involved in coalesing adjacent
** free blocks into a single big free block.
*/
static int freeSpace(MemPage *pPage, int start, int size){
  int addr, pbegin, hdr;
  unsigned char *data = pPage->aData;

  assert( pPage->pBt!=0 );
  assert( sqlite3PagerIswriteable(pPage->pDbPage) );
  assert( start>=pPage->hdrOffset+6+(pPage->leaf?0:4) );
  assert( (start + size)<=pPage->pBt->usableSize );
................................................................................
#endif

  /* Add the space back into the linked list of freeblocks */
  hdr = pPage->hdrOffset;
  addr = hdr + 1;
  while( (pbegin = get2byte(&data[addr]))<start && pbegin>0 ){
    assert( pbegin<=pPage->pBt->usableSize-4 );
    if( pbegin<=addr ) {
      return SQLITE_CORRUPT_BKPT;
    }
    addr = pbegin;
  }
  if ( pbegin>pPage->pBt->usableSize-4 ) {
    return SQLITE_CORRUPT_BKPT;
  }
  assert( pbegin>addr || pbegin==0 );
  put2byte(&data[addr], start);
  put2byte(&data[start], pbegin);
  put2byte(&data[start+2], size);
  pPage->nFree += size;

  /* Coalesce adjacent free blocks */
................................................................................
    int pnext, psize;
    assert( pbegin>addr );
    assert( pbegin<=pPage->pBt->usableSize-4 );
    pnext = get2byte(&data[pbegin]);
    psize = get2byte(&data[pbegin+2]);
    if( pbegin + psize + 3 >= pnext && pnext>0 ){
      int frag = pnext - (pbegin+psize);
      if( (frag<0) || (frag>data[pPage->hdrOffset+7]) ){
        return SQLITE_CORRUPT_BKPT;
      }
      data[pPage->hdrOffset+7] -= frag;
      put2byte(&data[pbegin], get2byte(&data[pnext]));
      put2byte(&data[pbegin+2], pnext+get2byte(&data[pnext+2])-pbegin);
    }else{
      addr = pbegin;
    }
  }
................................................................................
  if( data[hdr+1]==data[hdr+5] && data[hdr+2]==data[hdr+6] ){
    int top;
    pbegin = get2byte(&data[hdr+1]);
    memcpy(&data[hdr+1], &data[pbegin], 2);
    top = get2byte(&data[hdr+5]);
    put2byte(&data[hdr+5], top + get2byte(&data[pbegin+2]));
  }
  return SQLITE_OK;
}

/*
** Decode the flags byte (the first byte of the header) for a page
** and initialize fields of the MemPage structure accordingly.
**
** Only the following combinations are supported.  Anything different
................................................................................
** "sz" must be the number of bytes in the cell.
*/
static int dropCell(MemPage *pPage, int idx, int sz){
  int i;          /* Loop counter */
  int pc;         /* Offset to cell content of cell being deleted */
  u8 *data;       /* pPage->aData */
  u8 *ptr;        /* Used to move bytes around within data[] */
  int rc;         /* The return code */

  assert( idx>=0 && idx<pPage->nCell );
  assert( sz==cellSize(pPage, idx) );
  assert( sqlite3PagerIswriteable(pPage->pDbPage) );
  assert( sqlite3_mutex_held(pPage->pBt->mutex) );
  data = pPage->aData;
  ptr = &data[pPage->cellOffset + 2*idx];
  pc = get2byte(ptr);
  if ( (pc<pPage->hdrOffset+6+(pPage->leaf?0:4)) || (pc+sz>pPage->pBt->usableSize) ) {
    return SQLITE_CORRUPT_BKPT;
  }
  rc = freeSpace(pPage, pc, sz);
  if( rc!=SQLITE_OK ){
    return rc;
  }
  for(i=idx+1; i<pPage->nCell; i++, ptr+=2){
    ptr[0] = ptr[2];
    ptr[1] = ptr[3];
  }
  pPage->nCell--;
  put2byte(&data[pPage->hdrOffset+3], pPage->nCell);
  pPage->nFree += 2;
................................................................................
                                   || !pCur->pagesShuffled );
      }
    }
    sqlite3BtreeReleaseTempCursor(&leafCur);
  }else{
    TRACE(("DELETE: table=%d delete from leaf %d\n",
       pCur->pgnoRoot, pPage->pgno));
    rc = dropCell(pPage, idx, cellSizePtr(pPage, pCell));
    if( rc==SQLITE_OK ){
      rc = balance(pCur, 0);
    }
  }
  if( rc==SQLITE_OK ){
    moveToRoot(pCur);
  }
  return rc;
}

Changes to src/build.c.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
...
666
667
668
669
670
671
672
673




674
675
676
677
678
679
680
**     CREATE INDEX
**     DROP INDEX
**     creating ID lists
**     BEGIN TRANSACTION
**     COMMIT
**     ROLLBACK
**
** $Id: build.c,v 1.501 2008/11/11 18:28:59 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>

/*
** This routine is called when a new SQL statement is beginning to
** be parsed.  Initialize the pParse structure as needed.
................................................................................
  Token *pName2,      /* The "yyy" in the name "xxx.yyy" */
  Token **pUnqual     /* Write the unqualified object name here */
){
  int iDb;                    /* Database holding the object */
  sqlite3 *db = pParse->db;

  if( pName2 && pName2->n>0 ){
    assert( !db->init.busy );




    *pUnqual = pName2;
    iDb = sqlite3FindDb(db, pName1);
    if( iDb<0 ){
      sqlite3ErrorMsg(pParse, "unknown database %T", pName1);
      pParse->nErr++;
      return -1;
    }







|







 







|
>
>
>
>







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
...
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
**     CREATE INDEX
**     DROP INDEX
**     creating ID lists
**     BEGIN TRANSACTION
**     COMMIT
**     ROLLBACK
**
** $Id: build.c,v 1.502 2008/11/13 18:29:51 shane Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>

/*
** This routine is called when a new SQL statement is beginning to
** be parsed.  Initialize the pParse structure as needed.
................................................................................
  Token *pName2,      /* The "yyy" in the name "xxx.yyy" */
  Token **pUnqual     /* Write the unqualified object name here */
){
  int iDb;                    /* Database holding the object */
  sqlite3 *db = pParse->db;

  if( pName2 && pName2->n>0 ){
    if( db->init.busy ) {
      sqlite3ErrorMsg(pParse, "corrupt database");
      pParse->nErr++;
      return -1;
    }
    *pUnqual = pName2;
    iDb = sqlite3FindDb(db, pName1);
    if( iDb<0 ){
      sqlite3ErrorMsg(pParse, "unknown database %T", pName1);
      pParse->nErr++;
      return -1;
    }

Changes to src/vdbe.c.

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
....
2694
2695
2696
2697
2698
2699
2700
2701



2702
2703
2704
2705
2706
2707
2708
**
** Various scripts scan this source file in order to generate HTML
** documentation, headers files, or other derived files.  The formatting
** of the code in this file is, therefore, important.  See other comments
** in this file for details.  If in doubt, do not deviate from existing
** commenting and indentation practices when changing or adding code.
**
** $Id: vdbe.c,v 1.786 2008/11/05 16:37:35 drh Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
#include "vdbeInt.h"

/*
** The following global variable is incremented every time a cursor
................................................................................
  }
  if( pOp->p5 ){
    assert( p2>0 );
    assert( p2<=p->nMem );
    pIn2 = &p->aMem[p2];
    sqlite3VdbeMemIntegerify(pIn2);
    p2 = pIn2->u.i;
    assert( p2>=2 );



  }
  assert( i>=0 );
  pCur = allocateCursor(p, i, &pOp[-1], iDb, 1);
  if( pCur==0 ) goto no_mem;
  pCur->nullRow = 1;
  rc = sqlite3BtreeCursor(pX, p2, wrFlag, pOp->p4.p, pCur->pCursor);
  if( pOp->p4type==P4_KEYINFO ){







|







 







|
>
>
>







39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
....
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
**
** Various scripts scan this source file in order to generate HTML
** documentation, headers files, or other derived files.  The formatting
** of the code in this file is, therefore, important.  See other comments
** in this file for details.  If in doubt, do not deviate from existing
** commenting and indentation practices when changing or adding code.
**
** $Id: vdbe.c,v 1.787 2008/11/13 18:29:51 shane Exp $
*/
#include "sqliteInt.h"
#include <ctype.h>
#include "vdbeInt.h"

/*
** The following global variable is incremented every time a cursor
................................................................................
  }
  if( pOp->p5 ){
    assert( p2>0 );
    assert( p2<=p->nMem );
    pIn2 = &p->aMem[p2];
    sqlite3VdbeMemIntegerify(pIn2);
    p2 = pIn2->u.i;
    if( p2<2 ) {
      rc = SQLITE_CORRUPT_BKPT;
      goto abort_due_to_error;
    }
  }
  assert( i>=0 );
  pCur = allocateCursor(p, i, &pOp[-1], iDb, 1);
  if( pCur==0 ) goto no_mem;
  pCur->nullRow = 1;
  rc = sqlite3BtreeCursor(pX, p2, wrFlag, pOp->p4.p, pCur->pCursor);
  if( pOp->p4type==P4_KEYINFO ){

Changes to test/corruptC.test.

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
..
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
...
161
162
163
164
165
166
167
168




169
170


171
172



173
174
175
















































































176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

191









192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222












223
224
225
226
227
228
229
...
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# This file implements regression tests for SQLite library.
#
# This file implements tests to make sure SQLite does not crash or
# segfault if it sees a corrupt database file.  It creates a base
# data base file, then tests that single byte corruptions in 
# increasingly larger quantities are handled gracefully.
#
# $Id: corruptC.test,v 1.8 2008/11/12 18:21:36 danielk1977 Exp $

catch {file delete -force test.db test.db-journal test.bu}

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

# Set a uniform random seed
expr srand(0)

# Construct a compact, dense database for testing.
#
do_test corruptC-1.1 {
  execsql {
    PRAGMA auto_vacuum = 0;
    BEGIN;
    CREATE TABLE t1(x,y);
................................................................................
ifcapable {integrityck} {
  integrity_check corruptC-1.2
}

# Generate random integer
#
proc random {range} {
    return [expr {round(rand()*$range)}]
}

# Copy file $from into $to
#
proc copy_file {from to} {
  set f [open $from]
  fconfigure $f -translation binary
  set t [open $to w]
  fconfigure $t -translation binary
  puts -nonewline $t [read $f [file size $from]]
  close $t
  close $f
}

# Setup for the tests.  Make a backup copy of the good database in test.bu.
#
db close
copy_file test.db test.bu
sqlite3 db test.db
set fsize [file size test.db]













#

# first test some specific corruption tests found from earlier runs

#

# test that a corrupt content offset size is handled (seed 5577)
do_test corruptC-2.1 {
  db close
  copy_file test.bu test.db

................................................................................
  # insert corrupt byte(s)
  hexio_write test.db 619 [format %02x 0xe2]
  hexio_write test.db 3150 [format %02x 0xa8]

  sqlite3 db test.db
  catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
} {1 {database disk image is malformed}}





#
# now test for a series of quasi-random seeds


#
for {set tn 0} {$tn<=1024} {incr tn 1} {




  # Set a quasi-random random seed
  expr srand($tn)

















































































  # setup for test
  db close
  copy_file test.bu test.db
  sqlite3 db test.db

  # Seek to a random location in the file, and write a random single byte
  # value.  Then do various operations on the file to make sure that
  # the database engine can handle the corruption gracefully.
  #
  set last 0
  for {set i 1} {$i<=1024 && !$last} {incr i 1} {

    # insert random byte at random location
    db close

    hexio_write test.db [random $fsize] [format %02x [random 255]]









    sqlite3 db test.db

    # do a few random operations to make sure that if 
    # they error, they error gracefully instead of crashing.
    do_test corruptC-3.$tn.$i.1 {
      catchsql {SELECT count(*) FROM sqlite_master}
      set x {}
    } {}
    do_test corruptC-3.$tn.$i.2 {
      catchsql {SELECT count(*) FROM t1}
      set x {}
    } {}
    do_test corruptC-3.$tn.$i.3 {
      catchsql {SELECT count(*) FROM t1 WHERE x>13}
      set x {}
    } {}
    do_test corruptC-3.$tn.$i.4 {
      catchsql {SELECT count(*) FROM t2}
      set x {}
    } {}
    do_test corruptC-3.$tn.$i.5 {
      catchsql {SELECT count(*) FROM t2 WHERE x<13}
      set x {}
    } {}
    do_test corruptC-3.$tn.$i.6 {
      catchsql {BEGIN; UPDATE t1 SET y=1; ROLLBACK;}
      set x {}
    } {}
    do_test corruptC-3.$tn.$i.7 {
      catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
      set x {}












    } {}

    # check the integrity of the database.
    # once the corruption is detected, we can stop.
    ifcapable {integrityck} {
      set res [ catchsql {PRAGMA integrity_check} ]
      set ans [lindex $res 1]
................................................................................
      }
    }

    # Check that no page references were leaked.
    # TBD:  need to figure out why this doesn't work
    # work with ROLLBACKs...
    if {0} {
      do_test corruptC-3.$tn.$i.8 {
        set bt [btree_from_db db]
        db_enter db
        array set stats [btree_pager_stats $bt]
        db_leave db
        set stats(ref)
      } {0}
    }







|






<
<
<







 







|





|
<
<
<
<
<
<









>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
>







 








>
>
>
>
|
<
>
>
|
<
>
>
>

<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>











|



>
|
>
>
>
>
>
>
>
>
>




|



|



|



|



|



|



|


>
>
>
>
>
>
>
>
>
>
>
>







 







|







11
12
13
14
15
16
17
18
19
20
21
22
23
24



25
26
27
28
29
30
31
..
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
...
166
167
168
169
170
171
172
173
174
175
176
177
178

179
180
181

182
183
184
185


186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
...
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# This file implements regression tests for SQLite library.
#
# This file implements tests to make sure SQLite does not crash or
# segfault if it sees a corrupt database file.  It creates a base
# data base file, then tests that single byte corruptions in 
# increasingly larger quantities are handled gracefully.
#
# $Id: corruptC.test,v 1.9 2008/11/13 18:29:51 shane Exp $

catch {file delete -force test.db test.db-journal test.bu}

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




# Construct a compact, dense database for testing.
#
do_test corruptC-1.1 {
  execsql {
    PRAGMA auto_vacuum = 0;
    BEGIN;
    CREATE TABLE t1(x,y);
................................................................................
ifcapable {integrityck} {
  integrity_check corruptC-1.2
}

# Generate random integer
#
proc random {range} {
  return [expr {round(rand()*$range)}]
}

# Copy file $from into $to
#
proc copy_file {from to} {
  file copy -force $from $to






}

# Setup for the tests.  Make a backup copy of the good database in test.bu.
#
db close
copy_file test.db test.bu
sqlite3 db test.db
set fsize [file size test.db]

# Set a quasi-random random seed. 
if {[info exists SOAKTEST]} {
  # If we are doing SOAK tests, we want a different
  # random seed for each run.  Ideally we would like 
  # to use [clock clicks] or something like that here.
  set qseed [file mtime test.db]
} else {
  # If we are not doing soak tests,
  # make it repeatable.
  set qseed 0
}
expr srand($qseed)

#
# First test some specific corruption tests found from earlier runs
# with specific seeds.
#

# test that a corrupt content offset size is handled (seed 5577)
do_test corruptC-2.1 {
  db close
  copy_file test.bu test.db

................................................................................
  # insert corrupt byte(s)
  hexio_write test.db 619 [format %02x 0xe2]
  hexio_write test.db 3150 [format %02x 0xa8]

  sqlite3 db test.db
  catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
} {1 {database disk image is malformed}}

# corruption (seed 178692)
do_test corruptC-2.7 {
  db close
  copy_file test.bu test.db


  # insert corrupt byte(s)
  hexio_write test.db 3074 [format %02x 0xa0]


  sqlite3 db test.db
  catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
} {1 {database disk image is malformed}}



# corruption (seed 179069)
do_test corruptC-2.8 {
  db close
  copy_file test.bu test.db

  # insert corrupt byte(s)
  hexio_write test.db 1393 [format %02x 0x7d]
  hexio_write test.db 84 [format %02x 0x19]
  hexio_write test.db 3287 [format %02x 0x3b]
  hexio_write test.db 2564 [format %02x 0xed]
  hexio_write test.db 2139 [format %02x 0x55]

  sqlite3 db test.db
  catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;}
} {1 {database disk image is malformed}}

# corruption (seed 170434)
do_test corruptC-2.9 {
  db close
  copy_file test.bu test.db

  # insert corrupt byte(s)
  hexio_write test.db 2095 [format %02x 0xd6]

  sqlite3 db test.db
  catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;}
} {1 {database disk image is malformed}}

# corruption (seed 186504)
do_test corruptC-2.10 {
  db close
  copy_file test.bu test.db

  # insert corrupt byte(s)
  hexio_write test.db 3130 [format %02x 0x02]
  
  sqlite3 db test.db
  catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
} {1 {database disk image is malformed}}

# corruption (seed 1589)
do_test corruptC-2.11 {
  db close
  copy_file test.bu test.db

  # insert corrupt byte(s)
  hexio_write test.db 55 [format %02x 0xa7]
  
  sqlite3 db test.db
  catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;}
} {1 {database disk image is malformed}}

# corruption (seed 14166)
do_test corruptC-2.12 {
  db close
  copy_file test.bu test.db

  # insert corrupt byte(s)
  hexio_write test.db 974 [format %02x 0x2e]
  
  sqlite3 db test.db
  catchsql {SELECT count(*) FROM sqlite_master;}
} {1 {malformed database schema (t1i1) - corrupt database}}

# corruption (seed 218803)
do_test corruptC-2.13 {
  db close
  copy_file test.bu test.db

  # insert corrupt byte(s)
  hexio_write test.db 102 [format %02x 0x12]
  
  sqlite3 db test.db
  catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;}
} {1 {database disk image is malformed}}


#
# now test for a series of quasi-random seeds
for {set tn 0} {$tn<1024} {incr tn 1} {

  # setup for test
  db close
  copy_file test.bu test.db
  sqlite3 db test.db

  # Seek to a random location in the file, and write a random single byte
  # value.  Then do various operations on the file to make sure that
  # the database engine can handle the corruption gracefully.
  #
  set last 0
  for {set i 1} {$i<=512 && !$last} {incr i 1} {

    # insert random byte at random location
    db close
    set roffset [random $fsize]
    set rbyte [format %02x [random 255]]

    # You can uncomment the following to have it trace
    # exactly how it's corrupting the file.  This is 
    # useful for generating the "seed specific" tests
    # above.
    # set rline "$roffset $rbyte"
    # puts stdout $rline

    hexio_write test.db $roffset $rbyte
    sqlite3 db test.db

    # do a few random operations to make sure that if 
    # they error, they error gracefully instead of crashing.
    do_test corruptC-3.$tn.($qseed).$i.1 {
      catchsql {SELECT count(*) FROM sqlite_master}
      set x {}
    } {}
    do_test corruptC-3.$tn.($qseed).$i.2 {
      catchsql {SELECT count(*) FROM t1}
      set x {}
    } {}
    do_test corruptC-3.$tn.($qseed).$i.3 {
      catchsql {SELECT count(*) FROM t1 WHERE x>13}
      set x {}
    } {}
    do_test corruptC-3.$tn.($qseed).$i.4 {
      catchsql {SELECT count(*) FROM t2}
      set x {}
    } {}
    do_test corruptC-3.$tn.($qseed).$i.5 {
      catchsql {SELECT count(*) FROM t2 WHERE x<13}
      set x {}
    } {}
    do_test corruptC-3.$tn.($qseed).$i.6 {
      catchsql {BEGIN; UPDATE t1 SET y=1; ROLLBACK;}
      set x {}
    } {}
    do_test corruptC-3.$tn.($qseed).$i.7 {
      catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;}
      set x {}
    } {}
    do_test corruptC-3.$tn.($qseed).$i.8 {
      catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;}
      set x {}
    } {}
    do_test corruptC-3.$tn.($qseed).$i.9 {
      catchsql {BEGIN; DELETE FROM t2 WHERE x<13; ROLLBACK;}
      set x {}
    } {}
    do_test corruptC-3.$tn.($qseed).$i.10 {
      catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;}
      set x {}
    } {}

    # check the integrity of the database.
    # once the corruption is detected, we can stop.
    ifcapable {integrityck} {
      set res [ catchsql {PRAGMA integrity_check} ]
      set ans [lindex $res 1]
................................................................................
      }
    }

    # Check that no page references were leaked.
    # TBD:  need to figure out why this doesn't work
    # work with ROLLBACKs...
    if {0} {
      do_test corruptC-3.$tn.($qseed).$i.11 {
        set bt [btree_from_db db]
        db_enter db
        array set stats [btree_pager_stats $bt]
        db_leave db
        set stats(ref)
      } {0}
    }

Changes to test/soak.test.

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
..
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
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file is the driver for the "soak" tests. It is a peer of the
# quick.test and all.test scripts.
#
# $Id: soak.test,v 1.3 2008/07/12 14:52:20 drh Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl
rename finish_test really_finish_test
proc finish_test {} {}

# By default, guarantee that the tests will run for at least 1 hour.
................................................................................
#
# The general principle is to run those SQLite tests that use
# pseudo-random data in some way over and over again for a very 
# long time. The number of tests run depends on the value of 
# global variable $TIMEOUT - tests are run for at least $TIMEOUT 
# seconds.
#
#   fuzz.test   (pseudo-random SQL statements)
#   trans.test  (pseudo-random changes to a database followed by rollbacks)
#
# fuzzy malloc?
#
# Many database changes maintaining some kind of invariant. 
# Storing checksums etc.
#

# List of test files that are run by this file.
#
set SOAKTESTS {
  fuzz.test
  fuzz_malloc.test
  trans.test

}

set ISQUICK 1

set soak_starttime  [clock seconds]
set soak_finishtime [expr {$soak_starttime + $TIMEOUT}]








|







 







|
|
|
|











>







7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
..
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
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
# This file is the driver for the "soak" tests. It is a peer of the
# quick.test and all.test scripts.
#
# $Id: soak.test,v 1.4 2008/11/13 18:29:51 shane Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl
rename finish_test really_finish_test
proc finish_test {} {}

# By default, guarantee that the tests will run for at least 1 hour.
................................................................................
#
# The general principle is to run those SQLite tests that use
# pseudo-random data in some way over and over again for a very 
# long time. The number of tests run depends on the value of 
# global variable $TIMEOUT - tests are run for at least $TIMEOUT 
# seconds.
#
#   fuzz.test     (pseudo-random SQL statements)
#   trans.test    (pseudo-random changes to a database followed by rollbacks)
#   fuzz_malloc.test
#   corruptC.test (pseudo-random corruption to a database)
#
# Many database changes maintaining some kind of invariant. 
# Storing checksums etc.
#

# List of test files that are run by this file.
#
set SOAKTESTS {
  fuzz.test
  fuzz_malloc.test
  trans.test
  corruptC.test
}

set ISQUICK 1

set soak_starttime  [clock seconds]
set soak_finishtime [expr {$soak_starttime + $TIMEOUT}]