SwiftOS Security Guide
This guide describes the current SwiftOS security model for operators, application developers, and reviewers. It covers what is enforced today, how to inspect it from the guest, and which parts remain roadmap work.
Use this guide with:
- User Guide for shell behavior and available tools.
- Administration Guide for account and capability administration procedures.
- API Reference for exact syscall and structure layouts.
- Capabilities for the longer object-capability roadmap.
- Troubleshooting for failure diagnosis.
Security Model At A Glance
SwiftOS is not Unix-root-centered. A process carries an explicit security context:
principal:u32
session:u32
capability-mask:u64
cell:u32
The current system enforces security through several layers:
| Layer | Current enforcement |
|---|---|
| Address spaces | Each EL0 process has its own user address space |
| User/kernel boundary | EL0 enters the kernel only through the SwiftOS syscall ABI |
| Identity | console-login authenticates a principal from /etc/swos/passwd |
| Coarse capabilities | Filesystem read, tmpfs write, console login, and networking are gated |
| Handle rights | File, pipe, tty, socket, IPC endpoint, and opaque device-grant handles carry per-handle rights |
| Immutable base | /bin, /etc, /www, and model files come from a read-only base image |
| Writable scratch | Runtime writes go to tmpfs and are lost on reboot |
| Memory permissions | mmap/mprotect reject writable-executable mappings |
| Package overlays | Package payloads are read-only images attached at boot |
The main design direction is to move from coarse process-wide bits toward
object-scoped handles. Some of that already exists in file descriptors,
spawn_handles, IPC handle transfer, and filesystem confinement; the broader
C-series capability work is tracked in CAPABILITIES.md.
Current Guarantees
The checked-in system currently provides these practical guarantees:
- An ordinary process cannot call
login()to grant itself another principal or more capabilities unless it already holdscapConsole. - A process without
capFsReadcannot open base files or list directories. - A process without
capTmpWritecannot create, rename, unlink, chmod, or chown tmpfs nodes. - The base filesystem is read-only even for the fully capable seeded
rootprincipal. - A process without
capNetcannot create sockets or use the kernel resolver. - Read and write operations on an already-open descriptor are checked against per-handle rights.
spawnstarts a child with stdio only;spawn_handlesstarts a child with exactly the handles requested by the parent, with attenuated rights.- IPC handle passing moves one handle to the receiver and clears the sender's source fd on success.
- C5b-C5f device discovery and opaque grants are metadata-only handles: the
supervisor can discover
virtio-input.0when QEMU exposes it or thepseudo-input.0fallback on headless boots, inspect the grant, observe virtio-mmio base/length as discovery metadata, prove future authority bits stay clear, prove current grant rights remain metadata-only, and prove ownership movement, but cannot use it for MMIO, IRQ, DMA, or real virtio-input queue access. - Device discovery and claiming are gated to the boot authority; claimed device grants are inspectable and transferable but not duplicable.
confine(path)can narrow a process to a filesystem subtree and cannot widen an existing confinement.mmap/mprotectrejectPROT_WRITE | PROT_EXEC.
Choose A Security Verification
Start with the boundary you want to prove. Capture the guest command output alongside the focused test so another reviewer can distinguish an enforced guarantee from roadmap language.
| Boundary | Guest check | Expected evidence | Focused proof |
|---|---|---|---|
| Login adopts the selected principal | Log in as root, user, or guest, then run id |
Principal, session, and capability mask match the identity store | ./tests/console_login_test.sh |
| Restricted accounts cannot read base files | Log in as guest, then run cat /etc/motd |
Read fails while id still prints numeric context |
./tests/cap_enforce_test.sh |
| Non-network accounts cannot open sockets | Log in as user, then run /bin/nslookup example.com |
Socket or resolver operation fails before traffic is sent | ./tests/cap_enforce_test.sh |
| tmpfs mutation is capability-gated | Compare echo ok >/tmp/check.txt under accounts with and without capTmpWrite |
Writable account succeeds; restricted account fails | ./tests/swift_fileops_test.sh, ./tests/cap_enforce_test.sh |
| Base image stays immutable | Run echo no >/etc/motd |
Write fails even from the fully capable seeded root account |
./tests/vfs_disk_test.sh |
| Ownership and modes are visible | Run chmod, chown, and ls -l on a /tmp file |
Mode and owner metadata change on tmpfs only | ./tests/ls_l_test.sh, ./tests/swift_chmodown_test.sh |
| Explicit handle inheritance is attenuated | Run the spawn demo path or acceptance test | Child receives only listed handles and requested rights | ./tests/spawn_self_exec_test.sh |
| IPC handle transfer moves ownership | Run the socket-transfer acceptance test | Sender fd is cleared and receiver gets the transferred handle | ./tests/ipc_socket_transfer_test.sh |
| Device authority minting is capability-gated | Log in as guest, then run /bin/deviceauthdemo |
Discovery and claim both fail with access denied | make device-authority-cap-test |
| Device grants stay metadata-only | Run the C5 device-authority gate | Discovery metadata is visible, hardware authority bits stay clear, and grant rights do not expand | make c5-device-authority-test |
| W^X memory policy is enforced | Run /bin/mmapdemo or the acceptance test |
RW mapping can become RX, but RWX is rejected | ./tests/mmap_test.sh |
| Package content remains read-only and verified | Install or mount package fixtures, then run the package smoke | Payloads execute from read-only /usr paths and repository metadata is verified |
make package-overlay-test, make package-repo-install-test |
Current Limits
The current model is useful and testable, but it is not the final security architecture.
| Limit | Meaning |
|---|---|
| Coarse capability bits | capFsRead covers the readable namespace; it is not yet fs:read:/one/subtree |
| Mode and owner are metadata, not an access gate | chmod/chown change what ls -l reports, but read/write access is decided by capabilities and per-handle rights. chmod 600 does not hide a file from a process holding capFsRead. Only the execute bit (at exec) and the setuid bit on signed base binaries are acted on by the kernel |
| No production password policy | Passwords are salted SHA-256, not a memory-hard KDF |
| No target-side account management | Accounts are edited in base/etc/swos/passwd and repacked into the image |
| No persistent user data store | /tmp is scratch; reboot clears it |
capSpawn and capProcessInspect are scaffold bits |
They are part of the identity model, but not the main enforcement boundary yet |
capLogExport is explicit |
Local log export exists, but no seeded account receives the bit by default |
| Drivers and networking are still in kernel | C5d proves virtio-input or pseudo fallback discovery metadata plus an opaque device grant, but real MMIO/IRQ/DMA driver handoff remains roadmap work |
| Single global cell today | CellId exists as a field; real Cells are future work |
Do not describe the current system as a finished object-capability OS. It is a capability-aware OS with an active migration path toward object-scoped authority.
Accounts And Identity Store
The identity store lives in the immutable base image:
/etc/swos/passwd
The source file is:
base/etc/swos/passwd
Each non-comment line has this shape:
name:principal:session:caps:salt$sha256hex:shell
The password hash is:
SHA-256(salt + password)
This is better than plaintext for the current bring-up image, but it is not a production password storage policy. A stronger KDF and password rotation workflow remain future hardening work.
Seeded accounts:
| Login | Password | Principal | Session | Caps decimal | Caps hex | Purpose |
|---|---|---|---|---|---|---|
root |
swordfish |
1 | 1 | 63 | 0x3f |
Full seeded authority except reserved log export |
user |
swordfish |
2 | 2 | 14 | 0x0e |
Spawn, filesystem read, tmpfs write |
guest |
guest |
3 | 3 | 2 | 0x02 |
Restricted capability checks |
Compatibility files:
/etc/passwd
/etc/group
These exist for userland compatibility. They are not the kernel's source of security policy.
Capability Bits
The capability constants are defined in kernel/security/security.swift.
| Bit | Name | Mask | Current role |
|---|---|---|---|
| 0 | capConsole |
0x01 |
Allows SYS_LOGIN to adopt an authenticated context |
| 1 | capSpawn |
0x02 |
Identity-model spawn authority bit; seeded accounts use it |
| 2 | capFsRead |
0x04 |
Allows filesystem opens that read |
| 3 | capTmpWrite |
0x08 |
Allows tmpfs writes and namespace mutations |
| 4 | capProcessInspect |
0x10 |
Identity-model process-inspection authority bit |
| 5 | capNet |
0x20 |
Allows sockets and DNS resolver use |
| 6 | capLogExport |
0x40 |
Allows SYS_LOG_READ / SYS_LOG_STATS / logtail ring export; reserved for future sink installation |
Check the current process:
id
Examples:
principal=1(root) session=1 caps=0x3f
principal=2(user) session=2 caps=0xe
principal=3 session=3 caps=0x2
/bin/id prints the numeric context even when it cannot read the identity store
to resolve a name.
Login Flow
The boot context starts with:
principal=1 session=1 caps=capConsole|capSpawn|capFsRead|capTmpWrite|capProcessInspect|capNet
/bin/swos-init runs first when present, starts allowlisted boot services, and
then hands off to /bin/console-login. console-login reads
/etc/swos/passwd, prompts for a login name and password, verifies the salted
SHA-256 password field, calls SYS_LOGIN, and then execves the configured
shell.
Important behavior:
- Password input is not echoed.
- Wrong passwords return to the login prompt.
SYS_LOGINis denied unless the caller holdscapConsole.- The adopted principal, session, and capability mask survive the exec into the shell.
- When the shell exits, init returns to a fresh login prompt.
Acceptance evidence:
./tests/console_login_test.sh
Filesystem Security
SwiftOS has a two-tier filesystem:
| Tier | Writable | Authorization |
|---|---|---|
| Packed base image | No | Reads require capFsRead |
| tmpfs scratch | Yes | Reads require capFsRead; writes require capTmpWrite |
The open path enforces:
| Operation | Required capability |
|---|---|
| Open for read | capFsRead |
| Open for write | capTmpWrite |
O_CREAT |
capTmpWrite |
O_TRUNC |
capTmpWrite |
mkdir, rmdir, unlink, rename |
capTmpWrite |
chmod, chown |
capTmpWrite |
The base image stays read-only even when the caller has capTmpWrite.
Example: restricted guest cannot read the filesystem:
id
cat /etc/motd
ls /
Expected guest context:
principal=3 session=3 caps=0x2
Expected read/list failures include:
can't open '/etc/motd'
can't open '/'
Acceptance evidence:
./tests/cap_enforce_test.sh
Example: user can read and write tmpfs, but not network:
id
cat /etc/motd
echo ok >/tmp/user-ok.txt
cat /tmp/user-ok.txt
/bin/nslookup example.com
The file operations should work. The network command should fail without
capNet.
File Ownership And Modes
Base image files and tmpfs nodes carry owner and mode metadata. ls -l displays
that metadata:
ls -l /etc
echo sample >/tmp/mode-sample
chmod 600 /tmp/mode-sample
chown 2 /tmp/mode-sample
ls -l /tmp/mode-sample
Current authorization is still capability-first. Mode and owner metadata are visible and useful for compatibility, but the capability checks above are the primary enforcement boundary.
Acceptance evidence:
./tests/ls_l_test.sh
./tests/swift_chmodown_test.sh
Handle Rights
Every open file, pipe, tty, socket, IPC endpoint, or opaque device grant is
represented as a per-process descriptor with a handle kind and rights. Rights
are defined in
userland/lib/syscall.h and kernel/vfs/handle.swift.
Common rights:
| Right | Meaning |
|---|---|
READ |
May read from the handle |
WRITE |
May write to the handle |
DUPLICATE |
May duplicate the handle |
TRANSFER |
May pass or move the handle through supported APIs |
GETATTR |
May inspect handle metadata |
SETATTR |
May change supported metadata |
The open mode determines the read/write rights on a file handle. Subsequent
read, write, ftruncate, poll, and socket operations check the handle's
rights, not only the process capability mask.
Spawn With Explicit Handles
Plain spawn starts the child with stdio only. Use spawn_handles when a child
needs a specific file, pipe, socket, or endpoint.
The parent supplies:
struct swiftos_spawn_handle {
int source_fd;
int target_fd;
unsigned int rights;
unsigned int flags;
};
The child receives only the listed handles. Requested rights are attenuated: the child cannot receive rights the parent handle does not hold.
IPC Handle Transfer
IPC endpoints can send bytes and optionally move one handle. On successful transfer:
- The sender's source fd is cleared.
- The receiver gets a new fd.
- Endpoint send requires write and transfer rights.
- Endpoint receive requires read rights, and transfer rights when importing a transferred handle.
Acceptance evidence:
./tests/spawn_self_exec_test.sh
./tests/ipc_socket_transfer_test.sh
Opaque Device Grants
C5b adds the first device-shaped handle, C5c adds discovery metadata/manifest
matching, C5d keeps the discovered virtio-mmio base and length visible as
non-authoritative metadata, C5e proves future hardware-authority bits stay
clear, and C5f proves the current grant rights remain metadata-only for the
checked-in driver-service smoke. The
supervisor discovers the current input grant with device_discover, claims the
discovered name with device_claim, checks its metadata with device_info,
moves it to /bin/drvinputd over an IPC endpoint, and then proves the registry
reports the device as busy until the service exits and closes its final fd.
Security properties:
- The grant is a handle, not a global permission bit.
- Discovery returns metadata for the current input grant and then reports
exhaustion. In the focused gate that grant is
virtio-input.0; in headless boots it ispseudo-input.0. - The sender loses the fd when
ipc_sendmoves the handle. - A second
device_claimof the discovered name returns-16while the service owns the live grant. - The C5e metadata always carries
SWIFTOS_DEVICE_FLAG_NO_MMIO_GRANTand keeps theSWIFTOS_DEVICE_FLAG_HARDWARE_AUTHORITYmask clear.virtio-input.0includes MMIO base/length as manifest metadata only; there is still no userland mapping, IRQ endpoint, DMA window, or queue ownership. - C5f keeps the grant's runtime rights at metadata-only authority: no accidental read, write, transfer, or map expansion is accepted as real hardware access.
- Closing the final device fd releases the claim.
This is a security boundary smoke, not a production driver sandbox. Real device handoff still needs MMIO-range handles, IRQ endpoints, DMA windows, and a real userland driver.
Acceptance evidence:
make c5-device-metadata-test
make c5-device-discovery-test and make c5-device-handle-test remain
compatible aliases through the same driver-service harness.
Filesystem Confinement
confine(path) narrows the current process to a filesystem subtree. It is
confine-only: a process can move to the same or a deeper subtree, but cannot
widen its view after confinement.
Developer example:
#include "lib/syscall.h"
int main(void) {
if (confine("/www") < 0) {
write(2, "confine failed\n", 15);
return 1;
}
/* Opens outside /www are denied from this point. */
return 0;
}
Confinement is a current object-scoped primitive and a step toward the stronger handle/cell model.
Network Security
Network authority is currently coarse:
capNet
A process without capNet cannot create sockets and cannot use the kernel DNS
resolver. root has capNet in the seeded image; user and guest do not.
Operational example:
/bin/nslookup example.com
/bin/httpd
/bin/tcpecho
/bin/udpecho
These require both:
- QEMU launched with virtio-net.
- A principal holding
capNet.
Longer term, the architecture wants object-scoped grants such as
net:listen:tcp:8080, not one global network bit. That transition is part of
the C-series and service-ization roadmap.
Acceptance evidence:
./tests/virtio_net_test.sh
./tests/httpd_test.sh
./tests/tcp_echo_test.sh
./tests/udp_echo_test.sh
./tests/dns_test.sh
Memory Security
EL0 programs run in separate user address spaces. The kernel validates user buffers before copying data across the syscall boundary.
Memory mapping rules:
mmapcreates anonymous user memory.munmapremoves mappings.mprotectchanges permissions.PROT_WRITE | PROT_EXECis rejected.
This supports a W^X discipline for future language runtimes. A JIT-style flow
must map writable memory, write code, switch it to executable with mprotect,
and then execute.
Acceptance evidence:
./tests/mmap_test.sh
Immutable Base And Package Security
Installed system files come from immutable images:
build/base.imgfor the base OS and/bintools.- Package payload images for
/usroverlays.
Operational consequences:
- A running guest cannot edit
/bin,/etc,/www, or/models. - Changing base content requires rebuilding
build/base.img. - Package overlay content is attached read-only at boot.
- Local
pkg install FILEcan activate a local.swpkgthrough a writable package-store image, but package payload content remains read-only once active. pkg repo set URLandpkg update [URL]verify a signed repository catalog, reject expired or incompatible metadata, validate dependency entries, andpkg install NAMEverifies downloaded package hashes before reusing the local package-store install path.- Remove, upgrade, rollback, version-constraint solving, public hosted package channels, and package-level publisher signatures are not implemented yet.
The host .swpkg tool verifies package structure and payload hashes. Repository
signatures authenticate the fixture catalog, while catalog expiry,
target compatibility, dependency validation, and package SHA-256 checks reject
common stale or tampered repository states. Broader public repository policy
remains later package-management work.
Acceptance evidence:
./tests/vfs_disk_test.sh
make package-overlay-test
make package-local-install-test
Security Checklist
Before using an image as a security demo, verify:
make build
./tests/console_login_test.sh
./tests/cap_enforce_test.sh
./tests/ls_l_test.sh
./tests/swift_chmodown_test.sh
./tests/spawn_self_exec_test.sh
./tests/ipc_socket_transfer_test.sh
make c5-device-metadata-test
./tests/mmap_test.sh
make package-overlay-test
For a broader release gate:
make test
Reporting Security Issues
Include:
- Git commit and branch.
- Exact QEMU command.
- Serial log around the first failure.
- Account used and output of
id. - Whether the issue involves process identity, filesystem access, handle inheritance, IPC transfer, networking, memory permissions, or packages.
- The targeted test that should have caught it, if one exists.
If a security behavior is not backed by code and an acceptance test, treat it as roadmap language rather than current product behavior.