sandboxedjs 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -45
- package/bin/sandboxedjs.mjs +6 -4
- package/dist/index.cjs +3376 -188
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +536 -1
- package/dist/index.d.ts +536 -1
- package/dist/index.js +3359 -189
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -633,6 +633,8 @@ declare function createContext(init: ContextInit): ExecContext;
|
|
|
633
633
|
* container can execute it" is far more use than `Exec format error`.
|
|
634
634
|
*/
|
|
635
635
|
|
|
636
|
+
/** How many bytes of the file head a handler is given to identify it. */
|
|
637
|
+
declare const MAGIC_BYTES = 256;
|
|
636
638
|
interface ExecFormat {
|
|
637
639
|
/** Short identifier, e.g. `wasi`. Shown by `file` and in diagnostics. */
|
|
638
640
|
readonly name: string;
|
|
@@ -2026,6 +2028,539 @@ declare function isPythonAvailable(): Promise<boolean>;
|
|
|
2026
2028
|
|
|
2027
2029
|
declare const NPM_VERSION = "10.9.0";
|
|
2028
2030
|
|
|
2031
|
+
/**
|
|
2032
|
+
* WebAssembly as an executable format.
|
|
2033
|
+
*
|
|
2034
|
+
* This is what makes a `.wasm` file in `$PATH` a program. The format handler
|
|
2035
|
+
* registered here is asked by the kernel whenever a file starts with `\0asm`,
|
|
2036
|
+
* so a WASI binary is run through exactly the same path as `grep` or a shell
|
|
2037
|
+
* script: `chmod +x jq.wasm && ./jq.wasm .name < data.json` works, and so does
|
|
2038
|
+
* dropping it in `/usr/local/bin` and calling it `jq`.
|
|
2039
|
+
*
|
|
2040
|
+
* Nothing here knows the name of a single program. What it knows is how to read
|
|
2041
|
+
* a module's import section and tell which *convention* the module was built
|
|
2042
|
+
* for, because that is what decides whether it can run:
|
|
2043
|
+
*
|
|
2044
|
+
* - `wasi_snapshot_preview1` — a command module. Runs, on the container's own
|
|
2045
|
+
* filesystem. This covers `wasm32-wasi` C/C++/Rust/Zig and Go's `wasip1`.
|
|
2046
|
+
* - `wasi:cli/*` — a preview 2 component. Not an instantiable module at all;
|
|
2047
|
+
* it needs to be transpiled first, and saying so beats a type error.
|
|
2048
|
+
* - `env` + a JS glue file — Emscripten or wasm-bindgen output, which is only
|
|
2049
|
+
* half a program; the loader beside it is the entry point, not this file.
|
|
2050
|
+
* - `gojs` — `GOOS=js`, which needs Go's `wasm_exec.js` harness.
|
|
2051
|
+
*
|
|
2052
|
+
* Each of those gets a specific diagnosis instead of "Exec format error", which
|
|
2053
|
+
* matters more than it sounds: the difference between "this will never work"
|
|
2054
|
+
* and "you are holding it wrong" is most of the debugging.
|
|
2055
|
+
*/
|
|
2056
|
+
|
|
2057
|
+
declare function isWasmBinary(head: Uint8Array): boolean;
|
|
2058
|
+
type WasmFlavour = "wasi" | "wasi-component" | "emscripten" | "wasm-bindgen" | "go-js" | "core";
|
|
2059
|
+
interface WasmAnalysis {
|
|
2060
|
+
flavour: WasmFlavour;
|
|
2061
|
+
/** Distinct import module names, in declaration order. */
|
|
2062
|
+
modules: string[];
|
|
2063
|
+
/** True when the module wants a memory it does not own. */
|
|
2064
|
+
importsMemory: boolean;
|
|
2065
|
+
/** True when the imported memory is shared, i.e. the module wants threads. */
|
|
2066
|
+
wantsThreads: boolean;
|
|
2067
|
+
exports: string[];
|
|
2068
|
+
}
|
|
2069
|
+
/** Classify a compiled module by what it imports and exports. */
|
|
2070
|
+
declare function analyse(module: WebAssembly.Module): WasmAnalysis;
|
|
2071
|
+
/** Forget cached modules. Exposed for tests and for long-lived hosts. */
|
|
2072
|
+
declare function clearWasmCache(): void;
|
|
2073
|
+
/**
|
|
2074
|
+
* Run a WASI command module.
|
|
2075
|
+
*
|
|
2076
|
+
* `argv[0]` is the name the program was invoked as, so a module symlinked to
|
|
2077
|
+
* two names can tell which one it is — busybox-style multi-call binaries
|
|
2078
|
+
* depend on it.
|
|
2079
|
+
*/
|
|
2080
|
+
declare function runWasi(ctx: ExecContext, path: string, argv0?: string): Promise<number>;
|
|
2081
|
+
declare const wasmFormat: ExecFormat;
|
|
2082
|
+
|
|
2083
|
+
/**
|
|
2084
|
+
* A WASI `preview1` host implemented over the container's filesystem.
|
|
2085
|
+
*
|
|
2086
|
+
* This is the counterpart to `emscripten-fs.ts`, for the other half of the
|
|
2087
|
+
* WebAssembly world. Emscripten programs come with their own JavaScript runtime
|
|
2088
|
+
* and are handed a filesystem backend; WASI programs come as a bare `.wasm`
|
|
2089
|
+
* module and expect the *host* to be the operating system. So this file is that
|
|
2090
|
+
* operating system: every `wasi_snapshot_preview1` import a compiled program can
|
|
2091
|
+
* ask for, answered out of the same `Vfs` the shell, Node.js and Python use.
|
|
2092
|
+
*
|
|
2093
|
+
* The consequence is the point of the whole exercise. Anything on earth that
|
|
2094
|
+
* compiles to `wasm32-wasi` — and that is most C, C++, Rust, Zig and Go code
|
|
2095
|
+
* that does not need threads or sockets — becomes a program this container can
|
|
2096
|
+
* run, without the container knowing anything about it. `path_open("data.csv")`
|
|
2097
|
+
* from a Rust binary opens the same file `echo > data.csv` just created.
|
|
2098
|
+
*
|
|
2099
|
+
* Deliberate limits, all of which report a real errno rather than crashing:
|
|
2100
|
+
* - no threads: `wasi-threads` modules fail at instantiation, not mid-run;
|
|
2101
|
+
* - no sockets: `sock_*` returns ENOTSUP, since the container has no raw ones;
|
|
2102
|
+
* - `poll_oneoff` handles clock subscriptions only, which is what `sleep` and
|
|
2103
|
+
* most timeouts actually use.
|
|
2104
|
+
*
|
|
2105
|
+
* Everything is synchronous, because WASI is. Standard input is therefore read
|
|
2106
|
+
* to completion before the module starts, the same compromise the Emscripten
|
|
2107
|
+
* mount makes.
|
|
2108
|
+
*/
|
|
2109
|
+
|
|
2110
|
+
/** Thrown by `proc_exit` to unwind out of the module. */
|
|
2111
|
+
declare class WasiExit extends Error {
|
|
2112
|
+
readonly code: number;
|
|
2113
|
+
constructor(code: number);
|
|
2114
|
+
}
|
|
2115
|
+
interface WasiOptions {
|
|
2116
|
+
vfs: Vfs;
|
|
2117
|
+
cred: Cred;
|
|
2118
|
+
/** Full argv, including argv[0]. */
|
|
2119
|
+
args: string[];
|
|
2120
|
+
env: Record<string, string | undefined>;
|
|
2121
|
+
cwd: string;
|
|
2122
|
+
/** Standard input, already read to completion. */
|
|
2123
|
+
stdin?: Uint8Array;
|
|
2124
|
+
/** Called with each write to fd 1 and fd 2. */
|
|
2125
|
+
stdout: (bytes: Uint8Array) => void;
|
|
2126
|
+
stderr: (bytes: Uint8Array) => void;
|
|
2127
|
+
/**
|
|
2128
|
+
* Directories to preopen, as `guestName -> containerPath`. Programs can only
|
|
2129
|
+
* reach paths beneath a preopen, which is WASI's sandbox and is left intact:
|
|
2130
|
+
* the default grants `/`, so the container's own permissions do the limiting,
|
|
2131
|
+
* but a caller can hand over a single directory instead.
|
|
2132
|
+
*/
|
|
2133
|
+
preopens?: Record<string, string>;
|
|
2134
|
+
/** Monotonic-ish clock, so tests can pin time. */
|
|
2135
|
+
now?: () => number;
|
|
2136
|
+
/** Fills a buffer for `random_get`. */
|
|
2137
|
+
randomFill?: (buffer: Uint8Array) => void;
|
|
2138
|
+
}
|
|
2139
|
+
/**
|
|
2140
|
+
* A WASI preview1 implementation bound to one process.
|
|
2141
|
+
*
|
|
2142
|
+
* Construct it, hand `imports` to `WebAssembly.instantiate`, then call
|
|
2143
|
+
* `bind(instance)` before starting the module so the host can reach its memory.
|
|
2144
|
+
*/
|
|
2145
|
+
declare class WasiPreview1 {
|
|
2146
|
+
private memory;
|
|
2147
|
+
private readonly fds;
|
|
2148
|
+
private nextFd;
|
|
2149
|
+
private readonly opts;
|
|
2150
|
+
private readonly now;
|
|
2151
|
+
private readonly randomFill;
|
|
2152
|
+
/** Set once `proc_exit` has been seen, so the caller can tell exit 0 from a
|
|
2153
|
+
* module that simply returned. */
|
|
2154
|
+
exited: number | null;
|
|
2155
|
+
constructor(opts: WasiOptions);
|
|
2156
|
+
/** Give the host access to the instantiated module's memory. */
|
|
2157
|
+
bind(instance: WebAssembly.Instance): void;
|
|
2158
|
+
private get view();
|
|
2159
|
+
private get bytes();
|
|
2160
|
+
private readString;
|
|
2161
|
+
/** Scatter/gather list, as `fd_read` and `fd_write` take. */
|
|
2162
|
+
private iovecs;
|
|
2163
|
+
/**
|
|
2164
|
+
* Resolve a path supplied by the module against a directory descriptor.
|
|
2165
|
+
*
|
|
2166
|
+
* The result is confined to the descriptor's subtree: `..` that would climb
|
|
2167
|
+
* out of a preopen is rejected with ENOTCAPABLE, which is WASI's own answer
|
|
2168
|
+
* and stops a module reaching a directory it was never granted.
|
|
2169
|
+
*/
|
|
2170
|
+
private resolveAt;
|
|
2171
|
+
private errnoOf;
|
|
2172
|
+
private filetypeOf;
|
|
2173
|
+
/** Write a 64-byte `filestat` for `path`. */
|
|
2174
|
+
private writeFilestat;
|
|
2175
|
+
/** Persist an open file's buffer back to the filesystem. */
|
|
2176
|
+
private flush;
|
|
2177
|
+
/**
|
|
2178
|
+
* The `wasi_snapshot_preview1` namespace, plus `wasi_unstable` for older
|
|
2179
|
+
* toolchains — the two differ only in `fd_seek`'s argument order, which is
|
|
2180
|
+
* handled by the alias below.
|
|
2181
|
+
*/
|
|
2182
|
+
get imports(): WebAssembly.Imports;
|
|
2183
|
+
private syscalls;
|
|
2184
|
+
private readInto;
|
|
2185
|
+
private writeFrom;
|
|
2186
|
+
private allocateFd;
|
|
2187
|
+
private envPairs;
|
|
2188
|
+
/** Flush and drop every open descriptor. Safe to call twice. */
|
|
2189
|
+
close(): void;
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
/**
|
|
2193
|
+
* Native executable formats other than the one this container can execute.
|
|
2194
|
+
*
|
|
2195
|
+
* x86-64 Linux binaries are handled in `x86/`, on an emulated CPU. Mach-O and
|
|
2196
|
+
* PE are a different matter: they are not just machine code but a different
|
|
2197
|
+
* operating system's process model, loader and system-call interface, and none
|
|
2198
|
+
* of that is worth building for programs that were never going to be in a
|
|
2199
|
+
* Linux container anyway.
|
|
2200
|
+
*
|
|
2201
|
+
* So these handlers parse enough of each header to name the thing and say what
|
|
2202
|
+
* would work instead. `command not found` sends someone hunting for a missing
|
|
2203
|
+
* `$PATH` entry when the real answer is that they installed a macOS binary;
|
|
2204
|
+
* `Exec format error` is technically right and practically useless.
|
|
2205
|
+
*/
|
|
2206
|
+
|
|
2207
|
+
declare const machoFormat: ExecFormat;
|
|
2208
|
+
declare const peFormat: ExecFormat;
|
|
2209
|
+
declare function nativeFormats(): ExecFormat[];
|
|
2210
|
+
|
|
2211
|
+
declare class Memory {
|
|
2212
|
+
private readonly pages;
|
|
2213
|
+
/** Bump pointer for anonymous `mmap`, below the stack and above the heap. */
|
|
2214
|
+
mmapNext: number;
|
|
2215
|
+
/** Current program break, set by the loader and moved by `brk`. */
|
|
2216
|
+
brk: number;
|
|
2217
|
+
private page;
|
|
2218
|
+
/** Make `[address, address+length)` readable and writable. */
|
|
2219
|
+
map(address: number, length: number): void;
|
|
2220
|
+
unmap(address: number, length: number): void;
|
|
2221
|
+
isMapped(address: number): boolean;
|
|
2222
|
+
/** Reserve `length` bytes of anonymous space, page-aligned. */
|
|
2223
|
+
mmapAnonymous(length: number): number;
|
|
2224
|
+
read8(address: number): number;
|
|
2225
|
+
write8(address: number, value: number): void;
|
|
2226
|
+
/**
|
|
2227
|
+
* Read `size` bytes little-endian. Split across pages byte by byte rather
|
|
2228
|
+
* than assuming an access stays inside one — unaligned loads that straddle a
|
|
2229
|
+
* page boundary are entirely legal on x86.
|
|
2230
|
+
*/
|
|
2231
|
+
read(address: number, size: number): bigint;
|
|
2232
|
+
write(address: number, size: number, value: bigint): void;
|
|
2233
|
+
readBytes(address: number, length: number): Uint8Array;
|
|
2234
|
+
writeBytes(address: number, bytes: Uint8Array): void;
|
|
2235
|
+
/** Read a NUL-terminated string, as every path argument to a syscall is. */
|
|
2236
|
+
readCString(address: number, limit?: number): string;
|
|
2237
|
+
writeCString(address: number, text: string): number;
|
|
2238
|
+
get pageCount(): number;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
/**
|
|
2242
|
+
* An x86-64 interpreter.
|
|
2243
|
+
*
|
|
2244
|
+
* Decode, execute, repeat. No JIT, no basic-block caching — correctness first,
|
|
2245
|
+
* because a subtly wrong flag is far more expensive to chase than a slow loop.
|
|
2246
|
+
*
|
|
2247
|
+
* Register values are BigInt. That is the slow choice and a deliberate one:
|
|
2248
|
+
* x86-64 semantics are full of 64-bit shifts, sign extensions and
|
|
2249
|
+
* multiplications whose high half matters, and splitting every value into two
|
|
2250
|
+
* 32-bit halves multiplies the number of places a bug can hide by roughly the
|
|
2251
|
+
* number of instructions. Addresses stay as JavaScript numbers, since they fit
|
|
2252
|
+
* in 48 bits and are used on every memory access.
|
|
2253
|
+
*
|
|
2254
|
+
* What is implemented is the integer instruction set a compiler actually emits
|
|
2255
|
+
* for ordinary code: the arithmetic and logic groups, moves and sign/zero
|
|
2256
|
+
* extensions, LEA, the jump/call/return family, conditional moves and sets,
|
|
2257
|
+
* shifts and rotates, multiply and divide, the string operations with their
|
|
2258
|
+
* REP prefixes, the bit-test and bit-scan instructions, CMPXCHG and XADD.
|
|
2259
|
+
* Anything outside that raises `UnsupportedInstruction`, which names the
|
|
2260
|
+
* opcode and where it was found rather than failing silently.
|
|
2261
|
+
*/
|
|
2262
|
+
|
|
2263
|
+
interface CpuOptions {
|
|
2264
|
+
memory: Memory;
|
|
2265
|
+
/** Called on the `syscall` instruction; reads and writes registers directly. */
|
|
2266
|
+
onSyscall: (cpu: Cpu) => void | Promise<void>;
|
|
2267
|
+
/** Abort after this many instructions, so a runaway guest cannot hang the host. */
|
|
2268
|
+
instructionLimit?: number;
|
|
2269
|
+
}
|
|
2270
|
+
declare class Cpu {
|
|
2271
|
+
readonly memory: Memory;
|
|
2272
|
+
readonly regs: BigUint64Array<ArrayBuffer>;
|
|
2273
|
+
rip: number;
|
|
2274
|
+
cf: boolean;
|
|
2275
|
+
zf: boolean;
|
|
2276
|
+
sf: boolean;
|
|
2277
|
+
of: boolean;
|
|
2278
|
+
pf: boolean;
|
|
2279
|
+
af: boolean;
|
|
2280
|
+
df: boolean;
|
|
2281
|
+
/** `%fs` base, which is where thread-local storage lives on x86-64 Linux. */
|
|
2282
|
+
fsBase: number;
|
|
2283
|
+
gsBase: number;
|
|
2284
|
+
/**
|
|
2285
|
+
* The sixteen 128-bit vector registers, split into halves.
|
|
2286
|
+
*
|
|
2287
|
+
* Not optional extras: a compiler zeroes memory with `xorps`/`movups` and
|
|
2288
|
+
* finds a NUL byte with `pcmpeqb`/`pmovmskb`, so ordinary code reaches for
|
|
2289
|
+
* these within the first hundred instructions. Only the moves, the bitwise
|
|
2290
|
+
* operations and the byte compares are implemented — the arithmetic that
|
|
2291
|
+
* makes SSE interesting for numerics is not, and raises the usual error.
|
|
2292
|
+
*/
|
|
2293
|
+
readonly xmmLo: BigUint64Array<ArrayBuffer>;
|
|
2294
|
+
readonly xmmHi: BigUint64Array<ArrayBuffer>;
|
|
2295
|
+
instructions: number;
|
|
2296
|
+
private readonly limit;
|
|
2297
|
+
private readonly onSyscall;
|
|
2298
|
+
private rex;
|
|
2299
|
+
private hasRex;
|
|
2300
|
+
private opSize;
|
|
2301
|
+
private addrSize;
|
|
2302
|
+
private rep;
|
|
2303
|
+
private opcodeStart;
|
|
2304
|
+
constructor(opts: CpuOptions);
|
|
2305
|
+
get(index: number): bigint;
|
|
2306
|
+
set(index: number, value: bigint): void;
|
|
2307
|
+
private fetch8;
|
|
2308
|
+
private fetch;
|
|
2309
|
+
private fetchSigned;
|
|
2310
|
+
/**
|
|
2311
|
+
* Read a register at a given width.
|
|
2312
|
+
*
|
|
2313
|
+
* The 8-bit encodings are the awkward part: with no REX prefix, indices 4–7
|
|
2314
|
+
* name AH/CH/DH/BH — the *high* byte of the first four registers — while any
|
|
2315
|
+
* REX prefix at all reassigns them to SPL/BPL/SIL/DIL.
|
|
2316
|
+
*/
|
|
2317
|
+
private readReg;
|
|
2318
|
+
private writeReg;
|
|
2319
|
+
private modrm;
|
|
2320
|
+
/** Decode a ModRM byte, resolving the effective address when there is one. */
|
|
2321
|
+
private decodeModrm;
|
|
2322
|
+
private ripRelative;
|
|
2323
|
+
/**
|
|
2324
|
+
* Finish a RIP-relative address once the whole instruction has been decoded.
|
|
2325
|
+
* Called by the operand accessors, which run after any immediate is read.
|
|
2326
|
+
*/
|
|
2327
|
+
private effectiveAddress;
|
|
2328
|
+
private readRm;
|
|
2329
|
+
private writeRm;
|
|
2330
|
+
private setLogicFlags;
|
|
2331
|
+
private setAddFlags;
|
|
2332
|
+
private setSubFlags;
|
|
2333
|
+
/** Evaluate one of the sixteen condition codes shared by Jcc/SETcc/CMOVcc. */
|
|
2334
|
+
private condition;
|
|
2335
|
+
push(value: bigint): void;
|
|
2336
|
+
pop(): bigint;
|
|
2337
|
+
/**
|
|
2338
|
+
* Run until the guest exits.
|
|
2339
|
+
*
|
|
2340
|
+
* Yields to the event loop periodically: the interpreter is synchronous, and
|
|
2341
|
+
* without this a long-running guest would block the host completely.
|
|
2342
|
+
*/
|
|
2343
|
+
run(): Promise<number>;
|
|
2344
|
+
/**
|
|
2345
|
+
* Execute one instruction. Returns a promise only when the instruction was a
|
|
2346
|
+
* syscall that needs to await, so the common path allocates nothing.
|
|
2347
|
+
*/
|
|
2348
|
+
step(): Promise<void> | null;
|
|
2349
|
+
/** Set by an `fs:`/`gs:` prefix, consumed by the next memory operand. */
|
|
2350
|
+
private segmentBase;
|
|
2351
|
+
private takeSegment;
|
|
2352
|
+
private execute;
|
|
2353
|
+
/**
|
|
2354
|
+
* Read an immediate that follows the ModRM byte without disturbing the
|
|
2355
|
+
* pending RIP-relative displacement.
|
|
2356
|
+
*
|
|
2357
|
+
* x86 puts the immediate after the address bytes but resolves RIP-relative
|
|
2358
|
+
* addresses against the end of the *whole* instruction, so the immediate has
|
|
2359
|
+
* to be consumed first and the address computed afterwards.
|
|
2360
|
+
*/
|
|
2361
|
+
private peekImmediate;
|
|
2362
|
+
private applyArith;
|
|
2363
|
+
private shift;
|
|
2364
|
+
private setShiftFlags;
|
|
2365
|
+
private group3;
|
|
2366
|
+
/**
|
|
2367
|
+
* MOVS/STOS/LODS/SCAS/CMPS, with the REP prefixes.
|
|
2368
|
+
*
|
|
2369
|
+
* These are how a compiler open-codes `memcpy`, `memset` and `strlen`, so a
|
|
2370
|
+
* program that never calls libc still leans on them heavily.
|
|
2371
|
+
*/
|
|
2372
|
+
private stringOp;
|
|
2373
|
+
private executeTwoByte;
|
|
2374
|
+
private readXmmRm;
|
|
2375
|
+
private writeXmmRm;
|
|
2376
|
+
/** Byte `index` of a 128-bit value held as two halves. */
|
|
2377
|
+
private static byteOf;
|
|
2378
|
+
private static fromBytes;
|
|
2379
|
+
/** Shift each element of a vector register: /2 logical right, /4 arithmetic right, /6 left. */
|
|
2380
|
+
private packedShift;
|
|
2381
|
+
private executeSse;
|
|
2382
|
+
/**
|
|
2383
|
+
* CPUID, reporting a deliberately plain processor.
|
|
2384
|
+
*
|
|
2385
|
+
* Advertising SSE4.2 or AVX would be a trap: glibc's IFUNC resolvers select
|
|
2386
|
+
* string routines by feature bit, and would immediately pick vector code
|
|
2387
|
+
* this interpreter does not implement. Claiming the x86-64 baseline steers
|
|
2388
|
+
* every such resolver to the generic path.
|
|
2389
|
+
*/
|
|
2390
|
+
private cpuid;
|
|
2391
|
+
/** A snapshot for diagnostics when something goes wrong. */
|
|
2392
|
+
describe(): string;
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
/**
|
|
2396
|
+
* Cooperative threads for the emulated machine.
|
|
2397
|
+
*
|
|
2398
|
+
* The interpreter is one JavaScript loop with one register file, so there is no
|
|
2399
|
+
* parallelism to be had. But `clone` cannot simply fail, and it cannot simply
|
|
2400
|
+
* lie either: a Go program launches a monitor thread during startup and treats
|
|
2401
|
+
* a failed `clone` as fatal, while a `clone` that returns a thread id nothing
|
|
2402
|
+
* ever schedules leaves the main thread spinning in `FUTEX_WAIT` on a wake-up
|
|
2403
|
+
* that can never arrive. Both were observed before this existed.
|
|
2404
|
+
*
|
|
2405
|
+
* So threads are real, just not simultaneous. Each holds a saved copy of the
|
|
2406
|
+
* register file, and the scheduler swaps contexts at the points where a thread
|
|
2407
|
+
* cannot make progress anyway — `futex` waits, `sched_yield`, `nanosleep`. That
|
|
2408
|
+
* is enough for the usual shapes: a runtime that wants a background thread it
|
|
2409
|
+
* never waits on, a worker that blocks on a queue, a mutex handed between two
|
|
2410
|
+
* threads.
|
|
2411
|
+
*
|
|
2412
|
+
* What it cannot do is preempt. A thread that spins on a memory location
|
|
2413
|
+
* without ever entering the kernel keeps the machine to itself, exactly as a
|
|
2414
|
+
* cooperative scheduler implies.
|
|
2415
|
+
*/
|
|
2416
|
+
|
|
2417
|
+
declare class Scheduler {
|
|
2418
|
+
private readonly cpu;
|
|
2419
|
+
private readonly threads;
|
|
2420
|
+
private current;
|
|
2421
|
+
private nextId;
|
|
2422
|
+
constructor(cpu: Cpu);
|
|
2423
|
+
get currentId(): number;
|
|
2424
|
+
get count(): number;
|
|
2425
|
+
/**
|
|
2426
|
+
* Start a new thread at the caller's return address.
|
|
2427
|
+
*
|
|
2428
|
+
* A cloned thread resumes exactly where its parent is — immediately after the
|
|
2429
|
+
* `syscall` instruction — with its own stack and `rax` of zero, which is how
|
|
2430
|
+
* the child tells itself apart from the parent.
|
|
2431
|
+
*/
|
|
2432
|
+
spawn(stackPointer: bigint, tlsBase: number, setTls: boolean): number;
|
|
2433
|
+
/** Park the running thread on a futex, and run something else. */
|
|
2434
|
+
wait(address: number, timeoutMs: number, now: number): boolean;
|
|
2435
|
+
/** Make up to `count` threads parked on `address` runnable. Returns how many. */
|
|
2436
|
+
wake(address: number, count: number): number;
|
|
2437
|
+
/** Retire the running thread. Returns false when it was the last one. */
|
|
2438
|
+
exit(now: number): boolean;
|
|
2439
|
+
/**
|
|
2440
|
+
* Switch to the next runnable thread.
|
|
2441
|
+
*
|
|
2442
|
+
* Returns false when there is nothing else to run — the caller then has to
|
|
2443
|
+
* decide whether that is a finished program or a deadlock.
|
|
2444
|
+
*/
|
|
2445
|
+
yieldNow(now: number): boolean;
|
|
2446
|
+
/** True when every thread is parked — a genuine deadlock rather than a wait. */
|
|
2447
|
+
get allBlocked(): boolean;
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
/**
|
|
2451
|
+
* The Linux x86-64 syscall interface, served from the container's filesystem.
|
|
2452
|
+
*
|
|
2453
|
+
* This is the same idea as `wasi-preview1.ts` one layer down. WASI programs ask
|
|
2454
|
+
* for `path_open`; native Linux programs execute the `syscall` instruction with
|
|
2455
|
+
* a number in `rax` and arguments in `rdi, rsi, rdx, r10, r8, r9`, and expect a
|
|
2456
|
+
* result — or a negative errno — back in `rax`. Underneath, both end up in the
|
|
2457
|
+
* same `Vfs`, which is what lets an emulated binary read a file the shell wrote.
|
|
2458
|
+
*
|
|
2459
|
+
* Choosing this as the boundary is the whole design. Above it sits libc, with
|
|
2460
|
+
* two thousand symbols, IFUNC resolvers and symbol versioning; there is no way
|
|
2461
|
+
* to stand in front of that without already emulating instructions. Below it
|
|
2462
|
+
* there are about eighty calls that matter, and they have been stable for
|
|
2463
|
+
* decades. So libc runs as the machine code it is, and only its syscalls are
|
|
2464
|
+
* answered here.
|
|
2465
|
+
*/
|
|
2466
|
+
|
|
2467
|
+
interface LinuxOptions {
|
|
2468
|
+
vfs: Vfs;
|
|
2469
|
+
cred: Cred;
|
|
2470
|
+
cwd: string;
|
|
2471
|
+
memory: Memory;
|
|
2472
|
+
stdin: Uint8Array;
|
|
2473
|
+
stdout: (bytes: Uint8Array) => void;
|
|
2474
|
+
stderr: (bytes: Uint8Array) => void;
|
|
2475
|
+
hostname: string;
|
|
2476
|
+
now: () => number;
|
|
2477
|
+
/** Names every syscall the guest makes, for working out where it stalls. */
|
|
2478
|
+
trace?: (line: string) => void;
|
|
2479
|
+
}
|
|
2480
|
+
declare class LinuxSyscalls {
|
|
2481
|
+
private readonly files;
|
|
2482
|
+
private nextFd;
|
|
2483
|
+
private stdinPosition;
|
|
2484
|
+
private cwd;
|
|
2485
|
+
readonly opts: LinuxOptions;
|
|
2486
|
+
/** Syscall numbers the guest asked for that are not implemented. */
|
|
2487
|
+
readonly unimplemented: Set<number>;
|
|
2488
|
+
/**
|
|
2489
|
+
* Descriptors with no file behind them — epoll sets and eventfds. Tracked so
|
|
2490
|
+
* `close` accepts them and `epoll_ctl` can tell a real one from a wrong one.
|
|
2491
|
+
*/
|
|
2492
|
+
private readonly pseudoFds;
|
|
2493
|
+
/** Threads the guest believes it started; see the `clone` case. */
|
|
2494
|
+
threadsRequested: number;
|
|
2495
|
+
constructor(opts: LinuxOptions);
|
|
2496
|
+
/** Thread scheduler, attached once the CPU exists. */
|
|
2497
|
+
scheduler: Scheduler | null;
|
|
2498
|
+
/**
|
|
2499
|
+
* Set by a syscall that switched threads. The result register then belongs to
|
|
2500
|
+
* the thread we switched *to*, and must not be overwritten with ours.
|
|
2501
|
+
*/
|
|
2502
|
+
private switched;
|
|
2503
|
+
/** Entry point handed to the CPU. */
|
|
2504
|
+
handle: (cpu: Cpu) => void;
|
|
2505
|
+
private errnoOf;
|
|
2506
|
+
private resolve;
|
|
2507
|
+
private dispatch;
|
|
2508
|
+
private writeTo;
|
|
2509
|
+
private flush;
|
|
2510
|
+
private open;
|
|
2511
|
+
/** Fill a 144-byte `struct stat`. */
|
|
2512
|
+
private stat;
|
|
2513
|
+
/** `fstat` on stdin/stdout/stderr, which are character devices. */
|
|
2514
|
+
private statCharDevice;
|
|
2515
|
+
private getdents64;
|
|
2516
|
+
private writeBounded;
|
|
2517
|
+
/** `struct utsname`: six fixed 65-byte character arrays. */
|
|
2518
|
+
private uname;
|
|
2519
|
+
/** Persist everything still open. Called when the guest exits. */
|
|
2520
|
+
close(): void;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
/**
|
|
2524
|
+
* Native x86-64 Linux binaries, as an executable format.
|
|
2525
|
+
*
|
|
2526
|
+
* The kernel sees `\x7fELF`, hands the file here, and this boots a machine for
|
|
2527
|
+
* it: memory, a CPU, a Linux syscall layer over the container's filesystem, and
|
|
2528
|
+
* the process image a program expects at `_start`. From the guest's point of
|
|
2529
|
+
* view it is running on Linux. From the shell's point of view it is a command.
|
|
2530
|
+
*
|
|
2531
|
+
* Static binaries — `CGO_ENABLED=0 go build`, Rust's `musl` target, anything
|
|
2532
|
+
* linked with `-static` — need nothing else, because everything they call is
|
|
2533
|
+
* inside them and everything below that is a syscall.
|
|
2534
|
+
*
|
|
2535
|
+
* A dynamically linked binary needs its loader, and this will use a real one:
|
|
2536
|
+
* if `PT_INTERP` names a file that exists in the container, both images are
|
|
2537
|
+
* mapped and control goes to the loader, exactly as the kernel does it. That is
|
|
2538
|
+
* why `ld-linux-x86-64.so.2` and `libc.so.6` are not simulated — they are
|
|
2539
|
+
* ordinary x86-64 code, and a machine that can run the program can run them.
|
|
2540
|
+
* Copy them in and dynamic binaries work; leave them out and the failure says
|
|
2541
|
+
* precisely which file was missing.
|
|
2542
|
+
*/
|
|
2543
|
+
|
|
2544
|
+
interface RunElfOptions {
|
|
2545
|
+
/** Stop after this many instructions rather than spinning forever. */
|
|
2546
|
+
instructionLimit?: number;
|
|
2547
|
+
/** Called with every syscall, for diagnosing where a program stalls. */
|
|
2548
|
+
trace?: (line: string) => void;
|
|
2549
|
+
}
|
|
2550
|
+
/**
|
|
2551
|
+
* Load and run an x86-64 Linux binary.
|
|
2552
|
+
*
|
|
2553
|
+
* Returns the guest's exit status, or throws with something specific about why
|
|
2554
|
+
* it could not start — a missing loader, an instruction outside the implemented
|
|
2555
|
+
* set, or a wild memory access.
|
|
2556
|
+
*/
|
|
2557
|
+
declare function runElf(ctx: ExecContext, path: string, opts?: RunElfOptions): Promise<number>;
|
|
2558
|
+
/**
|
|
2559
|
+
* The ELF handler. Unlike the stub it replaces, this one runs the file.
|
|
2560
|
+
*/
|
|
2561
|
+
declare const elfFormat: ExecFormat;
|
|
2562
|
+
declare function x86Commands(): Command[];
|
|
2563
|
+
|
|
2029
2564
|
/**
|
|
2030
2565
|
* sandboxedjs — a Linux-like container that runs entirely inside Node.js.
|
|
2031
2566
|
*
|
|
@@ -2044,4 +2579,4 @@ declare const NPM_VERSION = "10.9.0";
|
|
|
2044
2579
|
* ```
|
|
2045
2580
|
*/
|
|
2046
2581
|
|
|
2047
|
-
export { ArithError, BufferSink, CallbackSink, type Command, CommandRegistry, Container, ContainerFs, type ContainerOptions, type ContextInit, type Cred, type DirEntry, ERRNO, type Env, type ErrnoCode, type ExecContext, type ExecOptions, type ExecResult, FileInput, FileOutput, type GroupEntry, type HttpResponse, IncompleteInputError, type InputStream, type Job, KERNEL_NAME, KERNEL_RELEASE, Kernel, type KernelOptions, type ListeningPort, type MountEntry, NODE_VERSION, NPM_VERSION, type NetInterface, type NetworkOptions, NetworkStack, NullInput, NullOutput, OS_RELEASE, type OutputStream, PYTHON_VERSION, type PasswdEntry, Pipe, Process, type ProcessKind, type ProcessOptions, type ProcessState, ProcessTable, ROOT_CRED, type ResolvedExecutable, type RootfsOptions, type RunOptions, type RunResult, SIGNALS, SIGNAL_NAMES, Session, type SessionInit, type SessionResult, type SessionRunOptions, Shell, ShellExit, type ShellIO, type ShellInit, Lexer as ShellLexer, type ShellOptions, ShellSyntaxError, type SpawnHandle, Stats, type Stdio, SysError, TeeOutput, Terminal, type TerminalOptions, UserDatabase, Variables, Vfs, type VirtualNode, type VirtualProvider, type WriteOptions, allCommands, applyChmod, braceExpand, buildRootfs, builtinNames, captureStdio, createContainer, createContext, createContainer as default, defineCommand, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, installUserland, isBuiltinName, isPythonAvailable, isSysError, makeCred, normalizeSignal, octalMode, parse as parseShell, parseUmask, path as posixPath, resetPidCounter, shellQuote, strerror, unameInfo };
|
|
2582
|
+
export { ArithError, BufferSink, CallbackSink, type Command, CommandRegistry, Container, ContainerFs, type ContainerOptions, type ContextInit, type Cred, type DirEntry, ERRNO, type Env, type ErrnoCode, type ExecContext, type ExecFormat, ExecFormatRegistry, type ExecOptions, type ExecResult, FileInput, FileOutput, type GroupEntry, Memory as GuestMemory, type HttpResponse, IncompleteInputError, type InputStream, type Job, KERNEL_NAME, KERNEL_RELEASE, Kernel, type KernelOptions, LinuxSyscalls, type ListeningPort, MAGIC_BYTES, type MountEntry, NODE_VERSION, NPM_VERSION, type NetInterface, type NetworkOptions, NetworkStack, NullInput, NullOutput, OS_RELEASE, type OutputStream, PYTHON_VERSION, type PasswdEntry, Pipe, Process, type ProcessKind, type ProcessOptions, type ProcessState, ProcessTable, ROOT_CRED, type ResolvedExecutable, type RootfsOptions, type RunOptions, type RunResult, SIGNALS, SIGNAL_NAMES, Session, type SessionInit, type SessionResult, type SessionRunOptions, Shell, ShellExit, type ShellIO, type ShellInit, Lexer as ShellLexer, type ShellOptions, ShellSyntaxError, type SpawnHandle, Stats, type Stdio, SysError, TeeOutput, Terminal, type TerminalOptions, UserDatabase, Variables, Vfs, type VirtualNode, type VirtualProvider, WasiExit, type WasiOptions, WasiPreview1, type WasmAnalysis, type WasmFlavour, type WriteOptions, Cpu as X86Cpu, allCommands, analyse as analyseWasm, applyChmod, braceExpand, buildRootfs, builtinNames, captureStdio, clearWasmCache, createContainer, createContext, createContainer as default, defineCommand, elfFormat, evalArith, exitCodeForSignal, expandPrompt, expandWord, expandWords, fnmatch, formatMode, getBuiltin, glob, globToRegex, hasMagic, installUserland, isBuiltinName, isPythonAvailable, isSysError, isWasmBinary, machoFormat, makeCred, nativeFormats, normalizeSignal, octalMode, parse as parseShell, parseUmask, peFormat, path as posixPath, resetPidCounter, runElf, runWasi, shellQuote, strerror, unameInfo, wasmFormat, x86Commands };
|