From 1c75ec573903de8294172e65e0dd88108a524924 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sat, 17 Jul 2021 15:13:44 +0100 Subject: feat: increased PID limits Processes spawned in snekbox now have up to 5 PIDs available, each sharing the same memory limits and environment as the parent python process. As far as I could see in testing this does appear safe and processes behave as expected even when detatching from the parent or exceeding memory limits. --- config/snekbox.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/snekbox.cfg b/config/snekbox.cfg index 73e36e1..9e15cc6 100644 --- a/config/snekbox.cfg +++ b/config/snekbox.cfg @@ -107,7 +107,7 @@ mount { cgroup_mem_max: 52428800 cgroup_mem_mount: "/sys/fs/cgroup/memory" -cgroup_pids_max: 1 +cgroup_pids_max: 5 cgroup_pids_mount: "/sys/fs/cgroup/pids" iface_no_lo: true -- cgit v1.2.3 From 34acf99741031ae3b9ace1cdf6da3c7d6ec604e2 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sat, 17 Jul 2021 15:14:38 +0100 Subject: chore: increase 3rd party thread limit environment variables We define a few environment variables to stop third party libraries trying to default to spawning more processes, with the PID limit modification we can increase these values. --- config/snekbox.cfg | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config/snekbox.cfg b/config/snekbox.cfg index 9e15cc6..bde8027 100644 --- a/config/snekbox.cfg +++ b/config/snekbox.cfg @@ -9,11 +9,11 @@ time_limit: 6 keep_env: false envar: "LANG=en_US.UTF-8" -envar: "OMP_NUM_THREADS=1" -envar: "OPENBLAS_NUM_THREADS=1" -envar: "MKL_NUM_THREADS=1" -envar: "VECLIB_MAXIMUM_THREADS=1" -envar: "NUMEXPR_NUM_THREADS=1" +envar: "OMP_NUM_THREADS=5" +envar: "OPENBLAS_NUM_THREADS=5" +envar: "MKL_NUM_THREADS=5" +envar: "VECLIB_MAXIMUM_THREADS=5" +envar: "NUMEXPR_NUM_THREADS=5" envar: "PYTHONPATH=/snekbox/user_base/lib/python3.9/site-packages" envar: "PYTHONIOENCODING=utf-8:strict" -- cgit v1.2.3 From 43e9523f28b7d4ab4c2fa0ab8f5c7bdd431e53d8 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sat, 17 Jul 2021 15:14:59 +0100 Subject: test: update pid limit tests to account for new increased limit --- tests/test_nsjail.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_nsjail.py b/tests/test_nsjail.py index 8955b4a..ec5c56f 100644 --- a/tests/test_nsjail.py +++ b/tests/test_nsjail.py @@ -50,7 +50,16 @@ class NsJailTests(unittest.TestCase): def test_subprocess_resource_unavailable(self): code = dedent(""" import subprocess - print(subprocess.check_output('kill -9 6', shell=True).decode()) + + # Max PIDs is 5. + for _ in range(6): + print(subprocess.Popen( + [ + '/usr/local/bin/python3', + '-c', + 'import time; time.sleep(1)' + ], + ).pid) """).strip() result = self.nsjail.python3(code) -- cgit v1.2.3 From 096a8d25b7d99032a99f85e07eec1c57b4cabde5 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sat, 17 Jul 2021 16:26:20 +0100 Subject: test: add test_multiprocess_resource_limits to test memory limit sharing This test ensures that spawned child processes inherit the same resource group as the parent by spawning 2 child processes which each allocate a 40MB object, it then verifies that one of the child processes was killed with SIGKILL for violating the resource quota. --- tests/test_nsjail.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_nsjail.py b/tests/test_nsjail.py index ec5c56f..c0ed96b 100644 --- a/tests/test_nsjail.py +++ b/tests/test_nsjail.py @@ -67,6 +67,34 @@ class NsJailTests(unittest.TestCase): self.assertIn("Resource temporarily unavailable", result.stdout) self.assertEqual(result.stderr, None) + def test_multiprocess_resource_limits(self): + code = dedent(""" + import time + from multiprocessing import Process + + def f(): + object = "A" * 40_000_000 + time.sleep(0.5) + + + proc_1 = Process(target=f) + proc_2 = Process(target=f) + + proc_1.start() + proc_2.start() + + proc_1.join() + proc_2.join() + + print(proc_1.exitcode, proc_2.exitcode) + """) + + result = self.nsjail.python3(code) + + exit_codes = result.stdout.strip().split() + self.assertIn("-9", exit_codes) + self.assertEqual(result.stderr, None) + def test_read_only_file_system(self): for path in ("/", "/etc", "/lib", "/lib64", "/snekbox", "/usr"): with self.subTest(path=path): -- cgit v1.2.3