memwrite: per-process (cr3) target and full-extent socket SRC

- CMD_MEMWRITE now carries a target page-table root (cr3) as its first field;
  cr3 == 0 keeps the kernel address-space default (backward-compatible). A control
  that has discovered a process's cr3 through its own read-only perception can
  write that process's private memory under the same exclusive write lease.
  Freshness of the cr3 is the control's responsibility — signaling does not
  validate it (that is perception, not coherence), mirroring the read side.

- A socket control can now carry an SRC larger than the inline frame budget: a
  length-prefixed SRC tail follows the CMD_MEMWRITE frame (flag SRC_PAYLOAD, the
  length being the frame's own len). A per-connection two-phase receiver
  accumulates the tail into a fixed conn-owned buffer up to the extent bound,
  matching the in-process payload path. A zero or over-bound length is a framing
  violation that closes the connection: leaving the promised tail unread would
  desync the stream and draining an arbitrary length would be a denial of service.

The capability, exclusive lease, source and extent gates are unchanged and
reused; only the event header gained the cr3 field and the socket transport
gained the tail receiver. The adapter resolves cr3 == 0 to the kernel root on
its worker thread and writes atomically.
This commit is contained in:
2026-06-20 21:21:20 +03:00
parent e9aee057c7
commit e6c7aed8eb
7 changed files with 309 additions and 39 deletions
+53 -9
View File
@@ -90,13 +90,14 @@ static int acquire_mw(void* ctl) {
return vmsig_inproc_send(ctl, &d);
}
/* CMD_MEMWRITE with inline SRC; corr for tracking. flags: VMSIG_MW_SRC_* (0 => no SRC). */
static int send_write(void* ctl, uint64_t gva, uint32_t len, uint32_t flags,
/* CMD_MEMWRITE with inline SRC; corr for tracking. flags: VMSIG_MW_SRC_* (0 => no SRC).
* cr3 selects the target AS: 0 => kernel default (System DTB), non-zero => a process AS. */
static int send_write(void* ctl, uint64_t cr3, uint64_t gva, uint32_t len, uint32_t flags,
const void* src, uint32_t corr) {
vmsig_event d; memset(&d, 0, sizeof d);
d.kind = VMSIG_EV_CMD_MEMWRITE; d.source = VMSIG_SRC_MEMCTX; d.dir = VMSIG_DIR_DOWN;
d.endpoint = 0; d.prio = VMSIG_PRIO_HIGH; d.corr = corr;
vmsig_memwrite mw = { gva, len, flags };
vmsig_memwrite mw = { cr3, gva, len, flags };
memcpy(d.inln, &mw, sizeof mw);
if ((flags & VMSIG_MW_SRC_INLINE) && src && len <= VMSIG_MEMWRITE_INLINE)
memcpy(d.inln + sizeof mw, src, len);
@@ -123,21 +124,21 @@ static void test_path_and_deny(void) {
/* 3) lease gate: without ACQUIRE the write is dropped at the gate (-1, no actuation). */
uint8_t pat[8] = { 0xDE, 0xAD, 0xBE, 0xEF, 1, 2, 3, 4 };
CHECK(send_write(A, 0x1000, 8, VMSIG_MW_SRC_INLINE, pat, 99) == -1,
CHECK(send_write(A, 0, 0x1000, 8, VMSIG_MW_SRC_INLINE, pat, 99) == -1,
"3: CMD_MEMWRITE without a lease is dropped by the gate");
/* acquire the MEMWRITE lease (synchronous intercept; UP reply paced by ctx). */
CHECK(acquire_mw(A) == 0, "acquire submitted");
/* 1) happy path: inline write -> queued -> ACT_ACK{ok=1}. Also drains the GRANTED reply. */
CHECK(send_write(A, 0x1000, 8, VMSIG_MW_SRC_INLINE, pat, 11) == 0,
CHECK(send_write(A, 0, 0x1000, 8, VMSIG_MW_SRC_INLINE, pat, 11) == 0,
"1: owner's CMD_MEMWRITE passes the gate");
/* 2) extent: len > MAX -> ACK{ok=0}, NOT actuated (queued ack on the loop thread). */
CHECK(send_write(A, 0x2000, VMSIG_MEMWRITE_MAX + 1, VMSIG_MW_SRC_INLINE, pat, 22) == 0,
CHECK(send_write(A, 0, 0x2000, VMSIG_MEMWRITE_MAX + 1, VMSIG_MW_SRC_INLINE, pat, 22) == 0,
"2: over-extent write is accepted by the gate (denied inside the adapter)");
/* 2b) missing SRC flag -> ACK{ok=0}. */
CHECK(send_write(A, 0x3000, 4, 0u, NULL, 33) == 0,
CHECK(send_write(A, 0, 0x3000, 4, 0u, NULL, 33) == 0,
"2b: no-SRC-flag write is accepted by the gate (denied inside the adapter)");
/* expect 3 ACKs (corr 11/22/33) + the GRANTED reply. */
@@ -160,6 +161,45 @@ static void test_path_and_deny(void) {
vmsig_ctx_free(ctx);
}
/* ---- 1b: cr3 default + explicit — the cr3 field threads the whole seam -------
* Stub does not actuate, so this asserts CONTRACT/ROUTE only: both a kernel-default
* (cr3==0) and an arbitrary process cr3 (cr3!=0) pass cap -> grant -> lease-gate ->
* route -> adapter -> ACT_ACK{ok=1}. A non-zero cr3 does NOT bypass any gate (it is
* read by the adapter only after the core admitted the command). */
static void test_cr3_default_and_explicit(void) {
printf("test_cr3_default_and_explicit\n");
vmsig_ctx* ctx = vmsig_ctx_new();
vmsig_core* core = vmsig_core_new(ctx);
cstate s; memset(&s, 0, sizeof s); s.core = core;
void* A = add_ctl(core, &s, VMSIG_CAP_MEMWRITE, 10);
CHECK(vmsig_core_add_adapter(core, vmsig_memctx_ops(), NULL, 0) >= 0, "add memctx");
CHECK(acquire_mw(A) == 0, "acquire submitted");
uint8_t pat[8] = { 0xAA, 0xBB, 0xCC, 0xDD, 5, 6, 7, 8 };
/* (a) kernel default: cr3 == 0 -> resolves to the adapter's System DTB on the worker. */
CHECK(send_write(A, 0, 0x4000, 8, VMSIG_MW_SRC_INLINE, pat, 71) == 0,
"1b-a: cr3==0 (kernel default) passes the gate");
/* (b) explicit process AS: an arbitrary non-zero cr3 is carried through unchanged. */
CHECK(send_write(A, 0xDEADBEEF000ull, 0x5000, 8, VMSIG_MW_SRC_INLINE, pat, 72) == 0,
"1b-b: cr3!=0 (process AS) passes the gate");
run_until_acks(&s, 2);
CHECK(s.granted == 1, "1b: lease GRANTED once");
int saw71_ok = -1, saw72_ok = -1;
for (int i = 0; i < s.nack; i++) {
if (s.ack_corr[i] == 71) saw71_ok = s.ack_ok[i];
if (s.ack_corr[i] == 72) saw72_ok = s.ack_ok[i];
}
CHECK(saw71_ok == 1, "1b-a: cr3==0 write ACKs ok=1 (kernel default, stub)");
CHECK(saw72_ok == 1, "1b-b: cr3!=0 write ACKs ok=1 (route proven, stub does not actuate)");
vmsig_core_free(core);
vmsig_ctx_free(ctx);
}
/* ---- 4: cap gate — no CAP_MEMWRITE cannot acquire the lease ----------------- */
static void test_cap_gate(void) {
printf("test_cap_gate\n");
@@ -195,11 +235,11 @@ static void test_inflight_fence(void) {
uint8_t pat[4] = { 1, 2, 3, 4 };
CHECK(acquire_mw(A) == 0, "A acquires");
/* A queues a write (corr=55): passes the gate (A owns), lands in the DOWN queue. */
CHECK(send_write(A, 0x1000, 4, VMSIG_MW_SRC_INLINE, pat, 55) == 0, "A queues write 55");
CHECK(send_write(A, 0, 0x1000, 4, VMSIG_MW_SRC_INLINE, pat, 55) == 0, "A queues write 55");
/* B preempts SYNCHRONOUSLY (acquire does not go through ctx). */
CHECK(acquire_mw(B) == 0, "B preempts");
/* B's own write (corr=66) — should actuate. */
CHECK(send_write(B, 0x2000, 4, VMSIG_MW_SRC_INLINE, pat, 66) == 0, "B queues write 66");
CHECK(send_write(B, 0, 0x2000, 4, VMSIG_MW_SRC_INLINE, pat, 66) == 0, "B queues write 66");
run_until_acks(&s, 1); /* B's 66 acks; A's 55 must be fenced (no ack) */
@@ -218,7 +258,11 @@ static void test_inflight_fence(void) {
int main(void) {
printf("test_memwrite\n");
/* POD layout: cr3+gva (2*u64) + len+flags (2*u32) = 24, +24 inline tail = inln[48]. */
CHECK(sizeof(vmsig_memwrite) == 24, "vmsig_memwrite header is 24 bytes");
CHECK(VMSIG_MEMWRITE_INLINE == 24u, "VMSIG_MEMWRITE_INLINE is 24");
test_path_and_deny();
test_cr3_default_and_explicit();
test_cap_gate();
test_inflight_fence();
cref_free_all();