From d9215dd4ce250c9ac1a316c85bf71a866a582e51 Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Fri, 14 Jun 2019 21:39:55 +0200 Subject: [PATCH] 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. --- atom/browser/api/atom_api_app.cc | 68 +++++++++------- atom/browser/api/atom_api_app.h | 14 +--- atom/browser/api/process_metric.cc | 109 ++++++++++++++++++++++++++ atom/browser/api/process_metric.h | 46 +++++++++++ docs/api/structures/process-metric.md | 11 +++ filenames.gni | 2 + spec-main/api-app-spec.ts | 21 +++-- 7 files changed, 222 insertions(+), 49 deletions(-) create mode 100644 atom/browser/api/process_metric.cc create mode 100644 atom/browser/api/process_metric.h diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index a558127bf84c..a94da63eb132 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -69,6 +69,25 @@ using atom::Browser; namespace mate { #if defined(OS_WIN) +template <> +struct Converter { + static v8::Local 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 { static bool FromV8(v8::Isolate* isolate, @@ -357,31 +376,10 @@ struct Converter { namespace atom { -ProcessMetric::ProcessMetric(int type, - base::ProcessId pid, - std::unique_ptr 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( - 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 metrics( - base::ProcessMetrics::CreateProcessMetrics( - handle, content::BrowserChildProcessHost::GetPortProvider())); + auto metrics = base::ProcessMetrics::CreateProcessMetrics( + handle, content::BrowserChildProcessHost::GetPortProvider()); #else - std::unique_ptr metrics( - base::ProcessMetrics::CreateProcessMetrics(handle)); + auto metrics = base::ProcessMetrics::CreateProcessMetrics(handle); #endif - app_metrics_[pid] = std::make_unique(process_type, pid, - std::move(metrics)); + app_metrics_[pid] = std::make_unique( + process_type, handle, std::move(metrics)); } void App::ChildProcessDisconnected(base::ProcessId pid) { @@ -1215,9 +1211,21 @@ std::vector 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); } diff --git a/atom/browser/api/atom_api_app.h b/atom/browser/api/atom_api_app.h index c10e4b0ecbb9..b4e3ec7f79b1 100644 --- a/atom/browser/api/atom_api_app.h +++ b/atom/browser/api/atom_api_app.h @@ -12,13 +12,12 @@ #include #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 metrics; - - ProcessMetric(int type, - base::ProcessId pid, - std::unique_ptr metrics); - ~ProcessMetric(); -}; - namespace api { class App : public AtomBrowserClient::Delegate, diff --git a/atom/browser/api/process_metric.cc b/atom/browser/api/process_metric.cc new file mode 100644 index 000000000000..f97d5995124a --- /dev/null +++ b/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 +#include + +#if defined(OS_WIN) +#include +#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 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(token_info_length); + TOKEN_MANDATORY_LABEL* token_label = + reinterpret_cast(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(*::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 diff --git a/atom/browser/api/process_metric.h b/atom/browser/api/process_metric.h new file mode 100644 index 000000000000..e8aaecad4d11 --- /dev/null +++ b/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 + +#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 metrics; + + ProcessMetric(int type, + base::ProcessHandle handle, + std::unique_ptr 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_ diff --git a/docs/api/structures/process-metric.md b/docs/api/structures/process-metric.md index 62b3d6fe377b..390495f279e5 100644 --- a/docs/api/structures/process-metric.md +++ b/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` diff --git a/filenames.gni b/filenames.gni index a1c6d5ad1b3f..d500f0f97d62 100644 --- a/filenames.gni +++ b/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", diff --git a/spec-main/api-app-spec.ts b/spec-main/api-app-spec.ts index 6444e67e9a69..3eae865d3ab3 100644 --- a/spec-main/api-app-spec.ts +++ b/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') {