/ Check-in [93545861a7]
Login

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

Overview
Comment:Fix for handling database files corrupted in such a was as to make a b-tree page a direct or indirect descendant of itself. (CVS 5689)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 93545861a70c190d67b0d1effdd8fe038d28811c
User & Date: danielk1977 2008-09-10 14:45:58
Context
2008-09-10
17:53
Fix some trivial cases where database corruption was causing an error code other than SQLITE_CORRUPT to be returned. (CVS 5690) check-in: 89fda074f6 user: danielk1977 tags: trunk
14:45
Fix for handling database files corrupted in such a was as to make a b-tree page a direct or indirect descendant of itself. (CVS 5689) check-in: 93545861a7 user: danielk1977 tags: trunk
13:09
Documentation updates: Describe recursion capabilities for the various callbacks. (CVS 5688) check-in: edd80811d7 user: drh 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
...
934
935
936
937
938
939
940



941
942
943
944
945
946
947
....
1091
1092
1093
1094
1095
1096
1097

1098
1099
1100
1101

1102
1103







1104
1105
1106
1107
1108

1109
1110
1111
1112
1113
1114
1115
....
6178
6179
6180
6181
6182
6183
6184
6185
6186
6187
6188
6189
6190
6191
6192
6193
6194
6195
6196
6197
6198
6199
** 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.509 2008/09/05 05:02:47 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"

................................................................................
  pBt = pPage->pBt;
  assert( pBt!=0 );
  assert( pParent==0 || pParent->pBt==pBt );
  assert( sqlite3_mutex_held(pBt->mutex) );
  assert( pPage->pgno==sqlite3PagerPagenumber(pPage->pDbPage) );
  assert( pPage == sqlite3PagerGetExtra(pPage->pDbPage) );
  assert( pPage->aData == sqlite3PagerGetData(pPage->pDbPage) );



  if( pPage->pParent!=pParent && (pPage->pParent!=0 || pPage->isInit) ){
    /* The parent page should never change unless the file is corrupt */
    return SQLITE_CORRUPT_BKPT;
  }
  if( pPage->isInit ) return SQLITE_OK;
  if( pPage->pParent==0 && pParent!=0 ){
    pPage->pParent = pParent;
................................................................................
  BtShared *pBt,          /* The database file */
  Pgno pgno,           /* Number of the page to get */
  MemPage **ppPage,    /* Write the page pointer here */
  MemPage *pParent     /* Parent of the page */
){
  int rc;
  assert( sqlite3_mutex_held(pBt->mutex) );

  if( pgno==0 ){
    return SQLITE_CORRUPT_BKPT; 
  }
  rc = sqlite3BtreeGetPage(pBt, pgno, ppPage, 0);

  if( rc==SQLITE_OK && (*ppPage)->isInit==0 ){
    rc = sqlite3BtreeInitPage(*ppPage, pParent);







    if( rc!=SQLITE_OK ){
      releasePage(*ppPage);
      *ppPage = 0;
    }
  }

  return rc;
}

/*
** Release a MemPage.  This should be called once for each prior
** call to sqlite3BtreeGetPage.
*/
................................................................................
  }

  rc = getAndInitPage(pBt, pgno, &pPage, pParent);
  if( rc ) goto cleardatabasepage_out;
  for(i=0; i<pPage->nCell; i++){
    pCell = findCell(pPage, i);
    if( !pPage->leaf ){
      rc = clearDatabasePage(pBt, get4byte(pCell), pPage->pParent, 1);
      if( rc ) goto cleardatabasepage_out;
    }
    rc = clearCell(pPage, pCell);
    if( rc ) goto cleardatabasepage_out;
  }
  if( !pPage->leaf ){
    rc = clearDatabasePage(pBt, get4byte(&pPage->aData[8]), pPage->pParent, 1);
    if( rc ) goto cleardatabasepage_out;
  }
  if( freePageFlag ){
    rc = freePage(pPage);
  }else if( (rc = sqlite3PagerWrite(pPage->pDbPage))==0 ){
    zeroPage(pPage, pPage->aData[0] | PTF_LEAF);
  }







|







 







>
>
>







 







>




>
|
|
>
>
>
>
>
>
>





>







 







|






|







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
....
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
....
6191
6192
6193
6194
6195
6196
6197
6198
6199
6200
6201
6202
6203
6204
6205
6206
6207
6208
6209
6210
6211
6212
** 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.510 2008/09/10 14:45:58 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"

................................................................................
  pBt = pPage->pBt;
  assert( pBt!=0 );
  assert( pParent==0 || pParent->pBt==pBt );
  assert( sqlite3_mutex_held(pBt->mutex) );
  assert( pPage->pgno==sqlite3PagerPagenumber(pPage->pDbPage) );
  assert( pPage == sqlite3PagerGetExtra(pPage->pDbPage) );
  assert( pPage->aData == sqlite3PagerGetData(pPage->pDbPage) );
  if( pPage==pParent ){
    return SQLITE_CORRUPT_BKPT;
  }
  if( pPage->pParent!=pParent && (pPage->pParent!=0 || pPage->isInit) ){
    /* The parent page should never change unless the file is corrupt */
    return SQLITE_CORRUPT_BKPT;
  }
  if( pPage->isInit ) return SQLITE_OK;
  if( pPage->pParent==0 && pParent!=0 ){
    pPage->pParent = pParent;
................................................................................
  BtShared *pBt,          /* The database file */
  Pgno pgno,           /* Number of the page to get */
  MemPage **ppPage,    /* Write the page pointer here */
  MemPage *pParent     /* Parent of the page */
){
  int rc;
  assert( sqlite3_mutex_held(pBt->mutex) );
  assert( !pParent || pParent->isInit );
  if( pgno==0 ){
    return SQLITE_CORRUPT_BKPT; 
  }
  rc = sqlite3BtreeGetPage(pBt, pgno, ppPage, 0);
  if( rc==SQLITE_OK ){
    if( (*ppPage)->isInit==0 ){
      rc = sqlite3BtreeInitPage(*ppPage, pParent);
    }else if( pParent && ((*ppPage)==pParent || (*ppPage)->pParent!=pParent) ){
      /* This condition indicates a loop in the b-tree structure (the scenario
      ** where database corruption has caused a page to be a direct or
      ** indirect descendant of itself).
      */ 
      rc = SQLITE_CORRUPT_BKPT;
    }
    if( rc!=SQLITE_OK ){
      releasePage(*ppPage);
      *ppPage = 0;
    }
  }

  return rc;
}

/*
** Release a MemPage.  This should be called once for each prior
** call to sqlite3BtreeGetPage.
*/
................................................................................
  }

  rc = getAndInitPage(pBt, pgno, &pPage, pParent);
  if( rc ) goto cleardatabasepage_out;
  for(i=0; i<pPage->nCell; i++){
    pCell = findCell(pPage, i);
    if( !pPage->leaf ){
      rc = clearDatabasePage(pBt, get4byte(pCell), pPage, 1);
      if( rc ) goto cleardatabasepage_out;
    }
    rc = clearCell(pPage, pCell);
    if( rc ) goto cleardatabasepage_out;
  }
  if( !pPage->leaf ){
    rc = clearDatabasePage(pBt, get4byte(&pPage->aData[8]), pPage, 1);
    if( rc ) goto cleardatabasepage_out;
  }
  if( freePageFlag ){
    rc = freePage(pPage);
  }else if( (rc = sqlite3PagerWrite(pPage->pDbPage))==0 ){
    zeroPage(pPage, pPage->aData[0] | PTF_LEAF);
  }

Added test/corruptB.test.

































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
# 2008 Sep 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.
#
# This file implements tests to make sure SQLite does not crash or
# segfault if it sees a corrupt database file.  It specifically focuses
# on loops in the B-Tree structure. A loop is formed in a B-Tree structure
# when there exists a page that is both an a descendent or ancestor of
# itself.
#
# $Id: corruptB.test,v 1.1 2008/09/10 14:45:58 danielk1977 Exp $

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


do_test corruptB-1.1 {
  execsql {
    PRAGMA auto_vacuum = 1;
    CREATE TABLE t1(x);
    INSERT INTO t1 VALUES(randomblob(200));
    INSERT INTO t1 SELECT randomblob(200) FROM t1;
    INSERT INTO t1 SELECT randomblob(200) FROM t1;
    INSERT INTO t1 SELECT randomblob(200) FROM t1;
    INSERT INTO t1 SELECT randomblob(200) FROM t1;
    INSERT INTO t1 SELECT randomblob(200) FROM t1;
  }
  expr {[file size test.db] > (1024*9)}
} {1}
integrity_check corruptB-1.2

file copy -force test.db bak.db

# Set the right-child of a B-Tree rootpage to refer to the root-page itself.
#
do_test corruptB-1.3.1 {
  set ::root [execsql {SELECT rootpage FROM sqlite_master}]
  set ::offset [expr {($::root-1)*1024}]
  hexio_write test.db [expr $offset+8] [hexio_render_int32 $::root]
} {4}
do_test corruptB-1.3.2 {
  sqlite3 db test.db
  catchsql { SELECT * FROM t1 }
} {1 {database disk image is malformed}}

# Set the left-child of a cell in a B-Tree rootpage to refer to the 
# root-page itself.
#
do_test corruptB-1.4.1 {
  db close
  file copy -force bak.db test.db
  set cell_offset [hexio_get_int [hexio_read test.db [expr $offset+12] 2]]
  hexio_write test.db [expr $offset+$cell_offset] [hexio_render_int32 $::root]
} {4}
do_test corruptB-1.4.2 {
  sqlite3 db test.db
  catchsql { SELECT * FROM t1 }
} {1 {database disk image is malformed}}

# Now grow the table B-Tree so that it is more than 2 levels high.
#
do_test corruptB-1.5.1 {
  db close
  file copy -force bak.db test.db
  sqlite3 db test.db
  execsql {
    INSERT INTO t1 SELECT randomblob(200) FROM t1;
    INSERT INTO t1 SELECT randomblob(200) FROM t1;
    INSERT INTO t1 SELECT randomblob(200) FROM t1;
    INSERT INTO t1 SELECT randomblob(200) FROM t1;
    INSERT INTO t1 SELECT randomblob(200) FROM t1;
    INSERT INTO t1 SELECT randomblob(200) FROM t1;
    INSERT INTO t1 SELECT randomblob(200) FROM t1;
  }
} {}

file copy -force test.db bak.db

# Set the right-child pointer of the right-child of the root page to point
# back to the root page.
#
do_test corruptB-1.6.1 {
  db close
  set iRightChild [hexio_get_int [hexio_read test.db [expr $offset+8] 4]]
  set c_offset [expr ($iRightChild-1)*1024]
  hexio_write test.db [expr $c_offset+8] [hexio_render_int32 $::root]
} {4}
do_test corruptB-1.6.2 {
  sqlite3 db test.db
  catchsql { SELECT * FROM t1 }
} {1 {database disk image is malformed}}

# Set the left-child pointer of a cell of the right-child of the root page to
# point back to the root page.
#
do_test corruptB-1.7.1 {
  db close
  file copy -force bak.db test.db
  set cell_offset [hexio_get_int [hexio_read test.db [expr $c_offset+12] 2]]
  hexio_write test.db [expr $c_offset+$cell_offset] [hexio_render_int32 $::root]
} {4}
do_test corruptB-1.7.2 {
  sqlite3 db test.db
  catchsql { SELECT * FROM t1 }
} {1 {database disk image is malformed}}

do_test corruptB-1.8.1 {
  db close
  set cell_offset [hexio_get_int [hexio_read test.db [expr $offset+12] 2]]
  set iLeftChild [
      hexio_get_int [hexio_read test.db [expr $offset+$cell_offset] 4]
  ]
  set c_offset [expr ($iLeftChild-1)*1024]
  hexio_write test.db [expr $c_offset+8] [hexio_render_int32 $::root]
} {4}
do_test corruptB-1.8.2 {
  sqlite3 db test.db
  catchsql { SELECT * FROM t1 }
} {1 {database disk image is malformed}}

# Set the left-child pointer of a cell of the right-child of the root page to
# point back to the root page.
#
do_test corruptB-1.9.1 {
  db close
  file copy -force bak.db test.db
  set cell_offset [hexio_get_int [hexio_read test.db [expr $c_offset+12] 2]]
  hexio_write test.db [expr $c_offset+$cell_offset] [hexio_render_int32 $::root]
} {4}
do_test corruptB-1.9.2 {
  sqlite3 db test.db
  catchsql { SELECT * FROM t1 }
} {1 {database disk image is malformed}}

finish_test