Skip to main content

test_mem_region/
mem_region.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
20struct Ctx {
21    pass: u64,
22    fail: u64,
23}
24
25/// Write a message to a file descriptor.
26fn write_fd(fd: usize, msg: &str) {
27    let _ = call::write(fd, msg.as_bytes());
28}
29
30/// Write to stdout.
31fn log(msg: &str) {
32    write_fd(1, msg);
33}
34
35/// Write to stderr.
36fn log_err(msg: &str) {
37    write_fd(2, msg);
38}
39
40/// Write an unsigned integer in decimal.
41fn 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
57/// Write an unsigned integer in hexadecimal.
58fn 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
74/// Print a section banner.
75fn section(title: &str) {
76    log("\n============================================================\n");
77    log("[test_mem_region] ");
78    log(title);
79    log("\n============================================================\n");
80}
81
82/// Record a successful check.
83fn 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
94/// Record a failed check.
95fn 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
104/// Check a syscall result and keep the successful value.
105fn 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
118/// Check that a syscall fails with a specific error.
119fn 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
146/// Print a short memory snapshot for one mapped view.
147fn 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
172/// Fill the source region with a deterministic pattern.
173fn 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
184/// Verify that two mappings expose identical bytes.
185fn 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
206/// Verify that a write through one alias is visible through another one.
207fn 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
235/// Print the metadata returned by handle_info.
236fn 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
248/// Print the metadata returned by mem_region_info.
249fn 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
261/// Create the anonymous source mapping used for export tests.
262fn 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
276/// Exercise invalid argument paths that should stay deterministic.
277fn 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
309/// Export the region and validate the returned metadata.
310fn 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", &region_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
390/// Map one handle and verify alias coherence.
391fn 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
411/// Exercise duplicate and grant lifecycles.
412fn 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
519/// Run the full MemoryRegion coverage suite.
520fn 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]
583/// Abort the process on panic.
584fn panic(_info: &PanicInfo) -> ! {
585    log_err("[test_mem_region] PANIC\n");
586    call::exit(250)
587}
588
589#[no_mangle]
590/// Userspace entry point.
591pub 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}