sqlcipher / sqlcipher/sqlcipher

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

Open
#617 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
7.3k
Forks
1.4k
PR merge metrics
No merged PRs in 30d

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running poc/reproduce_corrupt_page_integrity_poison.sh with the clean and corrupted database copies. Read sqlite3Codec() and the related error handling in src/sqlcipher.c, then inspect sqlite3pager_error() in src/pager.c and the cipher_integrity_check path around the cited lines. Done means a regression test shows only the actual failed page, or reports a clear aborted check, without fabricated failures on later pages.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, sqlite
Domain
databases, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.