Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added block_process_spawn option to AutograderSandbox.run_command(). #46

Merged
merged 1 commit into from May 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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