diff options
Diffstat (limited to 'services/sandbox-exec')
| -rw-r--r-- | services/sandbox-exec/.dockerignore | 1 | ||||
| -rw-r--r-- | services/sandbox-exec/.gitignore | 2 | ||||
| -rw-r--r-- | services/sandbox-exec/Dockerfile | 83 | ||||
| -rw-r--r-- | services/sandbox-exec/index.js | 38 | ||||
| -rw-r--r-- | services/sandbox-exec/lib/exec.js | 62 | ||||
| -rw-r--r-- | services/sandbox-exec/lib/exec_standalone.js | 67 | ||||
| -rw-r--r-- | services/sandbox-exec/package-lock.json | 33 | ||||
| -rw-r--r-- | services/sandbox-exec/package.json | 16 | ||||
| -rw-r--r-- | services/sandbox-exec/wasm/php-wasm.c | 24 |
9 files changed, 326 insertions, 0 deletions
diff --git a/services/sandbox-exec/.dockerignore b/services/sandbox-exec/.dockerignore new file mode 100644 index 0000000..07e6e47 --- /dev/null +++ b/services/sandbox-exec/.dockerignore @@ -0,0 +1 @@ +/node_modules diff --git a/services/sandbox-exec/.gitignore b/services/sandbox-exec/.gitignore new file mode 100644 index 0000000..89c10d6 --- /dev/null +++ b/services/sandbox-exec/.gitignore @@ -0,0 +1,2 @@ +/lib/php-wasm.* +/node_modules diff --git a/services/sandbox-exec/Dockerfile b/services/sandbox-exec/Dockerfile new file mode 100644 index 0000000..1b19e70 --- /dev/null +++ b/services/sandbox-exec/Dockerfile @@ -0,0 +1,83 @@ +FROM emscripten/emsdk:3.1.46 AS wasm-builder + +RUN git clone --depth=1 --branch=php-8.2.10 https://github.com/php/php-src + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + autoconf \ + bison \ + pkg-config \ + re2c \ + && \ + : + +RUN cd php-src && \ + ./buildconf --force && \ + emconfigure ./configure \ + --disable-all \ + --disable-mbregex \ + --disable-fiber-asm \ + --disable-cli \ + --disable-cgi \ + --disable-phpdbg \ + --enable-embed=static \ + --enable-short-tags \ + --enable-mbstring \ + --without-iconv \ + --without-libxml \ + --without-pcre-jit \ + --without-pdo-sqlite \ + --without-sqlite3 \ + && \ + EMCC_CFLAGS='-s ERROR_ON_UNDEFINED_SYMBOLS=0' emmake make -j$(nproc) && \ + mv libs/libphp.a .. && \ + make clean && \ + git clean -fd && \ + : + +COPY wasm/php-wasm.c /src/ + +RUN cd php-src && \ + emcc \ + -c \ + -o php-wasm.o \ + -I . \ + -I TSRM \ + -I Zend \ + -I main \ + ../php-wasm.c \ + && \ + mv php-wasm.o .. && \ + make clean && \ + git clean -fd && \ + : + +RUN emcc \ + -s ENVIRONMENT=node \ + -s ERROR_ON_UNDEFINED_SYMBOLS=0 \ + -s EXPORTED_RUNTIME_METHODS='["ccall"]' \ + -s EXPORT_ES6=1 \ + -s INITIAL_MEMORY=16777216 \ + -s INVOKE_RUN=0 \ + -s MODULARIZE=1 \ + -o php-wasm.js \ + php-wasm.o \ + libphp.a \ + ; + +FROM node:20.7 + +WORKDIR /app + +RUN mkdir /app/lib + +COPY --from=wasm-builder /src/php-wasm.js /src/php-wasm.wasm /app/lib/ + +COPY index.js package.json package-lock.json /app/ +COPY lib/exec.js /app/lib/ + +RUN npm install + +ENTRYPOINT ["node", "index.js"] + +EXPOSE 8888 diff --git a/services/sandbox-exec/index.js b/services/sandbox-exec/index.js new file mode 100644 index 0000000..8c256ab --- /dev/null +++ b/services/sandbox-exec/index.js @@ -0,0 +1,38 @@ +import { fork } from 'node:child_process' +import { serve } from '@hono/node-server' +import { Hono } from 'hono' + +const execPhp = (code, input, timeoutMsec) => { + return new Promise((resolve, _reject) => { + const proc = fork('./lib/exec.js'); + + proc.send({ code, input }); + + proc.on('message', (result) => { + resolve(result); + proc.kill(); + }); + + setTimeout(() => { + resolve({ + status: 'TLE', + stdout: '', + stderr: `Time Limit Exceeded: ${timeoutMsec} msec`, + }); + proc.kill(); + }, timeoutMsec); + }); +}; + +const app = new Hono(); + +app.post('/exec', async (c) => { + const { code, input, timeout } = await c.req.json(); + const result = await execPhp(code, input, timeout); + return c.json(result); +}); + +serve({ + fetch: app.fetch, + port: 8888, +}) diff --git a/services/sandbox-exec/lib/exec.js b/services/sandbox-exec/lib/exec.js new file mode 100644 index 0000000..e37dbb5 --- /dev/null +++ b/services/sandbox-exec/lib/exec.js @@ -0,0 +1,62 @@ +import PHPWasm from './php-wasm.js' + +process.once('message', async ({ code, input }) => { + const PRELUDE = ` + define('STDIN', fopen('php://stdin', 'r')); + define('STDOUT', fopen('php://stdout', 'r')); + define('STDERR', fopen('php://stderr', 'r')); + + `; + const BUFFER_MAX = 1024 * 1024 * 1; + + let stdinPos = 0; // bytewise + let stdinBuf = Buffer.from(input); + let stdoutPos = 0; // bytewise + let stdoutBuf = Buffer.alloc(BUFFER_MAX); + let stderrPos = 0; // bytewise + let stderrBuf = Buffer.alloc(BUFFER_MAX); + + const { ccall } = await PHPWasm({ + stdin: () => { + if (stdinBuf.length <= stdinPos) { + return null; + } + return stdinBuf.readUInt8(stdinPos++); + }, + stdout: (asciiCode) => { + if (asciiCode === null) { + return; // flush + } + if (BUFFER_MAX <= stdoutPos) { + return; // ignore + } + if (asciiCode < 0) { + asciiCode += 256; + } + stdoutBuf.writeUInt8(asciiCode, stdoutPos++); + }, + stderr: (asciiCode) => { + if (asciiCode === null) { + return; // flush + } + if (BUFFER_MAX <= stderrPos) { + return; // ignore + } + if (asciiCode < 0) { + asciiCode += 256; + } + stderrBuf.writeUInt8(asciiCode, stderrPos++); + }, + }); + + const result = ccall( + 'php_wasm_run', + 'number', ['string'], + [PRELUDE + code], + ); + process.send({ + status: result === 0 ? 'AC' : 'RE', + stdout: stdoutBuf.subarray(0, stdoutPos).toString(), + stderr: stderrBuf.subarray(0, stderrPos).toString(), + }); +}); diff --git a/services/sandbox-exec/lib/exec_standalone.js b/services/sandbox-exec/lib/exec_standalone.js new file mode 100644 index 0000000..3b34adb --- /dev/null +++ b/services/sandbox-exec/lib/exec_standalone.js @@ -0,0 +1,67 @@ +import { readFileSync } from 'node:fs'; +import PHPWasm from './php-wasm.js' + +const userCode = ` +while ($l = fgets(STDIN)) { + echo $l, PHP_EOL; +} +`; + +const PRELUDE = ` +define('STDIN', fopen('php://stdin', 'r')); +define('STDOUT', fopen('php://stdout', 'r')); +define('STDERR', fopen('php://stderr', 'r')); + +`; +const BUFFER_MAX = 1024 * 1024 * 1; + +let stdinPos = 0; // bytewise +let stdinBuf = Buffer.from(readFileSync('/dev/stdin')); +let stdoutPos = 0; // bytewise +let stdoutBuf = Buffer.alloc(BUFFER_MAX); +let stderrPos = 0; // bytewise +let stderrBuf = Buffer.alloc(BUFFER_MAX); + +const { ccall } = await PHPWasm({ + stdin: () => { + if (stdinBuf.length <= stdinPos) { + return null; + } + return stdinBuf.readUInt8(stdinPos++); + }, + stdout: (asciiCode) => { + if (asciiCode === null) { + return; // flush + } + if (BUFFER_MAX <= stdoutPos) { + return; // ignore + } + if (asciiCode < 0) { + asciiCode += 256; + } + stdoutBuf.writeUInt8(asciiCode, stdoutPos++); + }, + stderr: (asciiCode) => { + if (asciiCode === null) { + return; // flush + } + if (BUFFER_MAX <= stderrPos) { + return; // ignore + } + if (asciiCode < 0) { + asciiCode += 256; + } + stderrBuf.writeUInt8(asciiCode, stderrPos++); + }, +}); + +const result = ccall( + 'php_wasm_run', + 'number', ['string'], + [PRELUDE + userCode], +); +console.log({ + status: result === 0 ? 'AC' : 'RE', + stdout: stdoutBuf.subarray(0, stdoutPos).toString(), + stderr: stderrBuf.subarray(0, stderrPos).toString(), +}); diff --git a/services/sandbox-exec/package-lock.json b/services/sandbox-exec/package-lock.json new file mode 100644 index 0000000..332be88 --- /dev/null +++ b/services/sandbox-exec/package-lock.json @@ -0,0 +1,33 @@ +{ + "name": "sandbox-exec", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sandbox-exec", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.2.0", + "hono": "^3.7.2" + } + }, + "node_modules/@hono/node-server": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.2.0.tgz", + "integrity": "sha512-aHT8lDMLpd7ioXJ1/057+h+oE/k7rCOWmjklYDsE0jE4CoNB9XzG4f8dRHvw4s5HJFocaYDiGgYM/V0kYbQ0ww==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/hono": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/hono/-/hono-3.7.2.tgz", + "integrity": "sha512-5SWYrAQJlfjHggcDTnmKZd5zlUEXmoUiBjnmL6C1W8MX39/bUw6ZIvfEJZgpo7d7Z/vCJ5FRfkjIQPRH5yV/dQ==", + "engines": { + "node": ">=16.0.0" + } + } + } +} diff --git a/services/sandbox-exec/package.json b/services/sandbox-exec/package.json new file mode 100644 index 0000000..3c8a857 --- /dev/null +++ b/services/sandbox-exec/package.json @@ -0,0 +1,16 @@ +{ + "name": "sandbox-exec", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "nsfisis", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.2.0", + "hono": "^3.7.2" + } +} diff --git a/services/sandbox-exec/wasm/php-wasm.c b/services/sandbox-exec/wasm/php-wasm.c new file mode 100644 index 0000000..cef661f --- /dev/null +++ b/services/sandbox-exec/wasm/php-wasm.c @@ -0,0 +1,24 @@ +#include <stdio.h> +#include <emscripten.h> +#include <Zend/zend_execute.h> +#include <sapi/embed/php_embed.h> + +int EMSCRIPTEN_KEEPALIVE php_wasm_run(const char* code) { + zend_result result; + + int argc = 1; + char* argv[] = { "php.wasm", NULL }; + + PHP_EMBED_START_BLOCK(argc, argv); + + result = zend_eval_string_ex(code, NULL, "php.wasm code", 1); + + PHP_EMBED_END_BLOCK(); + + fprintf(stdout, "\n"); + fflush(stdout); + fprintf(stderr, "\n"); + fflush(stderr); + + return result == SUCCESS ? 0 : 1; +} |
