// 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.

#include "content/renderer/renderer_main_platform_delegate.h"

#include "base/android/build_info.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"

#ifdef USE_SECCOMP_BPF
#include "content/common/sandbox_linux/android/sandbox_bpf_base_policy_android.h"
#include "content/public/common/content_features.h"
#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
#endif

namespace content {

namespace {

// Scoper class to record a SeccompSandboxStatus UMA value.
class RecordSeccompStatus {
 public:
  enum SeccompSandboxStatus {
    NOT_SUPPORTED = 0,  // Seccomp is not supported.
    DETECTION_FAILED,   // Run-time detection of Seccomp+TSYNC failed.
    FEATURE_DISABLED,   // Sandbox was disabled by FeatureList.
    FEATURE_ENABLED,    // Sandbox was enabled by FeatureList.
    ENGAGED,            // Sandbox was enabled and successfully turned on.
    STATUS_MAX
    // This enum is used by an UMA histogram, so only append values.
  };

  RecordSeccompStatus() : status_(NOT_SUPPORTED) {}

  ~RecordSeccompStatus() {
    UMA_HISTOGRAM_ENUMERATION("Android.SeccompStatus.RendererSandbox", status_,
                              STATUS_MAX);
  }

  void set_status(SeccompSandboxStatus status) { status_ = status; }

 private:
  SeccompSandboxStatus status_;
  DISALLOW_COPY_AND_ASSIGN(RecordSeccompStatus);
};

#ifdef USE_SECCOMP_BPF
// Determines if the running device should support Seccomp, based on the Android
// SDK version.
bool IsSeccompBPFSupportedBySDK() {
  const auto info = base::android::BuildInfo::GetInstance();
  if (info->sdk_int() < 22) {
    // Seccomp was never available pre-Lollipop.
    return false;
  } else if (info->sdk_int() == 22) {
    // On Lollipop-MR1, only select Nexus devices have Seccomp available.
    const char* const kDevices[] = {
        "deb",   "flo",   "hammerhead", "mako",
        "manta", "shamu", "sprout",     "volantis",
    };

    for (const auto& device : kDevices) {
      if (strcmp(device, info->device()) == 0) {
        return true;
      }
    }
  } else {
    // On Marshmallow and higher, Seccomp is required by CTS.
    return true;
  }
  return false;
}
#endif  // USE_SECCOMP_BPF

}  // namespace

RendererMainPlatformDelegate::RendererMainPlatformDelegate(
    const MainFunctionParams& parameters) {}

RendererMainPlatformDelegate::~RendererMainPlatformDelegate() {
}

void RendererMainPlatformDelegate::PlatformInitialize() {
}

void RendererMainPlatformDelegate::PlatformUninitialize() {
}

bool RendererMainPlatformDelegate::EnableSandbox() {
  RecordSeccompStatus status_uma;

#ifdef USE_SECCOMP_BPF
  // Determine if Seccomp is available via the Android SDK version.
  if (!IsSeccompBPFSupportedBySDK())
    return true;

  // Do run-time detection to ensure that support is present.
  if (!sandbox::SandboxBPF::SupportsSeccompSandbox(
          sandbox::SandboxBPF::SeccompLevel::MULTI_THREADED)) {
    status_uma.set_status(RecordSeccompStatus::DETECTION_FAILED);
    LOG(WARNING) << "Seccomp support should be present, but detection "
        << "failed. Continuing without Seccomp-BPF.";
    return true;
  }

  // Seccomp has been detected, check if the field trial experiment should run.
  if (base::FeatureList::IsEnabled(features::kSeccompSandboxAndroid)) {
    status_uma.set_status(RecordSeccompStatus::FEATURE_ENABLED);

    sandbox::SandboxBPF sandbox(new SandboxBPFBasePolicyAndroid());
    CHECK(sandbox.StartSandbox(
        sandbox::SandboxBPF::SeccompLevel::MULTI_THREADED));

    status_uma.set_status(RecordSeccompStatus::ENGAGED);
  } else {
    status_uma.set_status(RecordSeccompStatus::FEATURE_DISABLED);
  }
#endif
  return true;
}

}  // namespace content