Skip to content

sqlite3_rekey_v2() returns SQLITE_OK after a read-only rekey failure #616

Description

@DBFuzz

Version and environment

Reproduced on SQLCipher commit 810db22f575ee7cf94ea96a3e91622b5fcece3dc, built on Linux amd64. Runtime: SQLite 3.53.3 / SQLCipher 4.17.0 Community.

Describe the bug

Calling sqlite3_rekey() on a read-only encrypted database returns SQLITE_OK, although the write transaction/page rewrite fails and the database remains encrypted with its old key.

Minimal reproduction

bash sqlcipher-009-sqlite3-rekey-v2-returns-sqlite-ok-after-read-only-rek-6d6e162c/poc/run_revalidation.sh

The included C harness creates a database with old-pass, closes it, changes its mode to 0444, and opens it with:

sqlite3_open_v2("file:/tmp/sqlcipher_rekey_readonly_fresh.db?mode=ro", &db,
                SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, 0);
sqlite3_key(db, "old-pass", 8);
int rc = sqlite3_rekey(db, "new-pass", 8);
printf("readonly_rekey_rc=%d (%s)\n", rc, sqlite3_errstr(rc));

It then closes the handle and independently reopens the file with each key.

Actual behavior/output

readonly_rekey_rc=0 (not an error)
reopen_with_old_key_rc=0
reopen_with_new_key_rc=26 (file is not a database)

The new-key verification also emits the expected HMAC/NOTADB diagnostics, proving the persisted key did not change.

Expected behavior

A rekey operation requiring writes must return a non-OK status, such as SQLITE_READONLY, when invoked on a read-only connection. SQLITE_OK must mean that the database was actually rekeyed.

Impact and scope

Applications using the C API may record a successful key rotation, discard the old key, or update audit state even though no rotation occurred. This report is limited to the API-level return value on the main database.

Relevant source/root cause

In src/sqlcipher.c, sqlite3_rekey_v2() stores failures from sqlite3BtreeBeginTrans() and page writes in local rc around lines 3784-3809 and rolls back, but returns SQLITE_OK unconditionally around line 3816.

Suggested regression test

Open an encrypted database with mode=ro, call both sqlite3_rekey() and sqlite3_rekey_v2(), and assert a non-OK result. Reopen with both keys and assert the old key remains valid and the new key remains invalid.

Reproduction bundle

Attached: sqlcipher-009-sqlite3-rekey-v2-returns-sqlite-ok-after-read-only-rek-6d6e162c_attachment.zip. It contains the focused PoC, validation evidence, and candidate metadata.

sqlcipher-009-sqlite3-rekey-v2-returns-sqlite-ok-after-read-only-rek-6d6e162c_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