aboutsummaryrefslogtreecommitdiffhomepage
path: root/services/sandbox-exec
diff options
context:
space:
mode:
Diffstat (limited to 'services/sandbox-exec')
-rw-r--r--services/sandbox-exec/.dockerignore1
-rw-r--r--services/sandbox-exec/.gitignore2
-rw-r--r--services/sandbox-exec/Dockerfile83
-rw-r--r--services/sandbox-exec/index.js38
-rw-r--r--services/sandbox-exec/lib/exec.js62
-rw-r--r--services/sandbox-exec/lib/exec_standalone.js67
-rw-r--r--services/sandbox-exec/package-lock.json33
-rw-r--r--services/sandbox-exec/package.json16
-rw-r--r--services/sandbox-exec/wasm/php-wasm.c24
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;
+}