@@ -111,9 +111,6 @@ func TestContainerizationAPISecurityIntegrationPython3(t *testing.T) {
111111
112112 h := integrationHarness {baseURL : testServer .URL }
113113 h .apiPort = mustExtractPort (t , h .baseURL )
114- if ! python3SupportAvailable (t , h .baseURL ) {
115- t .Skip ("python3 execution is not supported by the API yet" )
116- }
117114
118115 cases := []struct {
119116 name string
@@ -136,6 +133,77 @@ func TestContainerizationAPISecurityIntegrationPython3(t *testing.T) {
136133 }
137134}
138135
136+ func TestContainerizationAPISecurityIntegrationCpp (t * testing.T ) {
137+ if config .Config .Globals .ENABLE_QUEUE {
138+ t .Fatal ("ENABLE_QUEUE must be false for integration tests" )
139+ }
140+
141+ h := integrationHarness {baseURL : testServer .URL }
142+ h .apiPort = mustExtractPort (t , h .baseURL )
143+ runLanguageMirrorSuite (t , h , "cpp" , "cpp" )
144+ }
145+
146+ func TestContainerizationAPISecurityIntegrationJava (t * testing.T ) {
147+ if config .Config .Globals .ENABLE_QUEUE {
148+ t .Fatal ("ENABLE_QUEUE must be false for integration tests" )
149+ }
150+
151+ h := integrationHarness {baseURL : testServer .URL }
152+ h .apiPort = mustExtractPort (t , h .baseURL )
153+ runLanguageMirrorSuite (t , h , "java" , "java" )
154+ }
155+
156+ func runLanguageMirrorSuite (t * testing.T , h integrationHarness , language , ext string ) {
157+ t .Helper ()
158+
159+ cases := []struct {
160+ name string
161+ run func (* testing.T , integrationHarness )
162+ }{
163+ {name : "file privacy across request IDs" , run : func (t * testing.T , h integrationHarness ) {
164+ testFilesystemIsolationForLanguage (t , h , language , "file_privacy_write_read." + ext , "file_privacy_read_only." + ext )
165+ }},
166+ {name : "disk spammer is terminated and data is reclaimed" , run : func (t * testing.T , h integrationHarness ) {
167+ testDiskCleanupForLanguage (t , h , language , "disk_spammer." + ext )
168+ }},
169+ {name : "fork bomb does not poison subsequent requests" , run : func (t * testing.T , h integrationHarness ) {
170+ testForkBombContainmentForLanguage (t , h , language , "fork_bomb." + ext , "hello_world." + ext )
171+ }},
172+ {name : "network namespace blocks localhost bridge" , run : func (t * testing.T , h integrationHarness ) {
173+ testNetworkIsolationForLanguage (t , h , language , "network_localhost_bridge." + ext )
174+ }},
175+ {name : "memory hard limit triggers oom kill" , run : func (t * testing.T , h integrationHarness ) {
176+ testMemoryHardLimitForLanguage (t , h , language , "memory_bomb." + ext )
177+ }},
178+ {name : "io flood is bounded and returns before timeout" , run : func (t * testing.T , h integrationHarness ) {
179+ testIOFloodResilienceForLanguage (t , h , language , "io_spam." + ext )
180+ }},
181+ {name : "signal trap cannot survive forced timeout" , run : func (t * testing.T , h integrationHarness ) {
182+ testSignalTrapTimeoutForLanguage (t , h , language , "signal_trap." + ext )
183+ }},
184+ {name : "orphan grandchild is reaped after request exits" , run : func (t * testing.T , h integrationHarness ) {
185+ name := "orphanmakergc"
186+ if language == "python3" {
187+ name = "orphanpygc"
188+ }
189+ if language == "java" {
190+ name = "orphanjavagc"
191+ }
192+ testOrphanReapingForLanguage (t , h , language , "orphan_maker." + ext , name )
193+ }},
194+ {name : "inode bomb does not poison host temp filesystem" , run : func (t * testing.T , h integrationHarness ) {
195+ testInodeExhaustionForLanguage (t , h , language , "inode_bomb." + ext )
196+ }},
197+ {name : "privileged reboot syscall is denied" , run : func (t * testing.T , h integrationHarness ) {
198+ testPrivilegedSyscallDeniedForLanguage (t , h , language , "try_reboot." + ext )
199+ }},
200+ }
201+
202+ for _ , tc := range cases {
203+ t .Run (tc .name , func (t * testing.T ) { tc .run (t , h ) })
204+ }
205+ }
206+
139207func testFilesystemIsolation (t * testing.T , h integrationHarness ) {
140208 writeCode := mustLoadSampleCode (t , "file_privacy_write_read.c" , nil )
141209 writeResp := callSimpleExecute (t , h .baseURL , buildCRequest (writeCode , 4 , 32768 ))
@@ -447,6 +515,155 @@ func testPrivilegedSyscallDeniedPython3(t *testing.T, h integrationHarness) {
447515 }
448516}
449517
518+ func testFilesystemIsolationForLanguage (t * testing.T , h integrationHarness , language , writeFile , readFile string ) {
519+ writeCode := mustLoadSampleCode (t , writeFile , nil )
520+ writeResp := callSimpleExecute (t , h .baseURL , buildLanguageRequest (language , writeCode , 4 , 32768 ))
521+ if ! strings .Contains (writeResp .Output , "SecretData123" ) {
522+ t .Fatalf ("step A did not return secret in stdout; stdout=%q stderr=%q" , writeResp .Output , writeResp .Error )
523+ }
524+
525+ readCode := mustLoadSampleCode (t , readFile , nil )
526+ readResp := callSimpleExecute (t , h .baseURL , buildLanguageRequest (language , readCode , 4 , 32768 ))
527+ if ! strings .Contains (strings .ToLower (readResp .Error ), "no such file or directory" ) {
528+ t .Fatalf ("step B unexpectedly accessed file from another sandbox; stdout=%q stderr=%q" , readResp .Output , readResp .Error )
529+ }
530+ }
531+
532+ func testDiskCleanupForLanguage (t * testing.T , h integrationHarness , language , payload string ) {
533+ beforeFree := mustFreeBytes (t , os .TempDir ())
534+ beforeCount := countSandboxTempDirs (t )
535+
536+ code := mustLoadSampleCode (t , payload , nil )
537+ resp := callSimpleExecute (t , h .baseURL , buildLanguageRequest (language , code , 2 , 32768 ))
538+ stderrLower := strings .ToLower (resp .Error )
539+ if ! containsAny (stderrLower , []string {"execution timed out" , "memory limit exceeded" }) {
540+ t .Fatalf ("disk spammer was not terminated as expected; stdout=%q stderr=%q" , resp .Output , resp .Error )
541+ }
542+
543+ time .Sleep (400 * time .Millisecond )
544+ assertDiskReclaimed (t , beforeFree , mustFreeBytes (t , os .TempDir ()))
545+ assertNoSandboxLeak (t , beforeCount , countSandboxTempDirs (t ))
546+ }
547+
548+ func testForkBombContainmentForLanguage (t * testing.T , h integrationHarness , language , bombFile , helloFile string ) {
549+ bombCode := mustLoadSampleCode (t , bombFile , nil )
550+ _ = callSimpleExecute (t , h .baseURL , buildLanguageRequest (language , bombCode , 2 , 32768 ))
551+
552+ helloCode := mustLoadSampleCode (t , helloFile , nil )
553+ helloResp := callSimpleExecute (t , h .baseURL , buildLanguageRequest (language , helloCode , 4 , 32768 ))
554+ if ! strings .Contains (helloResp .Output , "Hello World" ) {
555+ t .Fatalf ("follow-up request failed after fork bomb; stdout=%q stderr=%q" , helloResp .Output , helloResp .Error )
556+ }
557+ if strings .Contains (strings .ToLower (helloResp .Error ), "resource temporarily unavailable" ) {
558+ t .Fatalf ("follow-up request indicates host PID exhaustion; stderr=%q" , helloResp .Error )
559+ }
560+ }
561+
562+ func testNetworkIsolationForLanguage (t * testing.T , h integrationHarness , language , payload string ) {
563+ replacements := map [string ]string {"__API_PORT__" : strconv .Itoa (h .apiPort )}
564+ code := mustLoadSampleCode (t , payload , replacements )
565+ resp := callSimpleExecute (t , h .baseURL , buildLanguageRequest (language , code , 3 , 32768 ))
566+
567+ combined := strings .ToLower (resp .Output + "\n " + resp .Error )
568+ if strings .Contains (combined , "connected" ) {
569+ t .Fatalf ("localhost bridge unexpectedly succeeded; stdout=%q stderr=%q" , resp .Output , resp .Error )
570+ }
571+ if ! containsAny (combined , []string {"connection refused" , "network is unreachable" , "no route to host" }) {
572+ t .Fatalf ("network isolation did not produce expected connect error; stdout=%q stderr=%q" , resp .Output , resp .Error )
573+ }
574+ }
575+
576+ func testMemoryHardLimitForLanguage (t * testing.T , h integrationHarness , language , payload string ) {
577+ code := mustLoadSampleCode (t , payload , nil )
578+ resp := callSimpleExecute (t , h .baseURL , buildLanguageRequest (language , code , 3 , 16384 ))
579+ if ! containsAny (strings .ToLower (resp .Error ), []string {"memory limit exceeded" , "killed" , "execution timed out" }) {
580+ t .Fatalf ("memory bomb did not terminate as expected; stdout=%q stderr=%q" , resp .Output , resp .Error )
581+ }
582+
583+ execDur , err := time .ParseDuration (resp .CPUTime )
584+ if err != nil {
585+ t .Fatalf ("failed to parse cpu_time %q: %v" , resp .CPUTime , err )
586+ }
587+ if execDur > 500 * time .Millisecond {
588+ t .Fatalf ("memory enforcement was too slow: cpu_time=%s stderr=%q" , resp .CPUTime , resp .Error )
589+ }
590+ }
591+
592+ func testIOFloodResilienceForLanguage (t * testing.T , h integrationHarness , language , payload string ) {
593+ code := mustLoadSampleCode (t , payload , nil )
594+ resp := callSimpleExecute (t , h .baseURL , buildLanguageRequest (language , code , 1 , 32768 ))
595+
596+ stderrLower := strings .ToLower (resp .Error )
597+ if ! containsAny (stderrLower , []string {"execution timed out" , "killed" , "memory limit exceeded" }) {
598+ t .Fatalf ("io flood did not terminate with expected error; stdout=%q stderr=%q" , resp .Output , resp .Error )
599+ }
600+
601+ if strings .TrimSpace (resp .Error ) == "" {
602+ t .Fatalf ("io flood returned empty stderr; expected bounded but non-empty error output" )
603+ }
604+
605+ const maxErrorBytes = (1 << 20 ) + 4096
606+ if len (resp .Error ) > maxErrorBytes {
607+ t .Fatalf ("stderr exceeded resilience cap: got=%d bytes cap=%d" , len (resp .Error ), maxErrorBytes )
608+ }
609+
610+ assertDurationNotExcessive (t , resp .CPUTime , 3 * time .Second , "io flood request" )
611+ }
612+
613+ func testSignalTrapTimeoutForLanguage (t * testing.T , h integrationHarness , language , payload string ) {
614+ code := mustLoadSampleCode (t , payload , nil )
615+ resp := callSimpleExecute (t , h .baseURL , buildLanguageRequest (language , code , 1 , 32768 ))
616+
617+ if ! strings .Contains (strings .ToLower (resp .Error ), "execution timed out" ) {
618+ t .Fatalf ("signal trap did not timeout as expected; stdout=%q stderr=%q" , resp .Output , resp .Error )
619+ }
620+
621+ assertDurationWindow (t , resp .CPUTime , 900 * time .Millisecond , 2500 * time .Millisecond , "signal trap timeout" )
622+ }
623+
624+ func testOrphanReapingForLanguage (t * testing.T , h integrationHarness , language , payload , orphanName string ) {
625+ code := mustLoadSampleCode (t , payload , nil )
626+ resp := callSimpleExecute (t , h .baseURL , buildLanguageRequest (language , code , 2 , 32768 ))
627+ assertDurationNotExcessive (t , resp .CPUTime , 3 * time .Second , "orphan maker request" )
628+
629+ time .Sleep (400 * time .Millisecond )
630+ if cnt := countProcessesByComm (t , orphanName ); cnt > 0 {
631+ t .Fatalf ("orphan grandchild leaked to host after request completion: count=%d stderr=%q" , cnt , resp .Error )
632+ }
633+ }
634+
635+ func testInodeExhaustionForLanguage (t * testing.T , h integrationHarness , language , payload string ) {
636+ beforeFree := mustFreeBytes (t , os .TempDir ())
637+ beforeCount := countSandboxTempDirs (t )
638+
639+ code := mustLoadSampleCode (t , payload , nil )
640+ resp := callSimpleExecute (t , h .baseURL , buildLanguageRequest (language , code , 4 , 32768 ))
641+
642+ combinedLower := strings .ToLower (resp .Output + "\n " + resp .Error )
643+ if ! containsAny (combinedLower , []string {"inode bomb completed" , "no space left" , "disk quota exceeded" }) {
644+ t .Fatalf ("inode exhaustion test produced unexpected result; stdout=%q stderr=%q" , resp .Output , resp .Error )
645+ }
646+
647+ time .Sleep (400 * time .Millisecond )
648+ assertDiskReclaimed (t , beforeFree , mustFreeBytes (t , os .TempDir ()))
649+ assertNoSandboxLeak (t , beforeCount , countSandboxTempDirs (t ))
650+ mustCreateAndDeleteTempFile (t )
651+ }
652+
653+ func testPrivilegedSyscallDeniedForLanguage (t * testing.T , h integrationHarness , language , payload string ) {
654+ code := mustLoadSampleCode (t , payload , nil )
655+ resp := callSimpleExecute (t , h .baseURL , buildLanguageRequest (language , code , 3 , 32768 ))
656+ assertDurationNotExcessive (t , resp .CPUTime , 3 * time .Second , "privileged reboot syscall" )
657+
658+ combinedLower := strings .ToLower (resp .Output + "\n " + resp .Error )
659+ if strings .Contains (combinedLower , "reboot succeeded unexpectedly" ) {
660+ t .Fatalf ("privileged reboot syscall unexpectedly succeeded; stdout=%q stderr=%q" , resp .Output , resp .Error )
661+ }
662+ if ! containsAny (combinedLower , []string {"operation not permitted" , "permission denied" , "bad system call" , "killed" , "hangup" }) {
663+ t .Fatalf ("expected privileged syscall denial signal was not observed; stdout=%q stderr=%q" , resp .Output , resp .Error )
664+ }
665+ }
666+
450667func mustLoadSampleCode (t * testing.T , fileName string , replacements map [string ]string ) string {
451668 t .Helper ()
452669 path := filepath .Join (sampleCodeDir , fileName )
@@ -463,44 +680,23 @@ func mustLoadSampleCode(t *testing.T, fileName string, replacements map[string]s
463680}
464681
465682func buildCRequest (code string , timeoutSec , maxMemoryKB uint ) simpleExecuteRequest {
466- return simpleExecuteRequest { Language : "c" , Code : code , Timeout : timeoutSec , MaxMemory : maxMemoryKB }
683+ return buildLanguageRequest ( "c" , code , timeoutSec , maxMemoryKB )
467684}
468685
469686func buildPython3Request (code string , timeoutSec , maxMemoryKB uint ) simpleExecuteRequest {
470- return simpleExecuteRequest { Language : "python3" , Code : code , Timeout : timeoutSec , MaxMemory : maxMemoryKB }
687+ return buildLanguageRequest ( "python3" , code , timeoutSec , maxMemoryKB )
471688}
472689
473- func python3SupportAvailable (t * testing.T , baseURL string ) bool {
474- t .Helper ()
475- req := buildPython3Request ("print('py-ok')" , 1 , 32768 )
476- body , err := json .Marshal (req )
477- if err != nil {
478- return false
479- }
480-
481- httpReq , err := http .NewRequest (http .MethodPost , baseURL + "/simple-execute" , bytes .NewReader (body ))
482- if err != nil {
483- return false
484- }
485- httpReq .Header .Set ("Content-Type" , "application/json" )
486-
487- httpResp , err := httpClient .Do (httpReq )
488- if err != nil {
489- return false
490- }
491- defer httpResp .Body .Close ()
492-
493- rawBody , err := io .ReadAll (httpResp .Body )
494- if err != nil || httpResp .StatusCode != http .StatusOK {
495- return false
496- }
690+ func buildCppRequest (code string , timeoutSec , maxMemoryKB uint ) simpleExecuteRequest {
691+ return buildLanguageRequest ("cpp" , code , timeoutSec , maxMemoryKB )
692+ }
497693
498- var parsed simpleExecuteResponse
499- if err := json .Unmarshal (rawBody , & parsed ); err != nil {
500- return false
501- }
694+ func buildJavaRequest (code string , timeoutSec , maxMemoryKB uint ) simpleExecuteRequest {
695+ return buildLanguageRequest ("java" , code , timeoutSec , maxMemoryKB )
696+ }
502697
503- return strings .Contains (parsed .Output , "py-ok" )
698+ func buildLanguageRequest (language , code string , timeoutSec , maxMemoryKB uint ) simpleExecuteRequest {
699+ return simpleExecuteRequest {Language : language , Code : code , Timeout : timeoutSec , MaxMemory : maxMemoryKB }
504700}
505701
506702func callSimpleExecute (t * testing.T , baseURL string , req simpleExecuteRequest ) simpleExecuteResponse {
0 commit comments