Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Binary file added .keys/install-cli_publisher_ed25519.pub
Binary file not shown.
Binary file added .keys/install-cli_publisher_mldsa65.pub
Binary file not shown.
Binary file added .keys/install_publisher_ed25519.pub
Binary file not shown.
Binary file added .keys/install_publisher_mldsa65.pub
Binary file not shown.
Binary file added .keys/linux_publisher_ed25519.pub
Binary file not shown.
Binary file added .keys/linux_publisher_mldsa65.pub
Binary file not shown.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ nonos-capsule-net-core = []
nonos-capsule-net-sockets = []
nonos-capsule-net-nym = []
nonos-capsule-socks5 = []
nonos-capsule-linux = []
nonos-capsule-market = []

# Layout-stable debug ring. Allocation-free, formatter-free fixed-VA
Expand Down Expand Up @@ -550,6 +551,7 @@ microkernel-desktop-base = [
"nonos-capsule-wallpaper-catalog",
"nonos-capsule-toolkit",
"nonos-capsule-about",
"nonos-capsule-linux",
"nonos-capsule-audio",
"nonos-capsule-driver-hda",
"nonos-capsule-boot-splash",
Expand Down
1 change: 1 addition & 0 deletions abi/caps.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ KEYRING = 0x0000_0000_1000_0000
ENTROPY = 0x0000_0000_2000_0000
APP_INSTALL = 0x0000_0000_4000_0000
ATTEST_READ = 0x0000_0000_8000_0000
FOREIGN_EXEC = 0x0000_0001_0000_0000

[groups]
BASIC = ["LOG","YIELD","TIME"]
Expand Down
77 changes: 77 additions & 0 deletions abi/syscalls.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ MFTK = 0x4B54464D
MFTW = 0x5754464D
MGPD = 0x4450474D
MIEW = 0x5745494D
MFSP = 0x5053464D
MFST = 0x5453464D
MFWT = 0x5457464D
MFRP = 0x5052464D
MPMP = 0x504D504D
MPCP = 0x5043504D
MPPT = 0x5450504D
MFTH = 0x4854464D
MPTL = 0x4C54504D
MFFK = 0x4B46464D
MPUN = 0x4E55504D
MIRW = 0x5752494D
MIRY = 0x5952494D
MKAR = 0x52414B4D
Expand Down Expand Up @@ -603,6 +614,72 @@ caps = ["valid_token"]
args = [{name="user_ptr",type="u64",dir="in"},{name="len",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MFSP]
nr = 0x5053464D
caps = ["ForeignExec"]
args = [{name="name_ptr",type="u64",dir="in"},{name="name_len",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MFST]
nr = 0x5453464D
caps = ["ForeignExec"]
args = [{name="pid",type="u32",dir="in"},{name="entry",type="u64",dir="in"},{name="rsp",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MFWT]
nr = 0x5457464D
caps = ["ForeignExec"]
args = [{name="out",type="u8*",dir="out"},{name="out_len",type="usize",dir="in"},{name="timeout_ms",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MFRP]
nr = 0x5052464D
caps = ["ForeignExec"]
args = [{name="pid",type="u32",dir="in"},{name="value",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MPMP]
nr = 0x504D504D
caps = ["ForeignExec"]
args = [{name="pid",type="u32",dir="in"},{name="addr",type="u64",dir="in"},{name="len",type="u64",dir="in"},{name="prot",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MPCP]
nr = 0x5043504D
caps = ["ForeignExec"]
args = [{name="pid",type="u32",dir="in"},{name="guest_addr",type="u64",dir="in"},{name="buf",type="u8*",dir="inout"},{name="len",type="u64",dir="in"},{name="to_guest",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MFTH]
nr = 0x4854464D
caps = ["ForeignExec"]
args = [{name="pid",type="u32",dir="in"},{name="entry",type="u64",dir="in"},{name="rsp",type="u64",dir="in"},{name="tls",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MPUN]
nr = 0x4E55504D
caps = ["ForeignExec"]
args = [{name="pid",type="u32",dir="in"},{name="addr",type="u64",dir="in"},{name="len",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MFFK]
nr = 0x4B46464D
caps = ["ForeignExec"]
args = [{name="pid",type="u32",dir="in"}]
ret = {type="i64"}

[desc.MPTL]
nr = 0x4C54504D
caps = ["ForeignExec"]
args = [{name="pid",type="u32",dir="in"},{name="base",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MPPT]
nr = 0x5450504D
caps = ["ForeignExec"]
args = [{name="pid",type="u32",dir="in"},{name="addr",type="u64",dir="in"},{name="len",type="u64",dir="in"},{name="prot",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MSPI]
nr = 0x4950534D
caps = ["valid_token"]
Expand Down
14 changes: 12 additions & 2 deletions mk/20-build.mk
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ include userland/capsule_clipboard/Capsule.mk
include userland/capsule_login/Capsule.mk
include userland/toolkit/Capsule.mk
include userland/capsule_about/Capsule.mk
include userland/capsule_linux/Capsule.mk
include userland/capsule_hello/Capsule.mk
include userland/capsule_gui_demo/Capsule.mk
include userland/capsule_game_2048/Capsule.mk
Expand Down Expand Up @@ -731,7 +732,7 @@ NONOS_DESKTOP_GUI_CAPSULE_CHECKS = \
$(driver-usb-hid_VERIFY) \
$(net-core_VERIFY) $(net-sockets_VERIFY) $(net-nym_VERIFY) \
$(policy_VERIFY) $(wallpaper_catalog_VERIFY) \
$(installer_VERIFY) \
$(installer_VERIFY) $(linux_VERIFY) \
$(input-router_VERIFY) $(compositor_VERIFY) $(wm_VERIFY) \
$(desktop-shell_VERIFY) $(image-codec_VERIFY) $(image-viewer_VERIFY) $(clipboard_VERIFY) \
$(login_VERIFY) $(wallpaper_VERIFY) $(toolkit_VERIFY) \
Expand Down Expand Up @@ -1139,7 +1140,7 @@ DESKTOP_BASE_SLUGS := proof-io ramfs keyring entropy crypto vfs \
driver-virtio-net driver-ps2-input driver-xhci driver-usb-hid \
net-core net-sockets net-nym socks5 policy wallpaper_catalog \
installer input-router compositor wm desktop-shell image-codec \
clipboard login wallpaper toolkit about boot-splash calculator \
clipboard login wallpaper toolkit about linux boot-splash calculator \
browser wallet-nonos terminal file-manager text-editor \
settings process-manager attest power \
audio driver-hda audio_player video-player
Expand All @@ -1164,6 +1165,15 @@ nonos-mk-desktop-gui-prod: $(DESKTOP_GUI_CAPSULE_ARTIFACTS) \
nonos-mk-check-deps nonos-mk-ensure-signing-key
$(call nonos_kernel_build,microkernel-desktop-gui + nonos-stark-attest,microkernel-desktop-gui$(_boot_comma)nonos-stark-attest)

# nonos-mk-install-prod: the desktop profile with the NVMe driver capsule in
# it. The desktop cut leaves NVMe out because a driver whose hardware is absent
# blocks on spawn; the install lane presents an NVMe target to QEMU, so the
# driver has a device and the installer has a disk that is not the store.
nonos-mk-install-prod: $(DESKTOP_GUI_CAPSULE_ARTIFACTS) $(driver-nvme_ARTIFACTS) \
nonos-mk-verify-desktop-gui-capsules \
nonos-mk-check-deps nonos-mk-ensure-signing-key
$(call nonos_kernel_build,microkernel-desktop-gui + nvme + install,microkernel-desktop-gui$(_boot_comma)nonos-stark-attest$(_boot_comma)nonos-capsule-driver-nvme)

# nonos-mk-smp-prod: the desktop profile with the secondary CPUs turned on.
# Same capsule set and the same attestation, so a difference between this boot
# and the single-CPU one is the AP bring-up and nothing else.
Expand Down
42 changes: 40 additions & 2 deletions src/arch/x86_64/asm/syscall.S
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ syscall_entry_asm:
below destroys them, so stash the user copies under the saved frame and
restore them on exit. One pad slot keeps 16-alignment since three
arg-saves are odd. Eleven slots -> rsp = 8 (mod 16). */
/* The callee-saved five, plus a pad to keep the parity the comment
above depends on. Nothing else on this path writes them to memory,
and Rust cannot read them either: by the time a handler runs, its
own prologue may already be using them. A forked child has to
resume with the parent's whole register state, so the capture has
to happen here or not at all. Six slots is 0 (mod 16), so every
offset below stays exactly where it was. */
push r15
push r14
push r13
push r12
push rbx
sub rsp, 8

sub rsp, 8
push rdx
push rsi
Expand All @@ -50,10 +64,23 @@ syscall_entry_asm:
mov r9, [rsp + 0x08]
mov r11, [rsp + 0x10]

/* Spill a6 as 7th arg, realigns to 16. */
/* a6 goes on the stack as the seventh argument; the eighth is a
pointer to the frame just saved. A pointer rather than a single
register because a forked child resumes with the parent's whole
state, and the frame holds all of it: the return address is the
saved rcx at offset 0x20 and the rest follows it. Taken before
the two pushes, so it points at the saved rax.

One push left rsp 16-aligned for the call. Two do not, so a pad goes
underneath them: the arguments themselves must sit at [rsp] and
[rsp+8] when the call executes. Cleanup grows from 0x10 to 0x20 for
the pad and the extra argument. */
mov rax, rsp
sub rsp, 8
push rax
push r11
call syscall_handler
add rsp, 0x10
add rsp, 0x20

/* SyscallSavedFrame{rax, r8, r9, r10, rcx, r11, rbp} at rsp. */
push rax
Expand All @@ -80,6 +107,17 @@ syscall_entry_asm:
pop rdx
add rsp, 8

/* The callee-saved five come back with their pad, in reverse. They
still hold the user's values, so this restores rather than
changes them; the point of saving was to give a supervisor a
complete frame to fork from. */
add rsp, 8
pop rbx
pop r12
pop r13
pop r14
pop r15

push rax
movabs rax, 0xffffffffffe08aff
and r11, rax
Expand Down
21 changes: 16 additions & 5 deletions src/arch/x86_64/syscall/manager/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use crate::security::hardening::speculation::kernel_entry;
use crate::syscall::contract::{dispatch as contract_dispatch, SyscallArgs};
use crate::syscall::numbers::SyscallNumber;
use crate::process::foreign::FRAME_WORDS;
use crate::syscall::types::errnos;

#[no_mangle]
Expand All @@ -28,17 +29,27 @@ pub(super) extern "C" fn syscall_handler(
arg4: u64,
arg5: u64,
arg6: u64,
frame: *const u64,
) -> u64 {
// A capsule reaching this point last controlled the branch predictors and
// the return stack. Refilling the RSB and re-asserting IBRS before any
// kernel branch runs is the whole point of the entry side, and it was the
// side with no caller: `kernel_exit` was wired on the return path, so
// mitigations were being applied leaving the kernel but not entering it.
// the return stack.
kernel_entry();

let Some(sc) = SyscallNumber::from_u64(number) else {
return (-(errnos::ENOSYS as i64)) as u64;
// A number this kernel does not know.
let args = [arg1, arg2, arg3, arg4, arg5, arg6];
// SAFETY: eK@nonos.systems - `frame` is the pointer the entry
// stub in syscall.S passed, naming the sixteen words it pushed
// on this kernel stack, which outlive this call. This is the
// one place that pointer is turned into a reference; everything
// downstream of it is safe code.
let saved = unsafe { &*(frame as *const [u64; FRAME_WORDS]) };
return match crate::process::foreign::redirect(number, args, saved) {
Some(value) => value,
None => (-(errnos::ENOSYS as i64)) as u64,
};
};
let _ = frame;
let result = contract_dispatch(sc, SyscallArgs::new([arg1, arg2, arg3, arg4, arg5, arg6]));
result.value as u64
}
1 change: 1 addition & 0 deletions src/capabilities/types/as_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ impl Capability {
Self::Entropy => "Entropy",
Self::AppInstall => "AppInstall",
Self::AttestRead => "AttestRead",
Self::ForeignExec => "ForeignExec",
}
}
}
1 change: 1 addition & 0 deletions src/capabilities/types/bit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl Capability {
Self::Entropy => 536870912,
Self::AppInstall => 1073741824,
Self::AttestRead => 2147483648,
Self::ForeignExec => 4294967296,
}
}
}
7 changes: 7 additions & 0 deletions src/capabilities/types/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,11 @@ pub enum Capability {
* the programs that render a receipt, not by every capsule with a token.
*/
AttestRead,
/*
* Hosting code this kernel has not verified: create a process with no
* capabilities, build its address space, and answer the syscalls it
* makes that the kernel refuses. One capsule holds this, and an
* auditor reading a capability set can see which.
*/
ForeignExec,
}
9 changes: 7 additions & 2 deletions src/process/core/table/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,13 @@ pub(crate) fn create_process_with_parent(
/// thread group. Returns the new thread id. The thread has no VMAs of its own,
/// so its teardown frees nothing of the shared address space.
pub fn spawn_thread(entry: u64, stack: u64) -> Result<Pid, &'static str> {
let parent_pid = CURRENT_PID.load(Ordering::Relaxed);
let parent = PROCESS_TABLE.find_by_pid(parent_pid).ok_or("no current process")?;
spawn_thread_in(CURRENT_PID.load(Ordering::Relaxed), entry, stack)
}

/// A thread in `parent_pid` rather than in the caller. The foreign path
/// needs this: the supervisor asks, the guest gets the thread.
pub fn spawn_thread_in(parent_pid: Pid, entry: u64, stack: u64) -> Result<Pid, &'static str> {
let parent = PROCESS_TABLE.find_by_pid(parent_pid).ok_or("no such process")?;
let tid = allocate_tid().ok_or("pid space exhausted")?;
let caps = compute_inherited_caps(tid, parent_pid);
let pcb = build_pcb(tid, parent_pid, "thread", ProcessState::Ready, Priority::Normal, 0, caps)?;
Expand Down
1 change: 1 addition & 0 deletions src/process/exit/teardown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,6 @@ pub fn teardown(pid: Pid, exit_code: i32, _by_signal: bool) {
crate::sched::remove_from_run_queue(pid);
clear_current_if(pid);
crate::process::scheduler::preemption::proc_ticks::clear(pid);
crate::process::foreign::clear(pid);
super::pending::enqueue(pid);
}
67 changes: 67 additions & 0 deletions src/process/foreign/exec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// NONOS Operating System
// Copyright (C) 2026 NONOS Contributors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! `MkForeignExec`: the same guest, a different program.

use crate::syscall::microkernel::errnos::{ERRNO_INVAL, ERRNO_PERM};

use super::exec_context::fresh;
use super::peer_guard::in_user_half;

type Saved = Option<crate::arch::context::SavedUser>;

/// What a parked guest receives when its supervisor has replaced the program
/// under it.
pub(super) const EXECED: u64 = u64::MAX;

pub fn sys_foreign_exec(pid: u64, entry: u64, rsp: u64) -> i64 {
let Some(caller) = crate::process::current_pid() else {
return ERRNO_INVAL;
};
let pid = pid as u32;
if super::registry::supervisor_of(pid) != Some(caller) {
return ERRNO_PERM;
}
if rsp == 0 || !in_user_half(entry, 1) || !in_user_half(rsp, 1) {
return ERRNO_INVAL;
}
let Some(previous) = swap(pid, Some(fresh(entry, rsp))) else {
return ERRNO_INVAL;
};
drop_tls(pid);
// Answering is what releases the guest.
match super::trap_reply::answer_raw(pid, EXECED) {
0 => 0,
err => {
swap(pid, previous);
err
}
}
}

/// Put a context in place and hand back the one it displaced.
fn swap(pid: u32, ctx: Saved) -> Option<Saved> {
crate::process::with_process(pid, |p| {
core::mem::replace(&mut *p.saved_user_context.lock(), ctx)
})
}

/// Forget the thread pointer the replaced runtime set: the scheduler writes
/// the control block's base on every switch, so leaving it would put the new
/// image back on the old TLS the first time it is preempted.
fn drop_tls(pid: u32) {
crate::process::with_process(pid, |pcb| pcb.set_tls_base(0));
}
Loading
Loading