Wecode uses Docker containers to execute student code in isolated, secure environments with strict resource limits.
Overview
Docker sandboxing provides:
- Isolation: Each submission runs in its own container
- Resource Limits: CPU time and memory constraints
- Security: Network isolation and restricted permissions
- Consistency: Same execution environment for all submissions
Docker Configuration
Language-Specific Images
Each language uses a specific Docker image:
languages_to_docker["c"]="gcc:14"
languages_to_docker["cpp"]="gcc:14"
languages_to_docker["py2"]="python:2"
languages_to_docker["py3"]="python:3"
languages_to_docker["numpy"]="truongan/wecodejudge:numpy"
languages_to_docker["java"]="openjdk:8"
languages_to_docker["pas"]="nacyot/pascal-fp_compiler:apt"
languages_to_docker["js"]="node:20"
Docker Execution Script
The run_judge_in_docker.sh script handles secure container execution:
#!/bin/bash
# This script is design to run the tester process in a docker container using sudo
# tester process require the $JAIL directory to be shared between the host and the container
# This script check if the sharing folder is the subfolder of ${HOME}
# It's to ensure user cannot abuse the scritp to escalate their privilege and share the whole system to the containter
# Usage: run_judge_in_docker share_directory docker_image command
share_directory=${1}
docker_image=${2}
shift 2
command=$@
owner=`stat -c '%U' $share_directory`
USER=`whoami`
dockername="$USER-$(basename $share_directory)"
if [ "$owner" = "$USER" ] || [ "$owner" = "$SUDO_USER" ]
then
docker run --rm \
-v $share_directory:/work:rw \
--name=$dockername \
--network none \
-u$UID \
-w /work \
$docker_image \
$command
EC=$?
exit $EC
else
echo "Share directory '$share_directory' does not belongs in your home directory '${HOME}'"
fi
Security Features
Critical Security Checks:
- Verifies directory ownership before mounting
- Prevents privilege escalation by checking directory ownership
- Ensures only user-owned directories can be shared with containers
Container Restrictions:
--rm: Automatically removes container after execution
--network none: No network access (prevents external communication)
-u$UID: Runs as the current user (not root)
-w /work: Sets working directory inside container
Resource Limits
Time Limits
Time limits are enforced at multiple levels:
TIMELIMIT=${7} # time limit in seconds (float)
TIMELIMITINT=${8} # integer time limit (should be > TIMELIMIT)
# Imposing time limit with ulimit
ulimit -t $TIMELIMITINT
if $TIMEOUT_EXISTS; then
# Run the command with REAL time limit of TIMELIMITINT*2
timeout -s9 $((TIMELIMITINT*2)) $CMD <$IN >out 2>err
else
# Run the command
$CMD <$IN >out 2>err
fi
Memory Limits
MEMLIMIT=${9} # memory limit in kB
For Python, additional memory is allocated for interpreter overhead:
if [ $EXT == "py2" ]; then
mem=$(pid=$(python2 >/dev/null 2>/dev/null & echo $!) && ps -p $pid -o vsz=; kill $pid >/dev/null 2>/dev/null;)
MEMLIMIT=$((MEMLIMIT+mem+5000))
elif [ $EXT == "py3" ]; then
mem=$(pid=$(python3 >/dev/null 2>/dev/null & echo $!) && ps -p $pid -o vsz=; kill $pid >/dev/null 2>/dev/null;)
MEMLIMIT=$((MEMLIMIT+mem+5000))
fi
For Java, memory is controlled via JVM flags:
languages_to_comm["java"]="java -Xmx${MEMLIMIT}k -Xms${MEMLIMIT}k solution"
Output Size Limits
OUTLIMIT=${10} # output size limit in Bytes
Compilation in Docker
C/C++ Compilation
$tester_dir/run_judge_in_docker.sh `pwd` ${languages_to_docker[$EXT]} $COMPILER code.$EXT $C_OPTIONS $C_WARNING_OPTION -o $EXEFILE >/dev/null 2>cerr
Compiler options:
C_OPTIONS=" -g -O2 -static "
C_WARNING_OPTION="-w" # Inhibit all warning messages
COMPILER="gcc -std=gnu11"
if [ "$EXT" = "cpp" ]; then
COMPILER="g++ -std=c++20"
fi
Java Compilation
$tester_dir/run_judge_in_docker.sh `pwd` ${languages_to_docker[$EXT]} javac solution.java >/dev/null 2>cerr
Security Policies
Java Security Policy
Java executions use a security policy file to restrict permissions:
The default policy grants no permissions. Modify this file carefully if your problems require specific Java permissions (file I/O, network access, etc.).
Shield Mechanism
The shield system wraps student code to prevent manipulation of the main function:
C Shield
/*
* @file shield.c
* Shield for C
*
* Author:
* Mohammad Javad Naderi <[email protected]>
*
*/
#include "def.h"
int themainmainfunction();
int main(int argc, char *argv[])
{
themainmainfunction();
return 0;
}
#include "code.c"
The shield:
- Defines the actual
main() function
- Renames student’s
main() to themainmainfunction()
- Calls the student’s code from the protected main
- Prevents students from controlling program entry/exit
Execution Flow
- Jail Creation: Temporary directory created (
jail-$RANDOM-{logfile})
- Compilation: Code compiled inside Docker container
- Test Execution: For each test case:
- Input file copied to jail
- Code executed in Docker with resource limits
- Output captured and compared
- Cleanup: Jail directory removed after testing
JAIL=jail-$RANDOM-`basename $LOGFILE`
if ! mkdir $JAIL; then
shj_log "Error: Folder 'tester' is not writable! Exiting..."
shj_finish "Judge Error"
fi
cd $JAIL
Error Detection
The system detects various runtime errors:
errors["SHJ_TIME"]="Time Limit Exceeded"
errors["SHJ_MEM"]="Memory Limit Exceeded"
errors["SHJ_HANGUP"]="Process hanged up"
errors["SHJ_SIGNAL"]="Killed by a signal"
errors["SHJ_OUTSIZE"]="Output Size Limit Exceeded"
Java-Specific Error Handling
if [ "$EXT" = "java" ]; then
if grep -iq -m 1 "Too small initial heap" out || grep -q -m 1 "java.lang.OutOfMemoryError" err; then
shj_log "Memory Limit Exceeded java"
echo "<span class=\"text-warning\">Memory Limit Exceeded</span>" >>$RESULTFILE
continue
fi
if grep -q -m 1 "Exception in" err; then
javaexceptionname=`grep -m 1 "Exception in" err | grep -m 1 -oE 'java\.[a-zA-Z\.]*' | head -1 | head -c 80`
javaexceptionplace=`grep -m 1 "$MAINFILENAME.java" err | head -1 | head -c 80`
shj_log "Exception: $javaexceptionname\nMaybe at:$javaexceptionplace"
if $DISPLAY_JAVA_EXCEPTION_ON && grep -q -m 1 "^$javaexceptionname\$" ../java_exceptions_list; then
echo "<span class=\"text-warning\">Runtime Error ($javaexceptionname)</span>" >>$RESULTFILE
else
echo "<span class=\"text-warning\">Runtime Error</span>" >>$RESULTFILE
fi
continue
fi
fi
Best Practices
- Pre-pull Images: Pull Docker images before judging to avoid delays
- Monitor Resources: Keep track of container resource usage
- Update Images: Regularly update Docker images for security patches
- Test Limits: Verify resource limits work as expected for your problems
- Clean Containers: Ensure
--rm flag removes containers after execution
Security Considerations:
- Never disable network isolation unless absolutely necessary
- Always verify directory ownership before mounting
- Keep Docker images updated for security patches
- Monitor for container escapes or privilege escalation attempts