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
42fn write_fd(fd: usize, msg: &str) {
44 let _ = call::write(fd, msg.as_bytes());
45}
46
47fn log(msg: &str) {
49 write_fd(1, msg);
50}
51
52fn log_err(msg: &str) {
54 write_fd(2, msg);
55}
56
57fn 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
74fn 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
91fn section(title: &str) {
93 log("\n============================================================\n");
94 log("[test_mem_region_proc] ");
95 log(title);
96 log("\n============================================================\n");
97}
98
99fn 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
111fn 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
121fn 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
135fn 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
163fn close_quiet(fd: usize) {
165 let _ = call::close(fd);
166}
167
168fn 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
183fn 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
201fn send_u8(fd: usize, value: u8) -> Result<(), Error> {
203 write_all(fd, &[value])
204}
205
206fn 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
213fn send_u64(fd: usize, value: u64) -> Result<(), Error> {
215 write_all(fd, &value.to_ne_bytes())
216}
217
218fn 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
225fn 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
234fn 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
242fn 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
257fn 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
276fn 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
289fn 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
302fn child_abort(code: usize, msg: &str) -> ! {
304 log_err("[child] ");
305 log_err(msg);
306 log_err("\n");
307 call::exit(code)
308}
309
310fn 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
391fn wait_exit_code(status: i32) -> Option<i32> {
393 if status & 0x7f == 0 {
394 Some((status >> 8) & 0xff)
395 } else {
396 None
397 }
398}
399
400fn 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", ®ion_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]
637fn panic(_info: &PanicInfo) -> ! {
639 log_err("[test_mem_region_proc] PANIC\n");
640 call::exit(250)
641}
642
643#[no_mangle]
644pub 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}