https://bugs.openldap.org/show_bug.cgi?id=10538
Issue ID: 10538 Summary: On Windows, a large value write can get silently lost Product: LMDB Version: 0.9.35 Hardware: x86_64 OS: Windows Status: UNCONFIRMED Keywords: needs_review Severity: normal Priority: --- Component: liblmdb Assignee: bugs@openldap.org Reporter: github@nicwatson.org Target Milestone: ---
On Windows, mdb_page_flush() writes each overflow page with a single WriteFile() whose write bytes is a 32-bit DWORD. A value whose overflow extent (psize * mp_pages) reaches 2**32 has its length truncated. An extent of exactly 2**32 truncates to 0, so WriteFile() writes nothing and the value is silently lost. The commit still "succeeds" (a 0-byte WriteFile returns TRUE), so the bug shows up as a value that reads back as zeroes instead of its contents.
Here's a reproducer. This only works on Windows 64-bit.
---
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <direct.h> #define MKDIR(d) _mkdir(d)
#include "lmdb.h"
#define CHK(expr) do { int rc_ = (expr); if (rc_) { \ fprintf(stderr, "%s:%d: %s: %s\n", __FILE__, __LINE__, #expr, \ mdb_strerror(rc_)); return 2; } } while (0)
int main(int argc, char **argv) { const char *dir = argc > 1 ? argv[1] : "lw-repro-db"; const size_t VALSIZE = (size_t)0xFFFFF000UL; MDB_env *env; MDB_txn *txn; MDB_dbi dbi; MDB_val key, val, got; unsigned char *p; char *buf;
MKDIR(dir);
buf = (char *)malloc(VALSIZE); if (!buf) { fprintf(stderr, "malloc(%zu) failed\n", VALSIZE); return 2; } memset(buf, 'x', VALSIZE); memcpy(buf, "HEAD", 4); memcpy(buf + VALSIZE - 4, "TAIL", 4);
CHK(mdb_env_create(&env)); CHK(mdb_env_set_mapsize(env, VALSIZE + (256UL << 20))); CHK(mdb_env_open(env, dir, 0, 0664)); CHK(mdb_txn_begin(env, NULL, 0, &txn)); CHK(mdb_dbi_open(txn, NULL, 0, &dbi)); key.mv_data = (void *)"big"; key.mv_size = 3; val.mv_data = buf; val.mv_size = VALSIZE; CHK(mdb_put(txn, dbi, &key, &val, 0)); CHK(mdb_txn_commit(txn)); /* the overflow-page WriteFile happens here */ free(buf);
CHK(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)); CHK(mdb_get(txn, dbi, &key, &got)); p = (unsigned char *)got.mv_data; if (got.mv_size != VALSIZE || memcmp(p, "HEAD", 4) != 0 || memcmp(p + VALSIZE - 4, "TAIL", 4) != 0) { fprintf(stderr, "BUG REPRODUCED: value truncated/lost: size=%zu " "head=%02x%02x%02x%02x tail=%02x%02x%02x%02x\n", got.mv_size, p[0], p[1], p[2], p[3], p[VALSIZE - 4], p[VALSIZE - 3], p[VALSIZE - 2], p[VALSIZE - 1]); mdb_txn_abort(txn); mdb_env_close(env); return 1; } mdb_txn_abort(txn); mdb_env_close(env);
printf("OK: committed and read back %zu-byte value intact\n", VALSIZE); return 0; }