From 09883cc7a49e40244a58465146ad6930bc05ee00 Mon Sep 17 00:00:00 2001 From: miaobyte Date: Sun, 1 Nov 2026 22:15:18 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=8E=9F=E5=9C=B0=E5=86=99=E5=8F=AA?= =?UTF-8?q?=E6=94=B9=20body=EF=BC=8C=E8=B6=8A=E7=95=8C=E7=A1=AC=E9=94=99?= =?UTF-8?q?=EF=BC=9BViewWrite=20=E8=A6=81=E6=B1=82=20len=20=3D=3D=20value?= =?UTF-8?q?=5Flen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - body 直写按 head 声明长度限制,越界直接硬错,不再静默写到容量区 - ViewWrite 契约收紧:len 必须等于 value_len - 新增契约测试 tests/test_inplace_body_only(旧库失败、新库通过) Co-Authored-By: Claude Code --- CMakeLists.txt | 5 ++ src/durable_abi.c | 2 +- src/kvspace.c | 30 +++++-- src/kvspace_shm.h | 3 + tests/test_inplace_body_only.c | 160 +++++++++++++++++++++++++++++++++ tests/test_slot_view.c | 12 ++- 6 files changed, 201 insertions(+), 11 deletions(-) create mode 100644 tests/test_inplace_body_only.c diff --git a/CMakeLists.txt b/CMakeLists.txt index fea9d9b..fa35485 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,11 @@ if(KVSPACE_BUILD_TESTS) target_link_libraries(test_slot_view PRIVATE kvspace-c) add_test(NAME slot_view COMMAND test_slot_view) + add_executable(test_inplace_body_only tests/test_inplace_body_only.c) + target_include_directories(test_inplace_body_only PRIVATE src) + target_link_libraries(test_inplace_body_only PRIVATE kvspace-c) + add_test(NAME inplace_body_only COMMAND test_inplace_body_only) + add_executable(test_art_alignment tests/test_art_alignment.c) target_include_directories(test_art_alignment PRIVATE src) target_compile_definitions(test_art_alignment PRIVATE P2_TEST_HOOKS) diff --git a/src/durable_abi.c b/src/durable_abi.c index f9f95a7..575277f 100644 --- a/src/durable_abi.c +++ b/src/durable_abi.c @@ -187,7 +187,7 @@ int kvspaceSetPart(void *h, const char *key, uint32_t offset, uint32_t err_cap) { int32_t total = 0; uint8_t *d = kvspaceShmGet((kvspace_t *)h, key, 1, &total); - if (!d || total < 0 || offset + buf_len > (uint32_t)total) { + if (!d || total < 0 || !kvspaceShmPartInBody(d, total, offset, buf_len)) { if (err && err_cap) snprintf(err, err_cap, "kvspace: set-part out of range at %s", key); return 1; diff --git a/src/kvspace.c b/src/kvspace.c index 24dabf9..011f3c0 100644 --- a/src/kvspace.c +++ b/src/kvspace.c @@ -809,7 +809,7 @@ int kvspaceShmViewWrite(kvspace_t *kv, kvspaceViewToken_t *tok, return -1; if (kvspaceShmViewCheck(kv, tok) != 0) return 1; - if (len > tok->body_cap) + if (len > tok->body_cap || len != tok->value_len) return 1; kv_gens_t *g = gens_at(kv, (int32_t)tok->node_id); if (!g) @@ -2788,6 +2788,22 @@ uint8_t *kvspaceShmGetByRef(kvspace_t *kv, kvspaceRef_t *ref, return raw; } +/* 定位写的前置条件:只允许改 body [8, 8+len),head 与 attr 一律不可写。 */ +int kvspaceShmPartInBody(const uint8_t *raw, int32_t total, uint32_t offset, + uint32_t buf_len) { + if (!raw || total < (int32_t)KVSPACE_XVALUE_HEAD_LEN) + return 0; + kvspaceXvalueHead h; + memcpy(&h, raw, sizeof h); + const uint64_t len = kvspaceXvalueHeadLen(&h); + if (len > (uint64_t)total - KVSPACE_XVALUE_HEAD_LEN) + return 0; + if (offset < KVSPACE_XVALUE_HEAD_LEN) + return 0; + const uint64_t rel = (uint64_t)offset - KVSPACE_XVALUE_HEAD_LEN; + return rel <= len && (uint64_t)buf_len <= len - rel; +} + int kvspaceShmSetPartByRef(kvspace_t *kv, kvspaceRef_t *ref, const char *key_fallback, uint32_t offset, const uint8_t *buf, uint32_t buf_len) { @@ -2814,10 +2830,12 @@ int kvspaceShmSetPartByRef(kvspace_t *kv, kvspaceRef_t *ref, /* per-box 物理容量:损坏 head 的 len 可能大于 box 但仍在全局内, * 不做这一步会跨 box 写坏相邻值。 */ uint64_t cap = sbo_allocated_size(kv->sbo_meta, off); + const uint64_t rel = + offset >= KVSPACE_XVALUE_HEAD_LEN + ? (uint64_t)offset - KVSPACE_XVALUE_HEAD_LEN + : UINT64_MAX; if (cap <= data_size - off && len <= cap - KVSPACE_XVALUE_HEAD_LEN && - (uint64_t)offset <= KVSPACE_XVALUE_HEAD_LEN + len && - (uint64_t)buf_len <= - KVSPACE_XVALUE_HEAD_LEN + len - (uint64_t)offset) { + rel <= len && (uint64_t)buf_len <= len - rel) { kv_gens_t *g = gens_at(kv, id); if (g) __atomic_add_fetch(&g->value_gen, 1u, @@ -2838,7 +2856,7 @@ int kvspaceShmSetPartByRef(kvspace_t *kv, kvspaceRef_t *ref, return -1; int32_t rl = 0; uint8_t *d = kvspaceShmGet(kv, key_fallback, 0, &rl); - if (!d || offset + buf_len > (uint32_t)rl) + if (!d || !kvspaceShmPartInBody(d, rl, offset, buf_len)) return -1; kvspaceShmResolveRef(kv, key_fallback, ref); kv_gens_t *g = gens_at(kv, (int32_t)ref->block_id); @@ -2852,7 +2870,7 @@ int kvspaceShmSetPartByRef(kvspace_t *kv, kvspaceRef_t *ref, uint8_t *raw; int32_t rl; if (read_value(kv, AV_OFF(hv), &raw, &rl) < 0 || - offset + buf_len > (uint32_t)rl) + !kvspaceShmPartInBody(raw, rl, offset, buf_len)) return -1; kv_gens_t *g2 = gens_at(kv, id); if (g2) diff --git a/src/kvspace_shm.h b/src/kvspace_shm.h index 7a9be9d..68f7884 100644 --- a/src/kvspace_shm.h +++ b/src/kvspace_shm.h @@ -58,6 +58,9 @@ uint8_t *kvspaceShmGetByRef(kvspace_t *kv, kvspaceRef_t *ref, /* shm slot view:返回映射内 head/body 与整 box 物理容量。0=成功,-1=不可视槽。 */ int kvspaceShmXvalueBodyView(kvspace_t *kv, const kvspaceRef_t *ref, const char *key_fallback, kvspaceXvalueBodyView_t *out); +/* 定位写的前置条件:只允许改 body [8, 8+len),head 与 attr 一律不可写。 */ +int kvspaceShmPartInBody(const uint8_t *raw, int32_t total, uint32_t offset, + uint32_t buf_len); int kvspaceShmSetPartByRef(kvspace_t *kv, kvspaceRef_t *ref, const char *key_fallback, uint32_t offset, const uint8_t *buf, uint32_t buf_len); diff --git a/tests/test_inplace_body_only.c b/tests/test_inplace_body_only.c new file mode 100644 index 0000000..fae88d1 --- /dev/null +++ b/tests/test_inplace_body_only.c @@ -0,0 +1,160 @@ +/* 原地写契约:只允许改 body [8, 8+len),head 与 attr 必须逐字节不变; + 改 head/attr 或改 body 长度的入口一律硬错。各入口用独立键,互不阻断。 */ +#include + +#include "test_util.h" + +#include +#include + +#include +#include +#include +#include + +struct Value { + uint8_t *raw; + uint32_t len; +}; + +static void put_i64(void *h, const char *key) { + uint8_t *wire = NULL; + uint32_t wlen = 0; + CHECK(kvspaceNewInt64(42, "int64", &wire, &wlen) == 0, "new int64"); + CHECK(kvspaceSetValue(h, key, wire, wlen, NULL, 0) == 0, "set %s", key); + free(wire); +} + +static void snap(void *h, const char *key, struct Value *out) { + uint8_t *raw = NULL; + out->len = 0; + if (kvspaceGet(h, key, 0, &raw, &out->len) != 0 || !raw) { + out->raw = NULL; + return; + } + out->raw = malloc(out->len); + memcpy(out->raw, raw, out->len); +} + +static int head_attr_same(const struct Value *a, const struct Value *b) { + return a->len == b->len && memcmp(a->raw, b->raw, 8) == 0 && + memcmp(a->raw + 16, b->raw + 16, a->len - 16) == 0; +} + +static void run(void) { + char dir[] = "/tmp/kvspace-inplace-XXXXXX"; + char *made = mkdtemp(dir); + REQUIRE(made != NULL, "mkdtemp"); + char dsn[256]; + snprintf(dsn, sizeof dsn, "shm://%s/db", dir); + void *h = kvspaceConnect(dsn); + REQUIRE(h != NULL, "connect"); + + /* 1. ViewWrite 改长度:拒绝且不改值;等长接受,head/attr 不变。 */ + put_i64(h, "/vw"); + struct Value vw0; + snap(h, "/vw", &vw0); + REQUIRE(vw0.raw != NULL && vw0.len == 26, "base /vw"); + const int qrc = kvspaceViewQuiesce(h, 0); + CHECK(qrc == 0, "quiesce"); + kvspaceViewToken_t tok; + memset(&tok, 0, sizeof tok); + const int vrc = kvspaceViewResolve(h, "/vw", &tok); + REQUIRE(vrc == 0, "view resolve"); + CHECK(tok.value_len == 8, "token value_len"); + uint8_t big[16]; + memset(big, 0xAA, sizeof big); + const int vbad = kvspaceViewWrite(h, &tok, big, 16); + CHECK(vbad != 0, "ViewWrite(len=16) must fail"); + struct Value vw1; + snap(h, "/vw", &vw1); + CHECK(vw1.raw != NULL, "rejected ViewWrite destroyed value"); + if (vw1.raw) { + CHECK(vw1.len == vw0.len && head_attr_same(&vw1, &vw0), + "rejected ViewWrite changed head/attr"); + CHECK(memcmp(vw1.raw + 8, vw0.raw + 8, 8) == 0, + "rejected ViewWrite changed body"); + int64_t five = 5; + const int vok = kvspaceViewWrite(h, &tok, (const uint8_t *)&five, 8); + CHECK(vok == 0, "ViewWrite(len=8)"); + struct Value vw2; + snap(h, "/vw", &vw2); + CHECK(vw2.raw != NULL && vw2.len == vw0.len && vw2.raw != NULL, + "ViewWrite geometry"); + if (vw2.raw) { + CHECK(head_attr_same(&vw2, &vw0), "ViewWrite changed head/attr"); + CHECK(*(int64_t *)(vw2.raw + 8) == 5, "ViewWrite body value"); + free(vw2.raw); + } + } + + /* 2. WriteInPlace 改 body 长度:硬错;等长接受,head/attr 不变。 */ + put_i64(h, "/wi"); + struct Value wi0; + snap(h, "/wi", &wi0); + REQUIRE(wi0.raw != NULL && wi0.len == 26, "base /wi"); + uint8_t *body = NULL; + const int wshort = kvspaceWriteInPlace(h, "/wi", 0, 4, &body, NULL, 0); + CHECK(wshort != 0, "WriteInPlace(body_len=4) must fail"); + const int wok = kvspaceWriteInPlace(h, "/wi", 0, 8, &body, NULL, 0); + CHECK(wok == 0, "WriteInPlace(body_len=8)"); + if (wok == 0) { + int64_t seven = 7; + memcpy(body, &seven, 8); + struct Value wi1; + snap(h, "/wi", &wi1); + REQUIRE(wi1.raw != NULL && wi1.len == wi0.len, "in-place geometry"); + CHECK(memcmp(wi1.raw, wi0.raw, 8) == 0, "WriteInPlace changed head"); + CHECK(memcmp(wi1.raw + 16, wi0.raw + 16, wi0.len - 16) == 0, + "WriteInPlace changed attr"); + CHECK(*(int64_t *)(wi1.raw + 8) == 7, "WriteInPlace body value"); + free(wi1.raw); + } + + /* 3. SetPart 写 head:硬错,整值不变。 */ + put_i64(h, "/sp"); + struct Value sp0; + snap(h, "/sp", &sp0); + REQUIRE(sp0.raw != NULL && sp0.len == 26, "base /sp"); + uint8_t patch[8] = {0xde, 0xad, 0xbe, 0xef, 0, 0, 0, 0}; + char err[128] = {0}; + const int sp_rc = kvspaceSetPart(h, "/sp", 0, patch, 8, err, sizeof err); + CHECK(sp_rc != 0, "SetPart(offset=0) must fail"); + struct Value sp1; + snap(h, "/sp", &sp1); + CHECK(sp1.raw != NULL, "value intact after rejected head write"); + if (sp1.raw) + CHECK(sp1.len == sp0.len && memcmp(sp1.raw, sp0.raw, sp0.len) == 0, + "head write changed value"); + + /* 4. SetPart 写 body:接受;head/attr 逐字节不变,body 变。 */ + if (sp1.raw) { + int64_t nine = 99; + CHECK(kvspaceSetPart(h, "/sp", 8, (const uint8_t *)&nine, 8, NULL, 0) == + 0, + "SetPart(body) must succeed"); + struct Value sp2; + snap(h, "/sp", &sp2); + REQUIRE(sp2.raw != NULL && sp2.len == sp0.len, "body write geometry"); + CHECK(memcmp(sp2.raw, sp0.raw, 8) == 0, "body write changed head"); + CHECK(memcmp(sp2.raw + 16, sp0.raw + 16, sp0.len - 16) == 0, + "body write changed attr"); + CHECK(*(int64_t *)(sp2.raw + 8) == 99, "body write body value"); + free(sp2.raw); + } + + free(vw0.raw); free(vw1.raw); + free(wi0.raw); + free(sp0.raw); free(sp1.raw); + kvspaceClose(h); +} + +int main(void) { + run(); + if (failures) { + fprintf(stderr, "%d failure(s)\n", failures); + return 1; + } + puts("inplace_body_only: ok"); + return 0; +} diff --git a/tests/test_slot_view.c b/tests/test_slot_view.c index fc455d2..cd9e513 100644 --- a/tests/test_slot_view.c +++ b/tests/test_slot_view.c @@ -127,8 +127,10 @@ int main(void) { kvspaceXvalueHead h17; memset(&h17, 0, sizeof h17); kvspaceXvalueHeadSetLen(&h17, 17); - CHECK(kvspaceShmSetPartByRef(kv, &s17, sk, 2, - (const uint8_t *)&h17, sizeof h17) == 0); + int32_t s17len = 0; + uint8_t *s17raw = kvspaceShmGet(kv, sk, 0, &s17len); + CHECK(s17raw != NULL && s17len >= (int32_t)(2 + sizeof h17)); + memcpy(s17raw + 2, &h17, sizeof h17); kvspaceXvalueBodyView_t v17; CHECK(kvspaceShmXvalueBodyView(kv, &s17, sk, &v17) == -1); CHECK(kvspaceShmXvalueBodyView(kv, &s17, NULL, &v17) == -1); @@ -144,8 +146,10 @@ int main(void) { kvspaceRef_t xr; memset(&xr, 0, sizeof xr); CHECK(kvspaceShmResolveRef(kv, "/a/corrupt", &xr) == 0); - CHECK(kvspaceShmSetPartByRef(kv, &xr, "/a/corrupt", 2, - (const uint8_t *)&big, sizeof big) == 0); + int32_t xrlen = 0; + uint8_t *xrraw = kvspaceShmGet(kv, "/a/corrupt", 0, &xrlen); + CHECK(xrraw != NULL && xrlen >= (int32_t)(2 + sizeof big)); + memcpy(xrraw + 2, &big, sizeof big); kvspaceRef_t cr; memset(&cr, 0, sizeof cr); CHECK(kvspaceShmResolveRef(kv, "/a/corrupt", &cr) == 0);