Skip to content

Commit

Permalink
Merge pull request #46 from eecs-autograder/no_fork_option
Browse files Browse the repository at this point in the history
Added block_process_spawn option to AutograderSandbox.run_command().
  • Loading branch information
james-perretta committed May 7, 2020
2 parents a6159e8 + 6d05868 commit e04405c
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 4 deletions.
7 changes: 7 additions & 0 deletions autograder_sandbox/autograder_sandbox.py
Expand Up @@ -272,6 +272,7 @@ def environment_variables(self) -> Mapping[str, str]:

def run_command(self,
args: List[str],
block_process_spawn: bool = False,
max_stack_size: Optional[int] = None,
max_virtual_memory: Optional[int] = None,
as_root: bool = False,
Expand All @@ -286,6 +287,9 @@ def run_command(self,
:param args: A list of strings that specify which command should
be run inside the sandbox.
:param block_process_spawn: If true, prevent the command from
spawning child processes by setting the nproc limit to 0.
:param max_stack_size: The maximum stack size, in bytes, allowed
for the command.
Expand Down Expand Up @@ -314,6 +318,9 @@ def run_command(self,
if stdin is None:
cmd.append('--stdin_devnull')

if block_process_spawn:
cmd += ['--block_process_spawn']

if max_stack_size is not None:
cmd += ['--max_stack_size', str(max_stack_size)]

Expand Down
4 changes: 4 additions & 0 deletions autograder_sandbox/docker-image-setup/cmd_runner.py
Expand Up @@ -27,6 +27,9 @@ def set_subprocess_rlimits():
os.setgid(grp.getgrnam('autograder').gr_gid)
os.setuid(pwd.getpwnam('autograder').pw_uid)

if args.block_process_spawn:
resource.setrlimit(resource.RLIMIT_NPROC, (0, 0))

if args.max_stack_size is not None:
resource.setrlimit(
resource.RLIMIT_STACK,
Expand Down Expand Up @@ -124,6 +127,7 @@ def set_subprocess_rlimits():
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--timeout", type=int)
parser.add_argument("--block_process_spawn", action='store_true', default=False)
parser.add_argument("--max_stack_size", type=int)
parser.add_argument("--max_virtual_memory", type=int)
parser.add_argument("--truncate_stdout", type=int)
Expand Down
28 changes: 24 additions & 4 deletions autograder_sandbox/tests.py
Expand Up @@ -420,6 +420,26 @@ def test_run_command_timeout_exceeded(self) -> None:
result = self.sandbox.run_command(["sleep", "10"], timeout=1)
self.assertTrue(result.timed_out)

def test_block_process_spawn(self) -> None:
cmd = ['bash', '-c', 'echo spam | cat > egg.txt']
with self.sandbox:
# Spawning processes is allowed by default
filename = _add_string_to_sandbox_as_file(
_PROCESS_SPAWN_PROG_TMPL.format(num_processes=12, sleep_time=3), '.py',
self.sandbox
)
result = self.sandbox.run_command(['python3', filename])
self.assertEqual(0, result.return_code)

result = self.sandbox.run_command(['python3', filename], block_process_spawn=True)
stdout = result.stdout.read().decode()
print(stdout)
stderr = result.stderr.read().decode()
print(stderr)
self.assertNotEqual(0, result.return_code)
self.assertIn('BlockingIOError', stderr)
self.assertIn('Resource temporarily unavailable', stderr)

def test_command_exceeds_stack_size_limit(self) -> None:
stack_size_limit = mb_to_bytes(5)
mem_to_use = stack_size_limit * 2
Expand Down Expand Up @@ -501,10 +521,10 @@ def _check_resource_limit_test_result(

def test_multiple_containers_dont_exceed_ulimits(self) -> None:
"""
One quirk of docker containers is that if there are multiple users
created in different containers but with the same UID, the resource
usage of all those users will contribute to hitting the same ulimits.
This test makes sure that valid processes aren't randomly cut off.
This is a sanity check to make sure that ulimits placed on
different containers with the same UID don't conflict. All
ulimits except for nproc are supposed to be process-linked
rather than UID-linked.
"""
self._do_parallel_container_stack_limit_test(
16, mb_to_bytes(20), mb_to_bytes(30))
Expand Down

0 comments on commit e04405c

Please sign in to comment.