Skip to content

One corrupt encrypted page poisons subsequent integrity_check reads on the same connection #617

Description

@DBFuzz

Version and environment

Reproduced from SQLCipher commit 810db22f575ee7cf94ea96a3e91622b5fcece3dc on Linux amd64, using SQLite 3.53.3 / SQLCipher 4.17.0 Community and the bundled SQLCipher 4 testkey database.

Describe the bug

After exactly one encrypted page fails HMAC verification, SQLCipher stores a persistent codec/pager error. PRAGMA cipher_integrity_check correctly identifies page 17 as the only modified page, but a subsequent standard PRAGMA integrity_check on the same connection reports many earlier pages and page 1 as unreadable. Those extra reports are caused by the poisoned state, not additional on-disk corruption.

Minimal reproduction

timeout 900 sqlcipher-022-single-corrupt-encrypted-page-poisons-current-integrit-f3ef876c/poc/reproduce_corrupt_page_integrity_poison.sh

The essential data mutation and query sequence is:

cp sqlcipher-4.0-testkey.db corrupt.db
dd if=/dev/zero of=corrupt.db bs=4096 count=1 seek=16 conv=notrunc
sqlcipher corrupt.db <<'SQL'
.bail off
PRAGMA key='testkey';
PRAGMA cipher_integrity_check;
PRAGMA integrity_check;
SELECT count(*) FROM sqlite_schema;
SQL

Run the same SQL on an unmodified copy as the control.

Actual behavior/output

The clean control returns ok, ok, and 1. The corrupt copy first returns:

HMAC verification failed for page 17
Tree 2 page 17: btreeInitPage() returns error code 11

It then reports pages 16 through 3 and page 1 as unable to get the page. error code=1. Stderr contains sqlcipher_codec_ctx_set_error 1, identified deferred error condition: 1, and finally file is not a database.

Expected behavior

The integrity diagnostics should identify the actual failed page without turning its page-local authentication error into unrelated later read failures. Subsequent pages should be independently checked when safe, or the check should stop with an explicit global-abort result rather than misreporting a wider corruption set.

Impact and scope

The cascade exaggerates corruption scope and can mislead repair, restore, monitoring, and forensic decisions. This candidate concerns diagnostic accuracy on the current connection, not acceptance of corrupted data.

Relevant source/root cause

sqlite3Codec() calls sqlcipher_codec_ctx_set_error() on decrypt failure around src/sqlcipher.c:3459-3470. Later reads detect this stored error around :3414-3423, set pager error, and return a zeroed buffer. sqlite3pager_error() around src/pager.c:8057-8060 moves the pager into PAGER_ERROR, while cipher_integrity_check itself can identify the page mismatch around src/sqlcipher.c:2096-2103.

Suggested regression test

Modify exactly one page in a multi-page encrypted fixture. Run cipher_integrity_check followed by integrity_check on the same connection and assert either a page-17-only diagnostic or a clear aborted-check status, never fabricated failures for untouched pages.

Reproduction bundle

Attached: sqlcipher-022-single-corrupt-encrypted-page-poisons-current-integrit-f3ef876c_attachment.zip. It contains the focused PoC, validation evidence, and candidate metadata.

sqlcipher-022-single-corrupt-encrypted-page-poisons-current-integrit-f3ef876c_attachment.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions