Skip to main content
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:
tester.sh:105
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:
run_judge_in_docker.sh:1
#!/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:
tester.sh:64
TIMELIMIT=${7}        # time limit in seconds (float)
TIMELIMITINT=${8}    # integer time limit (should be > TIMELIMIT)
runcode.sh:46
# 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

tester.sh:68
MEMLIMIT=${9}         # memory limit in kB
For Python, additional memory is allocated for interpreter overhead:
runcode.sh:29
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:
tester.sh:290
languages_to_comm["java"]="java -Xmx${MEMLIMIT}k -Xms${MEMLIMIT}k solution"

Output Size Limits

tester.sh:69
OUTLIMIT=${10}        # output size limit in Bytes

Compilation in Docker

C/C++ Compilation

compile_c.sh:51
$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:
compile_c.sh:7
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

compile_java.sh:7
$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:
java.policy:1
grant {
};
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

shield.c:1
/*
 * @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:
  1. Defines the actual main() function
  2. Renames student’s main() to themainmainfunction()
  3. Calls the student’s code from the protected main
  4. Prevents students from controlling program entry/exit

Execution Flow

  1. Jail Creation: Temporary directory created (jail-$RANDOM-{logfile})
  2. Compilation: Code compiled inside Docker container
  3. Test Execution: For each test case:
    • Input file copied to jail
    • Code executed in Docker with resource limits
    • Output captured and compared
  4. Cleanup: Jail directory removed after testing
tester.sh:156
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:
tester.sh:293
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

tester.sh:342
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

  1. Pre-pull Images: Pull Docker images before judging to avoid delays
  2. Monitor Resources: Keep track of container resource usage
  3. Update Images: Regularly update Docker images for security patches
  4. Test Limits: Verify resource limits work as expected for your problems
  5. 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

Build docs developers (and LLMs) love