Skip to content

Commit e3aa07a

Browse files
committed
tests: add integration tests for resource limits
Signed-off-by: Luminita Voicu <[email protected]>
1 parent 5b58c63 commit e3aa07a

File tree

2 files changed

+146
-1
lines changed

2 files changed

+146
-1
lines changed

tests/framework/jailer.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class JailerContext:
3636
extra_args = None
3737
api_socket_name = None
3838
cgroups = None
39+
resource_limits = None
3940

4041
def __init__(
4142
self,
@@ -49,6 +50,7 @@ def __init__(
4950
daemonize=True,
5051
new_pid_ns=False,
5152
cgroups=None,
53+
resource_limits=None,
5254
**extra_args
5355
):
5456
"""Set up jailer fields.
@@ -69,6 +71,7 @@ def __init__(
6971
self.extra_args = extra_args
7072
self.api_socket_name = DEFAULT_USOCKET_NAME
7173
self.cgroups = cgroups
74+
self.resource_limits = resource_limits
7275
self.ramfs_subdir_name = 'ramfs'
7376
self._ramfs_path = None
7477

@@ -113,7 +116,10 @@ def construct_param_list(self):
113116
if self.cgroups is not None:
114117
for cgroup in self.cgroups:
115118
jailer_param_list.extend(['--cgroup', str(cgroup)])
116-
# applying neccessory extra args if needed
119+
if self.resource_limits is not None:
120+
for limit in self.resource_limits:
121+
jailer_param_list.extend(['--resource-limit', str(limit)])
122+
# applying necessary extra args if needed
117123
if len(self.extra_args) > 0:
118124
jailer_param_list.append('--')
119125
for key, value in self.extra_args.items():

tests/integration_tests/security/test_jail.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
"""Tests that verify the jailer's behavior."""
4+
import http.client as http_client
45
import os
6+
import resource
57
import stat
68
import subprocess
9+
import time
710

11+
import psutil
12+
import requests
13+
import urllib3
14+
15+
from framework.builder import SnapshotBuilder
816
from framework.defs import FC_BINARY_NAME
917
from framework.jailer import JailerContext
1018
import host_tools.cargo_build as build_tools
@@ -19,6 +27,15 @@
1927
SOCK_STATS = stat.S_IFSOCK | REG_PERMS
2028
# These are the stats of the devices created by tha jailer.
2129
CHAR_STATS = stat.S_IFCHR | stat.S_IRUSR | stat.S_IWUSR
30+
# Limit on file size in bytes.
31+
FSIZE = 2097151
32+
# Limit on number of file descriptors.
33+
NOFILE = 1024
34+
# Resource limits to be set by the jailer.
35+
RESOURCE_LIMITS = [
36+
'no-file={}'.format(NOFILE),
37+
'fsize={}'.format(FSIZE),
38+
]
2239

2340

2441
def check_stats(filepath, stats, uid, gid):
@@ -130,6 +147,19 @@ def get_cpus(node):
130147
return open(node_cpus_path, 'r').readline().strip()
131148

132149

150+
def check_limits(pid, no_file, fsize):
151+
"""Verify resource limits against expected values."""
152+
# Fetch firecracker process limits for number of open fds
153+
(soft, hard) = resource.prlimit(pid, resource.RLIMIT_NOFILE)
154+
assert soft == no_file
155+
assert hard == no_file
156+
157+
# Fetch firecracker process limits for maximum file size
158+
(soft, hard) = resource.prlimit(pid, resource.RLIMIT_FSIZE)
159+
assert soft == fsize
160+
assert hard == fsize
161+
162+
133163
def test_cgroups(test_microvm_with_initrd):
134164
"""Test the cgroups are correctly set by the jailer."""
135165
test_microvm = test_microvm_with_initrd
@@ -198,6 +228,115 @@ def test_args_cgroups(test_microvm_with_initrd):
198228
)
199229

200230

231+
def test_args_default_resource_limits(test_microvm_with_initrd):
232+
"""Test the resource limits are correctly set by the jailer."""
233+
test_microvm = test_microvm_with_initrd
234+
235+
test_microvm.spawn()
236+
237+
# Get firecracker's PID
238+
pid = int(test_microvm.jailer_clone_pid)
239+
assert pid != 0
240+
241+
# Fetch firecracker process limits for number of open fds
242+
(soft, hard) = resource.prlimit(pid, resource.RLIMIT_NOFILE)
243+
# Check that the default limit was set.
244+
assert soft == 2048
245+
assert hard == 2048
246+
247+
# Fetch firecracker process limits for number of open fds
248+
(soft, hard) = resource.prlimit(pid, resource.RLIMIT_FSIZE)
249+
# Check that no limit was set
250+
assert soft == -1
251+
assert hard == -1
252+
253+
254+
def test_args_resource_limits(test_microvm_with_initrd):
255+
"""Test the resource limits are correctly set by the jailer."""
256+
test_microvm = test_microvm_with_initrd
257+
test_microvm.jailer.resource_limits = RESOURCE_LIMITS
258+
259+
test_microvm.spawn()
260+
261+
# Get firecracker's PID
262+
pid = int(test_microvm.jailer_clone_pid)
263+
assert pid != 0
264+
265+
# Check limit values were correctly set.
266+
check_limits(pid, NOFILE, FSIZE)
267+
268+
269+
def test_negative_file_size_limit(test_microvm_with_ssh):
270+
"""Test creating snapshot file fails when size exceeds `fsize` limit."""
271+
test_microvm = test_microvm_with_ssh
272+
test_microvm.jailer.resource_limits = ['fsize=1024']
273+
274+
test_microvm.spawn()
275+
test_microvm.basic_config()
276+
test_microvm.start()
277+
278+
snapshot_builder = SnapshotBuilder(test_microvm)
279+
# Create directory and files for saving snapshot state and memory.
280+
_snapshot_dir = snapshot_builder.create_snapshot_dir()
281+
282+
# Pause microVM for snapshot.
283+
response = test_microvm.vm.patch(state='Paused')
284+
assert test_microvm.api_session.is_status_no_content(response.status_code)
285+
286+
# Attempt to create a snapshot.
287+
try:
288+
test_microvm.snapshot.create(
289+
mem_file_path="/snapshot/vm.mem",
290+
snapshot_path="/snapshot/vm.vmstate",
291+
)
292+
except (
293+
http_client.RemoteDisconnected,
294+
urllib3.exceptions.ProtocolError,
295+
requests.exceptions.ConnectionError
296+
) as _error:
297+
test_microvm.expect_kill_by_signal = True
298+
# Check the microVM received signal `SIGXFSZ` (25),
299+
# which corresponds to exceeding file size limit.
300+
msg = 'Shutting down VM after intercepting signal 25, code 0'
301+
test_microvm.check_log_message(msg)
302+
time.sleep(1)
303+
# Check that the process was terminated.
304+
assert not psutil.pid_exists(test_microvm.jailer_clone_pid)
305+
else:
306+
assert False, "Negative test failed"
307+
308+
309+
def test_negative_no_file_limit(test_microvm_with_ssh):
310+
"""Test microVM is killed when exceeding `no-file` limit."""
311+
test_microvm = test_microvm_with_ssh
312+
test_microvm.jailer.resource_limits = ['no-file=3']
313+
314+
# pylint: disable=W0703
315+
try:
316+
test_microvm.spawn()
317+
except Exception as error:
318+
assert "No file descriptors available (os error 24)" in str(error)
319+
assert test_microvm.jailer_clone_pid is None
320+
else:
321+
assert False, "Negative test failed"
322+
323+
324+
def test_new_pid_ns_resource_limits(test_microvm_with_ssh):
325+
"""Test that Firecracker process inherits jailer resource limits."""
326+
test_microvm = test_microvm_with_ssh
327+
328+
test_microvm.jailer.daemonize = False
329+
test_microvm.jailer.new_pid_ns = True
330+
test_microvm.jailer.resource_limits = RESOURCE_LIMITS
331+
332+
test_microvm.spawn()
333+
334+
# Get Firecracker's PID.
335+
fc_pid = test_microvm.pid_in_new_ns
336+
# Check limit values were correctly set.
337+
check_limits(fc_pid, NOFILE, FSIZE)
338+
339+
201340
def test_new_pid_namespace(test_microvm_with_ssh):
202341
"""Test that Firecracker is spawned in a new PID namespace if requested."""
203342
test_microvm = test_microvm_with_ssh

0 commit comments

Comments
 (0)