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
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  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
  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

sql / error_delegate_util.cc [blame]

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "sql/error_delegate_util.h"

#include <ostream>  // Needed to compile NOTREACHED() with operator <<.
#include <string>

#include "base/files/file_path.h"
#include "base/notreached.h"
#include "third_party/sqlite/sqlite3.h"

namespace sql {

bool IsErrorCatastrophic(int sqlite_error_code) {
  // SQLite result codes are documented at https://www.sqlite.org/rescode.html
  int primary_error_code = sqlite_error_code & 0xff;

  // Within each group, error codes are sorted by their numerical values. This
  // matches the order used by the SQLite documentation describing them.
  switch (primary_error_code) {
    // Group of error codes that should never be returned by SQLite.
    //
    // If we do get these, our database schema / query pattern / data managed to
    // trigger a bug in SQLite. In development, we DCHECK to flag this SQLite
    // bug. In production, we [[fallback]] to corruption handling, because the
    // bug may be persistent, and corruption recovery will get the user unstuck.
    case SQLITE_INTERNAL:  // Bug in SQLite.
    case SQLITE_EMPTY:     // Marked for SQLite internal use.
    case SQLITE_FORMAT:    // Not currently used, according to SQLite docs.
    case SQLITE_NOTICE:    // Only used as an argument to sqlite3_log().
    case SQLITE_WARNING:   // Only used as an argument to sqlite3_log().
      NOTREACHED() << "SQLite returned result code marked for internal use: "
                   << sqlite_error_code;

    // Group of error codes that may only be returned by SQLite (given Chrome's
    // usage patterns) if a database is corrupted. DCHECK would not be
    // appropriate, since these can occur in production. Silently [[fallback]]
    // to corruption handling.
    case SQLITE_ERROR:
      // Generic/fallback error code.
      //
      // In production, database corruption leads our SQL statements being
      // flagged as invalid. For example, a SQL statement may reference a table
      // or column whose name got corrupted.
      //
      // In development, this error code shows up most often when passing
      // invalid SQL statements to SQLite. We have DCHECKs in sql::Statement and
      // sql::Database::Execute() that catch obvious SQL syntax errors. We can't
      // DCHECK when a SQL statement uses incorrect table/index/row names,
      // because that can legitimately happen in production, due to corruption.
      //
      // In 2022 we considered these errors as non-catastrophic, and we didn't
      // find ANY invalid SQL statements, and only found failed transactions
      // and schemas that didn't match the reported schema version, which both
      // suggest corruption. See https://crbug.com/1321483 for context.
      [[fallthrough]];
    case SQLITE_PERM:
      // Failed to get the requested access mode for a newly created database.
      // The database was just created, so error recovery will not cause data
      // loss. Error recovery steps, such as re-creating database files, may
      // fix the permission problems.
      [[fallthrough]];
    case SQLITE_CORRUPT:
      // Some form of database corruption was detected. The sql::Recovery code
      // may be able to recover some of the data.
      [[fallthrough]];
    case SQLITE_CANTOPEN:
      // Failed to open the database, for a variety of reasons. All the reasons
      // come down to some form of corruption. Here are some known reasons:
      // * One of the file names (database, journal, WAL, etc.) points to a
      //   directory, not a file. This indicates filesystem corruption. Most
      //   likely, some app messed with the user's Chrome file. It's also
      //   possible that the inode was corrupted and the is_dir bit flipped.
      // * One of the file names is a symlink, and SQLite was instructed not to
      //   follow symlinks. This should not occur in Chrome, we let SQLite use
      //   its default symlink handling.
      // * The WAL file has a format version that SQLite can't understand. This
      //   should not occur in Chrome, as we don't use WAL yet.
      [[fallthrough]];
    case SQLITE_MISMATCH:
      // SQLite was forced to perform an operation that involves incompatible
      // data types. An example is attempting to store a non-integer value in a
      // ROWID primary key.
      //
      // In production, database corruption can lead to this. For example, it's
      // possible that a schema is corrupted in such a way that the ROWID
      // primary key column's name is swapped with another column's name.
      [[fallthrough]];
    case SQLITE_NOLFS:
      // The database failed to grow past the filesystem size limit. This is
      // unlikely to happen in Chrome, but it is theoretically possible.
      [[fallthrough]];
    case SQLITE_NOTADB:
      // The database header is corrupted. The sql::Recovery code will not be
      // able to recovery any data, as SQLite will refuse to open the database.
      return true;

    // Group of result codes that are not error codes. These should never make
    // it to error handling code. In development, we DCHECK to flag this Chrome
    // bug. In production, we hope this is a transient error, such as a race
    // condition.
    case SQLITE_OK:    // Most used success code.
    case SQLITE_ROW:   // The statement produced a row of output.
    case SQLITE_DONE:  // A step has completed in a multi-step operation.
      NOTREACHED() << "Called with non-error result code " << sqlite_error_code;

    // Group of error codes that should not be returned by SQLite given Chrome's
    // usage patterns, even if the database gets corrupted. In development, we
    // DCHECK to flag this Chrome bug. In production, we hope the errors have
    // transient causes, such as race conditions.
    case SQLITE_LOCKED:
      // Conflict between two concurrently executing statements in the same
      // database connection.
      //
      // In theory, SQLITE_LOCKED could also signal a conflict between different
      // connections (in the same process) sharing a page cache, but Chrome only
      // uses private page caches.
      NOTREACHED() << "Conflict between concurrently executing SQL statements";
    case SQLITE_NOMEM:
      // Out of memory. This is most likely a transient error.
      //
      // There's a small chance that the error is caused by trying to exchange
      // too much data with SQLite. Most such errors result in SQLITE_TOOBIG.
      NOTREACHED() << "SQLite reported out-of-memory: " << sqlite_error_code;
    case SQLITE_INTERRUPT:
      // Chrome features don't use sqlite3_interrupt().
      NOTREACHED() << "SQLite returned INTERRUPT code: " << sqlite_error_code;
    case SQLITE_NOTFOUND:
      // Unknown opcode in sqlite3_file_control(). Chrome's features only use a
      // few built-in opcodes.
      NOTREACHED() << "SQLite returned NOTFOUND code: " << sqlite_error_code;
    case SQLITE_MISUSE:
      // SQLite API misuse, such as trying to use a prepared statement after it
      // was finalized. In development, we DCHECK to flag this Chrome bug. In
      // production, we hope this is a race condition, and therefore transient.
      NOTREACHED() << "SQLite returned MISUSE code: " << sqlite_error_code;
    case SQLITE_AUTH:
      // Chrome features don't install an authorizer callback. Only WebSQL does.
      NOTREACHED() << "SQLite returned AUTH code: " << sqlite_error_code;
    case SQLITE_RANGE:
      // Chrome uses DCHECKs to ensure the validity of column indexes passed to
      // sqlite3_bind() and sqlite3_column().
      NOTREACHED() << "SQLite returned RANGE code: " << sqlite_error_code;

    // Group of error codes that should may be returned by SQLite given Chrome's
    // usage patterns, even without database corruption. In development, we
    // DCHECK to flag this Chrome bug. In production, we hope the errors have
    // transient causes, such as race conditions.
    case SQLITE_ABORT:
      // SQLITE_ABORT may be returned when a ROLLBACK statement is executed
      // concurrently with a pending read or write, and Chrome features are
      // allowed to execute concurrent statements in the same transaction, under
      // some conditions.
      //
      // It may be worth noting that Chrome features don't use callback routines
      // that may abort SQL statements, such as passing a callback to
      // sqlite3_exec().
      [[fallthrough]];
    case SQLITE_BUSY:
      // Failed to grab a lock on the database. Another database connection
      // (most likely in another process) is holding the database lock. This
      // should not be a problem for exclusive databases, which are strongly
      // recommended for Chrome features.
      [[fallthrough]];
    case SQLITE_READONLY:
      // SQLite either failed to write to the database file or its associated
      // files (journal, WAL, etc.), or considers it unsafe to do so.
      //
      // Most error codes (SQLITE_READONLY_DIRECTORY, SQLITE_READONLY_RECOVERY,
      // SQLITE_READONLY_ROLLBACK, SQLITE_READONLY_CANTLOCK) mean that SQLite
      // failed to write to some file, or to create a file (which entails
      // writing to the directory containing the database).
      //
      // SQLITE_READONLY_CANTLOCK should never happen in Chrome, because we will
      // only allow enabling WAL on databases that use exclusive locking.
      //
      // Unlike all other codes, SQLITE_READONLY_DBMOVED signals that a file was
      // deleted or renamed. It is returned when SQLite realizes that the
      // database file was moved or unlinked from the filesystem after it was
      // opened, so the associated files (journal, WAL, etc.) would not be found
      // by another SQLite instance in the event of a crash. This was observed
      // on the iOS try bots.
      [[fallthrough]];
    case SQLITE_IOERR:
      // Catch-all for many errors reported by the VFS. Some of the errors
      // indicate media failure (SQLITE_IOERR_READ), while others indicate
      // transient problems (SQLITE_IOERR_LOCK). In the future, we may invest in
      // distinguishing between them. For now, since all the codes are bundled
      // up, we must assume that the error is transient.
      [[fallthrough]];
    case SQLITE_FULL:
      // The disk is full. This is definitely a transient error, and does not
      // indicate any database corruption. While it's true that the user will be
      // stuck in this state until some action is taken, we're unlikely to help
      // the user if we run our recovery code or delete our databases.
      [[fallthrough]];
    case SQLITE_PROTOCOL:
      // Gave up while attempting to grab a lock on a WAL database at the
      // beginning of a transaction. In theory, this should not be a problem in
      // Chrome, because we'll only allow enabling WAL on databases with
      // exclusive locking. However, other software on the user's system may
      // lock our databases in a way that triggers this error.
      [[fallthrough]];
    case SQLITE_SCHEMA:
      // The database schema was changed between the time when a prepared
      // statement was compiled, and when it was executing.
      //
      // This can happen in production. Databases that don't use exclusive
      // locking (recommended but not yet required for Chrome features) may be
      // changed from another process via legitimate use of SQLite APIs.
      // Databases that do use exclusive locks may still be mutated on-disk, on
      // operating systems where exclusive locks are only enforced via advisory
      // locking.
      //
      // When we mandate exclusive locks for all features in Chrome, we may
      // classify this error as database corruption, because it is an indicator
      // that another process is interfering with Chrome's schemas.
      [[fallthrough]];
    case SQLITE_TOOBIG:
      // SQLite encountered a string or blob whose length exceeds
      // SQLITE_MAX_LENGTH, or it was asked to execute a SQL statement whose
      // length exceeds SQLITE_MAX_SQL_LENGTH or SQLITE_LIMIT_SQL_LENGTH.
      //
      // A corrupted database could cause this in the following ways:
      // * SQLite could encounter an overly large string or blob because its
      //   size field got corrupted.
      // * SQLite could attempt to execute an overly large SQL statement while
      //   operating on a corrupted schema. (Some of SQLite's DDL statements
      //   involve executing SQL that includes schema content.)
      //
      // However, this could also occur due to a Chrome bug where we ask SQLite
      // to bind an overly large string or blob. So, we currently don't classify
      // this as definitely induced by corruption.
      [[fallthrough]];
    case SQLITE_CONSTRAINT:
      // This can happen in production, when executing SQL statements with the
      // semantics of "create a record if it doesn't exist, otherwise do
      // nothing".
      return false;
  }

  NOTREACHED() << "SQLite returned unknown result code: " << sqlite_error_code;
}

std::string GetCorruptFileDiagnosticsInfo(
    const base::FilePath& corrupted_file_path) {
  std::string corrupted_file_info("Corrupted file: ");
  corrupted_file_info +=
      corrupted_file_path.DirName().BaseName().AsUTF8Unsafe() + "/" +
      corrupted_file_path.BaseName().AsUTF8Unsafe() + "\n";
  return corrupted_file_info;
}

}  // namespace sql