reporting and audit:holepunch-clones docs; placeholder baseline automation; maintainer kernel-image sync script; protomux schema coupling in protocol tests; disk.os RPC hints (replication_operator_sketch, hyperblobs/blind v3); union/mirror VFS + warm-cache selective eviction tests; extension resolver coverage; curl/wget fall through PATH when BARE_OS_DELEGATE_ALLOW excludes delegates (kernel-runner) with handbook/ctx docs; socket contract / Wasm / boot budget strict path / hrpc allowlist tests; subprocess bridge meta; shell until gate + tar stat metadata; POSIX profile triplet pretest verifier; regenerate kernel bundle, seeder parity, and audit artifacts as needed. Covers KERNEL_CONTRACT, POSIX_DECLARED_PROFILE, handbook, developer-guide, scripts/README, and related reference docs.
216 lines
6.5 KiB
JavaScript
216 lines
6.5 KiB
JavaScript
/**
|
|
* Optional WebAssembly compile / instantiate for feature-gated kernel extensions.
|
|
* Uses global WebAssembly when present (Bare/V8); no Node built-ins.
|
|
*/
|
|
|
|
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.
|
|
*
|
|
* @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 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 (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
|
|
}
|
|
}
|
|
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
|
|
}
|
|
} catch (e) {
|
|
return { ok: false, reason: String(e && e.message ? e.message : e) }
|
|
}
|
|
}
|