a geicko-2 based round robin ranking system designed to test c++ battleship submissions battleship.dunkirk.sh
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: implement system isolation

+58 -10
+58 -10
internal/runner/runner.go
··· 1 1 package runner 2 2 3 3 import ( 4 + "context" 4 5 "fmt" 5 6 "log" 6 7 "os" ··· 9 10 "regexp" 10 11 "strconv" 11 12 "strings" 13 + "syscall" 12 14 "time" 13 15 14 16 "battleship-arena/internal/storage" ··· 23 25 return "./battleship-engine" 24 26 } 25 27 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) 32 + defer cancel() 33 + 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 48 + "--", 49 + } 50 + systemdArgs = append(systemdArgs, args...) 51 + 52 + cmd := exec.CommandContext(ctx, "systemd-run", systemdArgs...) 53 + 54 + // Set process group for cleanup 55 + cmd.SysProcAttr = &syscall.SysProcAttr{ 56 + Setpgid: true, 57 + } 58 + 59 + output, err := cmd.CombinedOutput() 60 + 61 + // Check for timeout 62 + if ctx.Err() == context.DeadlineExceeded { 63 + return output, fmt.Errorf("command timed out after %d seconds", timeoutSec) 64 + } 65 + 66 + return output, err 67 + } 68 + 26 69 func CompileSubmission(sub storage.Submission, uploadDir string) error { 27 70 storage.UpdateSubmissionStatus(sub.ID, "testing") 28 71 ··· 67 110 68 111 log.Printf("Compiling submission %d for %s", sub.ID, prefix) 69 112 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", 71 116 "-I", filepath.Join(enginePath, "src"), 72 117 "-o", filepath.Join(buildDir, "ai_"+prefix+".o"), 73 118 filepath.Join(enginePath, "src", sub.Filename), 74 - ) 75 - output, err := cmd.CombinedOutput() 119 + } 120 + 121 + output, err := runSandboxed(context.Background(), "compile-"+prefix, compileArgs, 60) 76 122 if err != nil { 77 123 return fmt.Errorf("compilation failed: %s", output) 78 124 } ··· 140 186 return 0, 0, 0 141 187 } 142 188 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", 144 192 "-o", combinedBinary, 145 193 mainPath, 146 194 filepath.Join(enginePath, "src", "battleship_light.cpp"), 147 - } 195 + ) 148 196 149 197 if prefix1 == prefix2 { 150 198 compileArgs = append(compileArgs, filepath.Join(enginePath, "src", fmt.Sprintf("memory_functions_%s.cpp", prefix1))) ··· 155 203 ) 156 204 } 157 205 158 - cmd := exec.Command("g++", compileArgs...) 159 - output, err := cmd.CombinedOutput() 206 + output, err := runSandboxed(context.Background(), "compile-match", compileArgs, 120) 160 207 if err != nil { 161 208 log.Printf("Failed to compile match binary: %s", output) 162 209 return 0, 0, 0 163 210 } 164 211 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) 167 215 if err != nil { 168 - log.Printf("Match execution failed: %v", err) 216 + log.Printf("Match execution failed: %v\n%s", err, output) 169 217 return 0, 0, 0 170 218 } 171 219