Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bf9d9db
mk: build the Linux-guest test image without first-boot setup
eKisNonos Sep 28, 2026
5ab0a09
exit: keep a thread group's page tables until its last member is gone
eKisNonos Sep 28, 2026
3fc1ac1
foreign: start a guest's clone child on its parent's registers
eKisNonos Sep 28, 2026
874f3ec
kill: let a foreign supervisor end the guests it hosts
eKisNonos Sep 28, 2026
a06600d
app_skeleton: let an app keep its window open when close is pressed
eKisNonos Sep 28, 2026
adb4a79
text_editor: never overwrite a file with text that was not read from it
eKisNonos Sep 28, 2026
7e51ab4
nonos_vt: an xterm-compatible terminal screen, parser and input encoder
eKisNonos Sep 28, 2026
4e36246
app_skeleton: name the Insert and function keys the input drivers send
eKisNonos Sep 28, 2026
bd52d02
terminal: run programs on an xterm-grade screen sized to the window
eKisNonos Sep 28, 2026
e8f2093
kernel: tell a program whether its streams reach a terminal
eKisNonos Sep 28, 2026
8de62e9
std: ask the kernel whether a standard stream is a terminal
eKisNonos Sep 28, 2026
5cc7edf
toolkit: draw characters the UI face lacks from its mono face
eKisNonos Sep 28, 2026
c6a4f6a
text_editor: draw folder chevrons in pixels, colour only code
eKisNonos Sep 28, 2026
c77637b
terminal: readline keys, and chords that type nothing
eKisNonos Sep 28, 2026
ca91842
terminal: help that fits 80 columns and names every key
eKisNonos Sep 28, 2026
c2f4fe8
terminal: drop rg, which no build registers, and name refusals
eKisNonos Sep 28, 2026
6ac5bb8
terminal: start a selection from the margin around the text
eKisNonos Sep 28, 2026
69bb50b
terminal: tell the programs it shows that they reach a screen
eKisNonos Sep 28, 2026
17ce995
build: drop std build caches when the std layer changes
eKisNonos Sep 28, 2026
c912534
terminal: take commands longer than the screen is wide
eKisNonos Sep 28, 2026
8bf7064
terminal: sixteen colours chosen for the window's ground
eKisNonos Sep 28, 2026
d8b8baf
terminal: stroke box-drawing characters across the whole cell
eKisNonos Sep 28, 2026
55c3c7f
wallet: talk to the RPC only after the server proves it is the host
eKisNonos Sep 28, 2026
596b428
wallet: show the recovery phrase, and make Esc go back
eKisNonos Sep 28, 2026
49dffbb
wallet: refuse to sign a transfer above a 1000 gwei gas price
eKisNonos Sep 28, 2026
9d3a712
wallet: draw the Shield screens and say when no shield service answers
eKisNonos Sep 28, 2026
e8dd078
wallet: make a withdrawal wait like a payment, and let the banner close
eKisNonos Sep 28, 2026
3fe559b
wallet: draw Swap on the Etna frame with every term in the open
eKisNonos Sep 28, 2026
23f3c55
wallet: let the Swap banner close, and word the early spend for both
eKisNonos Sep 28, 2026
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
  •  
  •  
  •  
17 changes: 16 additions & 1 deletion abi/syscalls.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ ENODEV = -19
ENOTDIR = -20
EISDIR = -21
EINVAL = -22
ENOTTY = -25
ETXTBSY = -26
ERANGE = -34
ENOSYS = -38
Expand Down Expand Up @@ -118,6 +119,8 @@ MTMS = 0x534D544D
MTRN = 0x4E52544D
MTRT = 0x5452544D
MTSP = 0x5053544D
MTTQ = 0x5154544D
MTTY = 0x5954544D
MWAT = 0x5441574D
CRND = 0x444E5243
CHSH = 0x48534843
Expand Down Expand Up @@ -662,7 +665,7 @@ 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"}]
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"},{name="from",type="u32",dir="in"}]
ret = {type="i64"}

[desc.MPTL]
Expand Down Expand Up @@ -803,6 +806,18 @@ caps = ["IPC"]
args = [{name="entry",type="u64",dir="in"},{name="stack",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MTTQ]
nr = 0x5154544D
caps = ["valid_token"]
args = [{name="fd",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MTTY]
nr = 0x5954544D
caps = ["IPC"]
args = [{name="pid",type="u64",dir="in"},{name="streams",type="u64",dir="in"},{name="cols",type="u64",dir="in"},{name="rows",type="u64",dir="in"}]
ret = {type="i64"}

[desc.MWAT]
nr = 0x5441574D
caps = ["IPC"]
Expand Down
13 changes: 13 additions & 0 deletions mk/20-build.mk
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,11 @@ $(NONOS_STD_PAL_STAMP): $(NONOS_STD_PAL_SRCS) | $(TARGET_DIR)/.nonos-toolchain.s
@echo "Applying NONOS std platform layer to rust-src..."
@PATH="$(HOME)/.cargo/bin:$$PATH" RUSTUP_TOOLCHAIN=$(TOOLCHAIN) \
toolchain/nonos-std/apply.sh
@# -Zbuild-std fingerprints the sysroot crates by version, not by their
@# sources, so a target dir that built std before the layer changed keeps
@# linking the old std and the tool ships without the new behaviour. The
@# layer just changed, so every such cache is stale: drop them.
@rm -rf userland/upstream-src/*/target userland/capsule_std_proof/target
@mkdir -p $(TARGET_DIR)
@touch $@

Expand Down Expand Up @@ -1187,10 +1192,18 @@ DESKTOP_GUI_CAPSULE_ARTIFACTS := $(DESKTOP_BASE_CAPSULE_ARTIFACTS) \
$(DESKTOP_STD_TOOL_ARTIFACTS) \
$(ZK_POLICY_ROOT)

# A Linux-guest test image boots unattended, and first-boot setup waits for
# keys nobody presses. Under the setup profile the apps, the Linux personality
# among them, spawn only once setup exits, so that image would never start its
# guest. It builds the desktop profile without first-boot setup instead.
nonos-mk-desktop-gui-prod: $(DESKTOP_GUI_CAPSULE_ARTIFACTS) \
nonos-mk-verify-desktop-gui-capsules \
nonos-mk-check-deps nonos-mk-ensure-signing-key
ifeq ($(NONOS_LINUX_GUESTS),1)
$(call nonos_kernel_build,microkernel-desktop-gui + nonos-stark-attest (unattended guest test),microkernel-desktop-gui$(_boot_comma)nonos-stark-attest)
else
$(call nonos_kernel_build,microkernel-setup-wizard + nonos-stark-attest,microkernel-setup-wizard$(_boot_comma)nonos-stark-attest)
endif

# 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
Expand Down
12 changes: 12 additions & 0 deletions src/memory/paging/manager/api/address_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ pub fn lookup_asid_for_process(process_id: u32) -> Option<u32> {
lock_responsive(&PAGING_MANAGER).lookup_asid_for_process(process_id)
}

/// Make `process_id` the owner of the tables `asid` names, so its release is
/// the one that frees them. False when there is no such address space.
pub fn hand_over_address_space(asid: u32, process_id: u32) -> bool {
match lock_responsive(&PAGING_MANAGER).address_spaces.get_mut(&asid) {
Some(space) => {
space.process_id = process_id;
true
}
None => false,
}
}

pub fn switch_to_process_address_space(process_id: u32) -> PagingResult<()> {
let asid = lookup_asid_for_process(process_id)
.ok_or(crate::memory::paging::error::PagingError::AddressSpaceNotFound)?;
Expand Down
4 changes: 2 additions & 2 deletions src/memory/paging/manager/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ mod stats;
mod tlb_ops;

pub use address_space::{
cleanup_address_space, create_address_space, get_process_cr3, lookup_asid_for_process,
switch_address_space, switch_to_process_address_space,
cleanup_address_space, create_address_space, get_process_cr3, hand_over_address_space,
lookup_asid_for_process, switch_address_space, switch_to_process_address_space,
};
pub use faults::handle_page_fault;
pub use init::{init, is_initialized};
Expand Down
33 changes: 30 additions & 3 deletions src/process/address_space/lifecycle/release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,36 @@ pub fn release(pcb: &Arc<ProcessControlBlock>) {
// table, so it freed whatever address space happened to be current; the
// ASID-scoped teardown frees the leaf frames as well, so it is the only
// path that touches the right tables.
if let Some(asid) = crate::memory::paging::manager::lookup_asid_for_process(pcb.pid) {
if crate::memory::paging::manager::cleanup_address_space(asid).is_err() {
crate::sys::serial::println(b"[EXIT] address_space_cleanup_failed");
let Some(asid) = crate::memory::paging::manager::lookup_asid_for_process(pcb.pid) else {
return;
};
/*
* A thread runs on its group's tables without owning them, and until it
* leaves the process table it can still be on a CPU under them, taking
* its own kill or parked on its kernel stack. Freed when the owner went
* first, they were reused under a running thread, and a threaded guest's
* exit triple faulted. They pass to a thread still in the table instead,
* and the last holder's release frees them.
*/
if let Some(heir) = holder_after(pcb) {
if crate::memory::paging::manager::hand_over_address_space(asid, heir.pid) {
// Its token names the ASID it runs in, which it now owns.
let _ = crate::process::caps::rebind_address_space(&heir);
return;
}
}
if crate::memory::paging::manager::cleanup_address_space(asid).is_err() {
crate::sys::serial::println(b"[EXIT] address_space_cleanup_failed");
}
}

fn holder_after(pcb: &ProcessControlBlock) -> Option<Arc<ProcessControlBlock>> {
let tables = pcb.cr3.load(Ordering::Acquire);
if tables == 0 {
return None;
}
crate::process::core::PROCESS_TABLE
.get_all_processes()
.into_iter()
.find(|p| p.pid != pcb.pid && p.cr3.load(Ordering::Acquire) == tables)
}
1 change: 1 addition & 0 deletions src/process/exit/finalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub(super) fn finalize_teardown(pid: Pid) {
let _ = crate::ipc::nonos_inbox::unregister_for_pid(pid);
}

crate::syscall::microkernel::tty_table::forget(pid);
crate::process::clear_interrupt_context(pid);
crate::process::clear_fpu_state(pid);
crate::process::core::init::reparent_orphans(pid);
Expand Down
60 changes: 58 additions & 2 deletions src/process/foreign/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,20 @@
//! A second thread inside a guest.

use super::peer_guard::{in_user_half, pid_arg};
use crate::arch::context::SavedUser;
use crate::process::core::{admit_thread, spawn_thread_parked};
use crate::syscall::microkernel::errnos::{ERRNO_INVAL, ERRNO_NOMEM, ERRNO_PERM};
use crate::syscall::microkernel::errnos::{ERRNO_INVAL, ERRNO_NOENT, ERRNO_NOMEM, ERRNO_PERM};

/// `MkForeignThread`: a thread in `pid`, sharing its address space and
/// supervised by the same caller.
pub fn sys_foreign_thread(pid: u64, entry: u64, rsp: u64, tls: u64) -> i64 {
///
/// `from` is zero, or the thread of that guest parked in the call that asked
/// for this one. Given, the new thread starts on a copy of its registers, as a
/// Linux clone child does: zero in the return register, the stack and entry
/// given here, and the parent's thread pointer unless `tls` names another. Go
/// hands the child its function and its thread state in registers and calls
/// through them, so a child started on fresh registers called address zero.
pub fn sys_foreign_thread(pid: u64, entry: u64, rsp: u64, tls: u64, from: u64) -> i64 {
let Some(caller) = crate::process::current_pid() else {
return ERRNO_INVAL;
};
Expand All @@ -44,16 +52,64 @@ pub fn sys_foreign_thread(pid: u64, entry: u64, rsp: u64, tls: u64) -> i64 {
if tls != 0 && !in_user_half(tls, 1) {
return ERRNO_INVAL;
}
let parent = match from {
0 => None,
raw => match parked_parent(caller, pid, raw) {
Ok(p) => Some(p),
Err(e) => return e,
},
};
let Ok(tid) = spawn_thread_parked(pid, entry, rsp) else {
return ERRNO_NOMEM;
};
let tls = match (tls, &parent) {
(0, Some((_, parent_tls))) => *parent_tls,
_ => tls,
};
if tls != 0 {
crate::process::with_process(tid, |pcb| pcb.set_tls_base(tls));
}
if let Some((mut regs, _)) = parent {
regs.rax = 0;
regs.rip = entry;
regs.rsp = rsp;
/*
* A saved context is what the switch resumes when no first entry is
* pending, which is how a forked child starts; the fresh entry the
* spawn prepared would otherwise win and drop every register.
*/
crate::process::with_process(tid, |pcb| {
pcb.pending_user_entry.lock().take();
*pcb.saved_user_context.lock() = Some(regs);
});
}
if !super::registry::insert(tid, caller) {
crate::process::exit::teardown(tid, ERRNO_NOMEM as i32, false);
return ERRNO_NOMEM;
}
admit_thread(tid);
tid as i64
}

/*
* The registers and thread pointer of the thread that asked. It must be one
* this caller supervises, in the same thread group as `pid`: a copy across
* groups would hand one guest another's register contents. It must also be
* parked in a call, since only then are its registers held aside.
*/
fn parked_parent(caller: u32, pid: u32, raw: u64) -> Result<(SavedUser, u64), i64> {
let from = pid_arg(raw)?;
if super::registry::supervisor_of(from) != Some(caller) {
return Err(ERRNO_PERM);
}
let group = |p: u32| crate::process::with_process(p, |pcb| pcb.thread_group_id());
let (Some(want), Some(have)) = (group(pid), group(from)) else {
return Err(ERRNO_INVAL);
};
if want != have {
return Err(ERRNO_PERM);
}
let regs = super::trap_frame::parked_frame(from).ok_or(ERRNO_NOENT)?;
let parent_tls = crate::process::with_process(from, |pcb| pcb.get_tls_base()).unwrap_or(0);
Ok((regs, parent_tls))
}
2 changes: 2 additions & 0 deletions src/syscall/abi/registry/mk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ pub(super) const ENTRIES: &[AbiEntry] = &[
e(b"MAPL", SyscallNumber::MkAppLaunch, "MkAppLaunch"),
e(b"MAIS", SyscallNumber::MkAppInstallStatus, "MkAppInstallStatus"),
e(b"MTRN", SyscallNumber::MkToolRun, "MkToolRun"),
e(b"MTTY", SyscallNumber::MkTtySet, "MkTtySet"),
e(b"MTTQ", SyscallNumber::MkTtyQuery, "MkTtyQuery"),
e(b"MSOW", SyscallNumber::MkStdoutWrite, "MkStdoutWrite"),
e(b"MSWR", SyscallNumber::MkStoreWrite, "MkStoreWrite"),
e(b"MCVF", SyscallNumber::MkCapsuleVerify, "MkCapsuleVerify"),
Expand Down
7 changes: 7 additions & 0 deletions src/syscall/contract/cap_table/mk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,13 @@ pub(super) fn check(caps: &CapabilityToken, number: SyscallNumber) -> Option<boo
* baked, attested set can be named.
*/
SyscallNumber::MkToolRun => caps.can_ipc(),
/*
* Saying what a child's streams are on is part of driving its stdio,
* so it needs what running the child needed. Asking about one's own
* streams reveals nothing about anyone else.
*/
SyscallNumber::MkTtySet => caps.can_ipc(),
SyscallNumber::MkTtyQuery => true,

_ => return None,
})
Expand Down
2 changes: 2 additions & 0 deletions src/syscall/dispatch/router/microkernel_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ pub(super) fn matches(nr: SyscallNumber) -> bool {
| MkAppLaunch
| MkAppInstallStatus
| MkToolRun
| MkTtySet
| MkTtyQuery
)
}

Expand Down
5 changes: 4 additions & 1 deletion src/syscall/microkernel/dispatch/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ use crate::syscall::microkernel::time::{
sys_time_adjust, sys_time_millis, sys_time_monotonic, sys_time_rtc,
};
use crate::syscall::microkernel::tool_run::sys_tool_run;
use crate::syscall::microkernel::tty::{sys_tty_query, sys_tty_set};
use crate::syscall::microkernel::wait::sys_wait;

pub(super) fn handle(nr: u64, a: Args) -> Option<i64> {
Expand Down Expand Up @@ -95,7 +96,7 @@ pub(super) fn handle(nr: u64, a: Args) -> Option<i64> {
SYS_PEER_MAP => sys_peer_map(a.a0, a.a1, a.a2, a.a3),
SYS_PEER_COPY => sys_peer_copy(a.a0, a.a1, a.a2, a.a3, a.a4),
SYS_PEER_PROTECT => sys_peer_protect(a.a0, a.a1, a.a2, a.a3),
SYS_FOREIGN_THREAD => sys_foreign_thread(a.a0, a.a1, a.a2, a.a3),
SYS_FOREIGN_THREAD => sys_foreign_thread(a.a0, a.a1, a.a2, a.a3, a.a4),
SYS_PEER_TLS => sys_peer_tls(a.a0, a.a1),
SYS_FOREIGN_FORK => sys_foreign_fork(a.a0),
SYS_PEER_UNMAP => sys_peer_unmap(a.a0, a.a1, a.a2),
Expand All @@ -112,6 +113,8 @@ pub(super) fn handle(nr: u64, a: Args) -> Option<i64> {
SYS_DEV_ROOT_CONFIRM => sys_dev_root_confirm(a.a0),
SYS_SPAWN_INSTANCE => sys_spawn_instance(a.a0, a.a1),
SYS_TOOL_RUN => sys_tool_run(a.a0, a.a1, a.a2, a.a3),
SYS_TTY_SET => sys_tty_set(a.a0, a.a1, a.a2, a.a3),
SYS_TTY_QUERY => sys_tty_query(a.a0),
_ => return None,
})
}
1 change: 1 addition & 0 deletions src/syscall/microkernel/errnos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub const ERRNO_BUSY: i64 = -16;
pub const ERRNO_EXIST: i64 = -17;
pub const ERRNO_NODEV: i64 = -19;
pub const ERRNO_INVAL: i64 = -22;
pub const ERRNO_NOTTY: i64 = -25;
pub const ERRNO_NOSYS: i64 = -38;
pub const ERRNO_NOTSUP: i64 = -95;
pub const ERRNO_TIMEDOUT: i64 = -110;
Expand Down
9 changes: 8 additions & 1 deletion src/syscall/microkernel/kill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,21 @@ pub fn sys_kill(pid: u64, sig: u64) -> i64 {
// unrelated pid needs the ProcessControl capability, held only by the
// process manager, so a compromised app cannot terminate other capsules.
let is_parent = caller != 0 && get_parent_pid(target) == Some(caller);
/*
* A foreign supervisor ends the guests it hosts. A guest thread's parent
* is its group leader, not the supervisor, so without this a guest's
* exit left its threads running, and once the supervisor was gone they
* ran on with no one to answer their calls.
*/
let supervises = caller != 0 && crate::process::foreign::supervisor_of(target) == Some(caller);
let controls = caller != 0
&& with_process(caller, |pcb| {
pcb.caps_bits.load(core::sync::atomic::Ordering::Relaxed)
& (Capability::ProcessControl.bit() | Capability::Admin.bit())
!= 0
})
.unwrap_or(false);
if !is_parent && !controls {
if !is_parent && !supervises && !controls {
return ERRNO_PERM;
}
if !pid_alive(target) {
Expand Down
2 changes: 2 additions & 0 deletions src/syscall/microkernel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub mod stdout_write;
pub mod store_write;
pub mod time;
pub mod tool_run;
pub mod tty;
pub mod tty_table;
pub mod wait;

pub use attest::sys_attest_status;
Expand Down
2 changes: 2 additions & 0 deletions src/syscall/microkernel/numbers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,5 @@ pub const SYS_SPAWN_INSTANCE: u64 = tag4(b"MSPI");
// Run a baked, attested command-line tool by name, parented to the caller so
// it can drive the tool's stdin and stdout. Gated on the IPC capability.
pub const SYS_TOOL_RUN: u64 = tag4(b"MTRN");
pub const SYS_TTY_SET: u64 = tag4(b"MTTY");
pub const SYS_TTY_QUERY: u64 = tag4(b"MTTQ");
Loading
Loading