//! The single trusted door between ring 0 and a process's memory. //! //! Kernel code never dereferences a user virtual address. It walks that address //! space's page tables through the physmap — kernel mappings throughout — and //! moves the bytes there. Three properties fall out of that one decision: //! //! - **A bad pointer fails the system call.** danos has no fault-recovering //! copy-in, so a raw dereference of an unmapped-but-in-range user page would //! halt the machine. Here it is a `false` return and an `-EFAULT`. //! - **The copy is a single fetch.** A struct pulled in once cannot be changed //! underneath the checks that follow it — no TOCTOU against a hostile pointer. //! - **It is SMAP-proof by construction.** No ring-0 access to a user-mapped //! page ever happens, so the CR4.SMAP bit needs no `stac` window anywhere //! (docs/os-development/smep-smap.md). There is no `stac` in this tree, and a //! change that adds one is wrong by definition. //! //! The walk enforces the permissions ring 3 itself would face: a read needs the //! leaf user-accessible (U/S at every level), a write needs it writable too //! (R/W at every level). So a syscall argument cannot steer the kernel at a //! kernel-only mapping, nor make it write a process's own read-only text — the //! properties that matter once shared or copy-on-write mappings exist, and the //! reason the "presence only" caveat that used to sit at the top of //! ipc-synchronous.zig is gone. //! //! Scope: this module knows only about *user* address spaces. The kernel side of //! a copy (an IPC reply staged in kernel memory, a bounce buffer) is trusted and //! translated without permission checks — see `resolve`. const std = @import("std"); const boot_handoff = @import("boot-handoff"); const abi = @import("abi"); const architecture = @import("architecture"); const page_size = abi.page_size; /// End of the user (low) canonical half. Every user buffer must lie below it, so /// kernel addresses and non-canonical values are refused by the range check /// alone, before any table is read. pub const user_half_end: u64 = 0x0000_8000_0000_0000; /// Whether `[virtual, virtual + len)` lies wholly inside the user half. The /// length is compared against the remaining span rather than added to the base, /// so a huge `len` cannot wrap the check. pub fn userRangeOk(virtual: u64, len: usize) bool { if (virtual >= user_half_end) return false; return len <= user_half_end - virtual; } /// Resolve one address for a copy. `address_space == 0` means the kernel's own /// tables — trusted, translated as-is. A real address space is a process's, and /// the walk demands what ring 3 would need: user-accessible, plus writable when /// this side of the copy is the destination. /// /// A user frame must also be reachable *through the physmap*, because that is how /// the copy loops touch it. The physmap covers RAM only: `paging.init` skips every /// `.mmio` region, while `mmio_map` hands a driver its device's BAR as an ordinary /// user-accessible mapping. Such a page satisfies the permission walk and would /// then fault ring 0 on the physmap alias — the very #PF this layer exists to make /// impossible — so coverage is confirmed before the address is returned, and an /// uncovered frame is refused like any other bad buffer. (Confirming coverage, /// rather than testing the `device_grant` bit, is what keeps physmap-backed RAM /// that merely carries that bit — a scanout surface — usable as a buffer.) pub fn resolve(address_space: u64, virtual: u64, for_write: bool) ?u64 { if (address_space == 0) return architecture.translate(architecture.kernelPageTable(), virtual); const physical = architecture.translateUser(address_space, virtual, for_write) orelse return null; if (architecture.translate(architecture.kernelPageTable(), boot_handoff.physicalToVirtual(physical)) == null) return null; return physical; } /// Copy `destination.len` bytes from `user_va` in address space `user_as` into the /// kernel buffer `destination`. False — never a #PF — if the range escapes the /// user half, or any source page is unmapped or not readable from ring 3. /// Handles page-straddling buffers. pub fn copyFromUser(user_as: u64, user_va: u64, destination: []u8) bool { if (user_as == 0) return false; // not a user address space if (!userRangeOk(user_va, destination.len)) return false; var off: usize = 0; while (off < destination.len) { const physical = resolve(user_as, user_va + off, false) orelse return false; const left = page_size - ((user_va + off) & (page_size - 1)); const n = @min(left, destination.len - off); const source: [*]const u8 = @ptrFromInt(boot_handoff.physicalToVirtual(physical)); @memcpy(destination[off..][0..n], source[0..n]); off += n; } return true; } /// The write direction: copy the kernel buffer `source` out to `user_va` in /// address space `user_as`. False — never a #PF, never a partial promise — if the /// range escapes the user half, or any destination page is unmapped, kernel-only, /// or read-only for ring 3. (A refusal mid-way may already have written earlier /// pages; the caller fails the whole system call, so the buffer's contents are /// meaningless either way.) /// /// The mirror of `copyFromUser`, and the only way kernel data reaches a user /// buffer outside the IPC path's `copyAcross`. pub fn copyToUser(user_as: u64, user_va: u64, source: []const u8) bool { if (user_as == 0) return false; // not a user address space if (!userRangeOk(user_va, source.len)) return false; var off: usize = 0; while (off < source.len) { const physical = resolve(user_as, user_va + off, true) orelse return false; const left = page_size - ((user_va + off) & (page_size - 1)); const n = @min(left, source.len - off); const destination: [*]u8 = @ptrFromInt(boot_handoff.physicalToVirtual(physical)); @memcpy(destination[0..n], source[off..][0..n]); off += n; } return true; } /// `copyToUser` for any fixed-layout value — the shape most write-direction /// system calls want (a `KlogStatus`, a `FileAttributes`). pub fn copyValueToUser(user_as: u64, user_va: u64, value: anytype) bool { return copyToUser(user_as, user_va, std.mem.asBytes(value)); }