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
20struct Ctx {
21 pass: u64,
22 fail: u64,
23}
24
25fn write_fd(fd: usize, msg: &str) {
27 let _ = call::write(fd, msg.as_bytes());
28}
29
30fn log(msg: &str) {
32 write_fd(1, msg);
33}
34
35fn log_err(msg: &str) {
37 write_fd(2, msg);
38}
39
40fn log_u64(mut value: u64) {
42 let mut buf = [0u8; 21];
43 if value == 0 {
44 log("0");
45 return;
46 }
47 let mut index = buf.len();
48 while value > 0 {
49 index -= 1;
50 buf[index] = b'0' + (value % 10) as u8;
51 value /= 10;
52 }
53 let text = unsafe { core::str::from_utf8_unchecked(&buf[index..]) };
54 log(text);
55}
56
57fn log_hex_u64(mut value: u64) {
59 let mut buf = [0u8; 16];
60 for index in (0..16).rev() {
61 let nibble = (value & 0xF) as u8;
62 buf[index] = if nibble < 10 {
63 b'0' + nibble
64 } else {
65 b'a' + (nibble - 10)
66 };
67 value >>= 4;
68 }
69 log("0x");
70 let text = unsafe { core::str::from_utf8_unchecked(&buf) };
71 log(text);
72}
73
74fn section(title: &str) {
76 log("\n============================================================\n");
77 log("[test_mem_region] ");
78 log(title);
79 log("\n============================================================\n");
80}
81
82fn ok(ctx: &mut Ctx, label: &str, value: usize) {
84 ctx.pass += 1;
85 log("[OK] ");
86 log(label);
87 log(" -> ");
88 log_u64(value as u64);
89 log(" (");
90 log_hex_u64(value as u64);
91 log(")\n");
92}
93
94fn fail(ctx: &mut Ctx, label: &str, err: Error) {
96 ctx.fail += 1;
97 log_err("[FAIL] ");
98 log_err(label);
99 log_err(" -> ");
100 log_err(err.name());
101 log_err("\n");
102}
103
104fn check_ok(ctx: &mut Ctx, label: &str, res: core::result::Result<usize, Error>) -> Option<usize> {
106 match res {
107 Ok(value) => {
108 ok(ctx, label, value);
109 Some(value)
110 }
111 Err(err) => {
112 fail(ctx, label, err);
113 None
114 }
115 }
116}
117
118fn check_expect_err(
120 ctx: &mut Ctx,
121 label: &str,
122 res: core::result::Result<usize, Error>,
123 expected: Error,
124) {
125 match res {
126 Ok(value) => {
127 ctx.fail += 1;
128 log_err("[FAIL] ");
129 log_err(label);
130 log_err(" -> expected ");
131 log_err(expected.name());
132 log_err(" got OK=");
133 log_u64(value as u64);
134 log_err("\n");
135 }
136 Err(err) => {
137 if err == expected {
138 ok(ctx, label, 0);
139 } else {
140 fail(ctx, label, err);
141 }
142 }
143 }
144}
145
146fn dump_memory_registers(label: &str, addr: usize, len: usize) {
148 let slice = unsafe { core::slice::from_raw_parts(addr as *const u8, len) };
149 let mid = PAGE_SIZE;
150 log("[memreg] ");
151 log(label);
152 log(" base=");
153 log_hex_u64(addr as u64);
154 log(" end=");
155 log_hex_u64((addr + len) as u64);
156 log(" len=");
157 log_u64(len as u64);
158 log(" samples={");
159 log("p0[0]=");
160 log_hex_u64(slice[0] as u64);
161 log(", p0[17]=");
162 log_hex_u64(slice[17] as u64);
163 log(", p1[0]=");
164 log_hex_u64(slice[mid] as u64);
165 log(", p1[127]=");
166 log_hex_u64(slice[mid + 127] as u64);
167 log(", last=");
168 log_hex_u64(slice[len - 1] as u64);
169 log("}\n");
170}
171
172fn seed_region(addr: usize) {
174 let slice = unsafe { core::slice::from_raw_parts_mut(addr as *mut u8, REGION_LEN) };
175 for index in 0..REGION_LEN {
176 let page = index / PAGE_SIZE;
177 let off = index % PAGE_SIZE;
178 slice[index] = (0x20u8)
179 .wrapping_add((page as u8).wrapping_mul(0x31))
180 .wrapping_add((off as u8).wrapping_mul(3));
181 }
182}
183
184fn verify_same_bytes(ctx: &mut Ctx, label: &str, left: usize, right: usize) {
186 let lhs = unsafe { core::slice::from_raw_parts(left as *const u8, REGION_LEN) };
187 let rhs = unsafe { core::slice::from_raw_parts(right as *const u8, REGION_LEN) };
188 for index in 0..REGION_LEN {
189 if lhs[index] != rhs[index] {
190 ctx.fail += 1;
191 log_err("[FAIL] ");
192 log_err(label);
193 log_err(" -> mismatch at index=");
194 log_u64(index as u64);
195 log_err(" left=");
196 log_hex_u64(lhs[index] as u64);
197 log_err(" right=");
198 log_hex_u64(rhs[index] as u64);
199 log_err("\n");
200 return;
201 }
202 }
203 ok(ctx, label, REGION_LEN);
204}
205
206fn verify_write_propagation(
208 ctx: &mut Ctx,
209 label: &str,
210 writer: usize,
211 reader: usize,
212 offset: usize,
213 value: u8,
214) {
215 unsafe {
216 core::ptr::write_volatile((writer + offset) as *mut u8, value);
217 }
218 let observed = unsafe { core::ptr::read_volatile((reader + offset) as *const u8) };
219 if observed == value {
220 ok(ctx, label, observed as usize);
221 } else {
222 ctx.fail += 1;
223 log_err("[FAIL] ");
224 log_err(label);
225 log_err(" -> expected=");
226 log_hex_u64(value as u64);
227 log_err(" observed=");
228 log_hex_u64(observed as u64);
229 log_err(" offset=");
230 log_u64(offset as u64);
231 log_err("\n");
232 }
233}
234
235fn log_handle_info(label: &str, info: &HandleInfo) {
237 log("[handle] ");
238 log(label);
239 log(" type=");
240 log_u64(info.resource_type as u64);
241 log(" perms=");
242 log_hex_u64(info.permissions as u64);
243 log(" resource=");
244 log_hex_u64(info.resource);
245 log("\n");
246}
247
248fn log_region_info(label: &str, info: &MemoryRegionInfo) {
250 log("[region] ");
251 log(label);
252 log(" size=");
253 log_u64(info.size);
254 log(" page_size=");
255 log_u64(info.page_size);
256 log(" flags=");
257 log_hex_u64(info.flags as u64);
258 log("\n");
259}
260
261fn map_source_region(ctx: &mut Ctx) -> Option<usize> {
263 check_ok(ctx, "mmap anon private RW 2 pages", unsafe {
264 syscall6(
265 number::SYS_MMAP,
266 0,
267 REGION_LEN,
268 PROT_READ | PROT_WRITE,
269 MAP_PRIVATE | MAP_ANON,
270 0,
271 0,
272 )
273 })
274}
275
276fn test_invalid_inputs(ctx: &mut Ctx, source: usize, handle: usize) {
278 section("Invalid inputs");
279
280 check_expect_err(
281 ctx,
282 "mem_region_export(source + 1)",
283 call::mem_region_export(source + 1),
284 Error::InvalidArgument,
285 );
286
287 let mut addr_out = 0usize;
288 check_expect_err(
289 ctx,
290 "mem_region_map(handle, source + 1)",
291 call::mem_region_map(handle, source + 1, &mut addr_out),
292 Error::InvalidArgument,
293 );
294
295 let mut region_info = MemoryRegionInfo {
296 size: 0,
297 page_size: 0,
298 flags: 0,
299 _reserved: 0,
300 };
301 check_expect_err(
302 ctx,
303 "mem_region_info(0)",
304 call::mem_region_info(0, &mut region_info),
305 Error::BadHandle,
306 );
307}
308
309fn test_export_and_metadata(ctx: &mut Ctx, source: usize) -> Option<usize> {
311 section("Export and metadata");
312
313 let handle = check_ok(
314 ctx,
315 "mem_region_export(source)",
316 call::mem_region_export(source),
317 )?;
318
319 let mut handle_info = HandleInfo {
320 resource_type: 0,
321 permissions: 0,
322 resource: 0,
323 };
324 if check_ok(
325 ctx,
326 "handle_info(region)",
327 call::handle_info(handle, &mut handle_info),
328 )
329 .is_some()
330 {
331 log_handle_info("original", &handle_info);
332 if handle_info.resource_type == 1 {
333 ok(
334 ctx,
335 "handle_info.resource_type == MemoryRegion",
336 handle_info.resource_type as usize,
337 );
338 } else {
339 ctx.fail += 1;
340 log_err("[FAIL] handle_info.resource_type == MemoryRegion -> got ");
341 log_u64(handle_info.resource_type as u64);
342 log_err("\n");
343 }
344 }
345
346 let mut region_info = MemoryRegionInfo {
347 size: 0,
348 page_size: 0,
349 flags: 0,
350 _reserved: 0,
351 };
352 if check_ok(
353 ctx,
354 "mem_region_info(region)",
355 call::mem_region_info(handle, &mut region_info),
356 )
357 .is_some()
358 {
359 log_region_info("original", ®ion_info);
360 if region_info.size == REGION_LEN as u64 {
361 ok(ctx, "region_info.size", region_info.size as usize);
362 } else {
363 ctx.fail += 1;
364 log_err("[FAIL] region_info.size -> got ");
365 log_u64(region_info.size);
366 log_err("\n");
367 }
368 if region_info.page_size == PAGE_SIZE as u64 {
369 ok(ctx, "region_info.page_size", region_info.page_size as usize);
370 } else {
371 ctx.fail += 1;
372 log_err("[FAIL] region_info.page_size -> got ");
373 log_u64(region_info.page_size);
374 log_err("\n");
375 }
376 if region_info.flags & ((PROT_READ | PROT_WRITE) as u32) == (PROT_READ | PROT_WRITE) as u32
377 {
378 ok(ctx, "region_info.flags has RW", region_info.flags as usize);
379 } else {
380 ctx.fail += 1;
381 log_err("[FAIL] region_info.flags has RW -> got ");
382 log_hex_u64(region_info.flags as u64);
383 log_err("\n");
384 }
385 }
386
387 Some(handle)
388}
389
390fn test_mapping_alias(ctx: &mut Ctx, source: usize, handle: usize, label: &str) -> Option<usize> {
392 let mut mapped_addr = 0usize;
393 let size = check_ok(
394 ctx,
395 label,
396 call::mem_region_map(handle, 0, &mut mapped_addr),
397 )?;
398 log("[map] ");
399 log(label);
400 log(" addr=");
401 log_hex_u64(mapped_addr as u64);
402 log(" size=");
403 log_u64(size as u64);
404 log("\n");
405 dump_memory_registers("source", source, REGION_LEN);
406 dump_memory_registers(label, mapped_addr, REGION_LEN);
407 verify_same_bytes(ctx, "alias bytes match source", source, mapped_addr);
408 Some(mapped_addr)
409}
410
411fn test_handle_lifecycle(ctx: &mut Ctx, source: usize, handle: usize, first_alias: usize) {
413 section("Handle lifecycle");
414
415 let dup = match check_ok(ctx, "handle_dup(region)", call::handle_dup(handle)) {
416 Some(value) => value,
417 None => return,
418 };
419
420 let mut dup_info = HandleInfo {
421 resource_type: 0,
422 permissions: 0,
423 resource: 0,
424 };
425 if check_ok(
426 ctx,
427 "handle_info(dup)",
428 call::handle_info(dup, &mut dup_info),
429 )
430 .is_some()
431 {
432 log_handle_info("dup", &dup_info);
433 }
434
435 let second_alias = match test_mapping_alias(ctx, source, dup, "mem_region_map(dup)") {
436 Some(value) => value,
437 None => return,
438 };
439
440 verify_write_propagation(
441 ctx,
442 "write alias1 -> source",
443 first_alias,
444 source,
445 PAGE_SIZE + 73,
446 0xD5,
447 );
448 verify_write_propagation(
449 ctx,
450 "write source -> alias2",
451 source,
452 second_alias,
453 91,
454 0x6B,
455 );
456
457 let self_pid = call::getpid().unwrap_or(0);
458 let granted = match check_ok(
459 ctx,
460 "handle_grant(region, self)",
461 call::handle_grant(handle, self_pid),
462 ) {
463 Some(value) => value,
464 None => {
465 let _ = check_ok(ctx, "handle_close(dup)", call::handle_close(dup));
466 return;
467 }
468 };
469
470 let third_alias = match test_mapping_alias(ctx, source, granted, "mem_region_map(granted)") {
471 Some(value) => value,
472 None => {
473 let _ = check_ok(ctx, "handle_close(dup)", call::handle_close(dup));
474 let _ = check_ok(ctx, "handle_revoke(granted)", call::handle_revoke(granted));
475 return;
476 }
477 };
478
479 verify_write_propagation(
480 ctx,
481 "write alias3 -> alias2",
482 third_alias,
483 second_alias,
484 PAGE_SIZE * 2 - 1,
485 0xA7,
486 );
487
488 let _ = check_ok(ctx, "handle_revoke(granted)", call::handle_revoke(granted));
489 let mut granted_info = HandleInfo {
490 resource_type: 0,
491 permissions: 0,
492 resource: 0,
493 };
494 check_expect_err(
495 ctx,
496 "handle_info(granted after revoke)",
497 call::handle_info(granted, &mut granted_info),
498 Error::BadHandle,
499 );
500
501 verify_write_propagation(
502 ctx,
503 "write alias2 after grant revoke -> source",
504 second_alias,
505 source,
506 PAGE_SIZE + 255,
507 0x3C,
508 );
509
510 let _ = check_ok(ctx, "handle_close(dup)", call::handle_close(dup));
511 check_expect_err(
512 ctx,
513 "handle_info(dup after close)",
514 call::handle_info(dup, &mut dup_info),
515 Error::BadHandle,
516 );
517}
518
519fn run_suite(ctx: &mut Ctx) {
521 section("MemoryRegion public capability suite");
522 log("[test_mem_region] exhaustive single-process mapping validation\n");
523 log("[test_mem_region] this binary is intended for manual Chevron execution\n");
524
525 let pid = call::getpid().unwrap_or(0);
526 let tid = call::gettid().unwrap_or(0);
527 log("[test_mem_region] pid=");
528 log_u64(pid as u64);
529 log(" tid=");
530 log_u64(tid as u64);
531 log(" page_size=");
532 log_u64(PAGE_SIZE as u64);
533 log(" region_len=");
534 log_u64(REGION_LEN as u64);
535 log("\n");
536
537 let source = match map_source_region(ctx) {
538 Some(value) => value,
539 None => return,
540 };
541 seed_region(source);
542 dump_memory_registers("source-seeded", source, REGION_LEN);
543
544 let handle = match test_export_and_metadata(ctx, source) {
545 Some(value) => value,
546 None => return,
547 };
548
549 test_invalid_inputs(ctx, source, handle);
550
551 let first_alias = match test_mapping_alias(ctx, source, handle, "mem_region_map(original)") {
552 Some(value) => value,
553 None => return,
554 };
555
556 verify_write_propagation(ctx, "write source -> alias1", source, first_alias, 17, 0xE1);
557 verify_write_propagation(
558 ctx,
559 "write alias1 -> source",
560 first_alias,
561 source,
562 PAGE_SIZE + 17,
563 0x4D,
564 );
565
566 test_handle_lifecycle(ctx, source, handle, first_alias);
567
568 let _ = check_ok(ctx, "handle_close(original)", call::handle_close(handle));
569 let mut handle_info = HandleInfo {
570 resource_type: 0,
571 permissions: 0,
572 resource: 0,
573 };
574 check_expect_err(
575 ctx,
576 "handle_info(original after close)",
577 call::handle_info(handle, &mut handle_info),
578 Error::BadHandle,
579 );
580}
581
582#[panic_handler]
583fn panic(_info: &PanicInfo) -> ! {
585 log_err("[test_mem_region] PANIC\n");
586 call::exit(250)
587}
588
589#[no_mangle]
590pub extern "C" fn _start() -> ! {
592 let mut ctx = Ctx { pass: 0, fail: 0 };
593
594 run_suite(&mut ctx);
595
596 section("Summary");
597 log("[summary] pass=");
598 log_u64(ctx.pass);
599 log(" fail=");
600 log_u64(ctx.fail);
601 log("\n");
602
603 if ctx.fail == 0 {
604 call::exit(0);
605 } else {
606 call::exit(1);
607 }
608}