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
Version and environment
Reproduced from SQLCipher commit
810db22f575ee7cf94ea96a3e91622b5fcece3dcon Linux amd64, using SQLite 3.53.3 / SQLCipher 4.17.0 Community and the bundled SQLCipher 4testkeydatabase.Describe the bug
After exactly one encrypted page fails HMAC verification, SQLCipher stores a persistent codec/pager error.
PRAGMA cipher_integrity_checkcorrectly identifies page 17 as the only modified page, but a subsequent standardPRAGMA integrity_checkon 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
The essential data mutation and query sequence is:
Run the same SQL on an unmodified copy as the control.
Actual behavior/output
The clean control returns
ok,ok, and1. The corrupt copy first returns:It then reports pages 16 through 3 and page 1 as
unable to get the page. error code=1. Stderr containssqlcipher_codec_ctx_set_error 1,identified deferred error condition: 1, and finallyfile 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()callssqlcipher_codec_ctx_set_error()on decrypt failure aroundsrc/sqlcipher.c:3459-3470. Later reads detect this stored error around:3414-3423, set pager error, and return a zeroed buffer.sqlite3pager_error()aroundsrc/pager.c:8057-8060moves the pager intoPAGER_ERROR, whilecipher_integrity_checkitself can identify the page mismatch aroundsrc/sqlcipher.c:2096-2103.Suggested regression test
Modify exactly one page in a multi-page encrypted fixture. Run
cipher_integrity_checkfollowed byintegrity_checkon 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