cred: isolate scripts in WASI and move RSA signing to AWS-LC
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import * as std from "qjs:std";
|
||||
import * as os from "qjs:os";
|
||||
import * as bjson from "qjs:bjson";
|
||||
|
||||
// See quickjs.h
|
||||
const JS_READ_OBJ_BYTECODE = 1 << 0;
|
||||
const JS_READ_OBJ_REFERENCE = 1 << 3;
|
||||
const JS_WRITE_OBJ_BYTECODE = 1 << 0;
|
||||
const JS_WRITE_OBJ_REFERENCE = 1 << 3;
|
||||
const JS_WRITE_OBJ_STRIP_SOURCE = 1 << 4;
|
||||
|
||||
/**
|
||||
* Trailer for standalone binaries. When some code gets bundled with the qjs
|
||||
* executable we add a 12 byte trailer. The first 8 bytes are the magic
|
||||
* string that helps us understand this is a standalone binary, and the
|
||||
* remaining 4 are the offset (from the beginning of the binary) where the
|
||||
* bundled data is located.
|
||||
*
|
||||
* The offset is stored as a 32bit little-endian number.
|
||||
*/
|
||||
const Trailer = {
|
||||
Magic: 'quickjs2',
|
||||
MagicSize: 8,
|
||||
DataSize: 4,
|
||||
Size: 12
|
||||
};
|
||||
|
||||
function encodeAscii(txt) {
|
||||
return new Uint8Array(txt.split('').map(c => c.charCodeAt(0)));
|
||||
}
|
||||
|
||||
function decodeAscii(buf) {
|
||||
return Array.from(buf).map(c => String.fromCharCode(c)).join('')
|
||||
}
|
||||
|
||||
export function compileStandalone(inFile, outFile, targetExe) {
|
||||
// Step 1: compile the source file to bytecode
|
||||
const js = std.loadFile(inFile);
|
||||
|
||||
if (!js) {
|
||||
throw new Error(`failed to open ${inFile}`);
|
||||
}
|
||||
|
||||
const code = std.evalScript(js, {
|
||||
compile_only: true,
|
||||
compile_module: true
|
||||
});
|
||||
const bytecode = new Uint8Array(bjson.write(code, JS_WRITE_OBJ_BYTECODE | JS_WRITE_OBJ_REFERENCE | JS_WRITE_OBJ_STRIP_SOURCE));
|
||||
|
||||
// Step 2: copy the bytecode to the end of the executable and add a marker.
|
||||
const exeFileName = targetExe ?? os.exePath() ?? globalThis.argv0;
|
||||
const exe = std.loadFile(exeFileName, { binary: true });
|
||||
|
||||
if (!exe) {
|
||||
throw new Error(`failed to open executable: ${exeFileName}`);
|
||||
}
|
||||
|
||||
const exeSize = exe.length;
|
||||
const newBuffer = exe.buffer.transfer(exeSize + bytecode.length + Trailer.Size);
|
||||
const newExe = new Uint8Array(newBuffer);
|
||||
|
||||
newExe.set(bytecode, exeSize);
|
||||
newExe.set(encodeAscii(Trailer.Magic), exeSize + bytecode.length);
|
||||
|
||||
const dw = new DataView(newBuffer, exeSize + bytecode.length + Trailer.MagicSize, Trailer.DataSize);
|
||||
|
||||
dw.setUint32(0, exeSize, true /* little-endian */);
|
||||
|
||||
// We use os.open() so we can set the permissions mask.
|
||||
const newFd = os.open(outFile, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o755);
|
||||
|
||||
if (newFd < 0) {
|
||||
throw new Error(`failed to create ${outFile}`);
|
||||
}
|
||||
if (os.write(newFd, newBuffer, 0, newBuffer.byteLength) < 0) {
|
||||
os.close(newFd);
|
||||
throw new Error(`failed to write to output file`);
|
||||
}
|
||||
os.close(newFd);
|
||||
}
|
||||
|
||||
export function runStandalone() {
|
||||
const file = os.exePath() ?? globalThis.argv0;
|
||||
const exe = std.open(file, 'rb');
|
||||
|
||||
if (!exe) {
|
||||
throw new Error(`failed to open executable: ${file}`);
|
||||
}
|
||||
|
||||
let r = exe.seek(-Trailer.Size, std.SEEK_END);
|
||||
if (r < 0) {
|
||||
throw new Error(`seek error: ${-r}`);
|
||||
}
|
||||
|
||||
const trailer = new Uint8Array(Trailer.Size);
|
||||
|
||||
exe.read(trailer.buffer, 0, Trailer.Size);
|
||||
|
||||
const magic = new Uint8Array(trailer.buffer, 0, Trailer.MagicSize);
|
||||
|
||||
// Shouldn't happen since qjs.c checks for it.
|
||||
if (decodeAscii(magic) !== Trailer.Magic) {
|
||||
exe.close();
|
||||
throw new Error('corrupted binary, magic mismatch');
|
||||
}
|
||||
|
||||
const dw = new DataView(trailer.buffer, Trailer.MagicSize, Trailer.DataSize);
|
||||
const offset = dw.getUint32(0, true /* little-endian */);
|
||||
const bytecode = new Uint8Array(offset - Trailer.Size);
|
||||
|
||||
r = exe.seek(offset, std.SEEK_SET);
|
||||
if (r < 0) {
|
||||
exe.close();
|
||||
throw new Error(`seek error: ${-r}`);
|
||||
}
|
||||
|
||||
exe.read(bytecode.buffer, 0, bytecode.length);
|
||||
if (exe.error()) {
|
||||
exe.close();
|
||||
throw new Error('read error');
|
||||
}
|
||||
exe.close();
|
||||
|
||||
const code = bjson.read(bytecode.buffer, 0, bytecode.length, JS_READ_OBJ_BYTECODE | JS_READ_OBJ_REFERENCE);
|
||||
|
||||
return std.evalScript(code, {
|
||||
eval_module: true
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user