Skip to main content

test_mem_region_proc/
mem_region_proc.rs

1#![no_std]
2#![no_main]
3
4use core::panic::PanicInfo;
5use strat9_syscall::{
6    call,
7    data::{HandleInfo, MemoryRegionInfo},
8    error::Error,
9    number, syscall6,
10};
11
12const PAGE_SIZE: usize = 4096;
13const REGION_PAGES: usize = 2;
14const REGION_LEN: usize = PAGE_SIZE * REGION_PAGES;
15const PROT_READ: usize = 1;
16const PROT_WRITE: usize = 2;
17const MAP_PRIVATE: usize = 1 << 1;
18const MAP_ANON: usize = 1 << 5;
19
20const MSG_READY: u8 = 0x10;
21const MSG_STAGE1_DONE: u8 = 0x11;
22const MSG_STAGE2_GO: u8 = 0x12;
23const MSG_STAGE2_DONE: u8 = 0x13;
24
25const OFF_CHILD_A: usize = 33;
26const OFF_CHILD_B: usize = PAGE_SIZE + 77;
27const OFF_PARENT_A: usize = 91;
28const OFF_PARENT_B: usize = PAGE_SIZE + 255;
29const OFF_CHILD_POST_REVOKE: usize = PAGE_SIZE + 511;
30
31const VAL_CHILD_A: u8 = 0xC3;
32const VAL_CHILD_B: u8 = 0xD7;
33const VAL_PARENT_A: u8 = 0x5A;
34const VAL_PARENT_B: u8 = 0x6E;
35const VAL_CHILD_POST_REVOKE: u8 = 0xE9;
36
37struct Ctx {
38    pass: u64,
39    fail: u64,
40}
41
42/// Write a message to a file descriptor.
43fn write_fd(fd: usize, msg: &str) {
44    let _ = call::write(fd, msg.as_bytes());
45}
46
47/// Write to stdout.
48fn log(msg: &str) {
49    write_fd(1, msg);
50}
51
52/// Write to stderr.
53fn log_err(msg: &str) {
54    write_fd(2, msg);
55}
56
57/// Write an unsigned integer in decimal.
58fn log_u64(mut value: u64) {
59    let mut buf = [0u8; 21];
60    if value == 0 {
61        log("0");
62        return;
63    }
64    let mut index = buf.len();
65    while value > 0 {
66        index -= 1;
67        buf[index] = b'0' + (value % 10) as u8;
68        value /= 10;
69    }
70    let text = unsafe { core::str::from_utf8_unchecked(&buf[index..]) };
71    log(text);
72}
73
74/// Write an unsigned integer in hexadecimal.
75fn log_hex_u64(mut value: u64) {
76    let mut buf = [0u8; 16];
77    for index in (0..16).rev() {
78        let nibble = (value & 0xF) as u8;
79        buf[index] = if nibble < 10 {
80            b'0' + nibble
81        } else {
82            b'a' + (nibble - 10)
83        };
84        value >>= 4;
85    }
86    log("0x");
87    let text = unsafe { core::str::from_utf8_unchecked(&buf) };
88    log(text);
89}
90
91/// Print a section banner.
92fn section(title: &str) {
93    log("\n============================================================\n");
94    log("[test_mem_region_proc] ");
95    log(title);
96    log("\n============================================================\n");
97}
98
99/// Record a successful check.
100fn ok(ctx: &mut Ctx, label: &str, value: usize) {
101    ctx.pass += 1;
102    log("[OK] ");
103    log(label);
104    log(" -> ");
105    log_u64(value as u64);
106    log(" (");
107    log_hex_u64(value as u64);
108    log(")\n");
109}
110
111/// Record a failed check.
112fn fail(ctx: &mut Ctx, label: &str, err: Error) {
113    ctx.fail += 1;
114    log_err("[FAIL] ");
115    log_err(label);
116    log_err(" -> ");
117    log_err(err.name());
118    log_err("\n");
119}
120
121/// Check a syscall result and keep the successful value.
122fn check_ok(ctx: &mut Ctx, label: &str, res: core::result::Result<usize, Error>) -> Option<usize> {
123    match res {
124        Ok(value) => {
125            ok(ctx, label, value);
126            Some(value)
127        }
128        Err(err) => {
129            fail(ctx, label, err);
130            None
131        }
132    }
133}
134
135/// Check that a syscall fails with a specific error.
136fn check_expect_err(
137    ctx: &mut Ctx,
138    label: &str,
139    res: core::result::Result<usize, Error>,
140    expected: Error,
141) {
142    match res {
143        Ok(value) => {
144            ctx.fail += 1;
145            log_err("[FAIL] ");
146            log_err(label);
147            log_err(" -> expected ");
148            log_err(expected.name());
149            log_err(" got OK=");
150            log_u64(value as u64);
151            log_err("\n");
152        }
153        Err(err) => {
154            if err == expected {
155                ok(ctx, label, 0);
156            } else {
157                fail(ctx, label, err);
158            }
159        }
160    }
161}
162
163/// Close a file descriptor and ignore errors.
164fn close_quiet(fd: usize) {
165    let _ = call::close(fd);
166}
167
168/// Write the full buffer to a file descriptor.
169fn write_all(fd: usize, mut buf: &[u8]) -> Result<(), Error> {
170    while !buf.is_empty() {
171        match call::write(fd, buf) {
172            Ok(0) => return Err(Error::Pipe),
173            Ok(count) => buf = &buf[count..],
174            Err(Error::Interrupted) | Err(Error::Again) => {
175                let _ = call::sched_yield();
176            }
177            Err(err) => return Err(err),
178        }
179    }
180    Ok(())
181}
182
183/// Read the full buffer from a file descriptor.
184fn read_exact(fd: usize, mut buf: &mut [u8]) -> Result<(), Error> {
185    while !buf.is_empty() {
186        match call::read(fd, buf) {
187            Ok(0) => return Err(Error::Pipe),
188            Ok(count) => {
189                let (_, tail) = buf.split_at_mut(count);
190                buf = tail;
191            }
192            Err(Error::Interrupted) | Err(Error::Again) => {
193                let _ = call::sched_yield();
194            }
195            Err(err) => return Err(err),
196        }
197    }
198    Ok(())
199}
200
201/// Send a single control byte through a pipe.
202fn send_u8(fd: usize, value: u8) -> Result<(), Error> {
203    write_all(fd, &[value])
204}
205
206/// Receive a single control byte from a pipe.
207fn recv_u8(fd: usize) -> Result<u8, Error> {
208    let mut byte = [0u8; 1];
209    read_exact(fd, &mut byte)?;
210    Ok(byte[0])
211}
212
213/// Send a native-endian 64-bit value through a pipe.
214fn send_u64(fd: usize, value: u64) -> Result<(), Error> {
215    write_all(fd, &value.to_ne_bytes())
216}
217
218/// Receive a native-endian 64-bit value from a pipe.
219fn recv_u64(fd: usize) -> Result<u64, Error> {
220    let mut bytes = [0u8; 8];
221    read_exact(fd, &mut bytes)?;
222    Ok(u64::from_ne_bytes(bytes))
223}
224
225/// Return the deterministic seeded byte for one offset.
226fn expected_seed(index: usize) -> u8 {
227    let page = index / PAGE_SIZE;
228    let off = index % PAGE_SIZE;
229    (0x20u8)
230        .wrapping_add((page as u8).wrapping_mul(0x31))
231        .wrapping_add((off as u8).wrapping_mul(3))
232}
233
234/// Fill the region with a deterministic seed.
235fn seed_region(addr: usize) {
236    let slice = unsafe { core::slice::from_raw_parts_mut(addr as *mut u8, REGION_LEN) };
237    for (index, byte) in slice.iter_mut().enumerate() {
238        *byte = expected_seed(index);
239    }
240}
241
242/// Create the anonymous source mapping used for export tests.
243fn map_source_region(ctx: &mut Ctx) -> Option<usize> {
244    check_ok(ctx, "mmap anon private RW 2 pages", unsafe {
245        syscall6(
246            number::SYS_MMAP,
247            0,
248            REGION_LEN,
249            PROT_READ | PROT_WRITE,
250            MAP_PRIVATE | MAP_ANON,
251            0,
252            0,
253        )
254    })
255}
256
257/// Check one byte in the source region.
258fn verify_byte(ctx: &mut Ctx, label: &str, addr: usize, offset: usize, expected: u8) {
259    let observed = unsafe { core::ptr::read_volatile((addr + offset) as *const u8) };
260    if observed == expected {
261        ok(ctx, label, observed as usize);
262    } else {
263        ctx.fail += 1;
264        log_err("[FAIL] ");
265        log_err(label);
266        log_err(" -> expected=");
267        log_hex_u64(expected as u64);
268        log_err(" observed=");
269        log_hex_u64(observed as u64);
270        log_err(" offset=");
271        log_u64(offset as u64);
272        log_err("\n");
273    }
274}
275
276/// Log handle metadata.
277fn log_handle_info(label: &str, info: &HandleInfo) {
278    log("[handle] ");
279    log(label);
280    log(" type=");
281    log_u64(info.resource_type as u64);
282    log(" perms=");
283    log_hex_u64(info.permissions as u64);
284    log(" resource=");
285    log_hex_u64(info.resource);
286    log("\n");
287}
288
289/// Log region metadata.
290fn log_region_info(label: &str, info: &MemoryRegionInfo) {
291    log("[region] ");
292    log(label);
293    log(" size=");
294    log_u64(info.size);
295    log(" page_size=");
296    log_u64(info.page_size);
297    log(" flags=");
298    log_hex_u64(info.flags as u64);
299    log("\n");
300}
301
302/// Exit the child process after logging a failure.
303fn child_abort(code: usize, msg: &str) -> ! {
304    log_err("[child] ");
305    log_err(msg);
306    log_err("\n");
307    call::exit(code)
308}
309
310/// Run the child-side logic of the multi-process test.
311fn child_main(parent_to_child_read: usize, child_to_parent_write: usize) -> ! {
312    if send_u8(child_to_parent_write, MSG_READY).is_err() {
313        child_abort(60, "failed to send ready handshake");
314    }
315
316    let granted_handle = match recv_u64(parent_to_child_read) {
317        Ok(value) => value as usize,
318        Err(_) => child_abort(61, "failed to receive granted handle"),
319    };
320
321    let mut handle_info = HandleInfo {
322        resource_type: 0,
323        permissions: 0,
324        resource: 0,
325    };
326    if call::handle_info(granted_handle, &mut handle_info).is_err() {
327        child_abort(62, "handle_info(granted) failed");
328    }
329    if handle_info.resource_type != 1 {
330        child_abort(63, "granted handle is not a MemoryRegion");
331    }
332
333    let mut region_info = MemoryRegionInfo {
334        size: 0,
335        page_size: 0,
336        flags: 0,
337        _reserved: 0,
338    };
339    if call::mem_region_info(granted_handle, &mut region_info).is_err() {
340        child_abort(64, "mem_region_info(granted) failed");
341    }
342    if region_info.size != REGION_LEN as u64 || region_info.page_size != PAGE_SIZE as u64 {
343        child_abort(65, "unexpected region metadata in child");
344    }
345
346    let mut mapped_addr = 0usize;
347    if call::mem_region_map(granted_handle, 0, &mut mapped_addr).is_err() {
348        child_abort(66, "mem_region_map(granted) failed");
349    }
350
351    let child_slice =
352        unsafe { core::slice::from_raw_parts_mut(mapped_addr as *mut u8, REGION_LEN) };
353    if child_slice[0] != expected_seed(0)
354        || child_slice[17] != expected_seed(17)
355        || child_slice[PAGE_SIZE] != expected_seed(PAGE_SIZE)
356        || child_slice[PAGE_SIZE + 127] != expected_seed(PAGE_SIZE + 127)
357    {
358        child_abort(67, "seed validation failed in child mapping");
359    }
360
361    child_slice[OFF_CHILD_A] = VAL_CHILD_A;
362    child_slice[OFF_CHILD_B] = VAL_CHILD_B;
363    if send_u8(child_to_parent_write, MSG_STAGE1_DONE).is_err() {
364        child_abort(68, "failed to send stage1 completion");
365    }
366
367    match recv_u8(parent_to_child_read) {
368        Ok(MSG_STAGE2_GO) => {}
369        _ => child_abort(69, "failed to receive stage2 go signal"),
370    }
371
372    if child_slice[OFF_PARENT_A] != VAL_PARENT_A || child_slice[OFF_PARENT_B] != VAL_PARENT_B {
373        child_abort(70, "parent writes not visible in child mapping");
374    }
375
376    if call::handle_revoke(granted_handle).is_err() {
377        child_abort(71, "handle_revoke(granted) failed");
378    }
379    if call::handle_info(granted_handle, &mut handle_info) != Err(Error::BadHandle) {
380        child_abort(72, "granted handle still visible after revoke");
381    }
382
383    child_slice[OFF_CHILD_POST_REVOKE] = VAL_CHILD_POST_REVOKE;
384    if send_u8(child_to_parent_write, MSG_STAGE2_DONE).is_err() {
385        child_abort(73, "failed to send stage2 completion");
386    }
387
388    call::exit(0)
389}
390
391/// Decode a wait status and return the process exit code when it exited normally.
392fn wait_exit_code(status: i32) -> Option<i32> {
393    if status & 0x7f == 0 {
394        Some((status >> 8) & 0xff)
395    } else {
396        None
397    }
398}
399
400/// Run the full multi-process MemoryRegion suite.
401fn run_suite(ctx: &mut Ctx) {
402    section("MemoryRegion cross-process grant/revoke suite");
403    log("[test_mem_region_proc] parent grants region to a real child process\n");
404    log("[test_mem_region_proc] child maps, writes, revokes handle, then writes again\n");
405
406    let (parent_to_child_read, parent_to_child_write) = match call::pipe() {
407        Ok((read_fd, write_fd)) => {
408            ok(ctx, "pipe(parent->child)", read_fd as usize);
409            (read_fd as usize, write_fd as usize)
410        }
411        Err(err) => {
412            fail(ctx, "pipe(parent->child)", err);
413            return;
414        }
415    };
416    let (child_to_parent_read, child_to_parent_write) = match call::pipe() {
417        Ok((read_fd, write_fd)) => {
418            ok(ctx, "pipe(child->parent)", read_fd as usize);
419            (read_fd as usize, write_fd as usize)
420        }
421        Err(err) => {
422            fail(ctx, "pipe(child->parent)", err);
423            close_quiet(parent_to_child_read);
424            close_quiet(parent_to_child_write);
425            return;
426        }
427    };
428
429    let child_pid = match check_ok(ctx, "fork()", call::fork()) {
430        Some(value) => value,
431        None => {
432            close_quiet(parent_to_child_read);
433            close_quiet(parent_to_child_write);
434            close_quiet(child_to_parent_read);
435            close_quiet(child_to_parent_write);
436            return;
437        }
438    };
439
440    if child_pid == 0 {
441        close_quiet(parent_to_child_write);
442        close_quiet(child_to_parent_read);
443        child_main(parent_to_child_read, child_to_parent_write);
444    }
445
446    close_quiet(parent_to_child_read);
447    close_quiet(child_to_parent_write);
448
449    match recv_u8(child_to_parent_read) {
450        Ok(MSG_READY) => ok(ctx, "child ready handshake", MSG_READY as usize),
451        Ok(other) => {
452            ctx.fail += 1;
453            log_err("[FAIL] child ready handshake -> unexpected byte=");
454            log_hex_u64(other as u64);
455            log_err("\n");
456            return;
457        }
458        Err(err) => {
459            fail(ctx, "child ready handshake", err);
460            return;
461        }
462    }
463
464    let source = match map_source_region(ctx) {
465        Some(value) => value,
466        None => return,
467    };
468    seed_region(source);
469
470    let handle = match check_ok(
471        ctx,
472        "mem_region_export(source)",
473        call::mem_region_export(source),
474    ) {
475        Some(value) => value,
476        None => return,
477    };
478
479    let mut handle_info = HandleInfo {
480        resource_type: 0,
481        permissions: 0,
482        resource: 0,
483    };
484    if check_ok(
485        ctx,
486        "handle_info(parent region)",
487        call::handle_info(handle, &mut handle_info),
488    )
489    .is_some()
490    {
491        log_handle_info("parent-original", &handle_info);
492    }
493
494    let mut region_info = MemoryRegionInfo {
495        size: 0,
496        page_size: 0,
497        flags: 0,
498        _reserved: 0,
499    };
500    if check_ok(
501        ctx,
502        "mem_region_info(parent region)",
503        call::mem_region_info(handle, &mut region_info),
504    )
505    .is_some()
506    {
507        log_region_info("parent-original", &region_info);
508    }
509
510    let granted_handle = match check_ok(
511        ctx,
512        "handle_grant(region, child_pid)",
513        call::handle_grant(handle, child_pid),
514    ) {
515        Some(value) => value,
516        None => return,
517    };
518
519    if let Some(value) = check_ok(
520        ctx,
521        "send granted handle to child",
522        send_u64(parent_to_child_write, granted_handle as u64).map(|_| 0),
523    ) {
524        let _ = value;
525    } else {
526        return;
527    }
528
529    match recv_u8(child_to_parent_read) {
530        Ok(MSG_STAGE1_DONE) => ok(ctx, "child stage1 completion", MSG_STAGE1_DONE as usize),
531        Ok(other) => {
532            ctx.fail += 1;
533            log_err("[FAIL] child stage1 completion -> unexpected byte=");
534            log_hex_u64(other as u64);
535            log_err("\n");
536            return;
537        }
538        Err(err) => {
539            fail(ctx, "child stage1 completion", err);
540            return;
541        }
542    }
543
544    verify_byte(
545        ctx,
546        "child write A visible in parent",
547        source,
548        OFF_CHILD_A,
549        VAL_CHILD_A,
550    );
551    verify_byte(
552        ctx,
553        "child write B visible in parent",
554        source,
555        OFF_CHILD_B,
556        VAL_CHILD_B,
557    );
558
559    unsafe {
560        core::ptr::write_volatile((source + OFF_PARENT_A) as *mut u8, VAL_PARENT_A);
561        core::ptr::write_volatile((source + OFF_PARENT_B) as *mut u8, VAL_PARENT_B);
562    }
563    ok(ctx, "parent wrote reply markers", VAL_PARENT_B as usize);
564
565    if let Some(value) = check_ok(
566        ctx,
567        "send stage2 go",
568        send_u8(parent_to_child_write, MSG_STAGE2_GO).map(|_| 0),
569    ) {
570        let _ = value;
571    } else {
572        return;
573    }
574
575    match recv_u8(child_to_parent_read) {
576        Ok(MSG_STAGE2_DONE) => ok(ctx, "child stage2 completion", MSG_STAGE2_DONE as usize),
577        Ok(other) => {
578            ctx.fail += 1;
579            log_err("[FAIL] child stage2 completion -> unexpected byte=");
580            log_hex_u64(other as u64);
581            log_err("\n");
582            return;
583        }
584        Err(err) => {
585            fail(ctx, "child stage2 completion", err);
586            return;
587        }
588    }
589
590    verify_byte(
591        ctx,
592        "child post-revoke write visible in parent",
593        source,
594        OFF_CHILD_POST_REVOKE,
595        VAL_CHILD_POST_REVOKE,
596    );
597
598    close_quiet(parent_to_child_write);
599    close_quiet(child_to_parent_read);
600
601    let mut status = -1i32;
602    match check_ok(
603        ctx,
604        "waitpid(child, blocking)",
605        call::waitpid_blocking(child_pid as isize, &mut status),
606    ) {
607        Some(_) => {
608            log("[wait] child raw status=");
609            log_u64(status as u64);
610            log("\n");
611            if wait_exit_code(status) == Some(0) {
612                ok(ctx, "child exited with code 0", 0);
613            } else {
614                ctx.fail += 1;
615                log_err("[FAIL] child exited with code 0 -> raw status=");
616                log_u64(status as u64);
617                log_err("\n");
618            }
619        }
620        None => return,
621    }
622
623    let _ = check_ok(
624        ctx,
625        "handle_close(parent region)",
626        call::handle_close(handle),
627    );
628    check_expect_err(
629        ctx,
630        "handle_info(parent region after close)",
631        call::handle_info(handle, &mut handle_info),
632        Error::BadHandle,
633    );
634}
635
636#[panic_handler]
637/// Abort the process on panic.
638fn panic(_info: &PanicInfo) -> ! {
639    log_err("[test_mem_region_proc] PANIC\n");
640    call::exit(250)
641}
642
643#[no_mangle]
644/// Userspace entry point.
645pub extern "C" fn _start() -> ! {
646    let mut ctx = Ctx { pass: 0, fail: 0 };
647
648    run_suite(&mut ctx);
649
650    section("Summary");
651    log("[summary] pass=");
652    log_u64(ctx.pass);
653    log(" fail=");
654    log_u64(ctx.fail);
655    log("\n");
656
657    if ctx.fail == 0 {
658        call::exit(0);
659    } else {
660        call::exit(1);
661    }
662}