A short, practical guide to booting and using TinyOS Enhanced. For what the
project is and its security scope, see the top-level README.md;
for design/security internals, see the other documents in this doc/ folder.
Reminder: this is an educational/hobby OS — single-core, 32-bit, console-only, QEMU-targeted, and not for production or untrusted networks.
The quickest way is the prebuilt demo ISO from the GitHub Releases page, run under QEMU. The kernel runs DHCP at boot, so attach a virtual NIC so it gets a lease immediately:
qemu-system-i386 -cpu Broadwell,+rdrand,+rdseed -cdrom tinyos.iso -m 256M \
-netdev user,id=net0 -device e1000,netdev=net0(Building from source instead? See Build & run in the README. The
-cpu ...,+rdrand,+rdseed flags matter — the kernel seeds its CSPRNG from the
hardware RNG at boot.)
You will see boot diagnostics scroll past — entropy/RNG health checks, stack-guard and ASLR init, the TinyOS banner, then driver and filesystem setup.
TinyOS ships with no default password. On the very first boot it walks you
through setting the root password:
First-Time Setup: Let's Create Your Password
For security, let's set up a root password.
Enter new root password: ********
Confirm new root password: ********
Root password set successfully!
The password is hashed with PBKDF2-HMAC-SHA256 (100,000 iterations) — there are no hard-coded credentials anywhere in the system.
Where is the password stored? Only in kernel memory — TinyOS has no on-disk
/etc/shadowor credential file by design, so hashes can't be lifted from a disk image, backup, or mounted filesystem. The trade-off is that accounts don't persist across reboots (you set the root password fresh each boot). See Kernel-Only Credential Store inSECURITY_HARDENING.md.
After setup you reach the login prompt:
TinyOS v2.0 Login System
TinyOS login: root
Password: ********
Login successful. Welcome, root!
Wrong passwords are counted; after several failed attempts the account locks temporarily. Once logged in you get the shell prompt:
$
Type help for the full command list. Commonly used commands:
| Command | What it does |
|---|---|
help, man <cmd> |
List commands / show help for one |
ls, ls C:, ls D: |
List the current dir, the FAT32 C: drive, the RAMFS D: drive |
cd, pwd |
Change / print working directory |
cat, edit <file> |
View / edit a file |
cp, mv, rm, mkdir, touch |
File operations |
find, grep, echo |
Search and text utilities |
mount |
Show mounted drives (C:=FAT32, D:=RAMFS) |
fatls |
List files on the FAT32 C: drive |
exec /hello.elf [&] |
Load and run a signed user program in ring 3 (& = background) |
jobs |
List this shell's background jobs |
ps, kill <pid> |
List / terminate processes |
passwd [user], su [user], logout |
Account management |
whoami, id, env, set, export, alias, history |
Session/environment |
dhcp [renew], ifconfig, ping, curl <url>, dig/net |
Networking |
aslr, pae, mem, auditlog, date, clear, reboot |
System / security info |
$ exec /hello.elf
Hello from ELF!
Every user binary is verified against a pinned ECDSA P-256 key before it runs.
The bundled hello.elf/shell are signed with that key, so they execute;
unsigned or tampered binaries are rejected (fail-closed) by default. (For local
development that accepts unsigned binaries, the kernel can be built with
-DELF_PERMISSIVE_SIGNATURES — see the README. The demo ISO is the enforced build.)
Append & to run a program in the background: the shell prints [pid] name and
returns to the prompt immediately instead of blocking until the child exits.
$ exec /sleeper.elf &
[25160] sleeper.elf
Sleeper started
$ jobs
PID STATE NAME
25160 SLEEP sleeper.elf
jobs lists only this shell's children (matched on both PID and generation, so
a recycled PID can't impersonate a job); ps shows every process on the system.
A background job you never wait for is still reaped automatically when it exits.
With the recommended QEMU command above, DHCP completes at boot and you can reach the internet from the shell:
$ dhcp
State: BOUND
Offered IP: 10.0.2.15
Gateway: 10.0.2.2
DNS Server: 10.0.2.3
$ curl http://google.com
... HTTP/1.0 301 Moved Permanently ...
$ curl 172.66.147.243
... HTTP/1.1 403 Forbidden ...
curl accepts a literal IPv4 address as well as a name. That path skips DNS
entirely, which makes it the way to test TCP when name resolution is unavailable
or deliberately diverted. Previously a dotted-quad was sent to the resolver as if
it were a hostname and failed with DNS resolution failed.
The address is in QEMU's internal user-mode (NAT) subnet 10.0.2.x — this is
normal and gives full outbound networking (DNS, TCP, HTTP). The guest is behind
QEMU's NAT, so it is not directly reachable from other machines on your LAN.
Getting an address on your real home-router subnet (e.g.
192.168.0.x) requires bridged networking (vmnet-bridgedon macOS), which only works over a wired Ethernet interface. macOS/vmnetcannot bridge a Wi-Fi interface — on a Wi-Fi-only Mac, QEMU fails withcannot create vmnet interfaceeven withsudo. Use a wired/USB-Ethernet adapter if you need a real-LAN lease; otherwise the NAT setup above is all you need to use and study the OS.
If you boot without a NIC, the kernel still tries DHCP and pauses ~30 seconds on
[NET] DHCP: Waiting for IP address... before timing out and continuing to the
shell. That wait is expected, not a hang.
Use reboot from the shell, or just close the QEMU window / press Ctrl-C in the
terminal running QEMU (or Ctrl-A then X if you launched with
-nographic/-serial mon:stdio).
See also: SHELL_FEATURES.md and
STDIN_FEATURES.md for shell internals,
USER_SYSTEM_TEST_GUIDE.md for the account system,
EDR_QUICK_REFERENCE.md for the security-monitoring layer, and
FIREWALL_AND_IDS_CONFIG.md for configuring the firewall and IDS.