// Copyright 2017 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_network_policy_linux.h"

#include <memory>

#include <fcntl.h>
#include <linux/ioctl.h>
#include <linux/net.h>
#include <linux/netlink.h>
#include <linux/sockios.h>
#include <linux/wireless.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/inotify.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <unistd.h>

#include "base/feature_list.h"
#include "build/build_config.h"
#include "sandbox/linux/bpf_dsl/bpf_dsl.h"
#include "sandbox/linux/bpf_dsl/bpf_dsl_forward.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/syscall_broker/broker_process.h"
#include "sandbox/linux/system_headers/linux_syscalls.h"
#include "sandbox/policy/features.h"
#include "sandbox/policy/linux/bpf_base_policy_linux.h"
#include "sandbox/policy/linux/sandbox_linux.h"

using sandbox::bpf_dsl::Allow;
using sandbox::bpf_dsl::Arg;
using sandbox::bpf_dsl::BoolExpr;
using sandbox::bpf_dsl::Error;
using sandbox::bpf_dsl::If;
using sandbox::bpf_dsl::ResultExpr;
using sandbox::bpf_dsl::Trap;
using sandbox::syscall_broker::BrokerProcess;

#define CASES SANDBOX_BPF_DSL_CASES

// Ioctl number used by sqlite.
#if !defined(F2FS_IOC_GET_FEATURES)
#define F2FS_IOC_GET_FEATURES _IOR(0xf5, 12, uint32_t)
#endif

#ifndef SOL_UDP
#define SOL_UDP 17
#endif
#ifndef UDP_GRO
#define UDP_GRO 104
#endif

namespace sandbox::policy {

namespace {

ResultExpr DefaultErrorResult() {
  return CrashSIGSYS();
}

ResultExpr RestrictIoctlForNetworkService() {
  const Arg<int> request(1);
  return Switch(request)
      // sqlite uses this ioctl for feature detection when creating databases,
      // and tries to use f2fs for atomic batch writes. This avoids a rollback
      // journal and is flash-storage friendly. But f2fs is only used on Android
      // so the network process does not need to allow all the ioctls necessary
      // for f2fs atomic batch writes, so just EPERM.
      .Case(F2FS_IOC_GET_FEATURES, Error(EPERM))
      // SIOCETHTOOL, SIOCGIWNAME, and SIOCGIFNAME are needed by
      // GetNetworkList() on Linux.
      .Cases({SIOCETHTOOL, SIOCGIWNAME, SIOCGIFNAME}, Allow())
      .Case(SIOCGIFINDEX, Allow())  // For glibc's __inet6_scopeid_pton().
      .Default(RestrictIoctl());
}

ResultExpr RestrictGetSockoptForNetworkService() {
  Arg<int> level(1);
  Arg<int> optname(2);

  ResultExpr socket_optname_switch =
      Switch(optname).Case(SO_ERROR, Allow()).Default(CrashSIGSYSSockopt());
  ResultExpr ipv6_optname_switch =
      Switch(optname)
          .Cases({IPV6_V6ONLY, IPV6_TCLASS}, Allow())
          .Default(CrashSIGSYSSockopt());
  ResultExpr tcp_optname_switch =
      Switch(optname).Case(TCP_INFO, Allow()).Default(CrashSIGSYSSockopt());
  ResultExpr ip_optname_switch =
      Switch(optname).Case(IP_TOS, Allow()).Default(CrashSIGSYSSockopt());

  return Switch(level)
      .Case(SOL_SOCKET, socket_optname_switch)
      .Case(SOL_IPV6, ipv6_optname_switch)
      .Case(SOL_TCP, tcp_optname_switch)
      .Case(SOL_IP, ip_optname_switch)
      .Default(CrashSIGSYSSockopt());
}

ResultExpr RestrictSetSockoptForNetworkService() {
  Arg<int> level(1);
  Arg<int> optname(2);

  ResultExpr socket_optname_switch =
      Switch(optname)
          .Cases({SO_KEEPALIVE, SO_REUSEADDR, SO_REUSEPORT, SO_RCVBUF,
                  SO_SNDBUF, SO_BROADCAST},
                 Allow())
          .Default(CrashSIGSYSSockopt());
  // glibc's getaddrinfo() needs to enable icmp with IP[V6]_RECVERR, for both
  // ipv4 and ipv6.
  //
  // A number of optnames are for APIs of extensions. These include:
  // * IP[V6[_MULTICAST_LOOP for UDPSocketPosix::SetMulticastOptions().
  // * IP_MULTICAST_TTL, IPV6_MULTICAST_HOPS for
  //   UDPSocketPosix::SetMulticastOptions().
  //
  // IP[V6]_MULTICAST_IF, IP_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP,
  // IPV6_JOIN_GROUP, IPV6_LEAVE_GROUP are for mDNS and extensions.
  //
  // IP_TOS and IPV6_TCLASS are for P2P sockets.
  //
  // MCAST_JOIN_SOURCE_GROUP, MCAST_LEAVE_SOURCE_GROUP are for source-specific
  // multicast (SSM/IGMPv3) used by the Direct Sockets API.
  ResultExpr ipv4_optname_switch =
      Switch(optname)
          .Cases({IP_RECVERR, IP_MTU_DISCOVER, IP_MULTICAST_LOOP,
                  IP_MULTICAST_TTL, IP_MULTICAST_IF, IP_ADD_MEMBERSHIP,
                  IP_DROP_MEMBERSHIP, IP_TOS, IP_RECVTOS,
                  MCAST_JOIN_SOURCE_GROUP, MCAST_LEAVE_SOURCE_GROUP},
                 Allow())
          .Default(CrashSIGSYSSockopt());
  ResultExpr ipv6_optname_switch =
      Switch(optname)
          .Cases({IPV6_RECVERR, IPV6_MTU_DISCOVER, IPV6_MULTICAST_LOOP,
                  IPV6_MULTICAST_HOPS, IPV6_MULTICAST_IF, IPV6_JOIN_GROUP,
                  IPV6_LEAVE_GROUP, IPV6_TCLASS, IPV6_V6ONLY, IPV6_RECVTCLASS,
                  MCAST_JOIN_SOURCE_GROUP, MCAST_LEAVE_SOURCE_GROUP},
                 Allow())
          .Default(CrashSIGSYSSockopt());
  ResultExpr tcp_optname_switch =
      Switch(optname)
          .Cases({TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_NODELAY}, Allow())
          .Default(CrashSIGSYSSockopt());
  ResultExpr udp_optname_switch =
      Switch(optname).Case(UDP_GRO, Allow()).Default(CrashSIGSYSSockopt());

  return Switch(level)
      .Case(SOL_SOCKET, socket_optname_switch)
      .Case(SOL_IP, ipv4_optname_switch)
      .Case(SOL_IPV6, ipv6_optname_switch)
      .Case(SOL_TCP, tcp_optname_switch)
      .Case(SOL_UDP, udp_optname_switch)
      .Default(CrashSIGSYSSockopt());
}

ResultExpr RestrictSocketForNetworkService() {
  Arg<int> domain(0);
  Arg<int> type(1);
  Arg<int> protocol(2);

  // Flags that can be set in |type| and should be allowed.
  const int kAllowedTypeFlags = SOCK_NONBLOCK | SOCK_CLOEXEC;

  // Unix sockets needed for Mojo and other communication.
  ResultExpr unix_type_switch =
      Switch(type & ~kAllowedTypeFlags)
          .Case(SOCK_STREAM,
                If(protocol == 0, Allow()).Else(CrashSIGSYSSocket()))
          .Case(SOCK_DGRAM, Error(EPERM))  // For glibc's __inet6_scopeid_pton.
          .Default(CrashSIGSYSSocket());

  // AddressTrackerLinux needs netlink sockets for tracking system network
  // changes (which is important for e.g. reestablishing connections when an IP
  // address changes). AddressTrackerLinux may run in the network service on
  // some systems.
#if BUILDFLAG(IS_LINUX)
  // AddressTrackerLinux is brokered on Linux.
  bool use_netlink_in_network_service = false;
#else   // !BUILDFLAG(IS_LINUX)
  // TODO(crbug.com/40220507): remove the netlink allowance when
  // AddressTrackerLinux no longer runs in the network service on ChromeOS.
  bool use_netlink_in_network_service = true;
#endif  // !BUILDFLAG(IS_LINUX)
  ResultExpr netlink_type_switch;
  if (use_netlink_in_network_service) {
    ResultExpr netlink_protocol_switch = Switch(protocol)
                                             .Case(NETLINK_ROUTE, Allow())
                                             .Default(CrashSIGSYSSocket());
    netlink_type_switch = Switch(type & ~kAllowedTypeFlags)
                              .Case(SOCK_RAW, netlink_protocol_switch)
                              .Default(CrashSIGSYSSocket());
  } else {
    netlink_type_switch = CrashSIGSYSSocket();
  }

  // Allow UDP and TCP sockets over ipv4 and ipv6.
  ResultExpr inet_type_switch =
      Switch(type & ~kAllowedTypeFlags)
          .Case(SOCK_DGRAM,
                If(AnyOf(protocol == 0, protocol == IPPROTO_UDP), Allow())
                    .Else(CrashSIGSYSSocket()))
          .Case(SOCK_STREAM,
                If(AnyOf(protocol == 0, protocol == SOL_TCP), Allow())
                    .Else(CrashSIGSYSSocket()))
          .Default(CrashSIGSYSSocket());

  return Switch(domain)
      .Case(AF_UNIX, unix_type_switch)
      .Case(AF_NETLINK, netlink_type_switch)
      .Cases({AF_INET, AF_INET6}, inet_type_switch)
      .Default(CrashSIGSYSSocket());
}

}  // namespace

NetworkProcessPolicy::NetworkProcessPolicy() = default;

NetworkProcessPolicy::~NetworkProcessPolicy() = default;

ResultExpr NetworkProcessPolicy::EvaluateSyscall(int sysno) const {
  auto* sandbox_linux = SandboxLinux::GetInstance();
  if (sandbox_linux->ShouldBrokerHandleSyscall(sysno)) {
    return sandbox_linux->HandleViaBroker(sysno);
  }

  // If the fine-grained syscall filter is allowed, allow all other syscalls.
  if (!base::FeatureList::IsEnabled(features::kNetworkServiceSyscallFilter)) {
    return Allow();
  }

  // inotify_add_watch should be handled above by the broker.
  if (SyscallSets::IsInotify(sysno) && sysno != __NR_inotify_add_watch) {
    return Allow();
  }

  switch (sysno) {
    // Restrictable syscalls not networking-specific:
    case __NR_fcntl:
      return RestrictFcntlCommands();
#if defined(__arm__) && defined(__ARM_EABI__)
    case __NR_arm_fadvise64_64: {
      Arg<int> advice(1);
#else
    case __NR_fadvise64: {
      Arg<int> advice(3);
#endif
      return If(advice == POSIX_FADV_WILLNEED, Allow())
          .Else(DefaultErrorResult());
    }
    case __NR_ioctl:
      return RestrictIoctlForNetworkService();

    // Mostly harmless, unrestrictable system calls:
    case __NR_sysinfo:
    case __NR_uname:
    case __NR_pwrite64:
    case __NR_pread64:
    case __NR_fdatasync:
    case __NR_fsync:
    case __NR_mremap:
#if !defined(__aarch64__)
    case __NR_getdents:
#endif
    case __NR_getdents64:
      return Allow();

    // Networking system calls:
    case __NR_getsockopt:
      return RestrictGetSockoptForNetworkService();
    case __NR_setsockopt:
      return RestrictSetSockoptForNetworkService();
    case __NR_listen:  // Used by extension APIs, and also the devtools server.
#if defined(__NR_accept)
    case __NR_accept:  // Same as listen().
#endif
    case __NR_accept4:  // Same as listen().
    case __NR_connect:
    case __NR_bind:
    case __NR_getsockname:
      // TODO(crbug.com/40052246): restrict sendmmsg() for the network service,
      // probably with RestrictSockSendFlags().
    case __NR_sendmmsg:
      return Allow();
    case __NR_socket:
      return RestrictSocketForNetworkService();
#if defined(__NR_socketcall)
    case __NR_socketcall:
      // Unfortunately there's no easy way to restrict socketcall as it uses a
      // struct pointer to pass its arguments. So this rewrites socketcall()
      // calls into direct sockets-API syscalls, which will be filtered like
      // normal.
      return CanRewriteSocketcall() ? RewriteSocketcallSIGSYS() : Allow();
#endif
  }

  return BPFBasePolicy::EvaluateSyscall(sysno);
}

}  // namespace sandbox::policy
