Skip to content

Commit ee89f75

Browse files
Merge pull request #12 from codewit-us/enhancement/handling-cpp-fork-attacks
Handling C++ Fork Bombs
2 parents 39f94a9 + 2bc30eb commit ee89f75

File tree

2 files changed

+77
-34
lines changed

2 files changed

+77
-34
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ FROM node:16-alpine
33
RUN apk update && apk add --no-cache \
44
g++ \
55
git \
6+
bash \
67
openjdk11 \
78
python3 \
89
py3-pip \

executor.js

Lines changed: 76 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -551,55 +551,97 @@ function compileCode(command, args, cwd) {
551551
*/
552552
function runProgram(command, args, stdin = '', timeout = 3000) {
553553
return new Promise((resolve, reject) => {
554-
const process = spawn(command, args, { cwd: path.dirname(command) });
554+
const shell = 'bash';
555+
const wrapperArgs = [
556+
'-c',
557+
`ulimit -u 50; ulimit -f 20480; exec "$@"`,
558+
'cmd',
559+
command,
560+
...args
561+
];
555562

556563
let stdout = '';
557564
let stderr = '';
558565
let finished = false;
566+
let killedByEvaluator = false;
567+
568+
const proc = spawn(shell, wrapperArgs, {
569+
cwd: path.dirname(command),
570+
detached: true,
571+
stdio: ['pipe','pipe','pipe']
572+
});
573+
574+
// Resource monitor (memory/threads)
575+
const interval = setInterval(async () => {
576+
try {
577+
// Example: use pidusage or ps to get memory/threads
578+
// let info = await pidusage(proc.pid);
579+
// if (info.memory > 300 * 1024 * 1024 || info.threadCount > 200) {
580+
// killedByEvaluator = true;
581+
// killGroup(proc.pid);
582+
// clearInterval(interval);
583+
// }
584+
} catch (_) {}
585+
}, 100);
586+
587+
const killGroup = (pid) => {
588+
killedByEvaluator = true;
589+
try { process.kill(-pid, 'SIGTERM'); } catch (_) {}
590+
setTimeout(() => { try { process.kill(-pid, 'SIGKILL'); } catch (_) {} }, 400);
591+
};
559592

560593
const timer = setTimeout(() => {
561594
if (!finished) {
562-
process.kill('SIGTERM');
563-
console.error('Execution timed out.');
564-
reject(new Error('Execution timed out'));
595+
killGroup(proc.pid);
596+
clearInterval(interval);
597+
finished = true;
598+
return reject(new Error('Execution timed out'));
565599
}
566600
}, timeout);
567601

568602
if (stdin) {
569-
process.stdin.write(stdin);
603+
proc.stdin.write(stdin);
570604
}
571-
process.stdin.end();
572-
573-
process.stdout.on('data', (data) => {
574-
stdout += data.toString();
575-
});
576-
577-
process.stderr.on('data', (data) => {
578-
stderr += data.toString();
579-
});
605+
proc.stdin.end();
606+
607+
proc.stdout.on('data', d => stdout += d.toString());
608+
proc.stderr.on('data', d => stderr += d.toString());
609+
610+
proc.on('close', (code, signal) => {
611+
if (finished) return;
612+
613+
clearTimeout(timer);
614+
clearInterval(interval);
615+
finished = true;
616+
617+
// If killed via signal (timeout/resource limits)
618+
if (signal || killedByEvaluator) {
619+
const reason = signal
620+
? `terminated by signal ${signal}`
621+
: 'terminated by evaluator';
622+
const err = new Error(`Execution terminated: ${reason}`);
623+
err.stdout = stdout;
624+
err.stderr = stderr;
625+
return reject(err);
626+
}
580627

581-
process.on('close', (code) => {
582-
if (!finished) {
583-
clearTimeout(timer);
584-
finished = true;
585-
if (code !== 0) {
586-
console.error(`Execution failed with code ${code}: stderr: ${stderr}, stdout: ${stdout}`);
587-
const error = new Error(`Execution failed with code ${code}`);
588-
error.stdout = stdout;
589-
error.stderr = stderr;
590-
return reject(error);
591-
}
592-
resolve({ stdout, stderr });
628+
// Normal exit code check
629+
if (code !== 0) {
630+
const err = new Error(`Execution failed with code ${code}`);
631+
err.stdout = stdout;
632+
err.stderr = stderr;
633+
return reject(err);
593634
}
635+
636+
resolve({ stdout, stderr });
594637
});
595638

596-
process.on('error', (err) => {
597-
if (!finished) {
598-
clearTimeout(timer);
599-
finished = true;
600-
console.error(`Failed to start execution process: ${err.message}`);
601-
reject(err);
602-
}
639+
proc.on('error', err => {
640+
if (finished) return;
641+
clearTimeout(timer);
642+
clearInterval(interval);
643+
finished = true;
644+
reject(new Error(`Failed to start process: ${err.message}`));
603645
});
604646
});
605647
}
@@ -610,7 +652,7 @@ function runProgram(command, args, stdin = '', timeout = 3000) {
610652
*/
611653
async function cleanupDir(dirPath) {
612654
try {
613-
await fs.rm(dirPath, { recursive: true, force: true }); // if you really want to delete
655+
await fs.rm(dirPath, { recursive: true, force: true });
614656
console.log(`Successfully deleted directory: ${dirPath}`);
615657
} catch (err) {
616658
console.error(`Failed to delete directory ${dirPath}: ${err.message}`);

0 commit comments

Comments
 (0)