Skip to content
Merged
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
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,6 @@ nonos-bootloader/keys

# Per-user kernel build configuration written by tools/nonos-config
.nonos-config

# Build output from vendored crates.io tool sources
userland/upstream-src/*/target/
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ nonos-capsule-std-proof = []
nonos-capsule-ripgrep = []
nonos-capsule-sd = []
nonos-capsule-tokio-smoke = []
# Bake the installed crates.io tools (grex, tokei, ...) into the image. Off for
# core builds, which do not cross-compile the tool binaries; on for the desktop.
nonos-tool-capsules = []
# Boot self-test: run each installed tool once with --version/-h so its output
# lands on the serial log, proving the whole run path end to end. Test builds only.
nonos-tool-selftest = ["nonos-tool-capsules"]
nonos-capsule-ramfs = []
nonos-capsule-wallpaper = []
nonos-capsule-compositor = []
Expand Down Expand Up @@ -494,6 +500,7 @@ microkernel-desktop-gui = [
"microkernel-core",
"nonos-production",
"capsule-serial-debug",
"nonos-tool-capsules",
"nonos-capsule-proof-io",
"nonos-capsule-tokio-smoke",
"nonos-capsule-ramfs",
Expand Down
2 changes: 1 addition & 1 deletion mk/10-qemu.mk
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ ifeq ($(QEMU_GL),1)
QEMU_GPU := -device virtio-vga-gl,xres=$(QEMU_XRES),yres=$(QEMU_YRES)
QEMU_DISPLAY := cocoa,gl=es,zoom-to-fit=on
else
QEMU_GPU := -device virtio-vga,disable-modern=on,vectors=0,xres=$(QEMU_XRES),yres=$(QEMU_YRES)
QEMU_GPU := -device virtio-vga,disable-modern=on,vectors=0,edid=on,xres=$(QEMU_XRES),yres=$(QEMU_YRES)
QEMU_DISPLAY := cocoa,zoom-to-fit=on
endif
# Keyboard/mouse via the q35 i8042 (PS/2). USB HID interrupt-IN transfers
Expand Down
46 changes: 46 additions & 0 deletions mk/20-build.mk
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,38 @@ $(UPSTREAM_TOKIO_SMOKE_BIN): $(NONOS_RT_OBJ) $(NONOS_STD_PAL_STAMP) \
.PHONY: nonos-mk-upstream-tokio-smoke
nonos-mk-upstream-tokio-smoke: $(UPSTREAM_TOKIO_SMOKE_BIN)

# Installed crates.io command-line tools, cross-compiled unmodified for
# x86_64-nonos from the byte-identical source vendored under upstream-src (cargo
# only honors [patch] for path builds, so shimmed crates must build with --path).
# Each tool's Capsule.mk consumes target/upstream-<bin>/bin/<bin>; this template
# is the one rule that builds it, so adding a tool is a name in NONOS_TOOL_BINS
# plus its `nonos-app add`, not a new recipe. getrandom-backed tools take the
# RDRAND backend, which nonos x86_64 always has.
NONOS_TOOL_BINS := grex dotenv-linter pastel jsonxf tokei huniq csview

# Default is to strip default features (they pull unix-only or update-check
# extras nonos does not want). grex and tokei gate their binary behind a `cli`
# feature, so those two re-enable it. Per-tool overrides live in <bin>_CARGO_FEATURES.
NONOS_TOOL_FEATURES_DEFAULT := --no-default-features
grex_CARGO_FEATURES := --no-default-features --features cli
tokei_CARGO_FEATURES := --no-default-features --features cli

define nonos_upstream_tool_rule
$(TARGET_DIR)/upstream-$(1)/bin/$(1): $(NONOS_RT_OBJ) $(NONOS_STD_PAL_STAMP) \
userland/x86_64-nonos-user.json | $(TARGET_DIR)/.nonos-toolchain.stamp
@echo "Building upstream $(1) for NONOS (unmodified crates.io source)..."
@cd userland/upstream-src/$(1) && RUSTUP_TOOLCHAIN=$(TOOLCHAIN) \
RUSTFLAGS="-Clink-arg=$(abspath $(NONOS_RT_OBJ)) --cfg getrandom_backend=\"rdrand\"" \
$(CARGO) install --path . $(or $($(1)_CARGO_FEATURES),$(NONOS_TOOL_FEATURES_DEFAULT)) \
--target $(abspath userland/x86_64-nonos-user.json) \
-Zbuild-std=std,panic_abort -Zbuild-std-features=compiler-builtins-mem \
--root $(abspath $(TARGET_DIR)/upstream-$(1)) --no-track --force --bin $(1)
endef
$(foreach t,$(NONOS_TOOL_BINS),$(eval $(call nonos_upstream_tool_rule,$(t))))

.PHONY: nonos-mk-upstream-tools
nonos-mk-upstream-tools: $(foreach t,$(NONOS_TOOL_BINS),$(TARGET_DIR)/upstream-$(t)/bin/$(t))

$(CAPSULE_SIGN_BIN):
@echo "Building capsule-sign host tool..."
@cd nonos-sign && cargo build --release --bin capsule-sign
Expand Down Expand Up @@ -354,6 +386,13 @@ include userland/capsule_proof_io/Capsule.mk
include userland/capsule_std_proof/Capsule.mk
include userland/capsule_ripgrep/Capsule.mk
include userland/capsule_sd/Capsule.mk
include userland/capsule_csview/Capsule.mk
include userland/capsule_huniq/Capsule.mk
include userland/capsule_tokei/Capsule.mk
include userland/capsule_jsonxf/Capsule.mk
include userland/capsule_pastel/Capsule.mk
include userland/capsule_dotenv-linter/Capsule.mk
include userland/capsule_grex/Capsule.mk
include userland/capsule_tokio_smoke/Capsule.mk
include userland/capsule_ramfs/Capsule.mk
include userland/capsule_keyring/Capsule.mk
Expand Down Expand Up @@ -774,6 +813,13 @@ nonos-mk-audio-player-smoketest-dev-test: $(proof-io_MANIFEST) $(audio_MANIFEST)
# is what ships and what the ISO carries; do not fold this into it.
nonos-mk-desktop-gui-prod: $(proof-io_ARTIFACTS) \
$(std-proof_ARTIFACTS) $(ripgrep_ARTIFACTS) $(sd_ARTIFACTS) \
$(csview_ARTIFACTS) \
$(huniq_ARTIFACTS) \
$(tokei_ARTIFACTS) \
$(jsonxf_ARTIFACTS) \
$(pastel_ARTIFACTS) \
$(dotenv-linter_ARTIFACTS) \
$(grex_ARTIFACTS) \
$(tokio-smoke_ARTIFACTS) $(ramfs_ARTIFACTS) \
$(keyring_ARTIFACTS) $(entropy_ARTIFACTS) $(crypto_ARTIFACTS) \
$(vfs_ARTIFACTS) $(driver-virtio-rng_ARTIFACTS) \
Expand Down
Binary file added screenshots/desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/launchpad.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 15 additions & 1 deletion src/services/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@ pub(crate) use reserved::is_reserved_service;
pub const MAX_SERVICES: usize = 256;
static ENDPOINTS: Mutex<Vec<ServiceEndpoint>> = Mutex::new(Vec::new());

/// Register a service endpoint on behalf of `pid`. This is the trusted core
/// path: the kernel spawn path calls it to publish a verified capsule's own
/// declared endpoint, so the only authorization it enforces is that `pid`
/// actually holds the capabilities the endpoint advertises. The runtime
/// `sys_service_register` syscall layers the caller-side register-right check
/// on top before reaching here (see `caller_has_register_right`).
pub fn register_endpoint(name: &str, port: u32, pid: u32, caps: u64) -> Result<(), RegError> {
if !auth::caller_can_register(pid, caps) {
if !auth::owner_has_required(pid, caps) {
return Err(RegError::PermissionDenied);
}
let mut eps = ENDPOINTS.lock();
Expand All @@ -50,6 +56,14 @@ pub fn register_endpoint(name: &str, port: u32, pid: u32, caps: u64) -> Result<(
Ok(())
}

/// True if the current process is allowed to register a service name it does
/// not already own. Gates the runtime `sys_service_register` syscall so an
/// ordinary capsule cannot squat a peer's service; publishers granted
/// `RegisterService` (e.g. net.core) pass.
pub(crate) fn caller_has_register_right() -> bool {
auth::caller_has_register_right()
}

pub fn lookup_service(name: &str) -> Option<ServiceEndpoint> {
ENDPOINTS.lock().iter().find(|e| e.name == name).cloned()
}
Expand Down
33 changes: 0 additions & 33 deletions src/services/registry/auth/caller_can_register.rs

This file was deleted.

2 changes: 1 addition & 1 deletion src/services/registry/auth/caller_has_register_right.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// 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/>.

pub(super) fn caller_has_register_right() -> bool {
pub(in crate::services::registry) fn caller_has_register_right() -> bool {
let token = crate::syscall::capabilities::current_caps_or_default();
token.can_register_service() || token.is_admin()
}
4 changes: 2 additions & 2 deletions src/services/registry/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
// 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/>.

mod caller_can_register;
mod caller_has_register_right;
mod owner_has_required;

pub(in crate::services::registry) use caller_can_register::caller_can_register;
pub(in crate::services::registry) use caller_has_register_right::caller_has_register_right;
pub(in crate::services::registry) use owner_has_required::owner_has_required;
2 changes: 1 addition & 1 deletion src/services/registry/auth/owner_has_required.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
// 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/>.

pub(super) fn owner_has_required(owner_pid: u32, required: u64) -> bool {
pub(in crate::services::registry) fn owner_has_required(owner_pid: u32, required: u64) -> bool {
required == 0 || crate::process::caps::has(owner_pid, required)
}
5 changes: 5 additions & 0 deletions src/syscall/contract/cap_table/mk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ pub(super) fn check(caps: &CapabilityToken, number: SyscallNumber) -> Option<boo
// kernel to open another window instance of an embedded app capsule.
SyscallNumber::MkSpawnInstance => caps.can_spawn_window(),

// Running a baked command-line tool needs only IPC: the tool is spawned
// parented to the caller so the caller can drive its stdio, and only the
// baked, attested set can be named.
SyscallNumber::MkToolRun => caps.can_ipc(),

_ => return None,
})
}
1 change: 1 addition & 0 deletions src/syscall/dispatch/router/microkernel_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub(super) fn matches(nr: SyscallNumber) -> bool {
| MkPioRelease
| MkDebug
| MkSpawnInstance
| MkToolRun
)
}

Expand Down
2 changes: 2 additions & 0 deletions src/syscall/microkernel/dispatch/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::syscall::microkernel::process::{
use crate::syscall::microkernel::futex::{sys_futex_wait, sys_futex_wake};
use crate::syscall::microkernel::procstat::sys_proc_stat;
use crate::syscall::microkernel::spawn_instance::sys_spawn_instance;
use crate::syscall::microkernel::tool_run::sys_tool_run;
use crate::syscall::microkernel::time::{
sys_time_adjust, sys_time_millis, sys_time_monotonic, sys_time_rtc,
};
Expand Down Expand Up @@ -63,6 +64,7 @@ pub(super) fn handle(nr: u64, a: Args) -> Option<i64> {
SYS_STDIN_READ => sys_stdin_read(a.a0, a.a1 as usize),
SYS_ATTEST_STATUS => sys_attest_status(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),
_ => return None,
})
}
14 changes: 7 additions & 7 deletions src/syscall/microkernel/ipc/call/sys_ipc_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ pub fn sys_ipc_call(
if crate::usercopy::validate_user_write(resp, resp_len).is_err() {
return ERRNO_FAULT;
}
// This call's correlation token. It is registered with the pending reply so
// the redirect-reply path (a service replying via `mk_ipc_send` to its fixed
// reply endpoint) stamps the reply with it, and it is sent on the request so
// an `mk_ipc_reply` service echoes it via `take_for_reply`. Either way the
// genuine reply carries this token; a forged injection can only carry 0.
// This call's correlation token, registered with the pending reply keyed by
// this caller's inbox. Both reply paths read it back from that same entry:
// the redirect path (a service replying via `mk_ipc_send` to its fixed reply
// endpoint) and the direct `mk_ipc_reply` path both stamp the reply with it.
// The genuine reply carries this token; a forged injection can only carry 0.
let token = next_call_token();
let inbox = reply_inbox::for_pid(pid);
let endpoint = lookup_port(ep as u32);
Expand All @@ -75,15 +75,15 @@ pub fn sys_ipc_call(
trace(pid, b"send", send_result);
if send_result < 0 {
if let Some(server_pid) = endpoint_pid {
pending_reply::remove(server_pid, &inbox);
let _ = pending_reply::remove(server_pid, &inbox);
}
return send_result;
}
let timeout = if timeout_ms == 0 { 5000 } else { timeout_ms };
let recv_result = recv_reply_correlated(pid, &inbox, resp, resp_len, timeout, token);
if recv_result < 0 {
if let Some(server_pid) = endpoint_pid {
pending_reply::remove(server_pid, &inbox);
let _ = pending_reply::remove(server_pid, &inbox);
}
}
if recv_result >= 24 && req_len >= 20 {
Expand Down
44 changes: 0 additions & 44 deletions src/syscall/microkernel/ipc/correlation.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/syscall/microkernel/ipc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

mod call;
mod correlation;
mod inbox_name;
mod lookup;
mod pending_reply;
Expand Down
25 changes: 16 additions & 9 deletions src/syscall/microkernel/ipc/pending_reply/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,24 @@

use super::state::PENDING;

pub(in crate::syscall::microkernel::ipc) fn remove(server_pid: u32, caller_inbox: &str) -> bool {
/// Remove the pending-reply entry for `caller_inbox` under `server_pid` and
/// return the per-call correlation token it carried. Matching is by the
/// caller's private inbox, so the token is the one that exact `mk_ipc_call`
/// registered, even when several callers have replies outstanding on the same
/// server. Returns `None` when there is no such pending call (a forged or
/// duplicate reply), which the caller treats as "drop".
pub(in crate::syscall::microkernel::ipc) fn remove(
server_pid: u32,
caller_inbox: &str,
) -> Option<u64> {
let mut map = PENDING.lock();
let Some(queue) = map.get_mut(&server_pid) else {
return false;
};
let Some(pos) = queue.iter().position(|(_, inbox, _)| inbox == caller_inbox) else {
return false;
};
queue.remove(pos);
let queue = map.get_mut(&server_pid)?;
let pos = queue.iter().position(|(_, inbox, _)| inbox == caller_inbox)?;
// main's entry is (caller_pid, inbox, token); return the token so the direct
// mk_ipc_reply path stamps the reply from the same entry it removes.
let (_, _, token) = queue.remove(pos)?;
if queue.is_empty() {
map.remove(&server_pid);
}
true
Some(token)
}
2 changes: 0 additions & 2 deletions src/syscall/microkernel/ipc/recv_from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use crate::process::current_pid;
use crate::syscall::microkernel::errnos::{ERRNO_FAULT, ERRNO_INVAL, ERRNO_NOENT, ERRNO_TIMEDOUT};
use core::sync::atomic::{AtomicU32, Ordering};

use super::correlation::note_request;
use super::inbox_name::resolve_for_recv;
use super::sender_pid::from_envelope;

Expand Down Expand Up @@ -94,7 +93,6 @@ fn deliver(
sender_pid_out: u64,
) -> i64 {
let sender_pid = from_envelope(&msg.from);
note_request(sender_pid, msg.correlation);
trace_dequeue(pid, sender_pid);
let copy_len = msg.data.len().min(len);
if crate::usercopy::copy_to_user(buf, &msg.data[..copy_len]).is_err() {
Expand Down
11 changes: 11 additions & 0 deletions src/syscall/microkernel/ipc/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ pub fn sys_service_register(name_ptr: u64, name_len: usize, port: u32) -> i64 {
if crate::services::registry::is_reserved_service(name, port) {
return ERRNO_PERM;
}
// A capsule may confirm the endpoint the kernel already published for it at
// spawn (idempotent self-registration); claiming any other name requires the
// RegisterService right. This lets an ordinary capsule re-assert its own
// service without holding the right, keeps a publisher like net.core able to
// register its extra sub-endpoints, and still bars a runtime capsule from
// squatting a peer's name.
let owns_already = crate::services::registry::lookup_service(name)
.map_or(false, |e| e.pid == pid && e.port == port);
if !owns_already && !crate::services::registry::caller_has_register_right() {
return ERRNO_PERM;
}
match register_endpoint(name, port, pid, required_caps(name, Capability::IPC.bit())) {
Ok(()) => 0,
Err(RegError::Full) => ERRNO_NOMEM,
Expand Down
Loading
Loading