mirror of
https://dev.lirent.ru/Vatrog/vm-automation-signaling.git
synced 2026-08-11 14:16:41 +03:00
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:
@@ -5,6 +5,7 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "vmsig.h"
|
||||
#include "vmsig_socket.h"
|
||||
#include "memctx.h" /* VMSIG_MEMWRITE_MAX: the adapter's extent bound (private) */
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -48,6 +49,12 @@ static vmsig_grant pol_deny(uint32_t uid, uint32_t pid, void* ud) {
|
||||
return g;
|
||||
}
|
||||
|
||||
static uint64_t now_ns(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
|
||||
}
|
||||
|
||||
static int connect_abstract(const char* name) {
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0) return -1;
|
||||
@@ -88,8 +95,120 @@ static void test_wire(void) {
|
||||
CHECK(vmsig_wire_decode(&bad, &x) == -1, "bad magic rejected");
|
||||
}
|
||||
|
||||
/* ===== variant B: socket CMD_MEMWRITE with a length-prefixed SRC tail (>INLINE) =====
|
||||
* Stub memctx adapter (no VM): proves the socket transport carries a frame + SRC tail
|
||||
* (with partial recv), routes through cap -> grant -> lease-gate -> adapter, and gets
|
||||
* ACT_ACK ok=1. Also: an over-cap len is a framing violation that closes the connection. */
|
||||
|
||||
/* Policy granting a MEMWRITE-capable poller (cap MEMWRITE|MEMCTX|OBSERVE). */
|
||||
static vmsig_grant pol_mw(uint32_t uid, uint32_t pid, void* ud) {
|
||||
(void)pid; (void)ud;
|
||||
vmsig_grant g; memset(&g, 0, sizeof g);
|
||||
g.principal = uid; g.endpoint_mask = 1ull << 0;
|
||||
g.source_mask = 0xFFFFFFFFu;
|
||||
g.cap_mask = VMSIG_CAP_MEMWRITE | VMSIG_CAP_MEMCTX | VMSIG_CAP_OBSERVE;
|
||||
g.arb_prio = 10;
|
||||
return g;
|
||||
}
|
||||
|
||||
/* Encode + write a single fixed frame. */
|
||||
static int send_frame(int fd, const vmsig_event* ev) {
|
||||
vmsig_wire w; vmsig_wire_encode(&w, ev);
|
||||
return (write(fd, &w, sizeof w) == (ssize_t)sizeof w) ? 0 : -1;
|
||||
}
|
||||
|
||||
/* Read fixed frames until an ACT_ACK with the wanted corr; return its ok flag (-1 on
|
||||
* timeout/EOF). The ACK inln layout from mc_memwrite_ack: {int ok; uint32 corr; uint32 origin}. */
|
||||
static int wait_ack(int fd, uint32_t want_corr, int ms) {
|
||||
struct timeval tv = { .tv_sec = 0, .tv_usec = 200 * 1000 };
|
||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
|
||||
uint64_t deadline = now_ns() + (uint64_t)ms * 1000000ull;
|
||||
vmsig_wire w; uint8_t* p = (uint8_t*)&w; size_t off = 0;
|
||||
while (now_ns() < deadline) {
|
||||
ssize_t n = read(fd, p + off, sizeof w - off);
|
||||
if (n <= 0) continue; /* timeout/EOF retry within deadline */
|
||||
off += (size_t)n;
|
||||
if (off < sizeof w) continue;
|
||||
off = 0;
|
||||
vmsig_event ev;
|
||||
if (vmsig_wire_decode(&w, &ev) != 0) continue;
|
||||
if (ev.kind == VMSIG_EV_ACT_ACK && ev.corr == want_corr) {
|
||||
int ok; memcpy(&ok, ev.inln, sizeof ok);
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void test_memwrite_tail(void) {
|
||||
printf("test_memwrite_tail\n");
|
||||
vmsig_ctx* ctx = vmsig_ctx_new();
|
||||
vmsig_core* core = vmsig_core_new(ctx);
|
||||
CHECK(vmsig_core_add_adapter(core, vmsig_memctx_ops(), NULL, 0) >= 0, "add memctx stub");
|
||||
const char* MW = "@vmsig-sock-mw-test";
|
||||
CHECK(vmsig_socket_attach(core, MW, pol_mw, NULL) == 0, "attach mw listener");
|
||||
|
||||
pthread_t th;
|
||||
pthread_create(&th, NULL, loop_main, core);
|
||||
|
||||
int fd = connect_abstract(MW);
|
||||
CHECK(fd >= 0, "client connected (mw)");
|
||||
if (fd >= 0) {
|
||||
/* acquire the MEMWRITE lease */
|
||||
vmsig_event d; memset(&d, 0, sizeof d);
|
||||
d.kind = VMSIG_EV_CMD_ACQUIRE; d.source = VMSIG_SRC_MEMCTX; d.dir = VMSIG_DIR_DOWN;
|
||||
d.endpoint = 0; d.prio = VMSIG_PRIO_HIGH;
|
||||
vmsig_lease_req lr = { VMSIG_LEASE_MEMWRITE, 0 };
|
||||
memcpy(d.inln, &lr, sizeof lr);
|
||||
CHECK(send_frame(fd, &d) == 0, "send ACQUIRE");
|
||||
|
||||
/* happy path: CMD_MEMWRITE(PAYLOAD, len=64) + 64-byte tail, written in two halves
|
||||
* to exercise the TAIL-phase partial accumulation. */
|
||||
const uint32_t len = 64u;
|
||||
uint8_t src[64];
|
||||
for (uint32_t i = 0; i < len; i++) src[i] = (uint8_t)(i + 1);
|
||||
vmsig_event mwe; memset(&mwe, 0, sizeof mwe);
|
||||
mwe.kind = VMSIG_EV_CMD_MEMWRITE; mwe.source = VMSIG_SRC_MEMCTX; mwe.dir = VMSIG_DIR_DOWN;
|
||||
mwe.endpoint = 0; mwe.prio = VMSIG_PRIO_HIGH; mwe.corr = 0x101;
|
||||
vmsig_memwrite mw = { 0, 0x1000, len, VMSIG_MW_SRC_PAYLOAD };
|
||||
memcpy(mwe.inln, &mw, sizeof mw);
|
||||
CHECK(send_frame(fd, &mwe) == 0, "send CMD_MEMWRITE frame (PAYLOAD)");
|
||||
CHECK(write(fd, src, 32) == 32, "send SRC tail part 1");
|
||||
struct timespec ts = { .tv_sec = 0, .tv_nsec = 5 * 1000000 };
|
||||
nanosleep(&ts, NULL); /* let the loop accumulate a partial tail */
|
||||
CHECK(write(fd, src + 32, 32) == 32, "send SRC tail part 2");
|
||||
CHECK(wait_ack(fd, 0x101, 1000) == 1, "B: payload-tail write ACKs ok=1 (stub)");
|
||||
|
||||
/* negative: an over-cap PAYLOAD len is a framing-contract violation. The server closes
|
||||
* the connection — it cannot safely skip the promised tail, and draining an arbitrary
|
||||
* length would be a DoS. Verify no ACK arrives and the socket reaches EOF (conn shut). */
|
||||
memset(&mwe.inln, 0, sizeof mwe.inln);
|
||||
mwe.corr = 0x102;
|
||||
vmsig_memwrite mw2 = { 0, 0x2000, VMSIG_MEMWRITE_MAX + 1u, VMSIG_MW_SRC_PAYLOAD };
|
||||
memcpy(mwe.inln, &mw2, sizeof mw2);
|
||||
CHECK(send_frame(fd, &mwe) == 0, "send CMD_MEMWRITE frame (over-cap)");
|
||||
/* No ACK arrives; the server shuts the conn, so the socket drains to EOF. A 1s recv
|
||||
* timeout bounds the wait if the server wrongly kept the connection open. */
|
||||
struct timeval rtv = { .tv_sec = 1, .tv_usec = 0 };
|
||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &rtv, sizeof rtv);
|
||||
uint8_t junk[80]; ssize_t rr;
|
||||
while ((rr = read(fd, junk, sizeof junk)) > 0) { /* drain any in-flight, then EOF */ }
|
||||
CHECK(rr == 0, "B: over-cap closed the connection (EOF)");
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
struct timespec t = { .tv_sec = 0, .tv_nsec = 50 * 1000000 };
|
||||
nanosleep(&t, NULL);
|
||||
vmsig_core_stop(core);
|
||||
pthread_join(th, NULL);
|
||||
vmsig_core_free(core);
|
||||
vmsig_ctx_free(ctx);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_wire();
|
||||
test_memwrite_tail();
|
||||
|
||||
printf("test_socket\n");
|
||||
vmsig_ctx* ctx = vmsig_ctx_new();
|
||||
|
||||
Reference in New Issue
Block a user