From 7690e746b5bb7ea30325e66c0182cfa748898c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Tro=C3=9Fbach?= Date: Tue, 3 Nov 2026 18:45:22 +0100 Subject: [PATCH] Clear a partly kept counter behind the end of a temporary file With a chunk size that is not a multiple of 8, a truncate can keep only a part of the counter value in front of the block behind the new end. mcTempDropTail() cleared a counter only if all 8 bytes were kept, so a block that a later write left out read as keystream instead of zeros. tempfiletest checks this once with a chunk size that keeps half of the counter of the second block. --- src/sqlite3mc_vfs.c | 5 +++-- test/tempfiletest.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/sqlite3mc_vfs.c b/src/sqlite3mc_vfs.c index 3e8f4da..ff30c88 100644 --- a/src/sqlite3mc_vfs.c +++ b/src/sqlite3mc_vfs.c @@ -317,7 +317,8 @@ static void mcTempPutCounter(uint8_t* header, sqlite3_uint64 counter) /* ** Remove what sits behind the end of the file: the rest of a failed write, or ** the blocks that a truncate kept because the file has a chunk size. Their -** counter value is cleared, so that they read as never written. +** counter value is cleared, also if only a part of it was kept, so that they +** read as never written. */ static void mcTempDropTail(sqlite3mc_file* mcFile) { @@ -333,7 +334,7 @@ static void mcTempDropTail(sqlite3mc_file* mcFile) pTemp->tailStale = (pReal->pMethods->xTruncate(pReal, physical) != SQLITE_OK); if (pReal->pMethods->xFileSize(pReal, &kept) != SQLITE_OK) { pTemp->tailStale = 1; return; } - while (b * slot + MC_TEMP_HEADER <= kept) + while (b * slot < kept) { if (pReal->pMethods->xWrite(pReal, zero, MC_TEMP_HEADER, b * slot) != SQLITE_OK) { diff --git a/test/tempfiletest.c b/test/tempfiletest.c index 17e22da..1beed5d 100644 --- a/test/tempfiletest.c +++ b/test/tempfiletest.c @@ -412,6 +412,49 @@ static int doTruncate(long op) return failures + checkDiskWrites(op); } +/* +** A truncate of a file with a chunk size that is not a multiple of 8 can keep +** a part of the counter value in front of the next block. A block that a later +** write leaves out must still read as zeros. The random operations hardly ever +** truncate that close to a block boundary, so this is checked once on its own, +** with the VFS below the spy, where the chunk size reaches the file on disk. +*/ +static int checkPartialCounter(sqlite3_vfs* vfs) +{ + int chunk = MC_TEMP_HEADER + MC_TEMP_BLOCK + MC_TEMP_HEADER / 2; /* keeps half of the counter of block 1 */ + sqlite3_file* f = (sqlite3_file*) sqlite3_malloc(vfs->szOsFile); + int outFlags, rc, k, failures = 0; + if (f == NULL) return 1; + memset(f, 0, vfs->szOsFile); + rc = vfs->xOpen(vfs, NULL, f, SQLITE_OPEN_TEMP_JOURNAL | SQLITE_OPEN_READWRITE | + SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE, &outFlags); + if (rc != SQLITE_OK) { printf(" open failed: %d\n", rc); sqlite3_free(f); return 1; } + f->pMethods->xFileControl(f, SQLITE_FCNTL_CHUNK_SIZE, &chunk); + memset(writeBuf, 0x5A, 3 * MC_TEMP_BLOCK); + rc = f->pMethods->xWrite(f, writeBuf, 3 * MC_TEMP_BLOCK, 0); + if (rc == SQLITE_OK) rc = f->pMethods->xTruncate(f, MC_TEMP_BLOCK); + if (rc == SQLITE_OK) rc = f->pMethods->xWrite(f, writeBuf, MC_TEMP_BLOCK, 2 * MC_TEMP_BLOCK); + if (rc == SQLITE_OK) rc = f->pMethods->xRead(f, readBuf, MC_TEMP_BLOCK, MC_TEMP_BLOCK); + if (rc != SQLITE_OK) + { + printf(" chunk size %d: rc %d\n", chunk, rc); + failures++; + } + else + { + for (k = 0; k < MC_TEMP_BLOCK && readBuf[k] == 0; k++) {} + if (k < MC_TEMP_BLOCK) + { + printf(" WRONG DATA WITHOUT ERROR with chunk size %d: block left out after a truncate is not zero at byte %d\n", chunk, k); + failures++; + } + } + f->pMethods->xClose(f); + sqlite3_free(f); + printf(" truncate with chunk size %d checked\n", chunk); + return failures; +} + int main(int argc, char** argv) { long ops = argc > 1 ? atol(argv[1]) : 100000, i; @@ -461,6 +504,7 @@ int main(int argc, char** argv) blockSize = ((sqlite3mc_file*) file)->tempCipher->blockSize; printf(" block size %d\n", blockSize); if (pageSize) { nDiskWrite = 0; failures += checkDiskWrites(-1); } + failures += checkPartialCounter(realVfs); } for (i = 3; i < argc; i++) {