Userspace Services
Technical documentation for the userspace service daemons and authentication system in Aegis OS
Userspace Services
Aegis ships a set of userspace services managed by the Vigil init system. These include authentication services (login and bastion), network daemons (DHCP client and HTTP server), a time synchronization daemon, and an OS installer. All services integrate with the kernel capability model – capabilities are granted at exec time as policy capabilities from /etc/aegis/caps.d/, not requested at runtime.
All services documented here are v1 software written in C. They are functional and tested, but have not undergone formal security audit. The authentication services in particular (login, bastion, libauth) handle sensitive operations – password verification, session elevation, identity switching – using C string manipulation and raw syscalls. As with any from-scratch C codebase at this scale, real exploitable vulnerabilities are likely present. Security findings to date have been hypothetical; the absence of known exploits should not be mistaken for security. The kernel’s capability system has already been implemented in Rust as the beginning of a planned gradual C-to-Rust migration that will eventually reach userspace services. Contributions are welcome – file issues or propose changes at exec/aegis.
Authentication Stack
libauth – Shared Authentication Library
Source: user/lib/libauth/ (auth.c, auth.h)
libauth provides credential verification and session management for both the text-mode login binary and the graphical display manager (bastion). It is compiled as a static library (libauth.a) linked by both consumers.
API
/* Full authentication: passwd + shadow + crypt.
* Returns 0 on success, -1 on failure. */
int auth_check(const char *username, const char *password,
int *uid, int *gid,
char *home, int homelen, char *shell, int shelllen);
/* Set process identity (setuid + setgid). */
void auth_set_identity(int uid, int gid);
/* Pre-register CAP_DELEGATE + CAP_QUERY as exec_caps for next execve.
* The shell inherits these capabilities. */
void auth_grant_shell_caps(void);
/* Mark the current session as authenticated. Kernel policy table
* uses this to grant admin-tier capabilities to subsequent processes. */
void auth_elevate_session(void);
Authentication Flow
auth_check(user, pass, &uid, &gid, home, shell)
|
+-> auth_lookup_passwd() -- parse /etc/passwd (colon-delimited)
| fields: username:x:uid:gid:gecos:home:shell
|
+-> auth_lookup_shadow() -- parse /etc/shadow (requires CAP_AUTH)
| fields: username:hash:...
|
+-> auth_verify() -- crypt(password, hash) == hash?
/etc/passwd is a standard Unix-format file parsed field-by-field with strchr(':'). /etc/shadow is read via raw open()/read() (not fopen()) since the file requires the AUTH capability kind to access (attempts without it return ENOCAP, errno 130).
The password verification path uses crypt() for hash comparison. As v1 C code, the parsing logic in auth_lookup_passwd and auth_lookup_shadow uses fixed-size buffers and manual string splitting – functional but not hardened against malformed credential files or timing attacks. This is representative of the overall v1 security posture: the mechanisms are architecturally sound, but the C implementation has not been battle-tested.
Identity and Capability Transition
After successful authentication, the login process:
-
Elevates the session –
auth_elevate_session()callssys_auth_session(syscall 364), marking the current session as authenticated in the kernel. This flips the security policy engine from service-tier to admin-tier policy capabilities for subsequently spawned processes. -
Sets identity –
auth_set_identity(uid, gid)callssys_setuid(105) andsys_setgid(106) to switch from root to the authenticated user. - Grants shell capabilities –
auth_grant_shell_caps()pre-registers capability kinds across the nextexecveviasys_cap_grant_exec(syscall 361):syscall(361, 5L, 1L); /* CAP_GRANT */ syscall(361, 13L, 1L); /* CAP_DELEGATE */ syscall(361, 14L, 1L); /* CAP_QUERY */ syscall(361, 16L, 1L); /* POWER */ - Launches the shell –
execve(shell, argv, environ)replaces the login process with the user’s configured shell.
login – Text-Mode Login
Source: user/bin/login/main.c
Vigil service: getty (mode: text, policy: respawn)
Policy capabilities: AUTH, SETUID (from kernel policy)
The text-mode login binary presents a classic TTY login prompt:
- Display
/etc/bannerif present - Prompt for username (canonical mode)
- Prompt for password (raw mode with
*echo and backspace handling via termios) - Authenticate via
auth_check() - On failure: 3-second delay, retry (max 3 attempts)
- On success: elevate session, set identity, set environment variables (
HOME,USER,LOGNAME,SHELL,PATH), clear screen, exec shell
The shell is launched as a login shell with a leading - prefix (e.g., -stsh), following the Unix convention. On exec failure, it falls back to /bin/sh.
/* Build login shell name with leading '-' */
char login_shell[64];
const char *base = strrchr(shell, '/');
const char *name = base ? base + 1 : shell;
login_shell[0] = '-';
memcpy(login_shell + 1, name, nlen);
char *argv[] = { login_shell, NULL };
execve(shell, argv, environ);
/* Fallback */
execve("/bin/sh", fb_argv, NULL);
bastion – Graphical Display Manager
Source: user/bin/bastion/main.c
Vigil service: bastion (mode: graphical, policy: respawn)
Policy capabilities: AUTH, FB, SETUID (from kernel policy)
Bastion is the graphical counterpart to login. It presents a login form rendered directly to the framebuffer using the Glyph toolkit, authenticates via the same libauth path, then spawns the Lumen compositor as the authenticated user’s session.
Key differences from login:
- Uses
SYS_FB_MAP(syscall 513) for framebuffer access - Renders with Glyph widgets (text inputs, buttons)
- Mouse input from
/dev/mouse - Handles lock/unlock via
SIGUSR1/SIGUSR2signals - Spawns Lumen via
fork()+execve("/bin/lumen", ...)rather than exec-replacing itself, so bastion stays resident to catch lock/unlock signals and re-render the login form on session exit - Displays network status by querying
sys_netcfg(syscall 500)
Network Services
dhcp – DHCP Client Daemon
Source: user/bin/dhcp/main.c
Vigil service: dhcp (policy: oneshot)
Policy capabilities: NET_ADMIN, NET_SOCKET (from kernel policy)
A full RFC 2131 DHCP client implementing the state machine: SELECTING -> REQUESTING -> BOUND -> RENEWING.
Protocol Implementation
SELECTING:
Send DHCPDISCOVER (broadcast, port 67)
Wait for DHCPOFFER
Extract: offered IP, server IP, subnet mask, gateway, DNS, lease time
REQUESTING:
Send DHCPREQUEST (broadcast, with option 50=offered IP, option 54=server IP)
Wait for DHCPACK or DHCPNAK
BOUND:
Apply configuration via sys_netcfg
Write DNS to /etc/resolv.conf
Sleep for T1 = lease/2 (minimum 30 seconds)
RENEWING:
Send DHCPREQUEST (unicast to server, ciaddr=current IP)
On success: update lease, sleep T1 again
On failure: restart from SELECTING
Network Configuration
The DHCP client applies acquired settings via sys_netcfg (syscall 500):
/* op=0: set IP/mask/gateway (all in network byte order) */
syscall(SYS_NETCFG, 0, (long)s_ip, (long)s_mask, (long)s_gateway);
DNS is written to /etc/resolv.conf (ramfs-backed, writable at runtime):
dprintf(fd, "nameserver %u.%u.%u.%u\n", d[0], d[1], d[2], d[3]);
Retry Strategy
| Attempt | Backoff |
|---|---|
| 1 | 0s (immediate) |
| 2 | 4s |
| 3 | 8s |
| 4 | 16s |
| 5 | 32s |
The socket has a 500ms receive timeout (SO_RCVTIMEO), with up to 3 retries per DISCOVER/REQUEST phase. After 5 total attempts, the daemon exits with status 1.
DHCP Options Parsed
| Option | Tag | Description |
|---|---|---|
| Subnet Mask | 1 | Default: 255.255.255.0 |
| Router | 3 | Default gateway |
| DNS Server | 6 | Written to /etc/resolv.conf |
| Lease Time | 51 | Default: 3600s |
| Message Type | 53 | OFFER(2), ACK(5), NAK(6) |
| Server Identifier | 54 | DHCP server IP |
NIC Detection
Before attempting DHCP, the client reads the MAC address via sys_netcfg op=1. If the MAC is all zeros (no NIC present), it exits immediately:
netcfg_info_t info;
syscall(SYS_NETCFG, 1, (long)&info, 0, 0);
if (mac_is_zero(info.mac)) {
dprintf(2, "[DHCP] no network interface, exiting\n");
return 0;
}
httpd – HTTP Server
Source: user/bin/httpd/main.c
Vigil service: httpd (policy: respawn)
Policy capabilities: NET_SOCKET (from kernel policy)
A minimal HTTP/1.0 server for testing the socket API. It listens on port 80, accepts connections, drains the request, and returns a fixed response:
HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 17
Hello from Aegis
The server uses SO_REUSEADDR and a backlog of 4. It processes one connection at a time (no concurrency). As a v1 implementation, httpd performs no input validation on the HTTP request beyond draining bytes from the socket – it exists purely as a socket API test fixture, not as a production web server.
chronos – SNTP Time Sync
Source: user/bin/chronos/main.c
Vigil service: chronos (policy: oneshot)
A simple SNTP client that:
- Sends a UDP NTP packet (version 4, mode 3) to
time.google.com(216.239.35.0:123) - Extracts the 32-bit transmit timestamp from the response (bytes 40-43)
- Converts from NTP epoch (1900) to Unix epoch by subtracting
2208988800 - Sets the system clock via
clock_settime(CLOCK_REALTIME, ...) - Re-syncs every hour
On failure (no network, no response), it retries every 60 seconds. Since it is a oneshot Vigil service, network availability is naturally handled: chronos starts, retries until DHCP completes, syncs, then loops internally.
Installer Services
installer – Text-Mode Installer
Source: user/bin/installer/main.c
A text-mode UI shell over libinstall.a. It collects disk selection and user credentials via stdin prompts, then delegates to install_run_all() which handles GPT writing, rootfs copying, ESP installation, GRUB configuration, and /etc/passwd creation.
Password entry uses termios raw mode with asterisk echo.
gui-installer – Graphical Installer
Source: user/bin/gui-installer/
The graphical counterpart to the text-mode installer, using the Glyph toolkit for rendering. Same installation backend (libinstall.a), different UI frontend.
Capability Requirements Summary
All capabilities are granted as policy capabilities at exec time via /etc/aegis/caps.d/. In addition, every process receives the six baseline capabilities at exec. No service requests capabilities at runtime.
| Service Binary | Policy Capabilities | Tier |
|---|---|---|
/bin/login |
AUTH, SETUID | Service |
/bin/bastion |
AUTH, FB, SETUID | Service |
/bin/dhcp |
NET_ADMIN, NET_SOCKET | Service |
/bin/httpd |
NET_SOCKET | Service |
/bin/stsh (post-auth) |
CAP_DELEGATE, CAP_QUERY, CAP_GRANT, POWER | Admin (via exec_caps) |
The two-tier model:
- Service tier: Policy capabilities granted to service binaries unconditionally (e.g., login always gets AUTH)
- Admin tier: Policy capabilities granted to processes in authenticated sessions (after
auth_elevate_session())
See the security policy engine documentation for details on how policy capabilities are resolved at exec time.
Note that the capability enforcement itself happens in the kernel’s Rust-implemented capability validator (kernel/cap/lib.rs), which is the most security-critical component and the first piece of Aegis to be written in Rust. The userspace code that requests and uses capabilities (libauth, stsh, service binaries) remains in C and carries the v1 caveats described above.
Syscalls Used by Services
| Number | Name | Used By |
|---|---|---|
| 105 | sys_setuid |
login, bastion (identity switch) |
| 106 | sys_setgid |
login, bastion (identity switch) |
| 361 | sys_cap_grant_exec |
login, bastion (shell cap pre-registration) |
| 364 | sys_auth_session |
login, bastion (session elevation) |
| 500 | sys_netcfg |
dhcp (network config), bastion (status display) |
| 513 | SYS_FB_MAP |
bastion (framebuffer access) |