Files
bare-operating-system/packages/bare-os-booter/lib/bare-os-wasm-kernel.js
T
Raven Scott 071edccfb3 Expand bounded awk/expr/test toward Issue 7; refresh man, profile 1.0.18,
posix matrix/dashboard, and syscalls/process_table schema alignment (v8).

Booter: replication_operator_sketch/corestore hints, HRPC allowlist tests,
Protomux cap channel 65536-byte bound + export, Wasm posix_profile_peek,
swarm-disk and security_posture docs.

Coreutils/kernel: pkg-swarm-index pathCapabilityEnvelopeVerify on get;
pathcap-verify --trusted failure hint; rebuild bins and sync seeder.

Docs: KERNEL_CONTRACT, kernel-extensions, capabilities index, environment
appendix (warm-cache tuning, cap channel, Wasm env), handbook observability,
vault threat model (multisig), developer-guide ctx/HRPC/Wasm, DOCUMENTATION
release-checklist note, release-checklist optional tier1 drift.

Changelog maintenance in bare-os-booter and bare-os-protocol.
2026-04-05 23:01:54 -04:00

296 lines
10 KiB
JavaScript

/**
* Optional WebAssembly compile / instantiate for feature-gated kernel extensions.
* Uses global WebAssembly when present (Bare/V8); no Node built-ins.
*/
import { BARE_OS_POSIX_PROFILE_VERSION } from 'bare-os-protocol/bare-os-posix-profile.js'
import { bareOsMonotonicNowMs } from './bare-os-monotonic-time.js'
const DEFAULT_MAX = 512 * 1024
/**
* @param {Uint8Array} source
* @param {{ maxBytes?: number }} [opts]
*/
export async function bareOsWasmKernelCompile(source, opts = {}) {
const maxB = Math.min(
DEFAULT_MAX,
Math.max(1024, Number(opts.maxBytes) || DEFAULT_MAX)
)
if (!(source instanceof Uint8Array) || source.byteLength === 0) {
return { ok: false, reason: 'invalid_source' }
}
if (source.byteLength > maxB) {
return { ok: false, reason: 'source_too_large' }
}
const W = globalThis.WebAssembly
if (!W || typeof W.compile !== 'function') {
return { ok: false, reason: 'webassembly_unavailable' }
}
try {
const mod = await W.compile(source)
/** @type {{ name: string }[]} */
let names = []
if (typeof W.Module.exports === 'function') {
names = W.Module.exports(mod).map((e) => String(e.name || ''))
}
return { ok: true, exportNames: names }
} catch (e) {
return { ok: false, reason: String(e && e.message ? e.message : e) }
}
}
/**
* Read bounded UTF-8 from Wasm linear memory (host view).
* @param {WebAssembly.Memory} memory
* @param {number} ptr
* @param {number} len
* @param {number} [maxLen]
*/
function readUtf8FromMemory(memory, ptr, len, maxLen = 4096) {
const buf = memory.buffer
const n = Math.min(len >>> 0, maxLen)
const p = ptr >>> 0
if (p + n > buf.byteLength) return ''
return new TextDecoder().decode(new Uint8Array(buf, p, n))
}
/**
* Write NUL-terminated UTF-8 into Wasm memory; returns bytes written (excl. NUL) or -1.
* @param {WebAssembly.Memory} memory
* @param {number} ptr
* @param {number} outCap
* @param {string} text
*/
function writeCStrToMemory(memory, ptr, outCap, text) {
const enc = new TextEncoder().encode(String(text || ''))
const cap = Math.max(0, outCap >>> 0)
if (cap < 2) return -1
const max = cap - 1
const n = Math.min(enc.length, max)
const p = ptr >>> 0
const buf = memory.buffer
if (p + cap > buf.byteLength) return -1
const u8 = new Uint8Array(buf, p, cap)
u8.set(enc.subarray(0, n))
u8[n] = 0
return n
}
/**
* Bounded `instantiate` with an isolated `Memory` and minimal `env` imports.
* Optional sync imports **`env.bare_os_pathconf`** / **`env.bare_os_umask_get`** when
* **`BARE_OS_WASM_KERNEL_SYSCALL=1`** (or **`opts.wasmSyscallImports: true`**) and **`ctx.bareOsPathconf`** exists.
* Also **`env.bare_os_wall_time_ms32`**: low 32 bits of wall time (`Date.now()`, signed i32 wrap) for bounded guest timing.
* With **`BARE_OS_WASM_KERNEL_MONOTONIC_MS=1`**, adds **`env.bare_os_monotonic_ms`**: floored monotonic milliseconds (`performance.now` when available), i32-shaped.
* With **`BARE_OS_WASM_KERNEL_HOSTNAME_IMPORT=1`** (requires syscall imports), adds **`env.bare_os_hostname_peek`**: writes session **`HOSTNAME`** (or **`bare-os`**) NUL-terminated into Wasm memory (**bounded**).
* With **`BARE_OS_WASM_KERNEL_CTX_API_PEEK=1`** (requires syscall imports), adds **`env.bare_os_ctx_api_version_peek`**: writes **`ctx.bareOsCtxApiVersion`** NUL-terminated (**bounded**).
* With **`BARE_OS_WASM_KERNEL_POSIX_PROFILE_PEEK=1`** (requires syscall imports), adds **`env.bare_os_posix_profile_peek`**: writes declared **`BARE_OS_POSIX_PROFILE_VERSION`** NUL-terminated (**bounded**).
*
* @param {Uint8Array} source
* @param {Record<string, unknown>} ctx
* @param {{
* maxBytes?: number,
* memoryInitialPages?: number,
* memoryMaxPages?: number,
* timeoutMs?: number,
* wasmSyscallImports?: boolean,
* shellEnv?: Record<string, string | undefined>
* }} [opts]
*/
export async function bareOsWasmKernelInstantiate(source, ctx, opts = {}) {
const maxB = Math.min(
DEFAULT_MAX,
Math.max(1024, Number(opts.maxBytes) || DEFAULT_MAX)
)
if (!(source instanceof Uint8Array) || source.byteLength === 0) {
return { ok: false, reason: 'invalid_source' }
}
if (source.byteLength > maxB) {
return { ok: false, reason: 'source_too_large' }
}
const W = globalThis.WebAssembly
if (!W || typeof W.instantiate !== 'function' || typeof W.Memory !== 'function') {
return { ok: false, reason: 'webassembly_unavailable' }
}
const memInitial = Math.min(
256,
Math.max(1, Number(opts.memoryInitialPages) || 1)
)
const memMax = Math.min(
256,
Math.max(memInitial, Number(opts.memoryMaxPages) || 8)
)
const timeoutMs = Math.min(
60000,
Math.max(50, Number(opts.timeoutMs) || 5000)
)
try {
const mod = await W.compile(source)
/** @type {{ name: string }[]} */
let exportNames = []
if (typeof W.Module.exports === 'function') {
exportNames = W.Module.exports(mod).map((e) => String(e.name || ''))
}
const memory = new W.Memory({ initial: memInitial, maximum: memMax })
const shellEnv =
opts.shellEnv && typeof opts.shellEnv === 'object'
? /** @type {Record<string, string | undefined>} */ (opts.shellEnv)
: {}
const wantSyscall =
opts.wasmSyscallImports === true ||
shellEnv.BARE_OS_WASM_KERNEL_SYSCALL === '1' ||
shellEnv.BARE_OS_WASM_KERNEL_SYSCALL === 'true'
const wantMonotonicMs =
wantSyscall &&
(shellEnv.BARE_OS_WASM_KERNEL_MONOTONIC_MS === '1' ||
shellEnv.BARE_OS_WASM_KERNEL_MONOTONIC_MS === 'true')
const wantHostname =
wantSyscall &&
(shellEnv.BARE_OS_WASM_KERNEL_HOSTNAME_IMPORT === '1' ||
shellEnv.BARE_OS_WASM_KERNEL_HOSTNAME_IMPORT === 'true')
const wantCtxApiPeek =
wantSyscall &&
(shellEnv.BARE_OS_WASM_KERNEL_CTX_API_PEEK === '1' ||
shellEnv.BARE_OS_WASM_KERNEL_CTX_API_PEEK === 'true')
const wantPosixBridgePeek =
wantSyscall &&
(shellEnv.BARE_OS_WASM_KERNEL_POSIX_BRIDGE_PEEK === '1' ||
shellEnv.BARE_OS_WASM_KERNEL_POSIX_BRIDGE_PEEK === 'true')
const wantPosixProfilePeek =
wantSyscall &&
(shellEnv.BARE_OS_WASM_KERNEL_POSIX_PROFILE_PEEK === '1' ||
shellEnv.BARE_OS_WASM_KERNEL_POSIX_PROFILE_PEEK === 'true')
const pathconfFn = ctx && typeof ctx.bareOsPathconf === 'function'
? ctx.bareOsPathconf.bind(ctx)
: null
/** @type {Record<string, unknown>} */
const envImports = {
memory,
bare_os_nop: () => 0
}
if (wantSyscall) {
envImports.bare_os_wall_time_ms32 = () => Date.now() | 0
}
if (wantMonotonicMs) {
envImports.bare_os_monotonic_ms = () =>
Math.floor(bareOsMonotonicNowMs()) | 0
}
if (wantSyscall && pathconfFn) {
envImports.bare_os_pathconf = (
pathPtr,
pathLen,
namePtr,
nameLen,
outPtr,
outCap
) => {
try {
const path = readUtf8FromMemory(
memory,
Number(pathPtr),
Number(pathLen)
).trim()
const name = readUtf8FromMemory(
memory,
Number(namePtr),
Number(nameLen)
).trim()
if (!path.startsWith('/') || !name) return -1
const v = pathconfFn(path, name)
if (v == null || v === undefined) return -2
return writeCStrToMemory(
memory,
Number(outPtr),
Number(outCap),
String(v)
)
} catch {
return -3
}
}
envImports.bare_os_umask_get = () => {
const m = parseInt(String(shellEnv.UMASK || '022'), 8)
return Number.isFinite(m) ? m & 0o777 : 0o22
}
if (wantHostname) {
envImports.bare_os_hostname_peek = (outPtr, outCap) => {
const vfsEnv =
ctx &&
ctx.vfs &&
ctx.vfs.env &&
typeof ctx.vfs.env === 'object'
? /** @type {Record<string, string>} */ (ctx.vfs.env)
: {}
const h = String(
vfsEnv.HOSTNAME || shellEnv.HOSTNAME || 'bare-os'
).slice(0, 256)
return writeCStrToMemory(memory, Number(outPtr), Number(outCap), h)
}
}
if (wantCtxApiPeek) {
envImports.bare_os_ctx_api_version_peek = (outPtr, outCap) => {
const v = String(
ctx && ctx.bareOsCtxApiVersion != null
? ctx.bareOsCtxApiVersion
: ''
).slice(0, 64)
return writeCStrToMemory(memory, Number(outPtr), Number(outCap), v)
}
}
if (wantPosixBridgePeek) {
envImports.bare_os_posix_bridge_peek = () => {
const sock =
shellEnv.BARE_OS_POSIX_SOCKET_FD_BRIDGE === '1' ||
shellEnv.BARE_OS_POSIX_SOCKET_FD_BRIDGE === 'true'
const fcntlWait =
shellEnv.BARE_OS_POSIX_FCNTL_BLOCKING_WAIT === '1' ||
shellEnv.BARE_OS_POSIX_FCNTL_BLOCKING_WAIT === 'true'
let flags = 0
if (sock) flags |= 1
if (fcntlWait) flags |= 2
return flags | 0
}
}
if (wantPosixProfilePeek) {
envImports.bare_os_posix_profile_peek = (outPtr, outCap) => {
const v = String(BARE_OS_POSIX_PROFILE_VERSION || '').slice(0, 32)
return writeCStrToMemory(memory, Number(outPtr), Number(outCap), v)
}
}
}
const importObject = {
env: envImports
}
const instP = W.instantiate(mod, importObject)
const done = await Promise.race([
instP,
new Promise((_, rej) =>
setTimeout(
() => rej(new Error('wasm_instantiate_timeout')),
timeoutMs
)
)
])
const instance = /** @type {{ instance: WebAssembly.Instance }} */ (done)
.instance
return {
ok: true,
exportNames,
instance,
memory,
memoryInitialPages: memInitial,
memoryMaxPages: memMax,
wasmSyscallImports: !!wantSyscall,
wasmPathconfImport: !!(wantSyscall && pathconfFn),
wasmWallClockMs32Import: !!wantSyscall,
wasmMonotonicMsImport: !!wantMonotonicMs,
wasmHostnamePeekImport: !!(wantSyscall && wantHostname),
wasmCtxApiVersionPeekImport: !!(wantSyscall && wantCtxApiPeek),
wasmPosixBridgePeekImport: !!(wantSyscall && wantPosixBridgePeek),
wasmPosixProfilePeekImport: !!(wantSyscall && wantPosixProfilePeek)
}
} catch (e) {
return { ok: false, reason: String(e && e.message ? e.message : e) }
}
}