// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "sandbox/policy/linux/bpf_hardware_video_decoding_policy_linux.h"

#include <linux/kcmp.h>

#include "base/notreached.h"
#include "media/gpu/buildflags.h"
#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h"
#include "sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h"
#include "sandbox/linux/seccomp-bpf-helpers/syscall_sets.h"
#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
#include "sandbox/linux/system_headers/linux_syscalls.h"
#include "sandbox/policy/linux/sandbox_linux.h"

using sandbox::bpf_dsl::AllOf;
using sandbox::bpf_dsl::Allow;
using sandbox::bpf_dsl::Arg;
using sandbox::bpf_dsl::Error;
using sandbox::bpf_dsl::If;
using sandbox::bpf_dsl::ResultExpr;

namespace sandbox::policy {

// static
HardwareVideoDecodingProcessPolicy::PolicyType
HardwareVideoDecodingProcessPolicy::ComputePolicyType(
    bool use_amd_specific_policies) {
  // TODO(b/210759684): the policy type computation is currently based on the
  // GPU. In reality, we should base this on the video decoding hardware. This
  // is good enough on ChromeOS but may be not good enough for a Linux system
  // with multiple GPUs.
#if BUILDFLAG(USE_VAAPI) && BUILDFLAG(USE_V4L2_CODEC)
  // Both backends are compiled in; the active backend is chosen at runtime by
  // the media process via the kPreferV4L2VideoAcceleration feature. Sandbox the
  // process with the union of what either backend needs (we cannot consult the
  // media layer here without violating sandbox layering, and broker permissions
  // are immutable once the sandbox is up). The VA-API half still depends on the
  // GPU, so we keep the Intel/AMD split here just like in the single-backend
  // case.
  return use_amd_specific_policies ? PolicyType::kVaapiOnAMDAndV4L2
                                   : PolicyType::kVaapiAndV4L2;
#elif BUILDFLAG(USE_VAAPI)
  return use_amd_specific_policies ? PolicyType::kVaapiOnAMD
                                   : PolicyType::kVaapiOnIntel;
#elif BUILDFLAG(USE_V4L2_CODEC)
  return PolicyType::kV4L2;
#else
  // TODO(b/195769334): the hardware video decoding sandbox is really only
  // useful when building with VA-API or V4L2 (otherwise, we're not really doing
  // hardware video decoding). Consider restricting the kHardwareVideoDecoding
  // sandbox type to exist only in those configurations so that the
  // HardwareVideoDecodingProcessPolicy is only compiled in those scenarios. As
  // it is now, kHardwareVideoDecoding exists for all ash-chrome builds because
  // chromeos/ash/experiences/arc/video/gpu_arc_video_service_host.cc depends on
  // it and that file is built for ash-chrome regardless of VA-API/V4L2. That
  // means that bots like linux-chromeos-rel end up compiling this policy.
  NOTREACHED();
#endif
}

HardwareVideoDecodingProcessPolicy::HardwareVideoDecodingProcessPolicy(
    PolicyType policy_type)
    : policy_type_(policy_type) {}

ResultExpr HardwareVideoDecodingProcessPolicy::EvaluateSyscall(
    int system_call_number) const {
  switch (policy_type_) {
    case PolicyType::kVaapiOnIntel:
      return EvaluateSyscallForVaapiOnIntel(system_call_number);
    case PolicyType::kVaapiOnAMD:
      return EvaluateSyscallForVaapiOnAMD(system_call_number);
    case PolicyType::kV4L2:
      return EvaluateSyscallForV4L2(system_call_number);
    case PolicyType::kVaapiAndV4L2:
      return EvaluateSyscallForVaapiAndV4L2(system_call_number);
    case PolicyType::kVaapiOnAMDAndV4L2:
      // EvaluateSyscallForVaapiOnAMD() is a strict superset of
      // EvaluateSyscallForV4L2() (it already allows ioctl and a restricted
      // sched_setaffinity, and crashes on truncate), so the union of the two
      // is simply the AMD policy.
      return EvaluateSyscallForVaapiOnAMD(system_call_number);
  }
  NOTREACHED();
}

ResultExpr HardwareVideoDecodingProcessPolicy::EvaluateSyscallForVaapiOnIntel(
    int system_call_number) const {
  if (SyscallSets::IsTruncate(system_call_number)) {
    // Explicitly disallow ftruncate()/truncate() to eliminate the possibility
    // that a video decoder process can change the size of a file (including,
    // e.g., a dma-buf).
    return CrashSIGSYS();
  }

  if (system_call_number == __NR_ioctl)
    return Allow();

  auto* sandbox_linux = SandboxLinux::GetInstance();
  if (sandbox_linux->ShouldBrokerHandleSyscall(system_call_number))
    return sandbox_linux->HandleViaBroker(system_call_number);

  return BPFBasePolicy::EvaluateSyscall(system_call_number);
}

ResultExpr HardwareVideoDecodingProcessPolicy::EvaluateSyscallForVaapiOnAMD(
    int system_call_number) const {
  if (SyscallSets::IsTruncate(system_call_number)) {
    // Explicitly disallow ftruncate()/truncate() to eliminate the possibility
    // that a video decoder process can change the size of a file (including,
    // e.g., a dma-buf).
    return CrashSIGSYS();
  }

  switch (system_call_number) {
    case __NR_getdents64:
    case __NR_ioctl:
    case __NR_sysinfo:
    case __NR_sched_setscheduler:
      return Allow();
    case __NR_sched_setaffinity:
      return RestrictSchedTarget(GetPolicyPid(), system_call_number);
    case __NR_kcmp: {
      const Arg<pid_t> pid1(0);
      const Arg<pid_t> pid2(1);
      const Arg<int> type(2);
      const pid_t policy_pid = GetPolicyPid();
      // Only allowed when comparing file handles for the calling thread.
      return If(AllOf(pid1 == policy_pid, pid2 == policy_pid,
                      type == KCMP_FILE),
                Allow())
          .Else(Error(EPERM));
    }
  }

  auto* sandbox_linux = SandboxLinux::GetInstance();
  if (sandbox_linux->ShouldBrokerHandleSyscall(system_call_number))
    return sandbox_linux->HandleViaBroker(system_call_number);

  return BPFBasePolicy::EvaluateSyscall(system_call_number);
}

ResultExpr HardwareVideoDecodingProcessPolicy::EvaluateSyscallForV4L2(
    int system_call_number) const {
  if (SyscallSets::IsTruncate(system_call_number)) {
    // Explicitly disallow ftruncate()/truncate() to eliminate the possibility
    // that a video decoder process can change the size of a file (including,
    // e.g., a dma-buf).
    return CrashSIGSYS();
  }

  if (system_call_number == __NR_ioctl)
    return Allow();

  if (system_call_number == __NR_sched_setaffinity) {
    return RestrictSchedTarget(GetPolicyPid(), system_call_number);
  }

  auto* sandbox_linux = SandboxLinux::GetInstance();
  if (sandbox_linux->ShouldBrokerHandleSyscall(system_call_number))
    return sandbox_linux->HandleViaBroker(system_call_number);

  return BPFBasePolicy::EvaluateSyscall(system_call_number);
}

ResultExpr HardwareVideoDecodingProcessPolicy::EvaluateSyscallForVaapiAndV4L2(
    int system_call_number) const {
  // Union of the VA-API-on-Intel and V4L2 filters. The runtime backend choice
  // is not visible at sandbox-build time, so we permit the superset of what
  // either backend needs.
  if (SyscallSets::IsTruncate(system_call_number)) {
    // Explicitly disallow ftruncate()/truncate() to eliminate the possibility
    // that a video decoder process can change the size of a file (including,
    // e.g., a dma-buf).
    return CrashSIGSYS();
  }

  if (system_call_number == __NR_ioctl) {
    return Allow();
  }

  // From the V4L2 policy: V4L2 drivers may pin worker threads.
  if (system_call_number == __NR_sched_setaffinity) {
    return RestrictSchedTarget(GetPolicyPid(), system_call_number);
  }

  auto* sandbox_linux = SandboxLinux::GetInstance();
  if (sandbox_linux->ShouldBrokerHandleSyscall(system_call_number)) {
    return sandbox_linux->HandleViaBroker(system_call_number);
  }

  return BPFBasePolicy::EvaluateSyscall(system_call_number);
}

}  // namespace sandbox::policy
