Skip to content

Repository files navigation

EreBUS

ci nightly

Object-based, capability-secured operating system for x86_64 UEFI. Own boot loader, own kernel, own tools.

Unix EreBUS
Everything is a file Everything is a typed object
Paths in one global namespace References only; no global namespace
Permissions are checked The reference is the permission (capability)
Saving into files Versioned snapshots of the object graph
Byte streams Typed messages
An application opens a file A window is a view onto an object

Consequence: a program can only reach what it was handed. No root, no lookup by name, no way to acquire authority. kernel/include/eb/object.h has no obj_find().

The manual: MANUAL.md (screen, terminal words, settings, programs, scripts, building, storage, network, nodes). Updated with every release.

Building

Requirements (Linux; here WSL2 with Ubuntu):

apt install clang lld nasm make qemu-system-x86 ovmf mtools \
            dosfstools xorriso gdb unifont python3-pil

Targets:

make          # loader, kernel, bootable image build/esp.img
make run      # QEMU, serial on the terminal
make shot     # headless; build/screen.png, build/serial.log
make debug    # halted, gdb on port 1234
make clean
sh tools/mkiso.sh          # build/erebus.iso
sh tools/<test>.sh         # one regression test (the table under Tests); KVM when /dev/kvm is writable

From Windows: wsl -d Ubuntu -- bash -lc "cd /mnt/c/erebus && make run".

Layout

boot/            UEFI loader (PE/COFF, MS ABI); boot.c, efi.h
common/          shared by loader and kernel: bootinfo.h, elf64.h
kernel/
  arch/x86_64/   start.S, isr.S, gdt.c, trap.c, syscall.c, linker.ld
  hw/            pci, ahci, xhci, ps2, cpu, timers, serial, 8259
  gfx/           framebuffer, font, shell (desktop); the decoder's and the renderer's jobs (picture.c, render.c), the check of a renderer's block (render_check.c)
  fs/            fat, gpt, settle (disks), install (boot-time offer)
  lang/          cc (C compiler), asm, gnu (AT&T translator), ld (linker)
  net/           e1000, igb, rtl8139, rtl8169, net, tls, ssh, pipe, wifi, crypto
  obj/           object store, capabilities, ports, snapshots, blob log, settings, journal
  sched/         threads, processes, scheduler
  mm/            frame allocator, page tables, heap
  term/          terminal words
  user/          ring-3 programs inside the kernel image (runner, foreman, standard programs)
  include/eb/    headers
programs/        the programs the kernel carries but does not contain: ring-3, linked on their own, carried as bytes
  decoder/       the picture decoder (png, jpeg, webp)
  renderer/      the page renderer (html, css): the page laid out into a display list the kernel paints
  lib/           what both share: mem*, the stack guard, the entry, the linker script
sdk/             the program interface for programs outside the tree (erebus.h, hello.c)
tools/           test scripts, image builders, helpers
build/           outputs (not in the repository)

Design decisions

  • Own UEFI loader: no foreign code in the trusted base.
  • Kernel in the upper half at -2 GiB; user programs in the lower half; no page-table switch on syscall.
  • Bitmap frame allocator: bookkeeping in one checkable region, 16 KiB per GiB.
  • Font: GNU Unifont 8x16, embedded at build time.
  • Serial as first output; it costs 20 ms per log line at 115200 baud (make shot SERIAL=null to compare).
  • No SSE/MMX in the kernel; the vector unit is enabled for user programs only and saved per process.
  • The 8259 pair for the legacy lines, the local APIC for message-signalled interrupts: no ACPI parsing needed for either.
  • Symmetric multiprocessing: the application processors are found in the ACPI MADT and run the same scheduler as the boot processor, kernel threads and ring-3 programs alike; device-touching threads stay on the boot processor by affinity. TLB is flushed on every switch rather than shot down by IPI. Exercised under QEMU -smp in the boot smoke and three battery lanes.
  • Interrupt stubs as a 16-byte table; tools/check-isr.sh verifies the layout.
  • No task bar, no window frames; every visible control is clickable; keyboard is a shortcut.
  • Names live on the reference, not the object.
  • Nothing is saved by hand; the kernel writes a snapshot when changes stop.
  • Everything hardware-dependent self-tests at boot and says so in the log.

What 1.0 guarantees

The list under Status is a build log: what was done, in the order it was done. This is the other list -- what the system promises, each promise with the test that checks it. A 1.0 is the point where every line here holds on real hardware, for a stranger, with formats and an interface that 1.x does not break; until then it is the measure.

Promise Checked by
The machine boots, self-tests every subsystem, and reaches idle on every start tools/bootsmoke.sh (CI, under TCG with -smp 4); every test in the battery boots it
What was in the graph is there after a restart, without anyone saving tools/persisttest.sh; tools/powerloss.sh cuts the power inside a write, a dozen times in the battery and a hundred in the release gate, and the store comes back whole every time
A store made by the last release is read by this one, and a node of the last release talks to a node of this one tools/oldstore.sh, tools/oldpipe.sh (CI and the battery); the formats and their numbers in kernel/include/eb/formats.h, MANUAL 17
The same source gives the same bytes; a published release can be traced to its tag tools/reproduce.sh (CI); SHA256SUMS and PROVENANCE on every release, tools/sign-release.sh
The machine builds, installs and boots its own kernel with its own compiler, assembler and linker, and that kernel builds it again; the self-built kernel does the work the clang-built one does tools/selfkernel.sh on the machine (release gate), tools/selfbuild.sh and a boot of its kernel on the host (CI), then tools/webtest.sh on that kernel (CI and gate: its browser and its self-built decoder); tools/stackframe-check.sh and tools/tramp-check.sh guard what makes that possible
Only a release signed with a trusted key is installed; the key can be rotated, and a lost key before a rotation does not strand a machine tools/update-test.sh, tools/rotate-test.sh; two keys built in
A program holds what it was given and nothing else; authority only narrows as it passes tools/relaytest.sh, tools/agenttest.sh; the capability self-tests at every boot (cap: self test passed -- isolation, attenuation, revocation, generations)
A program written against the written interface (MANUAL 18) runs on the machine tools/sdktest.sh with sdk/hello.c
A page over https is verified against the built-in authorities or refused under tls | strict; a certificate outside its authority's name constraints is refused tools/tlstest.sh, tools/pkitest.sh, tools/webtest.sh
Objects cross between machines sealed; a changed key at a known address is refused; discovery answers the own network and not the world tools/pipe-two.sh, tools/pipe-identity.sh, tools/pipe-local.sh; the wire dump in pipe-two shows nothing in the clear
Far work runs under a deadline and answers are signed by the node that produced them tools/pipe-code.sh, tools/pipe-quorum.sh, tools/pipe-vouch.sh
Sixty-four processors and sixty-four nodes are the edge, and the edge holds tools/limits.sh
The parsers on the wire and in the browser survive their fuzzers tools/fuzz/run.sh (CI, ASan and UBSan)
A picture that breaks its decoder breaks nothing else: the decoders run as a program in ring 3, started on the picture's bytes and ended after tools/decoder-fault.sh (CI and the battery): the decoder rebuilt to fault on a jpeg and hang on a webp, the browser going on without them; tools/webtest.sh shows every picture decoded by such a program
A page that breaks its renderer breaks nothing else: the page and stylesheet readers run as a program in ring 3, started on the page and ended once the kernel has checked and taken its display list tools/renderer-fault.sh (CI and the battery): the renderer rebuilt to fault on one page and hang on another, the browser saying so and laying out the next page without waiting; the kernel's check of the block fuzzed with the renderer (tools/fuzz/run.sh html)
It runs on real hardware one board so far (ASUS X99, below); more boards, Secure Boot and a nightly on hardware are what 1.0 still needs

What 1.0 needs beyond this list is named in Known limits: more boards, an outside review with fuzzers run for hours, a build of a tag by a third party with the same hash, and the remaining parsers of foreign input out of ring 0 -- the picture decoders and the page and stylesheet readers are out; the certificate checker, the wire's parsers and the compiler are not.

Status

Kernel core

  • UEFI loader: GOP, ELF loading, memory map, hands loader and kernel files to the kernel (bootinfo v3)
  • GDT/TSS with fault stacks, 256 IDT vectors, crash reports with symbol names
  • PIT tick, TSC calibrated against the PIT, RTC read at boot, clock set from the net
  • Direct map, per-section page tables, NX, CR0.WP, SMEP, SMAP, UMIP, W^X (make wx)
  • PAT: framebuffer mapped write-combining
  • Bitmap frame allocator, next-fit heap, guard pages under thread stacks
  • Preemptive round-robin threads, per-thread CPU accounting; sleeping with a deadline, events signalled from interrupt handlers
  • Local APIC on; MSI and MSI-X for pci devices (ahci, xhci, e1000e, igb, the I2xx cards), the legacy line through the 8259 where a device has none; the disk, usb and network threads sleep on their interrupts; the processor halts when nothing happens (load)
  • Ring-3 processes, own address spaces, 8 system calls, registers zeroed on return to user; yield rests a program until the next tick
  • Processes reaped by the next thread through the scheduler
  • SMP: application processors brought up from the ACPI MADT, local-apic and x2apic entries alike (real-mode trampoline, per-cpu GDT/TSS/syscall/idle/APIC timer), up to 64; one scheduler across all cores under spin.h locks; kernel threads and ring-3 programs roam, device-touching threads pinned to the boot processor; exercised under QEMU -smp (boot smoke, three battery lanes, tools/limits.sh with 64)
  • Kernel version from git describe (build/version.c), shown in the boot log and the desktop

Objects

  • Typed objects (text, bytes, list, picture, program, ...), reference counts, reference slots
  • Protection domains with capability tables; handles carry a generation; rights only narrow
  • Message ports; capabilities travel with the rights the sender let go of
  • Cycle collector (obj_collect), runs in the snapshot's quiet moment (make sweep)
  • Snapshots: two alternating slots, generation + checksum, 16 generations kept, time travel in the shell
  • Blob log for objects from 4 KiB up, content-addressed (SHA-256), compaction
  • Formats: every disk and wire format with its number and the oldest still read in kernel/include/eb/formats.h; formats in the terminal; the store's first sector carries format and identity (MANUAL.md 17)
  • Journal as a read-only text object
  • Settings as a text object, applied as typed (theme, save, clock, pointer, hints, slice, start, name, address, peer by address or node name, work, keys, door |, wlan |, update |, tls |, authority |, discovery |)
  • Activity table rewritten once a second
  • Nodes table nodes (name | key | address | version | may): one row per machine met through the pipe; the kernel writes key, address, version; the person writes name and may (work, update, vouch, all)
  • network page: every node with address, version, last heard, free memory, work flag, seal state; machines heard but not met; desk and transfer state
  • attention page: the notable subset of the log (a failed far job, a node gone quiet), with an unseen count in the status line
  • Program records survive reboots; delegations replayed by name

Desktop and terminal

  • Views: focus, graph (zoom/pan), columns, index, split, terminal, browser; text/bytes/structure/picture/html lenses
  • Editor with caret, mark/take/put, find bar, scrolling everywhere
  • Journal, settings, activity, "the machine", "the compiler", "the language" pages under system
  • Add palette: text, bytes, list, picture, task, standard programs
  • Bin for let-go references; turn off; restart
  • German keyboard layout (keys | german)
  • Terminal grammar: verb, name, to/at/with; words: help look where go back home find read write make copy rename let go run give end scan found point at send ask say build link compile assemble install take in write out disks settle yes networks join leave wifi address receive restart version load formats nodes allow forget trust vouch unvouch renew update
  • Boot-time offer: with no store and a keyboard present, the start-up lists the disks and takes a number, then yes; escape or 2 minutes of silence continues without a store

Programs and languages

  • Standard programs: agent, courier, clock, cipher, tally, sums, watch, wipe, reckon, pulse, foreman
  • Script language (one page): variables a..z, say, wait, get, put, if, skip, back, stop, tell, time, rest, show
  • Assembler (own dialect) and GNU/AT&T translator
  • C compiler: C11 subset incl. structs by value, bit fields, varargs, inline asm (GNU form), float/double, preprocessor
  • Linker: objects, kernel shape (kmain) or program image; symbol table for crash reports
  • build <list>: every .c/.S text in a list, headers beside them, background thread, report in the journal
  • install <bytes>: kernel.new → kernel.elf, previous as kernel.old; the loader falls back after two failed starts
  • install this kernel: loader and kernel the machine booted from onto the boot disk; store untouched
  • receive <n> bytes as <name>: a file in through the door as raw bytes
  • The picture decoders (png, baseline jpeg, webp, with inflate) as a program in ring 3, programs/decoder, written against sdk/erebus.h: the browser lays a picture's bytes into a fresh process, the program answers where its pixels lie, the kernel reads them back through the program's own page tables and ends it; a decoder that faults, hangs or refuses costs that one picture (tools/decoder-fault.sh). Built by clang and lld into the machine's own image format (programs/lib/program.ld, tools/mkimage.py) and carried by the kernel as bytes; the self-build builds it with the machine's own compiler (tools/selfprograms.sh)
  • The page renderer (html with the part of css that changes what a page says) as a program in ring 3, programs/renderer: the browser lays the page, its fetched sheets and its pictures' sizes into a fresh process, the program lays the whole flow out into a display list -- rectangles, runs of glyphs, pictures, fields, in the flow's own coordinates -- and names it; the kernel reads the block back through the program's own page tables, checks every count, offset, string and op in it (kernel/gfx/render_check.c, fuzzed), paints it at any scroll, and ends the program. A page is laid out again only when it or its sheets, pictures, folds, styles switch, search word or measure change; scrolling paints. Fields are drawn by the kernel with what was typed. A page that faults, hangs or refuses its renderer costs that page (tools/renderer-fault.sh); the html lens of a text goes the same way
  • Self-build: tools/selfbuild.sh (host, cchost), tools/selfkernel.sh (on the machine, through the door, under KVM: 99 objects in ~30 s, then a second generation); the decoder and renderer programs built the same way (tools/selfprograms.sh) and sent in as the texts of their images
  • Fuzzing of compiler, assembler, linker, certificate checker, page renderer, the wire, the pipe and the tls client (tools/fuzz/run.sh)

Storage

  • PCI enumeration; AHCI, up to 8 disks; roles: boot disk (port 0), store, exchange disk
  • USB disks through xhci (bulk-only transport, scsi, 64 KiB per transfer, blocks of 512 to 4096 bytes) in the same block layer; a stick made with tools/mkusb.sh boots the machine and carries its store; otherwise a usb disk is the exchange disk; settle on disk N works on one
  • The store's stick unplugged: noticed, changes wait in memory, the stick is taken back by its identity and saving resumes; another store plugged in meanwhile is refused
  • GPT read/write; store partition type E2EB0500-5354-4F52-4552-454255530001
  • FAT32: read, write, directories, rename, format; boot volume found via GPT EFI partition, MBR, or LBA 0
  • Exchange disk: take in, write out (root directory, 8.3 names, 64 KiB per file)
  • Foreign disks are never written (tools/foreigndisk.sh)
  • disks, settle on disk N, settle in partition P of disk N, settle in the free space of disk N, each confirmed with yes
  • USB stick image (tools/mkusb.sh, tools/stick.ps1), hybrid ISO (tools/mkiso.sh)

Input

  • PS/2 keyboard and mouse
  • USB xHCI: firmware handoff, port power, Intel port routing, root ports, hubs, hotplug with settle time
  • HID: keyboards (boot protocol), mice via report descriptor (buttons, 8/16-bit axes, wheel, report ids), composite devices
  • Key repeat in the driver

Network

  • ARP, DHCP, DNS, ICMP echo, TCP client, HTTP/1.0 fetch with redirects, static address (address | a.b.c.d)
  • Browser as a view of its own (no object in between): address line, links and forms by mouse or keyboard, GET and POST, cookies per RFC 6265 (persistent ones on the shelf as the cookies), bookmarks (bookmarks text, start page), png, baseline jpeg and webp in colour (each decoded by a program in ring 3), find on the page, chunked and gzip, utf-8 with Greek and Cyrillic; unverified servers marked; of the stylesheets the part that changes what a page says (display none and visibility, font-weight, color, text-align, list-style, font-size grown in whole steps, block or inline; media queries on width), navigation, headers, footers and asides folded to a line, the text held to a readable centred column, links coloured not underlined; plain shows the page as it came; no scripts
  • Drivers: e1000 family (8254x, 8257x/82574L, 82577–I219), igb family (82575/82576/82580/I350/I210/I211), RTL8139, RTL8168/8169
  • Card choice: first a card with link, then any card; unknown cards named in the log
  • TLS 1.3 client: X25519, AES-128-GCM, SHA-256; the server's certificate chain is walked to a trusted authority (ECDSA over P-256 and P-384, RSA up to 4096 bits in PKCS#1 v1.5 and PSS, SHA-256/384/512; host names from the subject alternative names, dates against the clock) and its CertificateVerify checked; built in: thirty-four roots from the Mozilla bundle plus the intermediates of github.com and the release cdn; authority | <base64 public key> adds one of your own; tls | strict refuses an unverified server, otherwise the page is marked
  • SSH door (server): curve25519-sha256, ssh-ed25519, aes128-gcm@openssh.com; keys from door | lines; exec and shell sessions
  • Object pipe between machines: X25519 handshake signed with the door key, AES-128-GCM records
  • Nodes: identity is the key; the first handshake writes a row into nodes; a different key from a known address is rejected until the row is removed; a known key from a new address updates the row
  • Rights per node: allow <node> work|update|vouch|all|nothing; far work runs for a node when work | welcomed or its row contains work; a kernel is installed only from a node whose row contains update; a node's signed vouches pin keys only when its row contains vouch
  • Transfers read from and write into objects directly, windowed (HAVE/TAKEN), up to 8 MiB; refusals carry a reason code
  • update <node>: sends this machine's kernel; the receiver installs it and restarts; update <node> with <kernel.elf>; update all; the loader falls back to kernel.old after two failed starts
  • Self-update: update | auto fetches a signed release package (update.pkg), verifies its ed25519 signature against the keys built into the kernel, installs and restarts; a one-line version asset is read first and the package fetched only when it names something newer; update check on demand; the signature (not the transport) is the safeguard, the transport's own verification comes on top
  • Release keys: two built in (one signs, one is kept apart against the loss of the first); a signed rotate asset moves machines to a new key, written into their settings, after which the previous key is refused (tools/sign-rotation.sh, tools/rotate-test.sh); every release carries SHA256SUMS and PROVENANCE
  • Program interface written down (MANUAL 18): entry, the eight calls and their registers, the message layout, the tags, the image format; sdk/erebus.h and sdk/hello.c are a program from outside the tree that runs on the machine (tools/sdktest.sh)
  • Discovery: broadcast scan, heartbeat to every known node every 30 s, HERE carries key, version and up to four other addresses (propagation across routers); discovery | local (default) answers the own network and known nodes only, open, known and quiet widen or narrow that; a machine not in the nodes table is told the name and the work flag and nothing that describes this machine or its network
  • Far work: ask <task>, ask <task> with <object> (the input rides to each worker -- a script's third gift, a compiled worker's letter box), as code, across N and any combination, split tasks summed or concatenated, answers name the machines that produced them (42 (4 parts by alpha, beta)), foreman for recurring tasks
  • Vouching: vouch <node> sends a signed statement that a key is recognised; a node that allows the voucher vouch pins the key before meeting it (identity beyond trust on first use, no rights implied); unvouch <node> withdraws it and a row that rested on it is dropped; the nodes table's via column says how each key came to be there
  • Split ranges everywhere: split P from LO to HI divides a compiled task too (each piece gets its range as a RANG message) and combines with across N (every piece on N machines, a majority per piece)
  • The line: a shared text for say between nodes
  • pack/unpack: a list as one bytes object for the pipe

Wireless

  • Station: scan, WPA2-PSK (PBKDF2, 4-way handshake, CCMP, GTK unwrap), open networks, auto-join of remembered networks
  • Crypto self-tests at boot (SHA-1, HMAC, PBKDF2, CCM, RFC 3394)
  • Test bench radio: 802.11 frames inside Ethernet frames to tools/wifi-ap.py
  • Driver for a real radio chip

Real hardware (ASUS X99, Broadwell-E, UEFI 2015)

  • Boots to the desktop; installed on a SATA SSD; reachable over ssh
  • Findings applied: framebuffer must be write-combining and never read back; USB ports need power and the chipset's port routing; a NIC must stop bus mastering before reset; the I210's PHY sleeps without a cable and is not asked then; the PCH NIC (I218) is never reset

Tests

Script Proves
tools/usbtest.sh USB keyboard and mouse found on xhci with the i8042 off, and a text typed over usb run
tools/usbhub.sh keyboard and mouse behind a hub
tools/usbplug.sh mouse unplugged and plugged back in; report descriptor self-test
tools/asmtest.sh, cctest.sh, cctest2.sh assembler and compiler on the machine: the machine page assembled, run and back on the next boot; the compiler page compiled, run and summed; the compiler's proof (tools/cc/proof.c, 28 checks, the exchange disk's FAT32 read for it) compiled and run, every check said ok
tools/termtest.sh terminal words through the screen: a text made, written, run and heard; a text that reads as a page laid out through the html lens by the renderer program in ring 3
tools/sshtest.sh door: exec, pipe, pty; foreign key refused
tools/sshmulti.sh three ssh visitors served at once through the one door
tools/sshrekey.sh the door survives a client-driven mid-session rekey
tools/pipe-two.sh object pipe: discovered by scan under discovery | local, sealed, crosses; nothing in the clear on the wire
tools/pipe-local.sh discovery answers the own network and not the world: a probe from another network (tools/pipe-probe.py, not EreBUS, raw frames on the wire) gets no HERE and the console says so; one from the own network is answered
tools/limits.sh the limits at their edge: 64 processors up with kernel work on the others (-smp 64); 64 nodes trusted through the door, the 65th refused, the table listing all 64
tools/powerloss.sh the power goes out inside a write of the store (QEMU killed outright, writes throttled so the cut lands inside), a dozen times; every boot restores a generation no older than the last seen finished and never the one the cut fell into (RUNS=100 in the pre-release gate); the firmware's variable store is fresh for every boot, since a cut can leave that unreadable and the firmware is not what is under test
tools/pipe-identity.sh the pipe refuses a changed key at a known address
tools/pipe-rotate.sh a key trusted before meeting; a renewed key propagated to a peer, signed old and new
tools/pipe-update.sh a kernel through the pipe: refused without the update right, installed and booted with it
tools/pipe-input.sh far work with an input object -- to a recipe and to a compiled image that reads it from its letter box
tools/pipe-code.sh compiled far work: a c task built and run on the worker, signed answer, a runaway task ended by the deadline, its failure raised on the attention page
tools/pipe-quorum.sh the same task on two machines; the verified majority makes the result
tools/pipe-vouch.sh a node vouches for a key; a peer that allows it pins the key before meeting, and ignores a vouch it has not allowed
tools/update-test.sh self-update from a local release: a forged package is refused, a correctly signed newer one is installed and the machine reboots into it (needs release-key.pem and python3)
tools/rotate-test.sh a signed rotation moves the machine to a new release key: the package signed with the previous key is refused after it, the one signed with the new key installed; without the rotation the new key is nobody's (needs release-key.pem)
tools/sdktest.sh a program from outside the tree (sdk/hello.c against sdk/erebus.h) goes in through the door, is compiled beside its header and run; it says hello, reads the clock, and measures a text it is given
tools/oldstore.sh, oldpipe.sh the last released kernel (built once from its tag) makes a store that today's kernel restores whole, its nodes table widened; a node of it and a node of today send an object across, sealed
tools/pkitest.sh the certificate checker on the host, built from the kernel's own files: known answers (RFC 6979, fixed RSA vectors), openssl-made chains good and bad (expired, wrong host, wildcard rules, signed by a non-authority, tampered), the live github.com and release-cdn chains against the built-in authorities, CertificateVerify signatures
tools/tlstest.sh the tls client against a server of its own: verified under an authority written into the settings (an ecdsa chain, an rsa chain), refused without one when `tls
tools/pipe-work.sh, pipe-desk.sh, pipe-foreman.sh far work, split tasks over three machines, standing tasks
tools/pipe-task.sh a task package with a manifest split across two machines, its pieces folded by the max combine rule to one result
tools/ssh-task.sh a package built by erebus-task fed into a node over ssh with the host's own ssh client; the desk takes it
tools/relaytest.sh, agenttest.sh, persisttest.sh capability passing between programs, rights following the reference, snapshots (also make relay, make agent, make persist)
tools/sticktest.sh one disk carries loader, kernel and store
tools/usbstick.sh the same stick as a usb disk, the only disk: booted from and its store found through xhci
tools/usbunplug.sh the store's stick unplugged while running: noticed, changes wait, another store refused, the stick taken back by its identity, the waiting changes written and restored on the next boot
tools/irqtest.sh the devices interrupt instead of being polled: e1000 on its legacy line, ahci and xhci by message, e1000e and igb by message, rtl8139 on its line, each getting its lease; keys through the usb keyboard; the processor idle 90% or more over a quiet spell (load)
tools/renderer-fault.sh the page renderer rebuilt with deliberate faults (a page that takes it down on a page fault, one that makes it never answer) beside an ordinary page: the ordinary page is laid out, the faulting one is reported and the browser goes on, the hanging one's renderer is ended after the limit and, left at once the second time, with the page, the next page laid out without waiting; every renderer process reaped
tools/decoder-fault.sh the picture decoder rebuilt with deliberate faults (a jpeg takes it down on a page fault, a webp makes it never answer) beside a png it decodes and bytes that are no picture: the page shows the png, goes on without the others, the hanging decoder is ended after the limit and, on a second visit, with the page; every decoder process reaped, the kernel untouched
tools/webtest.sh the browser against a server on the host: a page with utf-8 prose, links, a png, a jpeg and a webp decoded (each by a decoder program in ring 3, ended after; every page laid out by a renderer program the same way), its stylesheet fetched and read (a link hidden by a rule, the navigation folded), a link followed by keyboard while a slow picture is still coming and back, the navigation opened and its link followed, the styles turned off and on, a form posted with a cookie set and sent on the redirect, a bookmark kept, gzip and chunked pages; the next boot has the bookmark and the cookie
tools/foreigndisk.sh a foreign disk stays byte-identical
tools/settletest.sh, settlefree.sh settling whole / in free space
tools/installtest.sh boot-time offer on a non-empty disk
tools/renewtest.sh newer stick installs onto a settled disk; store kept
tools/nictest.sh every NIC family gets a lease; two cards, one cable
tools/wifitest.sh WPA2 against the virtual access point
tools/selfkernel.sh (KVM) kernel built on the machine from sources sent through the door
tools/selfbuild.sh, cctrial.sh kernel built by the machine's compiler on the host
tools/fuzz/run.sh fuzzing under the sanitizers: the language tools, the certificate checker, the page renderer with its styles, the stylesheet reader, the wire (frames, the tcp and http client, the door with ssh, the air), the pipe's datagrams sealed and plain, the tls client, the picture decoders and inflate (sh tools/fuzz/run.sh <seconds> [lang pki html css net pipe tls img])

Every test is a script under tools/: sh tools/<test>.sh after make, or BUILD=<dir> sh tools/<test>.sh to keep its images and logs in a directory of its own, which is how several run side by side (on the Linux file system; disk images on /mnt/c stall under parallel writes). All of them source tools/testlib.sh: KVM when /dev/kvm is writable (NOKVM=1 for TCG), waits on serial log lines and marker files instead of fixed sleeps. renew, update-test and tlstest run alone (renew rebuilds the kernel twice; the other two run a server on the host).

Measured on 32 cores under KVM, six at a time: about 15 minutes for all 44 tests, of which the parallel part is 6 (before that: 38 minutes sequential under KVM, 22 minutes under TCG). The longest is pipe-code, which twice waits out a compiled task's deadline. renew, update-test, rotate-test, oldstore, oldpipe and tlstest run alone (they rebuild the kernel, build the last released tag, or run a server on the host).

Using the ISO

  • Download: releases page, erebus.iso.
  • Write raw to a usb stick: dd if=erebus.iso of=/dev/sdX bs=4M, Rufus in DD mode, or balenaEtcher. Or burn a disc.
  • Firmware: UEFI boot, Secure Boot off (the loader is unsigned). No BIOS mode.
  • First start: the machine lists its disks and asks; a number and yes installs.
  • Installed machine, newer stick: boot the stick, install this kernel, remove the stick, restart.
  • QEMU: qemu-system-x86_64 -machine q35 -m 512M -bios /usr/share/OVMF/OVMF.fd -cdrom erebus.iso
  • Remote: put a public key into the settings (door | ssh-ed25519 ...), read the address with address, connect with any ssh client.
  • Several machines: scan, point at <name>, say hello (the handshake writes a row into both nodes tables); allow <node> update on the machine to be updated; update <node> on the other.

Known limits

  • USB disks are read and written 64 KiB at a time; a disk with 1024- to 4096-byte blocks is counted in 512-byte sectors, so a partition table another system wrote on such a disk in its own block size is not read.
  • A store's stick unplugged is awaited by its identity; the changes made meanwhile wait in memory and are lost if the machine is turned off before it is back.
  • A pci device with neither msi nor a legacy line the firmware routed is polled, every 10 ms for the network card and every 4 ms for the usb controller; the disk controller then waits by looking.
  • The browser reads no scripts; a page that is nothing without them is nothing here. Of a stylesheet it honours what changes the words (hiding, weight, colour, alignment, size in whole steps, block or inline) and lays out no boxes, so a page that is its layout reads in its order; selectors with pseudo-classes never match, @import is not followed, up to eight sheets of 2 MiB together and 10800 rules are read per page. The one font grows only by whole factors, so a size is rounded to the body's, twice it, or three times. Progressive jpeg and interlaced png show as their alternative text (so do svg and webp); a page is 2 MiB at most, a picture 1600 x 1200; pictures and sheets are fetched anew with every page; one connection at a time, so a page with many pictures fills in one by one.
  • No wireless chip driver.
  • TLS: thirty-four roots from the Mozilla bundle and the intermediates of github.com and its release cdn are built in; a host under another root is unverified unless its authority is written into the settings. A leaf whose names fall outside a signing authority's name constraints is refused (RFC 5280 dNSName permitted and excluded subtrees). No revocation checking. The self-update does not depend on any of it (the package is ed25519-signed).
  • Self-update: two release keys are built in and a signed rotation moves machines to a new one (tools/sign-rotation.sh); a key lost after a rotation to it cannot be replaced on deployed machines.
  • Far-work answers are signed by the node that produced them and checked against its key, but the computation itself is not otherwise verified; running the same task on several nodes and comparing is left for later.
  • Node identity is trust on first use; trust <name> <key> pins one beforehand, forget re-pins a changed key, renew key rotates a key under the old key's signature, vouch lets a node you have marked vouch pin a key for you and unvouch takes that back -- but a vouch is only as good as your trust in the voucher.
  • A quorum takes the answer a verified majority agree on, but does not otherwise check the computation; pieces times machines may not exceed eight.
  • A compiled task must fit one datagram (1024 bytes) and answers through the raw system-call ABI; one compile runs at a time per machine.
  • The ssh door serves up to four visitors at once (a fifth displaces the longest-idle); it honours a client-driven rekey but does not force one.
  • RTL8168/8169 driver written from documentation, untested on silicon.
  • Two HID inputs on one device: implemented, not tested on a real device.
  • The machine builds its own kernel with its own compiler, assembler and linker, at two levels. On the host, tools/selfbuild.sh compiles every source with those tools, links kernel.elf and boots it -- the fast, full check, and the one that reports all sources that fail at once. On the machine itself, tools/selfkernel.sh sends the sources through the door; the machine builds, installs and boots its own kernel there, on the build thread's own 128 KiB stack, and that kernel's own compiler builds a kernel again (a second generation). This exercises bn.c's 128-bit big-number arithmetic -- the compiler grew a partial unsigned __int128 (add, subtract, multiply, the bitwise operators and constant-count shifts, in a register pair; no divide, compare or variable shift, which bn.c does not need) -- and the SMP real-mode trampoline (ap_boot.S, a byte table since the assembler does no 16-bit mode; tools/tramp-check.sh holds it to its readable source tools/ap_boot_ref.S). The self-built kernel passes every self-test, the rsa/ecdsa certificate checks among them, brings up all cores, and its browser passes the same test as the clang-built one (tools/webtest.sh on build/self, in CI and the gate -- a self-built kernel once booted clean and fell over on its first page, the compiler having typed every number as long). The picture decoder and the page renderer, programs of their own, are built by the same tools in both (tools/selfprograms.sh) and go into the on-machine build as the texts of their images, since the machine cannot yet turn a program it built into such a text itself. tools/kvm-battery.sh is the release gate: the battery, the compiler stack-frame guard, the host self-build and boot, then the on-machine self-build -- because the host build, with the host compiler's megabytes of stack, cannot show what the machine's bounded stack does.

Releases

  • EreBUS 0.4.4: first published version.
  • EreBUS 0.5.0: nodes: identity by key, rights per node, kernel updates through the pipe, network page, work with inputs and provenance.
  • EreBUS 0.5.1: compiler fix (member lookup in structs with inner struct bodies); a self-built kernel can compile again; selfkernel test runs a second generation.
  • EreBUS 0.5.2: console messages of the assembler programs reworded to factual wording; test battery parallel and reliable under load, 22 tests in about four minutes. No change to what the machine does.
  • EreBUS 0.6.0: visual overhaul of the shell -- one warm ground, a single accent for agency and position, regions parted by rules, the focused name at double height. Structure, layout and behaviour unchanged.
  • EreBUS 0.7.0: serious far work -- answers signed with the node's door key and verified against its key; a kernel-enforced deadline that ends a runaway job with no system calls; compiled tasks (ask <task> as code) built and run on the worker under that deadline; a job ledger on the system shelf.
  • EreBUS 0.8.0: quorum far work (ask <task> across N), the result a verified majority agree on; forget <node> to re-pin a changed key; the ssh door serves several visitors at once and survives a mid-session rekey.
  • EreBUS 0.8.1: identity beyond trust-on-first-use -- trust <name> <key> pins a node before it is met, renew key rotates the door key and tells known nodes (signed old and new); the network page shows uptime and the journal notes a node going quiet or coming back.
  • EreBUS 0.8.2: the shell fills its own space -- home opens on a machine overview (load over the last minute, running programs, recent journal); a status line carries uptime, load, memory, threads, objects, nodes and address; the picked reference shows a preview of its target through the target's own lens. Structure and behaviour unchanged.
  • EreBUS 0.8.3: far work takes an input in every form (ask <task> with <object> alongside as code and across N; a compiled worker reads it from its letter box); vouching -- allow <node> vouch honours a node's signed vouches and vouch <node> tells known nodes a key is one you recognise, so they pin it before meeting; an attention page gathers notable events (a failed job, a node gone quiet) with an unseen count in the status line.
  • EreBUS 0.8.4: self-update from a signed release -- update | auto checks now and then and installs a newer version on its own; the package's ed25519 signature is verified against a key built into the kernel (the signature, not the transport, is what makes it safe), and the loader's kernel.old rollback still applies. update check looks on demand. Larger tcp receive window for faster downloads; clean connection close.
  • EreBUS 0.8.5: self-update fixes so it works against a real release host -- the fetch carries the long signed redirect URLs a CDN returns (the request and Location buffers were too small and truncated the token), and no further check runs once an install is pending, so a machine updates and restarts exactly once. Verified end to end against the GitHub release.
  • EreBUS 0.8.6: update check answers in the terminal. The check runs in the background (the download can take a while), so its outcome -- already current, a newer version installing, or the source unreachable -- is now printed back into the terminal where it was typed, not only into the log.
  • EreBUS 0.8.9: the web verified -- thirty-four roots from the Mozilla bundle join the built-in authorities, with P-384 and RSA-4096 under SHA-256/384/512, so Wikipedia, Google, Amazon, Microsoft, heise and GitHub verify from the machine; split works for compiled tasks and under a quorum; unvouch withdraws a vouch and the nodes table says how each key came to be there; the self-update reads a one-line version file first; usb disks are driven, and a stick made with tools/mkusb.sh carries the store.
  • EreBUS 0.9.9: the page renderer out of ring 0 -- the html and stylesheet readers are a program of their own (programs/renderer), started in ring 3 on each page with its fetched sheets and its pictures' sizes; it lays the whole flow out into a display list the kernel reads back, checks and paints at any scroll, so scrolling no longer renders and typing into a field needs no new layout. A page that faults, hangs or refuses its renderer costs that page; the next page is laid out without waiting for it (tools/renderer-fault.sh); the kernel's check of the block is fuzzed beside the renderer. The html lens of a text goes the same way. Also fixed: in the machine's compiler a comparison of two 32-bit operands is made in their common type, so x == -1 with x unsigned asks for 0xFFFFFFFF, and an unsigned int against a long compares signed (tools/cc/proof.c 26 to 28); a FAT32 volume with its sector count in the 16-bit field alone (a 16 MiB one from mkfs.fat) is mounted; and five tests that printed what they saw but could never say FAILED -- cctest2 among them, whose proof had not run since that volume was refused -- now check what they were written to show.
  • EreBUS 0.9.8: the picture decoders out of ring 0 -- png, jpeg and webp decode in a program of their own (programs/decoder), started in ring 3 on each picture's bytes and ended once the kernel has read the pixels back out of its memory; a decoder that faults, never answers or refuses costs that one picture, and the browser fetches the next picture while one decodes. The program is written against the sdk header, built by clang and lld into the machine's own image format (tools/mkimage.py) and by the machine's own compiler in the self-build, and carried by the kernel as bytes. tools/decoder-fault.sh rebuilds it to fault and to hang and shows the page going on. Found on the way: the machine's own compiler typed every number as long, so a 32-bit hash with a literal in it was computed in 64 bits and a self-built kernel's browser fell over on its first page; numbers have their C types now, and the self-built kernel's browser is in the gate and in CI.
  • EreBUS 0.9.7: the road to 1.0, first stretch -- self-hosting whole on the machine (the compiler's build thread on 128 KiB of stack, small code-walk frames, the on-machine build back in the release gate with two guards); two release keys built in and a signed rotation between them; SHA256SUMS and PROVENANCE on every release; the program interface written down (MANUAL 18) with sdk/ and a program from outside the tree run on the machine; discovery answering the own network by default and telling strangers nothing that describes the machine; the first reply to a new neighbour no longer lost to a garbled address; 64 processors (x2apic MADT entries read), 64 nodes; the power cut inside a store write survived a hundred times; the last release's store and pipe held to; a nightly gate, -Werror, and a list of what 1.0 guarantees with the test behind each line.
  • EreBUS 0.9.5: distributed task packages. A task text may begin with a key | value manifest -- kind (code or recipe), split, pieces, across (a quorum), combine, budget, input -- that carries the whole policy, so a distributed task is one artifact rather than loose steps at the shell. submit <task> hands it to the desk and the willing machines distribute the pieces; the pieces of a split fold by sum, concat, min, max, count or first. A package feeds into a node over ssh -- erebus-task prog.c --split 1 1000000 --ssh | ssh node -- and the nodes deal the work out themselves. erebus-task, a host tool (Windows or Linux) built from the machine's own compiler, packages a program and, for C, checks it builds for the node first. The payload rides one datagram (1 KiB) and no foreign binaries run -- a larger program, arbitrary-size results and other languages are the next milestone (a task VM).
  • EreBUS 0.9.4: the browser reads webp pictures and keeps a connection open. Lossless (VP8L) and lossy (VP8) still frames decode beside png and jpeg, exact against the reference on the test set; the wire from one fetch is held for the next fetch to the same host, and over https the tls session is held alongside, so a page and its pictures no longer each pay for a handshake (a stale reuse falls back to a fresh connection; a connection is kept only when the answer had a length and the server agreed to keep-alive). A table's cells line up in columns, <pre> and inline <code> sit on a faint ground, and a <blockquote> carries a left accent bar. The picture fuzzer now feeds webp.
  • EreBUS 0.9.3: the browser reads a page's structure. font-size grows the one bitmap font by whole steps, so a heading or anything a page sets large stands at twice or three times the body's height (a heading with no size of its own is set so by its level); the text is held to a readable column down the middle instead of running the whole screen; a link takes its colour, not an underline, so a page that is nothing but links is not a wall of lines. Media queries see the true window width, so a page lays itself out for a desktop rather than for the column.
  • EreBUS 0.9.2: the browser reads the stylesheets, as far as they change what a page says -- what a rule hides stays hidden (display none, visibility hidden, the hidden attribute), bold, colour, centred or right-set lines, bullets dropped, block or inline breaks; <style> blocks and linked sheets in the page's order, the style attribute over them, media queries on width; navigation, headers, footers and asides fold to one line with a count of what they hold, opened by a press; plain (or s) shows the page as it came. Fixed: a page asked for while a picture was still coming left the browser at loading ... for good. The renderer gathers a line before painting it; a stylesheet reader with its own fuzzer.
  • EreBUS 0.9.1: the browser -- a view of its own on the web with no object in between: an address line, links and forms by mouse or keyboard, GET and POST, cookies kept per RFC 6265 (persistent ones on the shelf as the cookies), bookmarks in a bookmarks text with the start page listing them, png and baseline jpeg drawn in colour, a search for a word on the page, chunked and gzip answers, utf-8 with Greek and Cyrillic in the font; an unverified server is marked, not refused. The http client grew a method, a body and cookies; the tls client sends any request; fuzzers for the decoders.
  • EreBUS 0.9.0: the machine at rest -- the network card, the usb controller and the disk controller interrupt (msi, msi-x, or their legacy line through the 8259, the local APIC now on) and their threads sleep on it, yield rests a program until the next tick, the processor halts when nothing happens and load says how much of the time; the parsers on the wire fuzzed (frames, the tcp and http client, the door with ssh, the air, the pipe's datagrams, the tls client) with three overruns closed; the store's stick can be unplugged and plugged back in, known by an identity the store now carries, changes waiting meanwhile and another store refused; usb disks with bigger blocks; every disk and wire format has its number in one place, formats prints them, and the manual states the promise.
  • EreBUS 0.8.8: a round of fixes across the system. Fetching was 80 times slower than the link: the tcp window was a fixed 32 KiB regardless of the room left in the receive ring, so the peer sent what could not be kept, every cut costing its retransmit timeout (a 2.8 MB package took 93 s; it takes 1.2 s now) -- the window is the room now, a window update goes out when room opens, a fetch stops at Content-Length instead of waiting for the close, and the card's receive ring is four times larger; the scheduler no longer lets the idle thread keep the processor for a whole slice while another thread is ready; a pipe handshake in progress is no longer thrown away when a second address is knocked on (a task across two machines restarted both against each other); tcp no longer ends a stream short when the peer's fin arrives ahead of lost data; dns names ending in a compression pointer; a certificate extension with a malformed boolean read an uninitialized element (found by the new fuzzer); a full-length version name overflowed the update report; bundle and fat32 length checks that could wrap or run on a hostile disk; the fuzzers cover the certificate checker and the page renderer now.
  • EreBUS 0.8.7: the tls client verifies the server. The certificate chain is walked to a trusted authority (ECDSA P-256 and RSA signatures, host names, dates) and the server's signature over the handshake is checked against the leaf's key; github.com and the release cdn verify against authorities built into the kernel, authority | adds one of your own, tls | strict refuses what does not verify. The browser marks a page verified or sealed, unverified; the log and journal say why.

License

Copyright (C) 2026 DustinHab

EreBUS is free software, licensed under the GNU Affero General Public License, version 3 or (at your option) any later version (AGPL-3.0-or-later). You may use, study, share and modify it. If you distribute it — or run a modified version that people reach over a network — you must pass on the complete corresponding source under the same license (see section 13 for the network case). There is no warranty. The full text is in the LICENSE file; third-party material (GNU Unifont, the TLS root authorities) is credited in NOTICE.

Contributing and security

How to build, test, and submit a change: CONTRIBUTING.md. The battery (sh tools/battery.sh) must be green before a change ships.

To report a security flaw, and for the threat model and known limits: SECURITY.md. Report privately, not in a public issue.

About

An object-based, capability-secured operating system for x86_64 UEFI. Objects travel between nodes over an encrypted object pipe, so separate machines form one decentralized network for shared computing. Each node carries its own compiler and can rebuild, patch and update its own kernel from source.

Resources

Contributing

Security policy

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages