0

SQL: fix fuzzer-discovered issue in recovery module

The fix is in cursor.cc, where a page ID was directly compared to
kInvalidPageId instead of being validated by the `IsValidPageId()` fn.

The rest of the modifications in this change are:
* changing some DCHECKs to CHECKs for improved security and bug
  discovery
* application of `IsValidPageId()` rather than direct comparison in
  more places, for correctness and not out of strict necessity

Bug: 1508758
Change-Id: Ic8d344f710b2008cf7d45bab608195d1d487f681
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5101280
Reviewed-by: Ayu Ishii <ayui@chromium.org>
Commit-Queue: Evan Stade <estade@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1235108}
This commit is contained in:
Evan Stade
2023-12-08 18:12:43 +00:00
committed by Chromium LUCI CQ
parent 0df3906776
commit f4cb61eec4
6 changed files with 39 additions and 36 deletions

@@ -63,7 +63,7 @@ int InnerPageDecoder::TryAdvance() {
// TODO(pwnall): UMA the error code. // TODO(pwnall): UMA the error code.
next_read_index_ = cell_count_ + 1; // End the reading process. next_read_index_ = cell_count_ + 1; // End the reading process.
return DatabasePageReader::kInvalidPageId; return DatabasePageReader::kHighestInvalidPageId;
} }
const uint8_t* const page_data = db_reader_->page_data(); const uint8_t* const page_data = db_reader_->page_data();
@@ -85,11 +85,11 @@ int InnerPageDecoder::TryAdvance() {
// Each cell needs 1 byte for the rowid varint, in addition to the 4 bytes // Each cell needs 1 byte for the rowid varint, in addition to the 4 bytes
// for the child page number that will be read below. Skip cells that // for the child page number that will be read below. Skip cells that
// obviously go over the page end. // obviously go over the page end.
return DatabasePageReader::kInvalidPageId; return DatabasePageReader::kHighestInvalidPageId;
} }
if (cell_pointer < kFirstCellOfsetInnerPageOffset) { if (cell_pointer < kFirstCellOfsetInnerPageOffset) {
// The pointer points into the cell's header. // The pointer points into the cell's header.
return DatabasePageReader::kInvalidPageId; return DatabasePageReader::kHighestInvalidPageId;
} }
return LoadBigEndianInt32(page_data + cell_pointer); return LoadBigEndianInt32(page_data + cell_pointer);

@@ -72,8 +72,9 @@ int VirtualCursor::Next() {
continue; continue;
} }
int next_page_id = inner_decoder->TryAdvance(); int next_page_id = inner_decoder->TryAdvance();
if (next_page_id == DatabasePageReader::kInvalidPageId) if (!DatabasePageReader::IsValidPageId(next_page_id)) {
continue; continue;
}
AppendPageDecoder(next_page_id); AppendPageDecoder(next_page_id);
} }

@@ -6,13 +6,14 @@
#include <limits> #include <limits>
#include "base/logging.h"
#include "sql/recover_module/table.h" #include "sql/recover_module/table.h"
#include "third_party/sqlite/sqlite3.h" #include "third_party/sqlite/sqlite3.h"
namespace sql { namespace sql {
namespace recover { namespace recover {
constexpr int DatabasePageReader::kInvalidPageId; constexpr int DatabasePageReader::kHighestInvalidPageId;
constexpr int DatabasePageReader::kMinPageSize; constexpr int DatabasePageReader::kMinPageSize;
constexpr int DatabasePageReader::kMaxPageSize; constexpr int DatabasePageReader::kMaxPageSize;
constexpr int DatabasePageReader::kDatabaseHeaderSize; constexpr int DatabasePageReader::kDatabaseHeaderSize;
@@ -25,16 +26,16 @@ static_assert(DatabasePageReader::kMaxPageId <= std::numeric_limits<int>::max(),
DatabasePageReader::DatabasePageReader(VirtualTable* table) DatabasePageReader::DatabasePageReader(VirtualTable* table)
: page_data_(std::make_unique<uint8_t[]>(table->page_size())), : page_data_(std::make_unique<uint8_t[]>(table->page_size())),
table_(table) { table_(table) {
DCHECK(table != nullptr); CHECK(table != nullptr);
DCHECK(IsValidPageSize(table->page_size())); CHECK(IsValidPageSize(table->page_size()));
} }
DatabasePageReader::~DatabasePageReader() = default; DatabasePageReader::~DatabasePageReader() = default;
int DatabasePageReader::ReadPage(int page_id) { int DatabasePageReader::ReadPage(int page_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_GT(page_id, kInvalidPageId); CHECK(IsValidPageId(page_id));
DCHECK_LE(page_id, kMaxPageId); CHECK_LE(page_id, kMaxPageId);
if (page_id_ == page_id) if (page_id_ == page_id)
return SQLITE_OK; return SQLITE_OK;
@@ -47,8 +48,8 @@ int DatabasePageReader::ReadPage(int page_id) {
"The |read_size| computation above may overflow"); "The |read_size| computation above may overflow");
page_size_ = read_size; page_size_ = read_size;
DCHECK_GE(page_size_, kMinUsablePageSize); CHECK_GE(page_size_, kMinUsablePageSize);
DCHECK_LE(page_size_, kMaxPageSize); CHECK_LE(page_size_, kMaxPageSize);
const int64_t read_offset = const int64_t read_offset =
static_cast<int64_t>(page_id - 1) * page_size + page_offset; static_cast<int64_t>(page_id - 1) * page_size + page_offset;
@@ -60,10 +61,10 @@ int DatabasePageReader::ReadPage(int page_id) {
int sqlite_status = int sqlite_status =
RawRead(sqlite_file, read_size, read_offset, page_data_.get()); RawRead(sqlite_file, read_size, read_offset, page_data_.get());
// |page_id_| needs to be set to kInvalidPageId if the read failed. // |page_id_| needs to be set to kHighestInvalidPageId if the read failed.
// Otherwise, future ReadPage() calls with the previous |page_id_| value // Otherwise, future ReadPage() calls with the previous |page_id_| value
// would return SQLITE_OK, but the page data buffer might be trashed. // would return SQLITE_OK, but the page data buffer might be trashed.
page_id_ = (sqlite_status == SQLITE_OK) ? page_id : kInvalidPageId; page_id_ = (sqlite_status == SQLITE_OK) ? page_id : kHighestInvalidPageId;
return sqlite_status; return sqlite_status;
} }
@@ -72,10 +73,10 @@ int DatabasePageReader::RawRead(sqlite3_file* sqlite_file,
int read_size, int read_size,
int64_t read_offset, int64_t read_offset,
uint8_t* result_buffer) { uint8_t* result_buffer) {
DCHECK(sqlite_file != nullptr); CHECK(sqlite_file != nullptr);
DCHECK_GE(read_size, 0); CHECK_GE(read_size, 0);
DCHECK_GE(read_offset, 0); CHECK_GE(read_offset, 0);
DCHECK(result_buffer != nullptr); CHECK(result_buffer != nullptr);
// Retry the I/O operations a few times if they fail. This is especially // Retry the I/O operations a few times if they fail. This is especially
// useful when recovering from database corruption. // useful when recovering from database corruption.

@@ -29,8 +29,9 @@ class VirtualTable;
// cursors. Instances are not thread-safe. // cursors. Instances are not thread-safe.
class DatabasePageReader { class DatabasePageReader {
public: public:
// Guaranteed to be an invalid page number. // Guaranteed to be an invalid page number. NB: use `IsValidPageId()` to
static constexpr int kInvalidPageId = 0; // validate a page id.
static constexpr int kHighestInvalidPageId = 0;
// Minimum database page size supported by SQLite. // Minimum database page size supported by SQLite.
static constexpr int kMinPageSize = 512; static constexpr int kMinPageSize = 512;
@@ -70,7 +71,7 @@ class DatabasePageReader {
// ReadPage() was never called. // ReadPage() was never called.
const uint8_t* page_data() const { const uint8_t* page_data() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(page_id_, kInvalidPageId) CHECK(IsValidPageId(page_id_))
<< "Successful ReadPage() required before accessing pager state"; << "Successful ReadPage() required before accessing pager state";
return page_data_.get(); return page_data_.get();
} }
@@ -86,10 +87,10 @@ class DatabasePageReader {
// ReadPage() was never called. // ReadPage() was never called.
int page_size() const { int page_size() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(page_id_, kInvalidPageId) CHECK(IsValidPageId(page_id_))
<< "Successful ReadPage() required before accessing pager state"; << "Successful ReadPage() required before accessing pager state";
DCHECK_GE(page_size_, kMinUsablePageSize); CHECK_GE(page_size_, kMinUsablePageSize);
DCHECK_LE(page_size_, kMaxPageSize); CHECK_LE(page_size_, kMaxPageSize);
return page_size_; return page_size_;
} }
@@ -99,7 +100,7 @@ class DatabasePageReader {
// ReadPage() was never called. // ReadPage() was never called.
int page_id() const { int page_id() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_NE(page_id_, kInvalidPageId) CHECK(IsValidPageId(page_id_))
<< "Successful ReadPage() required before accessing pager state"; << "Successful ReadPage() required before accessing pager state";
return page_id_; return page_id_;
} }
@@ -122,7 +123,7 @@ class DatabasePageReader {
// //
// Valid page IDs are positive 32-bit integers. // Valid page IDs are positive 32-bit integers.
static constexpr bool IsValidPageId(int64_t page_id) noexcept { static constexpr bool IsValidPageId(int64_t page_id) noexcept {
return page_id > kInvalidPageId && page_id <= kMaxPageId; return page_id > kHighestInvalidPageId && page_id <= kMaxPageId;
} }
// Low-level read wrapper. Returns a SQLite error code. // Low-level read wrapper. Returns a SQLite error code.
@@ -135,8 +136,8 @@ class DatabasePageReader {
private: private:
// Points to the last page successfully read by ReadPage(). // Points to the last page successfully read by ReadPage().
// Set to kInvalidPageId if the last read was unsuccessful. // Set to kHighestInvalidPageId if the last read was unsuccessful.
int page_id_ = kInvalidPageId; int page_id_ = kHighestInvalidPageId;
// Stores the bytes of the last page successfully read by ReadPage(). // Stores the bytes of the last page successfully read by ReadPage().
// The content is undefined if the last call to ReadPage() did not succeed. // The content is undefined if the last call to ReadPage() did not succeed.
const std::unique_ptr<uint8_t[]> page_data_; const std::unique_ptr<uint8_t[]> page_data_;

@@ -125,7 +125,7 @@ int MaxOverflowPayloadSize(int page_size) {
} // namespace } // namespace
LeafPayloadReader::LeafPayloadReader(DatabasePageReader* db_reader) LeafPayloadReader::LeafPayloadReader(DatabasePageReader* db_reader)
: db_reader_(db_reader), page_id_(DatabasePageReader::kInvalidPageId) {} : db_reader_(db_reader) {}
LeafPayloadReader::~LeafPayloadReader() = default; LeafPayloadReader::~LeafPayloadReader() = default;
@@ -194,7 +194,7 @@ bool LeafPayloadReader::Initialize(int64_t payload_size, int payload_offset) {
page_size) { page_size) {
// Corruption can result in overly large payload sizes. Reject the obvious // Corruption can result in overly large payload sizes. Reject the obvious
// case where the in-page payload extends past the end of the page. // case where the in-page payload extends past the end of the page.
page_id_ = DatabasePageReader::kInvalidPageId; page_id_ = DatabasePageReader::kHighestInvalidPageId;
return false; return false;
} }
@@ -205,7 +205,7 @@ bool LeafPayloadReader::Initialize(int64_t payload_size, int payload_offset) {
bool LeafPayloadReader::IsInitialized() const { bool LeafPayloadReader::IsInitialized() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return page_id_ != DatabasePageReader::kInvalidPageId; return DatabasePageReader::IsValidPageId(page_id_);
} }
bool LeafPayloadReader::ReadPayload(int64_t offset, bool LeafPayloadReader::ReadPayload(int64_t offset,
@@ -220,7 +220,7 @@ bool LeafPayloadReader::ReadPayload(int64_t offset,
DCHECK(buffer != nullptr); DCHECK(buffer != nullptr);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(page_id_ != DatabasePageReader::kInvalidPageId) DCHECK(IsInitialized())
<< "Initialize() not called, or last call did not succeed"; << "Initialize() not called, or last call did not succeed";
if (offset < inline_payload_size_) { if (offset < inline_payload_size_) {
@@ -337,4 +337,4 @@ bool LeafPayloadReader::PopulateNextOverflowPageId() {
} }
} // namespace recover } // namespace recover
} // namespace sql } // namespace sql

@@ -60,7 +60,7 @@ class LeafPayloadReader {
// payload_size(). // payload_size().
int inline_payload_size() const { int inline_payload_size() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(page_id_ != DatabasePageReader::kInvalidPageId) DCHECK(IsInitialized())
<< "Initialize() not called, or last call did not succeed"; << "Initialize() not called, or last call did not succeed";
DCHECK_LE(inline_payload_size_, payload_size_); DCHECK_LE(inline_payload_size_, payload_size_);
return inline_payload_size_; return inline_payload_size_;
@@ -74,7 +74,7 @@ class LeafPayloadReader {
// The return value is guaranteed to be at least inline_payload_size(). // The return value is guaranteed to be at least inline_payload_size().
int payload_size() const { int payload_size() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(page_id_ != DatabasePageReader::kInvalidPageId) DCHECK(IsInitialized())
<< "Initialize() not called, or last call did not succeed"; << "Initialize() not called, or last call did not succeed";
DCHECK_LE(inline_payload_size_, payload_size_); DCHECK_LE(inline_payload_size_, payload_size_);
return payload_size_; return payload_size_;
@@ -116,8 +116,8 @@ class LeafPayloadReader {
// The ID of the B-tree page containing the current payload's inline bytes. // The ID of the B-tree page containing the current payload's inline bytes.
// //
// Set to kInvalidPageId if the reader wasn't successfully initialized. // Set to kHighestInvalidPageId if the reader wasn't successfully initialized.
int page_id_ = DatabasePageReader::kInvalidPageId; int page_id_ = DatabasePageReader::kHighestInvalidPageId;
// The start of the current payload's inline bytes on the B-tree page. // The start of the current payload's inline bytes on the B-tree page.
// //