/* KMSeccomp.h
 *
 * Copyright (C) 2023 OpenKylin, Inc
 *
 * @note : Encapsulation of seccomp library
 */

#include "KMSeccomp.h"

#ifdef ENABLE_SECCOMP

#include "kmtranslation.h"
#include <iostream>
#include <limits.h>
#include <string.h>

KMSeccomp::SyscallBlock::SyscallBlock(int scall, int errnum, ScmpArgCmpPtr arg)
    : m_scall(scall)
    , m_errnum(errnum)
    , m_arg(arg)
{
}

KMSeccomp::SyscallBlock::~SyscallBlock() { }

KMSeccomp::SocketFamily::SocketFamily(int family, KMRunFlags flagsMask)
    : m_family(family)
    , m_flagsMask(flagsMask)
{
}

KMSeccomp::SocketFamily::~SocketFamily() { }

KMSeccomp::KMSeccomp() { }

KMSeccomp::~KMSeccomp()
{
    destroy();
}

/**
 * @brief : seccomp_init
 * @param : [in] defaultFilterAction，默认过滤行为
 * @note  : 需要KMSeccomp对象创建后，需要调用此初始化方法后才能进行后续使用
 */
bool KMSeccomp::seccompInit(unsigned int defaultFilterAction)
{
    destroy();

    m_seccomp = seccomp_init(defaultFilterAction);

    return m_seccomp != nullptr;
}

void KMSeccomp::destroy()
{
    if (m_seccomp != nullptr)
    {
        seccomp_release(m_seccomp);
    }

    m_seccomp = nullptr;
}

static std::vector<unsigned int> seccompX8664ExtraArches = { SCMP_ARCH_X86, 0 };

#  ifdef SCMP_ARCH_AARCH64
static std::vector<unsigned int> seccompAArch64ExtraArches = { SCMP_ARCH_ARM, 0 };
#  endif // !SCMP_ARCH_AARCH64

bool KMSeccomp::addArch2Filter(const std::string &arch, bool multiarch, std::string &errMsg)
{
    if (arch.empty())
    {
        return true;
    }

    unsigned int archIdInt = 0;
    std::vector<unsigned int> extraArchesVector;

    if (arch == "i386")
    {
        archIdInt = SCMP_ARCH_X86;
    }
    else if (arch == "x86_64")
    {
        archIdInt = SCMP_ARCH_X86_64;
        extraArchesVector = seccompX8664ExtraArches;
    }
    else if (arch == "arm")
    {
        archIdInt = SCMP_ARCH_ARM;
    }
#  ifdef SCMP_ARCH_AARCH64
    else if (arch == "aarch64")
    {
        archIdInt = SCMP_ARCH_AARCH64;
        extraArchesVector = seccompAArch64ExtraArches;
    }
#  endif // !SCMP_ARCH_AARCH64

    /* We only really need to handle arches on multiarch systems.  If only one arch is supported the default is fine */
    if (archIdInt != 0)
    {
        /*  This *adds* the target arch, instead of replacing the native one. This is not ideal, because we'd like to only
            allow the target arch, but we can't really disallow the native arch at this point, because then bubblewrap
            couldn't continue running. */
        int r = seccomp_arch_add(m_seccomp, archIdInt);
        if (r < 0 && r != -EEXIST)
        {
            errMsg = _("Failed to add architecture to seccomp filter: ") + KMSeccomp::strerror(r);
            return false;
        }

        if (multiarch && !extraArchesVector.empty())
        {
            for (unsigned int archId : extraArchesVector)
            {
                if (archId == 0)
                {
                    break;
                }

                r = seccomp_arch_add(m_seccomp, archId);
                if (r < 0 && r != -EEXIST)
                {
                    errMsg = _("Failed to add architecture to seccomp filter: ") + KMSeccomp::strerror(r);
                    return false;
                }
            }
        }
    }

    return true;
}

std::string KMSeccomp::strerror(int negativeErrno)
{
    if (negativeErrno >= 0)
    {
        return "Non-negative error value from libseccomp?";
    }

    if (negativeErrno <= INT_MIN)
    {
        return "Out of range error value from libseccomp?";
    }

    switch (negativeErrno)
    {
    case -EDOM:
        return "Architecture specific failure";

    case -EFAULT:
        return "Internal libseccomp failure (unknown syscall?)";

    case -ECANCELED:
        return "System failure beyond the control of libseccomp";

    default:
        break;
    }

    /* e.g. -ENOMEM: the result of strerror() is good enough */
    return ::strerror(-negativeErrno);
}

bool KMSeccomp::blockSyscall(const std::vector<SyscallBlock> &syscallBlocks, std::string &errMsg)
{
    /* TODO: Should we filter the kernel keyring syscalls in some way?
     * We do want them to be used by desktop apps, but they could also perhaps
     * leak system stuff or secrets from other apps.
     */
    int r = 0;
    for (const SyscallBlock &sysblock : syscallBlocks)
    {
        int scall = sysblock.m_scall;
        int errnum = sysblock.m_errnum;

        if (errnum != EPERM && errnum != ENOSYS)
        {
            std::cout << "Block syscall's errnum must be EPERM or ENOSYS." << std::endl;
            continue;
        }

        if (sysblock.m_arg)
        {
            r = seccomp_rule_add(m_seccomp, SCMP_ACT_ERRNO(errnum), scall, 1, *sysblock.m_arg);
        }
        else
        {
            r = seccomp_rule_add(m_seccomp, SCMP_ACT_ERRNO(errnum), scall, 0);
        }

        /* EFAULT means "internal libseccomp error", but in practice we get this for syscall numbers added via kmsyscallsprivate.h
         * when trying to filter them on a non-native architecture, because libseccomp cannot map the syscall number to a name and back to a
         * number for the non-native architecture. */
        if (r == -EFAULT)
        {
            std::cout << "Unable to block syscall " << scall << ": syscall not known to libseccomp?" << std::endl;
        }
        else if (r < 0)
        {
            errMsg = _("Failed to block syscall ") + std::to_string(scall) + ": " + KMSeccomp::strerror(r);
            return false;
        }
    }

    return true;
}

void KMSeccomp::blockSocket(const std::vector<KMSeccomp::SocketFamily> &socketFamilyAllows, KMRunFlags runFlags)
{
    /* Socket filtering doesn't work on e.g. i386, so ignore failures here
     * However, we need to user seccomp_rule_add_exact to avoid libseccomp doing
     * something else: https://github.com/seccomp/libseccomp/issues/8 */

    int lastAllowedFamily = -1;
    for (const SocketFamily &socketFamily : socketFamilyAllows)
    {
        int family = socketFamily.m_family;

        if (socketFamily.m_flagsMask != 0 && (socketFamily.m_flagsMask & runFlags) != socketFamily.m_flagsMask)
        {
            continue;
        }

        for (unsigned long disallowed = lastAllowedFamily + 1; disallowed < family; ++disallowed)
        {
            /* Blocklist the in-between valid families */
            seccomp_rule_add_exact(m_seccomp, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, new KMSeccomp::ScmpArgCmp{ 0, SCMP_CMP_EQ, disallowed, 0 });
        }

        lastAllowedFamily = family;
    }

    /* Blocklist the rest */
    seccomp_rule_add_exact(m_seccomp, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, new KMSeccomp::ScmpArgCmp{ 0, SCMP_CMP_GE, (unsigned long)(lastAllowedFamily + 1), 0 });
}

bool KMSeccomp::exportBpf(int fd, std::string &errMsg)
{
    int r = seccomp_export_bpf(m_seccomp, fd);
    if (r != 0)
    {
        errMsg = _("Failed to export bpf: ") + KMSeccomp::strerror(r);
        return false;
    }

    return true;
}

#endif // !ENABLE_SECCOMP
