New paste Repaste Download
#include <CLI/CLI.hpp>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <linux/landlock.h>
#include <linux/prctl.h>
#include <linux/seccomp.h>
#include <sys/syscall.h>
#include <string>
#include <string_view>
#include <unistd.h>
#include <vector>
#ifdef SUPPORT_SECCOMP
#include <seccomp.h>
#include <sys/prctl.h>
#endif
#ifndef landlock_create_ruleset
static inline int
landlock_create_ruleset (const struct landlock_ruleset_attr *const attr,
                         const size_t size, const uint32_t flags)
{
  return syscall (__NR_landlock_create_ruleset, attr, size, flags);
}
#endif
#ifndef landlock_create_ruleset
static inline int
landlock_add_rule (const int ruleset_fd,
                   const enum landlock_rule_type rule_type,
                   const void *const rule_attr,
                   const uint32_t flags)
{
  return syscall (__NR_landlock_add_rule, ruleset_fd,
                  rule_type, rule_attr, flags);
}
#endif
#ifndef landlock_create_ruleset
static inline int
landlock_restrict_self (const int ruleset_fd,
                        const uint32_t flags)
{
  return syscall (__NR_landlock_restrict_self, ruleset_fd, flags);
}
#endif
enum class SyscallFilterMode : uint8_t
{
  None = 0,
  Whitelist,
  Blacklist
};
struct Options
{
  std::vector<std::string> fs_ro;
  std::vector<std::string> fs_rw;
  std::vector<int> tcp_bind;
  std::vector<int> tcp_connect;
  std::vector<std::string> scopes;
#ifdef SUPPORT_SECCOMP
  std::vector<std::string> syscalls;
  SyscallFilterMode syscall_filter_mode = SyscallFilterMode::None;
#endif
  /* Includes program arguments.  */
  std::vector<std::string> command;
};
static bool
init_seccomp (
#ifndef SUPPORT_SECCOMP
              [[maybe_unused]]
#endif
              const Options &opts)
{
#ifdef SUPPORT_SECCOMP
  using std::cerr;
  scmp_filter_ctx ctx;
#define SECCOMP_INIT_FINAL_RULESET(VIOLATE_ACTION, RULESET_ACTION)                      \
  do                                                                                    \
    {                                                                                   \
      ctx = seccomp_init (VIOLATE_ACTION);                                              \
      if (ctx == nullptr)                                                               \
        {                                                                               \
          cerr << "Failed to initialize seccomp context, bailing.\n";                   \
          return false;                                                                 \
        }                                                                               \
      for (const auto &syscall : opts.syscalls)                                         \
        {                                                                               \
          const int resolved_syscall =                                                  \
            seccomp_syscall_resolve_name(syscall.c_str ());                             \
          if (resolved_syscall == __NR_SCMP_ERROR)                                      \
            {                                                                           \
              cerr << "Failed to resolve syscall '" << syscall << "', bailing.\n";      \
              seccomp_release (ctx);                                                    \
              return false;                                                             \
            }                                                                           \
          if (seccomp_rule_add_exact (ctx, RULESET_ACTION, resolved_syscall, 0) > 0)    \
            {                                                                           \
              cerr << "Failed to add seccomp rule " #RULESET_ACTION " for syscall '"    \
                   << syscall << "', bailing.\n";                                       \
              seccomp_release (ctx);                                                    \
              return false;                                                             \
            }                                                                           \
        }                                                                               \
    } while (0);
  
  switch (opts.syscall_filter_mode)
    {
    case SyscallFilterMode::Whitelist:
      SECCOMP_INIT_FINAL_RULESET (SCMP_ACT_KILL, SCMP_ACT_ALLOW)
      break;
    case SyscallFilterMode::Blacklist:
      SECCOMP_INIT_FINAL_RULESET (SCMP_ACT_ALLOW, SCMP_ACT_KILL)
      break;
    case SyscallFilterMode::None:
      return true;
    default:
      /* Unreachable.  */
      __builtin_unreachable ();
    }
#undef SECCOMP_INIT_FINAL_RULESET
  if (seccomp_load (ctx) < 0)
    {
      cerr << "Failed to load seccomp ruleset, bailing.\n";
      seccomp_release (ctx);
      return false;
    }
  seccomp_release (ctx);
  
  /* No new privs for the calling thread, preserved across execve.  */
  if (prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
    {
      cerr << "Failed to restrict calling thread to no new privileges: "
           << strerror (errno) << '\n';
      return false;
    }
#endif
  
  return true;
}
int
main (int argc, char **argv)
{
  using std::cout, std::cerr;
  /* Argument parsing.  */
  std::string_view progname = argv[0];
  CLI::App app{"Initalise and launch a restricted program environment"};
  Options opts;
  app.set_help_flag ("--help", "Display this help and exit");
  app.add_option ("--fs-ro", opts.fs_ro, "Add PATH to read-only allowed paths")
    ->type_name ("PATH");
  app.add_option ("--fs-rw", opts.fs_rw, "Add PATH to read-write allowed paths")
    ->type_name ("PATH");
  app.add_option ("--tcp-bind", opts.tcp_bind, "Allow binding to PORT")
    ->type_name ("PORT");
  app.add_option ("--tcp-connect", opts.tcp_connect, "Allow connecting to PORT")
    ->type_name ("PORT");
  app.add_option ("--scope", opts.scopes)
    ->type_name ("ACTION")
    ->check (CLI::IsMember ({"abstract-socket", "send-signal", "a", "s"}))
    ->transform (CLI::CheckedTransformer ({
          {"a", "abstract-socket"},
          {"s", "send-signal"}}))
    ->description (R"(Deny ACTION outside of the landlock domain
  - abstract-socket: Restrict opening abstract unix sockets
  - send-signal: Restrict sending signals)");
#ifdef SUPPORT_SECCOMP
  auto syscall_whitelist = app.add_option ("--syscall-whitelist", opts.syscalls,
                                           "Whitelist syscall")
    ->type_name ("SYSCALL");
  auto syscall_blacklist = app.add_option ("--syscall-blacklist", opts.syscalls,
                                           "Blacklist syscall")
    ->type_name ("SYSCALL");
  syscall_whitelist->excludes (syscall_blacklist);
  syscall_blacklist->excludes (syscall_whitelist);
#endif
  app.add_option ("COMMAND", opts.command, "Command to run (with arguments)")
    ->required ()
    ->allow_extra_args ();
  
  CLI11_PARSE (app, argc, argv);
  
  /* Initalise ruleset.  */
  int abi = landlock_create_ruleset (nullptr, 0,
                                     LANDLOCK_CREATE_RULESET_VERSION);
  if (abi < 0)
    {
      switch (errno)
        {
        case ENOSYS:
          /* Landlock not supported. */
          cerr << "The running kernel does not support Landlock, bailing.\n";
          break;
        case EOPNOTSUPP:
          /* Landlock disabled. */
          cerr << "The running kernel has Landlock supported disabled, bailing.\n";
          break;
        }
      return EXIT_FAILURE;
    }
#ifdef SUPPORT_SECCOMP
  if (syscall_whitelist->count () > 0)
    opts.syscall_filter_mode = SyscallFilterMode::Whitelist;
  else if (syscall_blacklist->count () > 0)
    opts.syscall_filter_mode = SyscallFilterMode::Blacklist;
#endif
  if (!init_seccomp(opts))
    return EXIT_FAILURE;
  std::vector<char *> args;
  for (const auto &arg : opts.command)
    args.push_back (const_cast<char *>(arg.c_str ()));
  args.push_back (nullptr);
  execvp(args.at (0), args.data ());
  /* If the command launched succesfully, this entire section should
     become unreachable.  */
  cerr << opts.command.at (0) << ": " << strerror (errno) << '\n';
  return EXIT_FAILURE;
}
Filename: landlock.cc. Size: 8kb. View raw, , hex, or download this file.

This paste expires on 2025-02-14 19:07:55.675396. Pasted through v1-api.