Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/firecracker
Path: blob/main/tests/integration_tests/security/test_jail.py
1958 views
1
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
# SPDX-License-Identifier: Apache-2.0
3
"""Tests that verify the jailer's behavior."""
4
import os
5
import stat
6
import subprocess
7
8
from framework.defs import FC_BINARY_NAME
9
from framework.jailer import JailerContext
10
import host_tools.cargo_build as build_tools
11
12
13
# These are the permissions that all files/dirs inside the jailer have.
14
REG_PERMS = stat.S_IRUSR | stat.S_IWUSR | \
15
stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | \
16
stat.S_IROTH | stat.S_IXOTH
17
DIR_STATS = stat.S_IFDIR | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
18
FILE_STATS = stat.S_IFREG | REG_PERMS
19
SOCK_STATS = stat.S_IFSOCK | REG_PERMS
20
# These are the stats of the devices created by tha jailer.
21
CHAR_STATS = stat.S_IFCHR | stat.S_IRUSR | stat.S_IWUSR
22
23
24
def check_stats(filepath, stats, uid, gid):
25
"""Assert on uid, gid and expected stats for the given path."""
26
st = os.stat(filepath)
27
28
assert st.st_gid == gid
29
assert st.st_uid == uid
30
assert st.st_mode ^ stats == 0
31
32
33
def test_default_chroot(test_microvm_with_ssh):
34
"""Test that the code base assigns a chroot if none is specified."""
35
test_microvm = test_microvm_with_ssh
36
37
# Start customizing arguments.
38
# Test that firecracker's default chroot folder is indeed `/srv/jailer`.
39
test_microvm.jailer.chroot_base = None
40
41
test_microvm.spawn()
42
43
# Test the expected outcome.
44
assert os.path.exists(test_microvm.jailer.api_socket_path())
45
46
47
def test_empty_jailer_id(test_microvm_with_ssh):
48
"""Test that the jailer ID cannot be empty."""
49
test_microvm = test_microvm_with_ssh
50
fc_binary, _ = build_tools.get_firecracker_binaries()
51
52
# Set the jailer ID to None.
53
test_microvm.jailer = JailerContext(
54
jailer_id="",
55
exec_file=fc_binary,
56
)
57
58
# pylint: disable=W0703
59
try:
60
test_microvm.spawn()
61
# If the exception is not thrown, it means that Firecracker was
62
# started successfully, hence there's a bug in the code due to which
63
# we can set an empty ID.
64
assert False
65
except Exception as err:
66
expected_err = "Jailer error: Invalid instance ID: invalid len (0);" \
67
" the length must be between 1 and 64"
68
assert expected_err in str(err)
69
70
71
def test_default_chroot_hierarchy(test_microvm_with_initrd):
72
"""Test the folder hierarchy created by default by the jailer."""
73
test_microvm = test_microvm_with_initrd
74
75
test_microvm.spawn()
76
77
# We do checks for all the things inside the chroot that the jailer crates
78
# by default.
79
check_stats(test_microvm.jailer.chroot_path(), DIR_STATS,
80
test_microvm.jailer.uid, test_microvm.jailer.gid)
81
check_stats(os.path.join(test_microvm.jailer.chroot_path(), "dev"),
82
DIR_STATS, test_microvm.jailer.uid, test_microvm.jailer.gid)
83
check_stats(os.path.join(test_microvm.jailer.chroot_path(), "dev/net"),
84
DIR_STATS, test_microvm.jailer.uid, test_microvm.jailer.gid)
85
check_stats(os.path.join(test_microvm.jailer.chroot_path(), "run"),
86
DIR_STATS, test_microvm.jailer.uid, test_microvm.jailer.gid)
87
check_stats(os.path.join(test_microvm.jailer.chroot_path(), "dev/net/tun"),
88
CHAR_STATS, test_microvm.jailer.uid, test_microvm.jailer.gid)
89
check_stats(os.path.join(test_microvm.jailer.chroot_path(), "dev/kvm"),
90
CHAR_STATS, test_microvm.jailer.uid, test_microvm.jailer.gid)
91
check_stats(os.path.join(test_microvm.jailer.chroot_path(),
92
"firecracker"), FILE_STATS, 0, 0)
93
94
95
def test_arbitrary_usocket_location(test_microvm_with_initrd):
96
"""Test arbitrary location scenario for the api socket."""
97
test_microvm = test_microvm_with_initrd
98
test_microvm.jailer.extra_args = {'api-sock': 'api.socket'}
99
100
test_microvm.spawn()
101
102
check_stats(os.path.join(test_microvm.jailer.chroot_path(),
103
"api.socket"), SOCK_STATS,
104
test_microvm.jailer.uid, test_microvm.jailer.gid)
105
106
107
def check_cgroups(cgroups, cgroup_location, jailer_id):
108
"""Assert that every cgroup in cgroups is correctly set."""
109
for cgroup in cgroups:
110
controller = cgroup.split('.')[0]
111
file_name, value = cgroup.split('=')
112
location = cgroup_location + '/{}/{}/{}/'.format(
113
controller,
114
FC_BINARY_NAME,
115
jailer_id
116
)
117
tasks_file = location + 'tasks'
118
file = location + file_name
119
120
assert open(file, 'r').readline().strip() == value
121
assert open(tasks_file, 'r').readline().strip().isdigit()
122
123
124
def get_cpus(node):
125
"""Retrieve CPUs from NUMA node."""
126
sys_node = '/sys/devices/system/node/node' + str(node)
127
assert os.path.isdir(sys_node)
128
node_cpus_path = sys_node + '/cpulist'
129
130
return open(node_cpus_path, 'r').readline().strip()
131
132
133
def test_cgroups(test_microvm_with_initrd):
134
"""Test the cgroups are correctly set by the jailer."""
135
test_microvm = test_microvm_with_initrd
136
test_microvm.jailer.cgroups = ['cpu.shares=2', 'cpu.cfs_period_us=200000']
137
test_microvm.jailer.numa_node = 0
138
139
test_microvm.spawn()
140
141
# Retrieve CPUs from NUMA node 0.
142
node_cpus = get_cpus(test_microvm.jailer.numa_node)
143
144
# Apending the cgroups that should be creating by --node option
145
# This must be changed once --node options is removed
146
cgroups = test_microvm.jailer.cgroups + [
147
'cpuset.mems=0',
148
'cpuset.cpus={}'.format(node_cpus)
149
]
150
151
# We assume sysfs cgroups are mounted here.
152
sys_cgroup = '/sys/fs/cgroup'
153
assert os.path.isdir(sys_cgroup)
154
155
check_cgroups(cgroups, sys_cgroup, test_microvm.jailer.jailer_id)
156
157
158
def test_node_cgroups(test_microvm_with_initrd):
159
"""Test the cgroups are correctly set by the jailer."""
160
test_microvm = test_microvm_with_initrd
161
test_microvm.jailer.cgroups = None
162
test_microvm.jailer.numa_node = 0
163
164
test_microvm.spawn()
165
166
# Retrieve CPUs from NUMA node 0.
167
node_cpus = get_cpus(test_microvm.jailer.numa_node)
168
169
# Apending the cgroups that should be creating by --node option
170
# This must be changed once --node options is removed
171
cgroups = [
172
'cpuset.mems=0',
173
'cpuset.cpus={}'.format(node_cpus)
174
]
175
176
# We assume sysfs cgroups are mounted here.
177
sys_cgroup = '/sys/fs/cgroup'
178
assert os.path.isdir(sys_cgroup)
179
180
check_cgroups(cgroups, sys_cgroup, test_microvm.jailer.jailer_id)
181
182
183
def test_args_cgroups(test_microvm_with_initrd):
184
"""Test the cgroups are correctly set by the jailer."""
185
test_microvm = test_microvm_with_initrd
186
test_microvm.jailer.cgroups = ['cpu.shares=2', 'cpu.cfs_period_us=200000']
187
188
test_microvm.spawn()
189
190
# We assume sysfs cgroups are mounted here.
191
sys_cgroup = '/sys/fs/cgroup'
192
assert os.path.isdir(sys_cgroup)
193
194
check_cgroups(
195
test_microvm.jailer.cgroups,
196
sys_cgroup,
197
test_microvm.jailer.jailer_id
198
)
199
200
201
def test_new_pid_namespace(test_microvm_with_ssh):
202
"""Test that Firecracker is spawned in a new PID namespace if requested."""
203
test_microvm = test_microvm_with_ssh
204
205
test_microvm.jailer.daemonize = False
206
test_microvm.jailer.new_pid_ns = True
207
208
test_microvm.spawn()
209
210
# Check that the PID file exists.
211
fc_pid = test_microvm.pid_in_new_ns
212
assert fc_pid is not None
213
214
# Validate the PID.
215
stdout = subprocess.check_output("pidof firecracker", shell=True)
216
assert str(fc_pid) in stdout.strip().decode()
217
218
# Get the thread group IDs in each of the PID namespaces of which
219
# Firecracker process is a member of.
220
nstgid_cmd = "cat /proc/{}/status | grep NStgid".format(fc_pid)
221
nstgid_list = subprocess.check_output(
222
nstgid_cmd,
223
shell=True
224
).decode('utf-8').strip().split("\t")[1:]
225
226
# Check that Firecracker's PID namespace is nested. `NStgid` should
227
# report two values and the last one should be 1, because Firecracker
228
# becomes the init(1) process of the new PID namespace it is spawned in.
229
assert len(nstgid_list) == 2
230
assert int(nstgid_list[1]) == 1
231
assert int(nstgid_list[0]) == fc_pid
232
233