# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""Tests that the seccomp filters don't let blacklisted syscalls through."""

import os

from subprocess import run

import pytest

import host_tools.cargo_build as host  # pylint:disable=import-error


@pytest.fixture
def tmp_basic_jailer(test_session_root_path):
    """Build `demo_basic_jailer`, required for the basic seccomp tests.

    :return: The paths of the built binary.
    """
    binaries_srcdir = os.path.normpath(
        os.path.join(
            os.getcwd(),
            'integration_tests/security/demo_advanced_seccomp/'
        )
    )
    build_path = os.path.join(
        test_session_root_path,
        host.CARGO_RELEASE_REL_PATH
    )
    run("cd {} && CARGO_TARGET_DIR={} cargo build --release".format(
        binaries_srcdir, build_path), shell=True, check=True)

    release_binaries_path = os.path.join(
        host.CARGO_RELEASE_REL_PATH,
        host.RELEASE_BINARIES_REL_PATH
    )
    release_binaries_path = os.path.join(
        test_session_root_path,
        release_binaries_path
    )
    demo_basic_jailer = os.path.normpath(
        os.path.join(
            release_binaries_path,
            'demo_basic_jailer'
        )
    )

    yield demo_basic_jailer

    os.remove(demo_basic_jailer)


@pytest.fixture
def tmp_advanced_seccomp_binaries(test_session_root_path):
    """
    Build binaries required for the advanced seccomp tests.

    Build `demo_advanced_jailer`, `demo_harmless_firecracker`, and
    `demo_malicious_firecracker.
    :return: The paths of the built binaries.
    """
    binaries_srcdir = os.path.normpath(
        os.path.join(
            os.getcwd(),
            'integration_tests/security/demo_advanced_seccomp/'
        )
    )
    build_path = os.path.join(
        test_session_root_path,
        host.CARGO_RELEASE_REL_PATH
    )
    run("cd {} && CARGO_TARGET_DIR={} cargo build --release".format(
        binaries_srcdir, build_path), shell=True, check=True)

    release_binaries_path = os.path.join(
        host.CARGO_RELEASE_REL_PATH,
        host.RELEASE_BINARIES_REL_PATH
    )
    release_binaries_path = os.path.join(
        test_session_root_path,
        release_binaries_path
    )
    demo_advanced_jailer = os.path.normpath(
        os.path.join(
            release_binaries_path,
            'demo_advanced_jailer'
        )
    )
    demo_harmless_firecracker = os.path.normpath(
        os.path.join(
            release_binaries_path,
            'demo_harmless_firecracker'
        )
    )
    demo_malicious_firecracker = os.path.normpath(
        os.path.join(
            release_binaries_path,
            'demo_malicious_firecracker'
        )
    )

    yield \
        demo_advanced_jailer, \
        demo_harmless_firecracker, \
        demo_malicious_firecracker

    os.remove(demo_advanced_jailer)
    os.remove(demo_harmless_firecracker)
    os.remove(demo_malicious_firecracker)


def test_seccomp_ls(tmp_basic_jailer):
    """Assert that the seccomp filters deny a blacklisted syscall."""
    # pylint: disable=redefined-outer-name
    # The fixture pattern causes a pylint false positive for that rule.

    # Path to the `ls` binary, which attempts to execute `SYS_access`,
    # blacklisted for Firecracker.
    ls_command_path = '/bin/ls'
    demo_jailer = tmp_basic_jailer

    assert os.path.exists(demo_jailer)

    # Compile the mini jailer.
    outcome = run([demo_jailer, ls_command_path])

    # The seccomp filters should send SIGSYS (31) to the binary. `ls` doesn't
    # handle it, so it will exit with error.
    assert outcome.returncode != 0


def test_advanced_seccomp_harmless(tmp_advanced_seccomp_binaries):
    """
    Test `demo_harmless_firecracker`.

    Test that the built demo jailer allows the built demo harmless firecracker.
    """
    # pylint: disable=redefined-outer-name
    # The fixture pattern causes a pylint false positive for that rule.

    demo_advanced_jailer, demo_harmless_firecracker, _ =\
        tmp_advanced_seccomp_binaries

    assert os.path.exists(demo_advanced_jailer)
    assert os.path.exists(demo_harmless_firecracker)

    outcome = run([demo_advanced_jailer, demo_harmless_firecracker])

    # The demo harmless firecracker should have terminated gracefully.
    assert outcome.returncode == 0


def test_advanced_seccomp_malicious(tmp_advanced_seccomp_binaries):
    """
    Test `demo_malicious_firecracker`.

    Test that the built demo jailer denies the built demo malicious
    firecracker.
    """
    # pylint: disable=redefined-outer-name
    # The fixture pattern causes a pylint false positive for that rule.

    demo_advanced_jailer, _, demo_malicious_firecracker =\
        tmp_advanced_seccomp_binaries

    assert os.path.exists(demo_advanced_jailer)
    assert os.path.exists(demo_malicious_firecracker)

    outcome = run([demo_advanced_jailer, demo_malicious_firecracker])

    # The demo malicious firecracker should have received `SIGSYS`.
    assert outcome.returncode != 0

[((686, 751), 'os.path.join', 'os.path.join', (['test_session_root_path', 'host.CARGO_RELEASE_REL_PATH'], {}), '(test_session_root_path, host.CARGO_RELEASE_REL_PATH)\n', (698, 751), False, 'import os\n'), ((934, 1007), 'os.path.join', 'os.path.join', (['host.CARGO_RELEASE_REL_PATH', 'host.RELEASE_BINARIES_REL_PATH'], {}), '(host.CARGO_RELEASE_REL_PATH, host.RELEASE_BINARIES_REL_PATH)\n', (946, 1007), False, 'import os\n'), ((1058, 1117), 'os.path.join', 'os.path.join', (['test_session_root_path', 'release_binaries_path'], {}), '(test_session_root_path, release_binaries_path)\n', (1070, 1117), False, 'import os\n'), ((1321, 1349), 'os.remove', 'os.remove', (['demo_basic_jailer'], {}), '(demo_basic_jailer)\n', (1330, 1349), False, 'import os\n'), ((1834, 1899), 'os.path.join', 'os.path.join', (['test_session_root_path', 'host.CARGO_RELEASE_REL_PATH'], {}), '(test_session_root_path, host.CARGO_RELEASE_REL_PATH)\n', (1846, 1899), False, 'import os\n'), ((2082, 2155), 'os.path.join', 'os.path.join', (['host.CARGO_RELEASE_REL_PATH', 'host.RELEASE_BINARIES_REL_PATH'], {}), '(host.CARGO_RELEASE_REL_PATH, host.RELEASE_BINARIES_REL_PATH)\n', (2094, 2155), False, 'import os\n'), ((2206, 2265), 'os.path.join', 'os.path.join', (['test_session_root_path', 'release_binaries_path'], {}), '(test_session_root_path, release_binaries_path)\n', (2218, 2265), False, 'import os\n'), ((2891, 2922), 'os.remove', 'os.remove', (['demo_advanced_jailer'], {}), '(demo_advanced_jailer)\n', (2900, 2922), False, 'import os\n'), ((2927, 2963), 'os.remove', 'os.remove', (['demo_harmless_firecracker'], {}), '(demo_harmless_firecracker)\n', (2936, 2963), False, 'import os\n'), ((2968, 3005), 'os.remove', 'os.remove', (['demo_malicious_firecracker'], {}), '(demo_malicious_firecracker)\n', (2977, 3005), False, 'import os\n'), ((3418, 3445), 'os.path.exists', 'os.path.exists', (['demo_jailer'], {}), '(demo_jailer)\n', (3432, 3445), False, 'import os\n'), ((3492, 3527), 'subprocess.run', 'run', (['[demo_jailer, ls_command_path]'], {}), '([demo_jailer, ls_command_path])\n', (3495, 3527), False, 'from subprocess import run\n'), ((4115, 4151), 'os.path.exists', 'os.path.exists', (['demo_advanced_jailer'], {}), '(demo_advanced_jailer)\n', (4129, 4151), False, 'import os\n'), ((4163, 4204), 'os.path.exists', 'os.path.exists', (['demo_harmless_firecracker'], {}), '(demo_harmless_firecracker)\n', (4177, 4204), False, 'import os\n'), ((4220, 4274), 'subprocess.run', 'run', (['[demo_advanced_jailer, demo_harmless_firecracker]'], {}), '([demo_advanced_jailer, demo_harmless_firecracker])\n', (4223, 4274), False, 'from subprocess import run\n'), ((4818, 4854), 'os.path.exists', 'os.path.exists', (['demo_advanced_jailer'], {}), '(demo_advanced_jailer)\n', (4832, 4854), False, 'import os\n'), ((4866, 4908), 'os.path.exists', 'os.path.exists', (['demo_malicious_firecracker'], {}), '(demo_malicious_firecracker)\n', (4880, 4908), False, 'import os\n'), ((4924, 4979), 'subprocess.run', 'run', (['[demo_advanced_jailer, demo_malicious_firecracker]'], {}), '([demo_advanced_jailer, demo_malicious_firecracker])\n', (4927, 4979), False, 'from subprocess import run\n'), ((1190, 1246), 'os.path.join', 'os.path.join', (['release_binaries_path', '"""demo_basic_jailer"""'], {}), "(release_binaries_path, 'demo_basic_jailer')\n", (1202, 1246), False, 'import os\n'), ((2341, 2400), 'os.path.join', 'os.path.join', (['release_binaries_path', '"""demo_advanced_jailer"""'], {}), "(release_binaries_path, 'demo_advanced_jailer')\n", (2353, 2400), False, 'import os\n'), ((2499, 2563), 'os.path.join', 'os.path.join', (['release_binaries_path', '"""demo_harmless_firecracker"""'], {}), "(release_binaries_path, 'demo_harmless_firecracker')\n", (2511, 2563), False, 'import os\n'), ((2663, 2728), 'os.path.join', 'os.path.join', (['release_binaries_path', '"""demo_malicious_firecracker"""'], {}), "(release_binaries_path, 'demo_malicious_firecracker')\n", (2675, 2728), False, 'import os\n'), ((576, 587), 'os.getcwd', 'os.getcwd', ([], {}), '()\n', (585, 587), False, 'import os\n'), ((1724, 1735), 'os.getcwd', 'os.getcwd', ([], {}), '()\n', (1733, 1735), False, 'import os\n')]