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:

  1. Elevates the sessionauth_elevate_session() calls sys_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.

  2. Sets identityauth_set_identity(uid, gid) calls sys_setuid (105) and sys_setgid (106) to switch from root to the authenticated user.

  3. Grants shell capabilitiesauth_grant_shell_caps() pre-registers capability kinds across the next execve via sys_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 */
    
  4. Launches the shellexecve(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:

  1. Display /etc/banner if present
  2. Prompt for username (canonical mode)
  3. Prompt for password (raw mode with * echo and backspace handling via termios)
  4. Authenticate via auth_check()
  5. On failure: 3-second delay, retry (max 3 attempts)
  6. 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/SIGUSR2 signals
  • 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:

  1. Sends a UDP NTP packet (version 4, mode 3) to time.google.com (216.239.35.0:123)
  2. Extracts the 32-bit transmit timestamp from the response (bytes 40-43)
  3. Converts from NTP epoch (1900) to Unix epoch by subtracting 2208988800
  4. Sets the system clock via clock_settime(CLOCK_REALTIME, ...)
  5. 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)