Path: blob/main/tests/integration_tests/security/test_jail.py
1958 views
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.1# SPDX-License-Identifier: Apache-2.02"""Tests that verify the jailer's behavior."""3import os4import stat5import subprocess67from framework.defs import FC_BINARY_NAME8from framework.jailer import JailerContext9import host_tools.cargo_build as build_tools101112# These are the permissions that all files/dirs inside the jailer have.13REG_PERMS = stat.S_IRUSR | stat.S_IWUSR | \14stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | \15stat.S_IROTH | stat.S_IXOTH16DIR_STATS = stat.S_IFDIR | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR17FILE_STATS = stat.S_IFREG | REG_PERMS18SOCK_STATS = stat.S_IFSOCK | REG_PERMS19# These are the stats of the devices created by tha jailer.20CHAR_STATS = stat.S_IFCHR | stat.S_IRUSR | stat.S_IWUSR212223def check_stats(filepath, stats, uid, gid):24"""Assert on uid, gid and expected stats for the given path."""25st = os.stat(filepath)2627assert st.st_gid == gid28assert st.st_uid == uid29assert st.st_mode ^ stats == 0303132def test_default_chroot(test_microvm_with_ssh):33"""Test that the code base assigns a chroot if none is specified."""34test_microvm = test_microvm_with_ssh3536# Start customizing arguments.37# Test that firecracker's default chroot folder is indeed `/srv/jailer`.38test_microvm.jailer.chroot_base = None3940test_microvm.spawn()4142# Test the expected outcome.43assert os.path.exists(test_microvm.jailer.api_socket_path())444546def test_empty_jailer_id(test_microvm_with_ssh):47"""Test that the jailer ID cannot be empty."""48test_microvm = test_microvm_with_ssh49fc_binary, _ = build_tools.get_firecracker_binaries()5051# Set the jailer ID to None.52test_microvm.jailer = JailerContext(53jailer_id="",54exec_file=fc_binary,55)5657# pylint: disable=W070358try:59test_microvm.spawn()60# If the exception is not thrown, it means that Firecracker was61# started successfully, hence there's a bug in the code due to which62# we can set an empty ID.63assert False64except Exception as err:65expected_err = "Jailer error: Invalid instance ID: invalid len (0);" \66" the length must be between 1 and 64"67assert expected_err in str(err)686970def test_default_chroot_hierarchy(test_microvm_with_initrd):71"""Test the folder hierarchy created by default by the jailer."""72test_microvm = test_microvm_with_initrd7374test_microvm.spawn()7576# We do checks for all the things inside the chroot that the jailer crates77# by default.78check_stats(test_microvm.jailer.chroot_path(), DIR_STATS,79test_microvm.jailer.uid, test_microvm.jailer.gid)80check_stats(os.path.join(test_microvm.jailer.chroot_path(), "dev"),81DIR_STATS, test_microvm.jailer.uid, test_microvm.jailer.gid)82check_stats(os.path.join(test_microvm.jailer.chroot_path(), "dev/net"),83DIR_STATS, test_microvm.jailer.uid, test_microvm.jailer.gid)84check_stats(os.path.join(test_microvm.jailer.chroot_path(), "run"),85DIR_STATS, test_microvm.jailer.uid, test_microvm.jailer.gid)86check_stats(os.path.join(test_microvm.jailer.chroot_path(), "dev/net/tun"),87CHAR_STATS, test_microvm.jailer.uid, test_microvm.jailer.gid)88check_stats(os.path.join(test_microvm.jailer.chroot_path(), "dev/kvm"),89CHAR_STATS, test_microvm.jailer.uid, test_microvm.jailer.gid)90check_stats(os.path.join(test_microvm.jailer.chroot_path(),91"firecracker"), FILE_STATS, 0, 0)929394def test_arbitrary_usocket_location(test_microvm_with_initrd):95"""Test arbitrary location scenario for the api socket."""96test_microvm = test_microvm_with_initrd97test_microvm.jailer.extra_args = {'api-sock': 'api.socket'}9899test_microvm.spawn()100101check_stats(os.path.join(test_microvm.jailer.chroot_path(),102"api.socket"), SOCK_STATS,103test_microvm.jailer.uid, test_microvm.jailer.gid)104105106def check_cgroups(cgroups, cgroup_location, jailer_id):107"""Assert that every cgroup in cgroups is correctly set."""108for cgroup in cgroups:109controller = cgroup.split('.')[0]110file_name, value = cgroup.split('=')111location = cgroup_location + '/{}/{}/{}/'.format(112controller,113FC_BINARY_NAME,114jailer_id115)116tasks_file = location + 'tasks'117file = location + file_name118119assert open(file, 'r').readline().strip() == value120assert open(tasks_file, 'r').readline().strip().isdigit()121122123def get_cpus(node):124"""Retrieve CPUs from NUMA node."""125sys_node = '/sys/devices/system/node/node' + str(node)126assert os.path.isdir(sys_node)127node_cpus_path = sys_node + '/cpulist'128129return open(node_cpus_path, 'r').readline().strip()130131132def test_cgroups(test_microvm_with_initrd):133"""Test the cgroups are correctly set by the jailer."""134test_microvm = test_microvm_with_initrd135test_microvm.jailer.cgroups = ['cpu.shares=2', 'cpu.cfs_period_us=200000']136test_microvm.jailer.numa_node = 0137138test_microvm.spawn()139140# Retrieve CPUs from NUMA node 0.141node_cpus = get_cpus(test_microvm.jailer.numa_node)142143# Apending the cgroups that should be creating by --node option144# This must be changed once --node options is removed145cgroups = test_microvm.jailer.cgroups + [146'cpuset.mems=0',147'cpuset.cpus={}'.format(node_cpus)148]149150# We assume sysfs cgroups are mounted here.151sys_cgroup = '/sys/fs/cgroup'152assert os.path.isdir(sys_cgroup)153154check_cgroups(cgroups, sys_cgroup, test_microvm.jailer.jailer_id)155156157def test_node_cgroups(test_microvm_with_initrd):158"""Test the cgroups are correctly set by the jailer."""159test_microvm = test_microvm_with_initrd160test_microvm.jailer.cgroups = None161test_microvm.jailer.numa_node = 0162163test_microvm.spawn()164165# Retrieve CPUs from NUMA node 0.166node_cpus = get_cpus(test_microvm.jailer.numa_node)167168# Apending the cgroups that should be creating by --node option169# This must be changed once --node options is removed170cgroups = [171'cpuset.mems=0',172'cpuset.cpus={}'.format(node_cpus)173]174175# We assume sysfs cgroups are mounted here.176sys_cgroup = '/sys/fs/cgroup'177assert os.path.isdir(sys_cgroup)178179check_cgroups(cgroups, sys_cgroup, test_microvm.jailer.jailer_id)180181182def test_args_cgroups(test_microvm_with_initrd):183"""Test the cgroups are correctly set by the jailer."""184test_microvm = test_microvm_with_initrd185test_microvm.jailer.cgroups = ['cpu.shares=2', 'cpu.cfs_period_us=200000']186187test_microvm.spawn()188189# We assume sysfs cgroups are mounted here.190sys_cgroup = '/sys/fs/cgroup'191assert os.path.isdir(sys_cgroup)192193check_cgroups(194test_microvm.jailer.cgroups,195sys_cgroup,196test_microvm.jailer.jailer_id197)198199200def test_new_pid_namespace(test_microvm_with_ssh):201"""Test that Firecracker is spawned in a new PID namespace if requested."""202test_microvm = test_microvm_with_ssh203204test_microvm.jailer.daemonize = False205test_microvm.jailer.new_pid_ns = True206207test_microvm.spawn()208209# Check that the PID file exists.210fc_pid = test_microvm.pid_in_new_ns211assert fc_pid is not None212213# Validate the PID.214stdout = subprocess.check_output("pidof firecracker", shell=True)215assert str(fc_pid) in stdout.strip().decode()216217# Get the thread group IDs in each of the PID namespaces of which218# Firecracker process is a member of.219nstgid_cmd = "cat /proc/{}/status | grep NStgid".format(fc_pid)220nstgid_list = subprocess.check_output(221nstgid_cmd,222shell=True223).decode('utf-8').strip().split("\t")[1:]224225# Check that Firecracker's PID namespace is nested. `NStgid` should226# report two values and the last one should be 1, because Firecracker227# becomes the init(1) process of the new PID namespace it is spawned in.228assert len(nstgid_list) == 2229assert int(nstgid_list[1]) == 1230assert int(nstgid_list[0]) == fc_pid231232233