···
···
"battleship-arena/internal/storage"
···
return "./battleship-engine"
28
+
// runSandboxed executes a command in a systemd-run sandbox with resource limits
29
+
func runSandboxed(ctx context.Context, name string, args []string, timeoutSec int) ([]byte, error) {
30
+
// Create context with timeout
31
+
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSec)*time.Second)
34
+
// Build systemd-run command with security properties
35
+
systemdArgs := []string{
36
+
"--user", // Run as current user (not system-wide)
37
+
"--scope", // Create transient scope unit
38
+
"--quiet", // Suppress systemd output
39
+
"--collect", // Automatically clean up after exit
40
+
"--property=MemoryMax=512M", // Max 512MB RAM
41
+
"--property=CPUQuota=200%", // Max 2 CPU cores worth
42
+
"--property=TasksMax=50", // Max 50 processes/threads
43
+
"--property=PrivateNetwork=true", // Isolate network (no internet)
44
+
"--property=PrivateTmp=true", // Private /tmp
45
+
"--property=ProtectHome=true", // Make /home inaccessible
46
+
"--property=ProtectSystem=strict", // Read-only /usr, /boot, /etc
47
+
"--property=NoNewPrivileges=true", // Prevent privilege escalation
50
+
systemdArgs = append(systemdArgs, args...)
52
+
cmd := exec.CommandContext(ctx, "systemd-run", systemdArgs...)
54
+
// Set process group for cleanup
55
+
cmd.SysProcAttr = &syscall.SysProcAttr{
59
+
output, err := cmd.CombinedOutput()
61
+
// Check for timeout
62
+
if ctx.Err() == context.DeadlineExceeded {
63
+
return output, fmt.Errorf("command timed out after %d seconds", timeoutSec)
func CompileSubmission(sub storage.Submission, uploadDir string) error {
storage.UpdateSubmissionStatus(sub.ID, "testing")
···
log.Printf("Compiling submission %d for %s", sub.ID, prefix)
70
-
cmd := exec.Command("g++", "-std=c++11", "-c", "-O3",
113
+
// Compile in sandbox with 60 second timeout
114
+
compileArgs := []string{
115
+
"g++", "-std=c++11", "-c", "-O3",
"-I", filepath.Join(enginePath, "src"),
"-o", filepath.Join(buildDir, "ai_"+prefix+".o"),
filepath.Join(enginePath, "src", sub.Filename),
75
-
output, err := cmd.CombinedOutput()
121
+
output, err := runSandboxed(context.Background(), "compile-"+prefix, compileArgs, 60)
return fmt.Errorf("compilation failed: %s", output)
···
143
-
compileArgs := []string{"-std=c++11", "-O3",
189
+
// Compile match binary in sandbox with 120 second timeout
190
+
compileArgs := []string{"g++"}
191
+
compileArgs = append(compileArgs, "-std=c++11", "-O3",
filepath.Join(enginePath, "src", "battleship_light.cpp"),
compileArgs = append(compileArgs, filepath.Join(enginePath, "src", fmt.Sprintf("memory_functions_%s.cpp", prefix1)))
···
158
-
cmd := exec.Command("g++", compileArgs...)
159
-
output, err := cmd.CombinedOutput()
206
+
output, err := runSandboxed(context.Background(), "compile-match", compileArgs, 120)
log.Printf("Failed to compile match binary: %s", output)
165
-
cmd = exec.Command(combinedBinary, strconv.Itoa(numGames))
166
-
output, err = cmd.CombinedOutput()
212
+
// Run match in sandbox with 300 second timeout (1000 games should be ~60s, give headroom)
213
+
runArgs := []string{combinedBinary, strconv.Itoa(numGames)}
214
+
output, err = runSandboxed(context.Background(), "run-match", runArgs, 300)
168
-
log.Printf("Match execution failed: %v", err)
216
+
log.Printf("Match execution failed: %v\n%s", err, output)