// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H_
#define SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H_

#include <stdint.h>

#include <memory>

#include "base/files/scoped_file.h"
#include "base/macros.h"
#include "sandbox/linux/bpf_dsl/codegen.h"
#include "sandbox/linux/bpf_dsl/policy.h"
#include "sandbox/sandbox_export.h"

namespace sandbox {
struct arch_seccomp_data;

// This class can be used to apply a syscall sandboxing policy expressed in a
// bpf_dsl::Policy object to the current process.
// Syscall sandboxing policies get inherited by subprocesses and, once applied,
// can never be removed for the lifetime of the process.
class SANDBOX_EXPORT SandboxBPF {
 public:
  enum class SeccompLevel {
    SINGLE_THREADED,
    MULTI_THREADED,
  };

  // Ownership of |policy| is transfered here to the sandbox object.
  // nullptr is allowed for unit tests.
  explicit SandboxBPF(std::unique_ptr<bpf_dsl::Policy> policy);
  // NOTE: Setting a policy and starting the sandbox is a one-way operation.
  // The kernel does not provide any option for unloading a loaded sandbox. The
  // sandbox remains engaged even when the object is destructed.
  ~SandboxBPF();

  // Detect if the kernel supports the specified seccomp level.
  // See StartSandbox() for a description of these.
  static bool SupportsSeccompSandbox(SeccompLevel level);

  // This is the main public entry point. It sets up the resources needed by
  // the sandbox, and enters Seccomp mode.
  // The calling process must provide a |level| to tell the sandbox which type
  // of kernel support it should engage.
  // SINGLE_THREADED will only sandbox the calling thread. Since it would be a
  // security risk, the sandbox will also check that the current process is
  // single threaded and crash if it isn't the case.
  // MULTI_THREADED requires more recent kernel support and allows to sandbox
  // all the threads of the current process. Be mindful of potential races,
  // with other threads using disallowed system calls either before or after
  // the sandbox is engaged.
  //
  // It is possible to stack multiple sandboxes by creating separate "Sandbox"
  // objects and calling "StartSandbox()" on each of them. Please note, that
  // this requires special care, though, as newly stacked sandboxes can never
  // relax restrictions imposed by earlier sandboxes. Furthermore, installing
  // a new policy requires making system calls, that might already be
  // disallowed.
  // Finally, stacking does add more kernel overhead than having a single
  // combined policy. So, it should only be used if there are no alternatives.
  bool StartSandbox(SeccompLevel level) WARN_UNUSED_RESULT;

  // The sandbox needs to be able to access files in "/proc/self/". If
  // this directory is not accessible when "StartSandbox()" gets called, the
  // caller must provide an already opened file descriptor by calling
  // "SetProcFd()".
  // The sandbox becomes the new owner of this file descriptor and will
  // close it when "StartSandbox()" executes or when the sandbox object
  // disappears.
  void SetProcFd(base::ScopedFD proc_fd);

  // Checks whether a particular system call number is valid on the current
  // architecture.
  static bool IsValidSyscallNumber(int sysnum);

  // UnsafeTraps require some syscalls to always be allowed.
  // This helper function returns true for these calls.
  static bool IsRequiredForUnsafeTrap(int sysno);

  // From within an UnsafeTrap() it is often useful to be able to execute
  // the system call that triggered the trap. The ForwardSyscall() method
  // makes this easy. It is more efficient than calling glibc's syscall()
  // function, as it avoid the extra round-trip to the signal handler. And
  // it automatically does the correct thing to report kernel-style error
  // conditions, rather than setting errno. See the comments for TrapFnc for
  // details. In other words, the return value from ForwardSyscall() is
  // directly suitable as a return value for a trap handler.
  static intptr_t ForwardSyscall(const struct arch_seccomp_data& args);

 private:
  friend class SandboxBPFTestRunner;

  // Assembles a BPF filter program from the current policy. After calling this
  // function, you must not call any other sandboxing function.
  CodeGen::Program AssembleFilter();

  // Assembles and installs a filter based on the policy that has previously
  // been configured with SetSandboxPolicy().
  void InstallFilter(bool must_sync_threads);

  base::ScopedFD proc_fd_;
  bool sandbox_has_started_;
  std::unique_ptr<bpf_dsl::Policy> policy_;

  DISALLOW_COPY_AND_ASSIGN(SandboxBPF);
};

}  // namespace sandbox

#endif  // SANDBOX_LINUX_SECCOMP_BPF_SANDBOX_BPF_H_
