# 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

[((26, 17, 29, 5), 'os.path.join', 'os.path.join', ({(27, 8, 27, 30): 'test_session_root_path', (28, 8, 28, 35): 'host.CARGO_RELEASE_REL_PATH'}, {}), '(test_session_root_path, host.CARGO_RELEASE_REL_PATH)', False, 'import os\n'), ((33, 28, 36, 5), 'os.path.join', 'os.path.join', ({(34, 8, 34, 35): 'host.CARGO_RELEASE_REL_PATH', (35, 8, 35, 38): 'host.RELEASE_BINARIES_REL_PATH'}, {}), '(host.CARGO_RELEASE_REL_PATH, host.RELEASE_BINARIES_REL_PATH)', False, 'import os\n'), ((37, 28, 40, 5), 'os.path.join', 'os.path.join', ({(38, 8, 38, 30): 'test_session_root_path', (39, 8, 39, 29): 'release_binaries_path'}, {}), '(test_session_root_path, release_binaries_path)', False, 'import os\n'), ((50, 4, 50, 32), 'os.remove', 'os.remove', ({(50, 14, 50, 31): 'demo_basic_jailer'}, {}), '(demo_basic_jailer)', False, 'import os\n'), ((68, 17, 71, 5), 'os.path.join', 'os.path.join', ({(69, 8, 69, 30): 'test_session_root_path', (70, 8, 70, 35): 'host.CARGO_RELEASE_REL_PATH'}, {}), '(test_session_root_path, host.CARGO_RELEASE_REL_PATH)', False, 'import os\n'), ((75, 28, 78, 5), 'os.path.join', 'os.path.join', ({(76, 8, 76, 35): 'host.CARGO_RELEASE_REL_PATH', (77, 8, 77, 38): 'host.RELEASE_BINARIES_REL_PATH'}, {}), '(host.CARGO_RELEASE_REL_PATH, host.RELEASE_BINARIES_REL_PATH)', False, 'import os\n'), ((79, 28, 82, 5), 'os.path.join', 'os.path.join', ({(80, 8, 80, 30): 'test_session_root_path', (81, 8, 81, 29): 'release_binaries_path'}, {}), '(test_session_root_path, release_binaries_path)', False, 'import os\n'), ((107, 4, 107, 35), 'os.remove', 'os.remove', ({(107, 14, 107, 34): 'demo_advanced_jailer'}, {}), '(demo_advanced_jailer)', False, 'import os\n'), ((108, 4, 108, 40), 'os.remove', 'os.remove', ({(108, 14, 108, 39): 'demo_harmless_firecracker'}, {}), '(demo_harmless_firecracker)', False, 'import os\n'), ((109, 4, 109, 41), 'os.remove', 'os.remove', ({(109, 14, 109, 40): 'demo_malicious_firecracker'}, {}), '(demo_malicious_firecracker)', False, 'import os\n'), ((122, 11, 122, 38), 'os.path.exists', 'os.path.exists', ({(122, 26, 122, 37): 'demo_jailer'}, {}), '(demo_jailer)', False, 'import os\n'), ((125, 14, 125, 49), 'subprocess.run', 'run', ({(125, 18, 125, 48): '[demo_jailer, ls_command_path]'}, {}), '([demo_jailer, ls_command_path])', False, 'from subprocess import run\n'), ((144, 11, 144, 47), 'os.path.exists', 'os.path.exists', ({(144, 26, 144, 46): 'demo_advanced_jailer'}, {}), '(demo_advanced_jailer)', False, 'import os\n'), ((145, 11, 145, 52), 'os.path.exists', 'os.path.exists', ({(145, 26, 145, 51): 'demo_harmless_firecracker'}, {}), '(demo_harmless_firecracker)', False, 'import os\n'), ((147, 14, 147, 68), 'subprocess.run', 'run', ({(147, 18, 147, 67): '[demo_advanced_jailer, demo_harmless_firecracker]'}, {}), '([demo_advanced_jailer, demo_harmless_firecracker])', False, 'from subprocess import run\n'), ((166, 11, 166, 47), 'os.path.exists', 'os.path.exists', ({(166, 26, 166, 46): 'demo_advanced_jailer'}, {}), '(demo_advanced_jailer)', False, 'import os\n'), ((167, 11, 167, 53), 'os.path.exists', 'os.path.exists', ({(167, 26, 167, 52): 'demo_malicious_firecracker'}, {}), '(demo_malicious_firecracker)', False, 'import os\n'), ((169, 14, 169, 69), 'subprocess.run', 'run', ({(169, 18, 169, 68): '[demo_advanced_jailer, demo_malicious_firecracker]'}, {}), '([demo_advanced_jailer, demo_malicious_firecracker])', False, 'from subprocess import run\n'), ((42, 8, 45, 9), 'os.path.join', 'os.path.join', ({(43, 12, 43, 33): 'release_binaries_path', (44, 12, 44, 31): '"""demo_basic_jailer"""'}, {}), "(release_binaries_path, 'demo_basic_jailer')", False, 'import os\n'), ((84, 8, 87, 9), 'os.path.join', 'os.path.join', ({(85, 12, 85, 33): 'release_binaries_path', (86, 12, 86, 34): '"""demo_advanced_jailer"""'}, {}), "(release_binaries_path, 'demo_advanced_jailer')", False, 'import os\n'), ((90, 8, 93, 9), 'os.path.join', 'os.path.join', ({(91, 12, 91, 33): 'release_binaries_path', (92, 12, 92, 39): '"""demo_harmless_firecracker"""'}, {}), "(release_binaries_path, 'demo_harmless_firecracker')", False, 'import os\n'), ((96, 8, 99, 9), 'os.path.join', 'os.path.join', ({(97, 12, 97, 33): 'release_binaries_path', (98, 12, 98, 40): '"""demo_malicious_firecracker"""'}, {}), "(release_binaries_path, 'demo_malicious_firecracker')", False, 'import os\n'), ((22, 12, 22, 23), 'os.getcwd', 'os.getcwd', ({}, {}), '()', False, 'import os\n'), ((64, 12, 64, 23), 'os.getcwd', 'os.getcwd', ({}, {}), '()', False, 'import os\n')]