Fix SkHermesMessage payload-aliasing defect (task 3.8 findings log)
sk_hermes_send_one() -- the single funnel every sender, including sk_hermes_publish(), already goes through -- used to store the caller's own payload_addr pointer as-is. Two sends before either drains meant both messages pointed at the same caller-owned buffer, whichever send wrote last silently winning: real, confirmed live (a second identity thumbdrive attached at boot alongside Zuse's own left its WIREBIND pairing silently never happening). SkHermesMessage gains an inline payload_buf[SK_HERMES_CHUNK_MAX_ PAYLOAD] field; sk_hermes_send_one() now memcpy()s the caller's payload into it and points payload_addr at that copy instead. No sender or reader call site needed to change -- every existing reader already only ever reads through payload_addr, which still points at valid bytes of the same length, now message-owned. g_kh_blk_attach_buf/ g_kh_console_cmd_buf (repl.c, tasks 3.8/3.9) no longer need to survive past their own send call; their doc comments, which had claimed the old aliasing shape was benign, are corrected. Verified the original bug is actually gone: reproduced the exact original scenario (a real, sequentially-minted rajames identity attached at boot alongside Zuse's own, two simultaneous BLK-ATTACH- EVENT sends in one idle-loop pass) -- WIREBIND pairing, USE, and the Stage D relay all now work where WIREBIND previously silently failed. Standard three-ISA acceptance also clean: zero UNKNOWN WORD, dict_hash identical across all three and matching every prior acceptance run in this document. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
21734b1a52
commit
5c07745c18
@@ -73,6 +73,13 @@
|
||||
#define SK_HERMES_MSG_MAX 32
|
||||
#define SK_HERMES_MEMBER_MAX 64
|
||||
|
||||
/* SK_HERMES_CHUNK_MAX_PAYLOAD - one block (1024 bytes, 64x16, SXLIV.1),
|
||||
* moved up here (was originally defined much further down, next to
|
||||
* SkHermesChunkHeader) so SkHermesMessage's own payload_buf field below
|
||||
* can be sized against it. See this file's chunking section, further
|
||||
* down, for the full sizing rationale -- not restated here. */
|
||||
#define SK_HERMES_CHUNK_MAX_PAYLOAD 1024
|
||||
|
||||
/*
|
||||
* SK_HERMES_Q_SLOT - per-message admission heat, task 2.2 (item 28).
|
||||
* messaging.4th:44-46 derives Q.SLOT as the reservoir remaining after
|
||||
@@ -98,11 +105,35 @@
|
||||
* reserved sentinels MSG-NACKED(253)/MSG-DELIVERED(255)).
|
||||
* @field from Sending VM (mirrors MSG-FROM@).
|
||||
* @field to Target VM (mirrors MSG-TO@).
|
||||
* @field payload_addr Out-of-line payload address (mirrors MSG-PADDR@).
|
||||
* @field payload_addr Payload address a reader dereferences (mirrors
|
||||
* MSG-PADDR@). CORRECTED 2026-09-22 (real defect,
|
||||
* FABRIC-3.6.md task 3.8's own findings log, "a real
|
||||
* payload-aliasing defect"): this used to be the
|
||||
* caller's own out-of-line pointer, stored as-is --
|
||||
* two sends before either drained meant both
|
||||
* messages pointed at the same caller-owned buffer,
|
||||
* whichever send wrote last silently winning. Now
|
||||
* always points at this SAME message's own
|
||||
* `payload_buf` below, which sk_hermes_send_one()
|
||||
* copies into at send time -- every message owns its
|
||||
* payload bytes, independent of whatever the caller
|
||||
* does with its own buffer afterward. Every existing
|
||||
* reader (sk_hermes_drain_checkpoint(),
|
||||
* sk_hermes_reassemble()) is unaffected: `payload_addr`
|
||||
* still points at valid payload bytes of the same
|
||||
* length, just message-owned instead of caller-owned.
|
||||
* FABRIC-3.5.md SXLIII.6.1 (item 44, still open): a
|
||||
* payload above INPUT_BUFFER_SIZE-1 (1024) bytes cannot
|
||||
* be drained in one interpret call -- bound or chunk it
|
||||
* before Phase 3, not here.
|
||||
* @field payload_buf Backing storage for `payload_addr` above -- see its
|
||||
* own doc comment. Sized to SK_HERMES_CHUNK_MAX_PAYLOAD,
|
||||
* the uniform bound sk_hermes_send_one()/
|
||||
* sk_hermes_publish() already enforce on `payload_len`
|
||||
* (defined just above, moved up in this same fix so it
|
||||
* is in scope here). No reader should touch this field
|
||||
* directly -- go through `payload_addr`, which is what
|
||||
* every existing reader already does.
|
||||
* @field payload_len Payload length in bytes (mirrors MSG-PLEN@).
|
||||
* @field stadium_cell Index into the Stadium cell array this message's
|
||||
* heat currently occupies, or a sentinel meaning "none"
|
||||
@@ -126,6 +157,7 @@ typedef struct {
|
||||
VMUuid from;
|
||||
VMUuid to;
|
||||
void *payload_addr;
|
||||
uint8_t payload_buf[SK_HERMES_CHUNK_MAX_PAYLOAD];
|
||||
uint32_t payload_len;
|
||||
int32_t stadium_cell;
|
||||
uint32_t seq;
|
||||
@@ -411,8 +443,12 @@ int sk_hermes_queues_boot_init(void);
|
||||
* response encode context (e.g. task 3.6's GRANT
|
||||
* uses this to tell the requester which private
|
||||
* channel was just created).
|
||||
* @param payload_addr Out-of-line payload address, passed through
|
||||
* unchanged.
|
||||
* @param payload_addr Caller-owned buffer, read and copied into the
|
||||
* message's own storage before this function
|
||||
* returns (2026-09-22 fix, this struct's own
|
||||
* payload_addr field doc comment has the full
|
||||
* account) -- the caller does not need to keep
|
||||
* it alive past this call.
|
||||
* @param payload_len Refused (-1) if it exceeds
|
||||
* SK_HERMES_CHUNK_MAX_PAYLOAD.
|
||||
* @return 0 on success, -1 on any refusal (bound, reservoir, arena, or
|
||||
@@ -450,8 +486,10 @@ int sk_hermes_send_one(VMUuid from, VMUuid to, uint32_t type, uint32_t channel_i
|
||||
* channel from sk_hermes_channel_create()). Refused
|
||||
* (-1) if invalid/not in use.
|
||||
* @param type Message type code, passed through unchanged.
|
||||
* @param payload_addr Out-of-line payload address, passed through
|
||||
* unchanged.
|
||||
* @param payload_addr Caller-owned buffer, read once per subscriber via
|
||||
* sk_hermes_send_one() (its own doc comment has the
|
||||
* full account) -- the caller does not need to keep
|
||||
* it alive past this call.
|
||||
* @param payload_len Payload length in bytes. Refused (-1, no
|
||||
* allocation attempted) if it exceeds
|
||||
* SK_HERMES_CHUNK_MAX_PAYLOAD (task 3.5, added here
|
||||
@@ -585,10 +623,11 @@ int sk_hermes_drain_checkpoint(VM *vm);
|
||||
* (3.8+, the same boundary chunk-aware drain is already deferred past).
|
||||
*/
|
||||
|
||||
/* One block = 1024 bytes (64x16, SXLIV.1) -- the uniform bound every
|
||||
* message's payload_len must satisfy, chunked or not (see this
|
||||
* section's own sizing note above). */
|
||||
#define SK_HERMES_CHUNK_MAX_PAYLOAD 1024
|
||||
/* SK_HERMES_CHUNK_MAX_PAYLOAD (1024 bytes, 64x16, SXLIV.1) -- the
|
||||
* uniform bound every message's payload_len must satisfy, chunked or
|
||||
* not (see this section's own sizing note above) -- is defined earlier
|
||||
* in this file now, next to SkHermesMessage's own payload_buf field,
|
||||
* which needs it in scope. Not redefined here. */
|
||||
|
||||
/* SkHermesChunkHeader - prepended to a chunk carrier's own payload,
|
||||
* ahead of up to SK_HERMES_CHUNK_MAX_SLICE bytes of real content.
|
||||
|
||||
Reference in New Issue
Block a user