Browse Source

feat: add creationTime / sandboxed / integrityLevel to app.getAppMetrics() (#18718)

This is useful for checking which processes are sandboxed on OS level.

Regarding creationTime, since the pid can be reused after a process dies, it is useful to use both the pid and the creationTime to uniquely identify a process.
Milan Burda 5 years ago
parent
commit
d9215dd4ce

+ 38 - 30
atom/browser/api/atom_api_app.cc

@@ -69,6 +69,25 @@ using atom::Browser;
 namespace mate {
 
 #if defined(OS_WIN)
+template <>
+struct Converter<atom::ProcessIntegrityLevel> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+                                   atom::ProcessIntegrityLevel value) {
+    switch (value) {
+      case atom::ProcessIntegrityLevel::Untrusted:
+        return mate::StringToV8(isolate, "untrusted");
+      case atom::ProcessIntegrityLevel::Low:
+        return mate::StringToV8(isolate, "low");
+      case atom::ProcessIntegrityLevel::Medium:
+        return mate::StringToV8(isolate, "medium");
+      case atom::ProcessIntegrityLevel::High:
+        return mate::StringToV8(isolate, "high");
+      default:
+        return mate::StringToV8(isolate, "unknown");
+    }
+  }
+};
+
 template <>
 struct Converter<Browser::UserTask> {
   static bool FromV8(v8::Isolate* isolate,
@@ -357,31 +376,10 @@ struct Converter<content::CertificateRequestResultType> {
 
 namespace atom {
 
-ProcessMetric::ProcessMetric(int type,
-                             base::ProcessId pid,
-                             std::unique_ptr<base::ProcessMetrics> metrics) {
-  this->type = type;
-  this->pid = pid;
-  this->metrics = std::move(metrics);
-}
-
-ProcessMetric::~ProcessMetric() = default;
-
 namespace api {
 
 namespace {
 
-class AppIdProcessIterator : public base::ProcessIterator {
- public:
-  AppIdProcessIterator() : base::ProcessIterator(nullptr) {}
-
- protected:
-  bool IncludeEntry() override {
-    return (entry().parent_pid() == base::GetCurrentProcId() ||
-            entry().pid() == base::GetCurrentProcId());
-  }
-};
-
 IconLoader::IconSize GetIconSizeByString(const std::string& size) {
   if (size == "small") {
     return IconLoader::IconSize::SMALL;
@@ -550,7 +548,7 @@ App::App(v8::Isolate* isolate) {
 
   base::ProcessId pid = base::GetCurrentProcId();
   auto process_metric = std::make_unique<atom::ProcessMetric>(
-      content::PROCESS_TYPE_BROWSER, pid,
+      content::PROCESS_TYPE_BROWSER, base::GetCurrentProcessHandle(),
       base::ProcessMetrics::CreateCurrentProcessMetrics());
   app_metrics_[pid] = std::move(process_metric);
   Init(isolate);
@@ -825,15 +823,13 @@ void App::ChildProcessLaunched(int process_type, base::ProcessHandle handle) {
   auto pid = base::GetProcId(handle);
 
 #if defined(OS_MACOSX)
-  std::unique_ptr<base::ProcessMetrics> metrics(
-      base::ProcessMetrics::CreateProcessMetrics(
-          handle, content::BrowserChildProcessHost::GetPortProvider()));
+  auto metrics = base::ProcessMetrics::CreateProcessMetrics(
+      handle, content::BrowserChildProcessHost::GetPortProvider());
 #else
-  std::unique_ptr<base::ProcessMetrics> metrics(
-      base::ProcessMetrics::CreateProcessMetrics(handle));
+  auto metrics = base::ProcessMetrics::CreateProcessMetrics(handle);
 #endif
-  app_metrics_[pid] = std::make_unique<atom::ProcessMetric>(process_type, pid,
-                                                            std::move(metrics));
+  app_metrics_[pid] = std::make_unique<atom::ProcessMetric>(
+      process_type, handle, std::move(metrics));
 }
 
 void App::ChildProcessDisconnected(base::ProcessId pid) {
@@ -1215,9 +1211,21 @@ std::vector<mate::Dictionary> App::GetAppMetrics(v8::Isolate* isolate) {
 #endif
 
     pid_dict.Set("cpu", cpu_dict);
-    pid_dict.Set("pid", process_metric.second->pid);
+    pid_dict.Set("pid", process_metric.second->process.Pid());
     pid_dict.Set("type", content::GetProcessTypeNameInEnglish(
                              process_metric.second->type));
+    pid_dict.Set("creationTime",
+                 process_metric.second->process.CreationTime().ToJsTime());
+
+#if defined(OS_MACOSX)
+    pid_dict.Set("sandboxed", process_metric.second->IsSandboxed());
+#elif defined(OS_WIN)
+    auto integrity_level = process_metric.second->GetIntegrityLevel();
+    auto sandboxed = ProcessMetric::IsSandboxed(integrity_level);
+    pid_dict.Set("integrityLevel", integrity_level);
+    pid_dict.Set("sandboxed", sandboxed);
+#endif
+
     result.push_back(pid_dict);
   }
 

+ 1 - 13
atom/browser/api/atom_api_app.h

@@ -12,13 +12,12 @@
 #include <vector>
 
 #include "atom/browser/api/event_emitter.h"
+#include "atom/browser/api/process_metric.h"
 #include "atom/browser/atom_browser_client.h"
 #include "atom/browser/browser.h"
 #include "atom/browser/browser_observer.h"
 #include "atom/common/native_mate_converters/callback.h"
 #include "atom/common/promise_util.h"
-#include "base/process/process_iterator.h"
-#include "base/process/process_metrics.h"
 #include "base/task/cancelable_task_tracker.h"
 #include "chrome/browser/icon_manager.h"
 #include "chrome/browser/process_singleton.h"
@@ -49,17 +48,6 @@ namespace atom {
 enum class JumpListResult : int;
 #endif
 
-struct ProcessMetric {
-  int type;
-  base::ProcessId pid;
-  std::unique_ptr<base::ProcessMetrics> metrics;
-
-  ProcessMetric(int type,
-                base::ProcessId pid,
-                std::unique_ptr<base::ProcessMetrics> metrics);
-  ~ProcessMetric();
-};
-
 namespace api {
 
 class App : public AtomBrowserClient::Delegate,

+ 109 - 0
atom/browser/api/process_metric.cc

@@ -0,0 +1,109 @@
+// Copyright (c) 2019 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#include "atom/browser/api/process_metric.h"
+
+#include <memory>
+#include <utility>
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#if defined(OS_MACOSX)
+extern "C" int sandbox_check(pid_t pid, const char* operation, int type, ...);
+#endif
+
+namespace atom {
+
+ProcessMetric::ProcessMetric(int type,
+                             base::ProcessHandle handle,
+                             std::unique_ptr<base::ProcessMetrics> metrics) {
+  this->type = type;
+  this->metrics = std::move(metrics);
+
+#if defined(OS_WIN)
+  HANDLE duplicate_handle = INVALID_HANDLE_VALUE;
+  ::DuplicateHandle(::GetCurrentProcess(), handle, ::GetCurrentProcess(),
+                    &duplicate_handle, 0, false, DUPLICATE_SAME_ACCESS);
+  this->process = base::Process(duplicate_handle);
+#else
+  this->process = base::Process(handle);
+#endif
+}
+
+ProcessMetric::~ProcessMetric() = default;
+
+#if defined(OS_WIN)
+
+ProcessIntegrityLevel ProcessMetric::GetIntegrityLevel() const {
+  HANDLE token = nullptr;
+  if (!::OpenProcessToken(process.Handle(), TOKEN_QUERY, &token)) {
+    return ProcessIntegrityLevel::Unknown;
+  }
+
+  base::win::ScopedHandle token_scoped(token);
+
+  DWORD token_info_length = 0;
+  if (::GetTokenInformation(token, TokenIntegrityLevel, nullptr, 0,
+                            &token_info_length) ||
+      ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+    return ProcessIntegrityLevel::Unknown;
+  }
+
+  auto token_label_bytes = std::make_unique<char[]>(token_info_length);
+  TOKEN_MANDATORY_LABEL* token_label =
+      reinterpret_cast<TOKEN_MANDATORY_LABEL*>(token_label_bytes.get());
+  if (!::GetTokenInformation(token, TokenIntegrityLevel, token_label,
+                             token_info_length, &token_info_length)) {
+    return ProcessIntegrityLevel::Unknown;
+  }
+
+  DWORD integrity_level = *::GetSidSubAuthority(
+      token_label->Label.Sid,
+      static_cast<DWORD>(*::GetSidSubAuthorityCount(token_label->Label.Sid) -
+                         1));
+
+  if (integrity_level >= SECURITY_MANDATORY_UNTRUSTED_RID &&
+      integrity_level < SECURITY_MANDATORY_LOW_RID) {
+    return ProcessIntegrityLevel::Untrusted;
+  }
+
+  if (integrity_level >= SECURITY_MANDATORY_LOW_RID &&
+      integrity_level < SECURITY_MANDATORY_MEDIUM_RID) {
+    return ProcessIntegrityLevel::Low;
+  }
+
+  if (integrity_level >= SECURITY_MANDATORY_MEDIUM_RID &&
+      integrity_level < SECURITY_MANDATORY_HIGH_RID) {
+    return ProcessIntegrityLevel::Medium;
+  }
+
+  if (integrity_level >= SECURITY_MANDATORY_HIGH_RID &&
+      integrity_level < SECURITY_MANDATORY_SYSTEM_RID) {
+    return ProcessIntegrityLevel::High;
+  }
+
+  return ProcessIntegrityLevel::Unknown;
+}
+
+// static
+bool ProcessMetric::IsSandboxed(ProcessIntegrityLevel integrity_level) {
+  return integrity_level > ProcessIntegrityLevel::Unknown &&
+         integrity_level < ProcessIntegrityLevel::Medium;
+}
+
+#elif defined(OS_MACOSX)
+
+bool ProcessMetric::IsSandboxed() const {
+#if defined(MAS_BUILD)
+  return true;
+#else
+  return sandbox_check(process.Pid(), nullptr, 0) != 0;
+#endif
+}
+
+#endif  // defined(OS_MACOSX)
+
+}  // namespace atom

+ 46 - 0
atom/browser/api/process_metric.h

@@ -0,0 +1,46 @@
+// Copyright (c) 2019 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef ATOM_BROWSER_API_PROCESS_METRIC_H_
+#define ATOM_BROWSER_API_PROCESS_METRIC_H_
+
+#include <memory>
+
+#include "base/process/process.h"
+#include "base/process/process_handle.h"
+#include "base/process/process_metrics.h"
+
+namespace atom {
+
+#if defined(OS_WIN)
+enum class ProcessIntegrityLevel {
+  Unknown,
+  Untrusted,
+  Low,
+  Medium,
+  High,
+};
+#endif
+
+struct ProcessMetric {
+  int type;
+  base::Process process;
+  std::unique_ptr<base::ProcessMetrics> metrics;
+
+  ProcessMetric(int type,
+                base::ProcessHandle handle,
+                std::unique_ptr<base::ProcessMetrics> metrics);
+  ~ProcessMetric();
+
+#if defined(OS_WIN)
+  ProcessIntegrityLevel GetIntegrityLevel() const;
+  static bool IsSandboxed(ProcessIntegrityLevel integrity_level);
+#elif defined(OS_MACOSX)
+  bool IsSandboxed() const;
+#endif
+};
+
+}  // namespace atom
+
+#endif  // ATOM_BROWSER_API_PROCESS_METRIC_H_

+ 11 - 0
docs/api/structures/process-metric.md

@@ -3,3 +3,14 @@
 * `pid` Integer - Process id of the process.
 * `type` String - Process type (Browser or Tab or GPU etc).
 * `cpu` [CPUUsage](cpu-usage.md) - CPU usage of the process.
+* `creationTime` Number - Creation time for this process.
+    The time is represented as number of milliseconds since epoch.
+    Since the `pid` can be reused after a process dies,
+    it is useful to use both the `pid` and the `creationTime` to uniquely identify a process.
+* `sandboxed` Boolean (optional) _macOS_ _Windows_ - Whether the process is sandboxed on OS level.
+* `integrityLevel` String (optional) _Windows_ - One of the following values:
+  * `untrusted`
+  * `low`
+  * `medium`
+  * `high`
+  * `unknown`

+ 2 - 0
filenames.gni

@@ -122,6 +122,8 @@ filenames = {
     "atom/browser/api/gpu_info_enumerator.h",
     "atom/browser/api/gpuinfo_manager.cc",
     "atom/browser/api/gpuinfo_manager.h",
+    "atom/browser/api/process_metric.cc",
+    "atom/browser/api/process_metric.h",
     "atom/browser/api/save_page_handler.cc",
     "atom/browser/api/save_page_handler.h",
     "atom/browser/auto_updater.cc",

+ 15 - 6
spec-main/api-app-spec.ts

@@ -931,13 +931,22 @@ describe('app module', () => {
       expect(appMetrics).to.be.an('array').and.have.lengthOf.at.least(1, 'App memory info object is not > 0')
 
       const types = []
-      for (const { pid, type, cpu } of appMetrics) {
-        expect(pid).to.be.above(0, 'pid is not > 0')
-        expect(type).to.be.a('string').that.does.not.equal('')
+      for (const entry of appMetrics) {
+        expect(entry.pid).to.be.above(0, 'pid is not > 0')
+        expect(entry.type).to.be.a('string').that.does.not.equal('')
+        expect(entry.creationTime).to.be.a('number').that.is.greaterThan(0)
 
-        types.push(type)
-        expect(cpu).to.have.ownProperty('percentCPUUsage').that.is.a('number')
-        expect(cpu).to.have.ownProperty('idleWakeupsPerSecond').that.is.a('number')
+        types.push(entry.type)
+        expect(entry.cpu).to.have.ownProperty('percentCPUUsage').that.is.a('number')
+        expect(entry.cpu).to.have.ownProperty('idleWakeupsPerSecond').that.is.a('number')
+
+        if (process.platform !== 'linux') {
+          expect(entry.sandboxed).to.be.a('boolean')
+        }
+
+        if (process.platform === 'win32') {
+          expect(entry.integrityLevel).to.be.a('string')
+        }
       }
 
       if (process.platform === 'darwin') {