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.
This commit is contained in:
Milan Burda 2019-06-14 21:39:55 +02:00 committed by Shelley Vohr
parent 0bdc05bf24
commit d9215dd4ce
7 changed files with 222 additions and 49 deletions

View file

@ -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);
}

View file

@ -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,

View file

@ -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

View file

@ -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_

View file

@ -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`

View file

@ -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",

View file

@ -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') {