/* common/KMExecer.cpp
 *
 * Copyright (C) 2023 OpenKylin, Inc
 */

#include "KMExecer.h"

#include <glib.h>
#include <sys/prctl.h>
#include <iostream>
#include <set>
#include <fcntl.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>

#ifdef ENABLE_SECCOMP
#  include <seccomp.h>
#endif

#include "KMFolder.h"
#include "KMLogger.h"
#include "KMSeccomp.h"
#include "KMUtil.h"

static char *kaimingBoxEmptyEnv[] = { nullptr };

KMExecer::KMExecer()
{
    // 获取环境变量
    gchar **envs = g_get_environ();
    int i = 0;
    while (envs[i])
    {
        string keyVal = envs[i];
        int index = keyVal.find('=');
        if (index > 0)
        {
            string key = keyVal.substr(0, index);
            string val = keyVal.substr(index + 1);
            m_envs[key] = val;
        }
        i++;
    }
}

KMExecer::KMExecer(const std::map<std::string, std::string> &defaultEnvs)
{
    if (defaultEnvs.empty())
    {
        // 获取环境变量
        gchar **envs = g_get_environ();
        int i = 0;
        while (envs[i])
        {
            string keyVal = envs[i];
            int index = keyVal.find('=');
            if (index > 0)
            {
                string key = keyVal.substr(0, index);
                string val = keyVal.substr(index + 1);
                m_envs[key] = val;
            }
            i++;
        }
    }
    else
    {
        m_envs = defaultEnvs;
    }
}

KMExecer::~KMExecer() { }

/**
 * @brief : 生成 .ld.so/cache 文件使用，暂时设置复制 args 和 fds
 */
KMExecer::KMExecer(const KMExecer &other)
{
    m_args = other.getArgs();
    m_fds = other.getFds();
    m_progress = other.getProgress();
}

void KMExecer::run(bool shareSpace, bool block)
{
    allowFD();
    if (block)
    {
        string progress = m_progress;

        // if (shareSpace)
        // {
        //     char *argv[256];
        //     memset(argv, 0x00, sizeof(char*) * 256);
        //     argv[0] = m_progress.data();
        //     for (int i = 0; i < m_args.size(); i++)
        //     {
        //         argv[i+1] = m_args.at(i).data();
        //     }
        //     int error = execvp (m_progress.c_str(), (char**)argv);
        // }
        // else
        // {
        for (auto &arg : m_args)
        {
            progress.append(" ").append(arg);
        }
        system(progress.data());
        // }
    }
    else
    {
        const char *argv[64];
        argv[0] = m_progress.data();
        int i = 1;
        for (auto &arg : m_args)
        {
            argv[i++] = arg.c_str();
        }
        argv[i] = NULL;
        bool ret = g_spawn_async(NULL, (char **)argv, NULL, (GSpawnFlags)(G_SPAWN_SEARCH_PATH | G_SPAWN_LEAVE_DESCRIPTORS_OPEN), NULL, NULL, NULL, NULL);
        if (ret)
        {
            kmlogger.debug("dbus_proxy start ok");
        }
        else
        {
            kmlogger.debug("dbus_proxy start error");
        }
    }
}

void KMExecer::setProgress(const string &progress)
{
    m_progress = progress;
}

void KMExecer::insertArg(const string &arg)
{
    m_args.insert(m_args.begin(), arg);
}

void KMExecer::addArg(const string &arg)
{
    m_args.push_back(arg);
}

void KMExecer::addArgs(const vector<string> &args)
{
    for (auto &arg : args)
    {
        addArg(arg);
    }
}

void KMExecer::addArgsDataFd(const std::string &op, int fd, const std::string &pathOptional)
{
    std::string fd_str = std::to_string(fd);
    addFd(fd);
    if (pathOptional.empty())
    {
        addArgs({ op, fd_str });
    }
    else
    {
        addArgs({ op, fd_str, pathOptional });
    }
}

void KMExecer::removeRangeArgs(int start, int end)
{
    if (start > end || end > m_args.size())
    {
        return;
    }

    if (start < 0)
    {
        start = 0;
    }

    if (end < 0)
    {
        end = m_args.size();
    }

    m_args.erase(m_args.begin() + start, m_args.begin() + end);
}

void KMExecer::insertArg(int pos, const std::string &arg)
{
    if (pos < 0)
    {
        pos = 0;
    }

    if (pos > m_args.size())
    {
        pos = m_args.size();
    }

    m_args.insert(m_args.begin() + pos, arg);
}

bool KMExecer::addLdSoConfig()
{
    string contents = "include /run/kaiming/ld.so.conf.d/app-*.conf\n"
                      "include /app/etc/ld.so.conf\n"
                      "/app/lib\n"
                      "include /run/kaiming/ld.so.conf.d/runtime-*.conf\n";

    if (!bindMountData("ld-so-conf", contents, "/etc/ld.so.conf"))
    {
        return false;
    }

    return true;
}

void KMExecer::bindDev(const string &src, const string &dest)
{
    if (!fs::exists(src))
    {
        return;
    }
    addArg("--dev-bind");
    addArg(src);
    addArg(dest);
}

void KMExecer::bindFd(int fd, const string &dest)
{
    m_fds.push_back(fd);
    addArg("--ro-bind-data");
    addArg(to_string(fd));
    addArg(dest);
}

void KMExecer::bindMount(const string &src, const string &dest, bool readOnly, bool _try)
{
    if (!fs::exists(src))
    {
        return;
    }

    string bindArg = string("--").append(readOnly ? "ro-bind" : "bind").append(_try ? "-try" : "");
    addArg(bindArg);
    addArg(src);
    addArg(dest);
}

bool KMExecer::bindMountData(const string &name, const string &str, const string &dest)
{
    int memfd = ::memfd_create(name.c_str(), MFD_CLOEXEC | MFD_ALLOW_SEALING);
    if (memfd == -1)
    {
        kmlogger.error("bind mount data : memfd_create errorn\n");
        return false;
    }

    int len = str.length();
    {
        if (::ftruncate(memfd, len) < 0)
        {
            return false;
        }

        const uint8_t *p = (const uint8_t *)str.data();
        int nbytes = len;
        while (nbytes > 0)
        {
            ssize_t k = ::write(memfd, p, nbytes);
            if (k < 0)
            {
                if (errno == EINTR)
                {
                    continue;
                }

                return false;
            }

            if (k == 0) /* Can't really happen */
            {
                errno = EIO;
                return false;
            }

            p += k;
            nbytes -= k;
        }

        if (::lseek(memfd, 0, SEEK_SET) < 0)
        {
            return false;
        }
    }

    // 下面几行改为使用addArgsDataFd(const std::string& op, int fd, const std::string& pathOptional)替换
    // addArg("--ro-bind-data");
    // addArg(to_string(memfd));
    // addArg(dest);
    // m_fds.push_back(memfd);
    addArgsDataFd("--ro-bind-data", memfd, dest);
    return true;
}

void KMExecer::syncFd(int fd)
{
    addArg("--sync-fd");
    addArg(to_string(fd));
    m_fds.push_back(fd);
}

void KMExecer::lockFile(const string &file)
{
    if (!fs::exists(file))
    {
        return;
    }

    addArg("--lock-file");
    addArg(file);
}

void KMExecer::symlink(const string &src, const string &dest)
{
    // symlink 不能重复链接
    if (m_setSymlinks.find(dest) != m_setSymlinks.end())
    {
        return;
    }

    m_setSymlinks.insert(dest);
    addArg("--symlink");
    addArg(src);
    addArg(dest);
}

void KMExecer::tmpfs(const string &target)
{
    addArg("--tmpfs");
    addArg(target);
}

void KMExecer::env2Args()
{
    for (auto pair : m_envs)
    {
        if (pair.second.length() > 0)
        {
            setEnvArgs(pair.first, pair.second);
        }
    }
}

void KMExecer::packingArgs(int start, int end, bool oneArg)
{
    g_autofree gchar *data = nullptr;
    gchar *ptr;
    gsize dataLen = 0;
    int i;

    if (end == -1)
    {
        end = m_args.size();
    }

    for (i = 0; i < end; i++)
    {
        dataLen += m_args.at(i).length() + 1;
    }

    data = g_new(gchar, dataLen);
    ptr = data;
    for (i = start; i < end; i++)
    {
        ptr = g_stpcpy(ptr, m_args.at(i).data()) + 1;
        // cout << "[lc test] " << m_args.at(i) << endl;
    }

    if (!buffer2Tmpfile("box-args", data, dataLen))
    {
        exit(-1);
    }

    if (oneArg)
    {
        m_args.clear();
        m_args.push_back(string("--args=").append(to_string(m_argsFd)));
        m_fds.push_back(m_argsFd);
    }
    else if (m_argsFd >= 0)
    {
        m_args.clear();
        m_args.push_back("--args");
        m_args.push_back(to_string(m_argsFd));
        m_fds.push_back(m_argsFd);
    }
}

/**
 * @brief : 添加环境变量
 */
void KMExecer::addEnv(const string &key, const string &val)
{
    if (key.empty())
    {
        return;
    }

    m_envs[key] = val;
}

void KMExecer::addEnvs(const map<string, string> &envs)
{
    for (auto pair : envs)
    {
        if (pair.first.empty())
        {
            continue;
        }

        m_envs[pair.first] = pair.second;
    }
}

void KMExecer::removeEnv(const string &key)
{
    m_envs.erase(key);
}

void KMExecer::init() { }

void KMExecer::allowFD()
{
    int max_open_fds = ::sysconf(_SC_OPEN_MAX);
    int fd;

    for (fd = 3; fd < max_open_fds; fd++)
    {
        ::fcntl(fd, F_SETFD, FD_CLOEXEC);
    }

    for (auto &cfd : m_fds)
    {
        ::lseek(cfd, 0, SEEK_SET);
        ::fcntl(cfd, F_SETFD, 0);
    }
}

bool KMExecer::buffer2Tmpfile(const string &name, const char *str, int len)
{
    if (str == nullptr)
    {
        return false;
    }

    if (len == -1)
    {
        len = strlen(str);
    }
    errno = 0;

    m_argsFd = ::memfd_create(name.data(), MFD_CLOEXEC | MFD_ALLOW_SEALING);
    if (m_argsFd == -1)
    {
        // need log
        return false;
    }
    // need log
    if (::ftruncate(m_argsFd, len) < 0)
    {
        return false;
    }
    const uint8_t *p = (const uint8_t *)str;
    while (len > 0)
    {
        ssize_t r = ::write(m_argsFd, p, len);
        if (r < 0)
        {
            if (errno == EINTR)
            {
                continue;
            }

            return false;
        }

        if (r == 0) /* Can't really happen */
        {
            errno = EIO;
            return false;
        }

        p += r;
        len -= r;
    }
    if (::lseek(m_argsFd, 0, SEEK_SET) < 0)
    {
        return false;
    }

    return true;
}

void KMExecer::setEnvArgs(const string &name, const string &value)
{
    addArg("--setenv");
    addArg(name);
    addArg(value);
}

void KMExecer::addBindArg(const std::string &type, const std::string &src, const std::string &dest)
{
    fs::path path(dest);
    std::string destDirname = path.parent_path();
    std::string destDirnameReal = KMUtil::realpath(destDirname);

    if (!destDirnameReal.empty())
    {
        std::string destBasename = path.filename();
        std::string destReal = KMUtil::buildFilename(destDirnameReal, destBasename);

        addArgs({ type, src, destReal });
    }
}

int KMExecer::execvpe()
{
    for (auto &arg : m_args)
    {
        m_execvpeArgs.push_back(arg.c_str());
    }

    m_execvpeArgs.push_back(nullptr);

    // 环境变量设置已经写到文件box-args中，此处环境变量设置为null结尾的空列表
    return ::execvpe(m_progress.c_str(), (char **)m_execvpeArgs.data(), kaimingBoxEmptyEnv);
}

#ifdef ENABLE_SECCOMP
bool KMExecer::setupSeccomp(const std::string &arch, unsigned long allowedPersonality, KMRunFlags runFlags, std::string &errMsg)
{
    bool multiarch = (runFlags & KAIMING_RUN_FLAG_MULTIARCH) != 0;
    bool devel = (runFlags & KAIMING_RUN_FLAG_DEVEL) != 0;

    KMSeccomp seccomp;
    if (!seccomp.seccompInit(SCMP_ACT_ALLOW))
    {
        errMsg = "Initialize seccomp failed";
        return false;
    }

    /**** BEGIN NOTE ON CODE SHARING
     *
     * There are today a number of different Linux container
     * implementations.  That will likely continue for long into the
     * future.  But we can still try to share code, and it's important
     * to do so because it affects what library and application writers
     * can do, and we should support code portability between different
     * container tools.
     *
     * This syscall blocklist is copied from linux-user-chroot, which was in turn
     * clearly influenced by the Sandstorm.io blocklist.
     *
     * If you make any changes here, I suggest sending the changes along
     * to other sandbox maintainers.  Using the libseccomp list is also
     * an appropriate venue:
     * https://groups.google.com/forum/#!forum/libseccomp
     *
     * A non-exhaustive list of links to container tooling that might
     * want to share this blocklist:
     *
     *  https://github.com/sandstorm-io/sandstorm
     *    in src/sandstorm/supervisor.c++
     *  https://git.gnome.org/browse/linux-user-chroot
     *    in src/setup-seccomp.c
     *
     * Other useful resources:
     * https://github.com/systemd/systemd/blob/HEAD/src/shared/seccomp-util.c
     * https://github.com/moby/moby/blob/HEAD/profiles/seccomp/default.json
     *
     **** END NOTE ON CODE SHARING
     */

    static std::vector<KMSeccomp::SyscallBlock> syscallBlocks = {
        /* Block dmesg */
        { SCMP_SYS(syslog), EPERM },
        /* Useless old syscall */
        { SCMP_SYS(uselib), EPERM },
        /* Don't allow disabling accounting */
        { SCMP_SYS(acct), EPERM },
        /* 16-bit code is unnecessary in the sandbox, and modify_ldt is a
        historic source of interesting information leaks. */
        { SCMP_SYS(modify_ldt), EPERM },
        /* Don't allow reading current quota use */
        { SCMP_SYS(quotactl), EPERM },

        /* Don't allow access to the kernel keyring */
        { SCMP_SYS(add_key), EPERM },
        { SCMP_SYS(keyctl), EPERM },
        { SCMP_SYS(request_key), EPERM },

        /* Scary VM/NUMA ops */
        { SCMP_SYS(move_pages), EPERM },
        { SCMP_SYS(mbind), EPERM },
        { SCMP_SYS(get_mempolicy), EPERM },
        { SCMP_SYS(set_mempolicy), EPERM },
        { SCMP_SYS(migrate_pages), EPERM },

        /* Don't allow subnamespace setups: */
        { SCMP_SYS(unshare), EPERM },
        { SCMP_SYS(setns), EPERM },
        { SCMP_SYS(mount), EPERM },
        { SCMP_SYS(umount), EPERM },
        { SCMP_SYS(umount2), EPERM },
        { SCMP_SYS(pivot_root), EPERM },
        { SCMP_SYS(chroot), EPERM },
#  if defined(__s390__) || defined(__s390x__) || defined(__CRIS__)
        /* Architectures with CONFIG_CLONE_BACKWARDS2: the child stack
         * and flags arguments are reversed so the flags come second */
        { SCMP_SYS(clone), new KMSeccomp::ScmpArgCmp{ 1, SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER } },
#  else
        /* Normally the flags come first */
        { SCMP_SYS(clone), EPERM, new KMSeccomp::ScmpArgCmp{ 0, SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER } },
#  endif

        /* Don't allow faking input to the controlling tty (CVE-2017-5226) */
        // {SCMP_SYS (ioctl), EPERM, &SCMP_A1 (SCMP_CMP_MASKED_EQ, 0xFFFFFFFFu, (int) TIOCSTI)},
        { SCMP_SYS(ioctl), EPERM, new KMSeccomp::ScmpArgCmp{ 1, SCMP_CMP_MASKED_EQ, 0xFFFFFFFFu, (int)TIOCSTI } },
        /* In the unlikely event that the controlling tty is a Linux virtual
         * console (/dev/tty2 or similar), copy/paste operations have an effect
         * similar to TIOCSTI (CVE-2023-28100) */
        { SCMP_SYS(ioctl), EPERM, new KMSeccomp::ScmpArgCmp{ 1, SCMP_CMP_MASKED_EQ, 0xFFFFFFFFu, (int)TIOCLINUX } },

        /* seccomp can't look into clone3()'s struct clone_args to check whether
         * the flags are OK, so we have no choice but to block clone3().
         * Return ENOSYS so user-space will fall back to clone().
         * (GHSA-67h7-w3jq-vh4q; see also https://github.com/moby/moby/commit/9f6b562d) */
        { SCMP_SYS(clone3), ENOSYS },

        /* New mount manipulation APIs can also change our VFS. There's no
         * legitimate reason to do these in the sandbox, so block all of them
         * rather than thinking about which ones might be dangerous.
         * (GHSA-67h7-w3jq-vh4q) */
        { SCMP_SYS(open_tree), ENOSYS },
        { SCMP_SYS(move_mount), ENOSYS },
        { SCMP_SYS(fsopen), ENOSYS },
        { SCMP_SYS(fsconfig), ENOSYS },
        { SCMP_SYS(fsmount), ENOSYS },
        { SCMP_SYS(fspick), ENOSYS },
        { SCMP_SYS(mount_setattr), ENOSYS }
    };

    if (!seccomp.addArch2Filter(arch, multiarch, errMsg))
    {
        KMError(errMsg);
        return false;
    }

    if (!seccomp.blockSyscall(syscallBlocks, errMsg))
    {
        KMError(errMsg);
        return false;
    }

    if (!devel)
    {
        std::vector<KMSeccomp::SyscallBlock> syscallNondevelBlocks = { /* Profiling operations; we expect these to be done by tools from outside
                                                                        * the sandbox.  In particular perf has been the source of many CVEs.
                                                                        */
                                                                       { SCMP_SYS(perf_event_open), EPERM },
                                                                       /* Don't allow you to switch to bsd emulation or whatnot */
                                                                       { SCMP_SYS(personality), EPERM, new KMSeccomp::ScmpArgCmp{ 0, SCMP_CMP_NE, allowedPersonality, 0 } },
                                                                       { SCMP_SYS(ptrace), EPERM }
        };

        if (!seccomp.blockSyscall(syscallNondevelBlocks, errMsg))
        {
            KMError(errMsg);
            return false;
        }
    }

    static std::vector<KMSeccomp::SocketFamily> socketFamilyAllows = { /* NOTE: Keep in numerical order */
                                                                       { AF_UNSPEC, KAIMING_RUN_FLAG_DEFAULT },  { AF_LOCAL, KAIMING_RUN_FLAG_DEFAULT }, { AF_INET, KAIMING_RUN_FLAG_DEFAULT },       { AF_INET6, KAIMING_RUN_FLAG_DEFAULT },
                                                                       { AF_NETLINK, KAIMING_RUN_FLAG_DEFAULT }, { AF_CAN, KAIMING_RUN_FLAG_CANBUS },    { AF_BLUETOOTH, KAIMING_RUN_FLAG_BLUETOOTH }
    };
    seccomp.blockSocket(socketFamilyAllows, runFlags);

    KMTmpfile seccomp_tmpf;
    if (!KMUtil::openAnonymousTmpfileFull(O_RDWR | O_CLOEXEC, "/tmp", seccomp_tmpf, errMsg))
    {
        KMError(errMsg);
        return false;
    }

    if (!seccomp.exportBpf(seccomp_tmpf.fd(), errMsg))
    {
        KMError(errMsg);
        return false;
    }

    ::lseek(seccomp_tmpf.fd(), 0, SEEK_SET);

    addArgsDataFd("--seccomp", seccomp_tmpf.stealFd(), "");

    return true;
}
#endif
