mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
CI caught the real defect: the win32 guard test read
isWindowsProcessStartTimeAvailable() as true and then found 0 rows
carrying creationTimeMs. Unlike node-pty, this package publishes a
prebuilt .node at the same build/Release path node-gyp writes to, so
pnpm patches the source tree and leaves that binary alone. A host then
holds a patched lib/index.js -- ProcessDataFlag.CreationTime and all --
over a binary that ignores flag 4, and neither a load check nor a path
check can see the difference.
So the binary now says so itself: addon.cc exports
supportedProcessDataFlags, lib/index.js re-exports it, and
- windows-process-tree-creation-time.cjs asserts it during install,
which is what forces a from-source rebuild. It is shared by the Node
probe in ensure-native-runtime.mjs and the Electron probe in
rebuild-native-deps.mjs, exactly as node-pty-job-ownership.cjs is --
the Electron half matters because that probe decides onlyModules, so
without it the packaged app would ship the stale prebuilt.
- isWindowsProcessStartTimeAvailable() gates on the reported bit, not
the enum. Believing the enum is worse than reporting false: the
descendant snapshot returns null forever and the exit proof latches
unverifiable while structured chat believes it has a reaper.
rebuildNodeRuntimeModules could not actually have rebuilt this package:
the patched binding.gyp includes deps/node-addon-api, which the tarball
does not ship, and node-gyp must run from the physical dir.
Also closes the relay repair path's divergence: repairCreationTimeSources
wrote the C++ but not the buildNode splat or the tree-node typing, and
assertPatchApplied checked neither, so a repaired tree passed as patched
with buildProcessTree silently dropping the field.
The guard test is unchanged.
667 lines
26 KiB
Diff
667 lines
26 KiB
Diff
diff --git a/binding.gyp b/binding.gyp
|
|
index 855bd4b86f0a3c18c7594212c0e42b6e35bc4001..0bb2af7923b6e6f1f0da40cae8067304cd1fea14 100644
|
|
--- a/binding.gyp
|
|
+++ b/binding.gyp
|
|
@@ -3,7 +3,6 @@
|
|
{
|
|
"target_name": "windows_process_tree",
|
|
"dependencies": [
|
|
- "<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except",
|
|
],
|
|
"conditions": [
|
|
['OS=="win"', {
|
|
@@ -14,13 +13,12 @@
|
|
"src/process_worker.cc",
|
|
"src/process_commandline.cc"
|
|
],
|
|
- "include_dirs": [],
|
|
+ "include_dirs": ["deps/node-addon-api"],
|
|
+ "defines": ["NAPI_CPP_EXCEPTIONS", "_HAS_EXCEPTIONS=1"],
|
|
"libraries": [ 'psapi.lib' ],
|
|
- "msvs_configuration_attributes": {
|
|
- "SpectreMitigation": "Spectre"
|
|
- },
|
|
"msvs_settings": {
|
|
"VCCLCompilerTool": {
|
|
+ "ExceptionHandling": 1,
|
|
<<<<<<< HEAD
|
|
"AdditionalOptions": [
|
|
"/guard:cf",
|
|
"/sdl",
|
|
diff --git a/src/process.cc b/src/process.cc
|
|
index 3eea92077c4d1d433119361d5c432881859131e9..738775f6fcdfb676054386fe34c0380327ed1863 100644
|
|
--- a/src/process.cc
|
|
+++ b/src/process.cc
|
|
@@ -1,108 +1,112 @@
|
|
-/*---------------------------------------------------------------------------------------------
|
|
- * Copyright (c) Microsoft Corporation. All rights reserved.
|
|
- * Licensed under the MIT License. See License.txt in the project root for license information.
|
|
- *--------------------------------------------------------------------------------------------*/
|
|
-
|
|
-#include "process.h"
|
|
-#include "process_commandline.h"
|
|
-
|
|
-#include <tlhelp32.h>
|
|
-#include <psapi.h>
|
|
-#include <limits>
|
|
-
|
|
-uint32_t GetRawProcessList(std::vector<ProcessInfo>& process_info,
|
|
- DWORD process_data_flags) {
|
|
- // Fetch the PID and PPIDs
|
|
- PROCESSENTRY32 process_entry = { 0 };
|
|
- DWORD parent_pid = 0;
|
|
- uint32_t process_count = 0;
|
|
- HANDLE snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
- process_entry.dwSize = sizeof(PROCESSENTRY32);
|
|
- if (Process32First(snapshot_handle, &process_entry)) {
|
|
- do {
|
|
- if (process_entry.th32ProcessID != 0) {
|
|
- ProcessInfo pinfo;
|
|
- pinfo.pid = process_entry.th32ProcessID;
|
|
- pinfo.ppid = process_entry.th32ParentProcessID;
|
|
-
|
|
- if (MEMORY & process_data_flags) {
|
|
- GetProcessMemoryUsage(pinfo);
|
|
- }
|
|
-
|
|
- if (COMMANDLINE & process_data_flags) {
|
|
- GetProcessCommandLine(pinfo);
|
|
- }
|
|
-
|
|
- strcpy(pinfo.name, process_entry.szExeFile);
|
|
- process_info.push_back(std::move(pinfo));
|
|
- process_count++;
|
|
- }
|
|
- } while (process_count < 1024 && Process32Next(snapshot_handle, &process_entry));
|
|
- }
|
|
-
|
|
- CloseHandle(snapshot_handle);
|
|
- return process_count;
|
|
-}
|
|
-
|
|
-void GetProcessMemoryUsage(ProcessInfo& process_info) {
|
|
- DWORD pid = process_info.pid;
|
|
- HANDLE hProcess;
|
|
- PROCESS_MEMORY_COUNTERS pmc;
|
|
-
|
|
- hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
|
|
-
|
|
- if (hProcess == NULL) {
|
|
- return;
|
|
- }
|
|
-
|
|
- if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) {
|
|
- process_info.memory = (DWORD)pmc.WorkingSetSize;
|
|
- }
|
|
-
|
|
- CloseHandle(hProcess);
|
|
-}
|
|
-
|
|
-// Per documentation, it is not recommended to add or subtract values from the FILETIME
|
|
-// structure, or to cast it to ULARGE_INTEGER as this can cause alignment faults on 64-bit Windows.
|
|
-// Copy the high and low part to a ULARGE_INTEGER and peform arithmetic on that instead.
|
|
-// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx
|
|
-ULONGLONG GetTotalTime(const FILETIME* kernelTime, const FILETIME* userTime) {
|
|
- ULARGE_INTEGER kt, ut;
|
|
- kt.LowPart = (*kernelTime).dwLowDateTime;
|
|
- kt.HighPart = (*kernelTime).dwHighDateTime;
|
|
-
|
|
- ut.LowPart = (*userTime).dwLowDateTime;
|
|
- ut.HighPart = (*userTime).dwHighDateTime;
|
|
-
|
|
- return kt.QuadPart + ut.QuadPart;
|
|
-}
|
|
-
|
|
-void GetCpuUsage(Cpu& cpu_info, bool first_pass) {
|
|
- DWORD pid = cpu_info.pid;
|
|
- HANDLE hProcess;
|
|
-
|
|
- hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
|
|
-
|
|
- if (hProcess == NULL) {
|
|
- return;
|
|
- }
|
|
-
|
|
- FILETIME creationTime, exitTime, kernelTime, userTime;
|
|
- FILETIME sysIdleTime, sysKernelTime, sysUserTime;
|
|
- if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime)
|
|
- && GetSystemTimes(&sysIdleTime, &sysKernelTime, &sysUserTime)) {
|
|
- if (first_pass) {
|
|
- cpu_info.initialProcRunTime = GetTotalTime(&kernelTime, &userTime);
|
|
- cpu_info.initialSystemTime = GetTotalTime(&sysKernelTime, &sysUserTime);
|
|
- } else {
|
|
- ULONGLONG endProcTime = GetTotalTime(&kernelTime, &userTime);
|
|
- ULONGLONG endSysTime = GetTotalTime(&sysKernelTime, &sysUserTime);
|
|
-
|
|
- cpu_info.cpu = 100.0 * (endProcTime - cpu_info.initialProcRunTime) / (endSysTime - cpu_info.initialSystemTime);
|
|
- }
|
|
- } else {
|
|
- cpu_info.cpu = std::numeric_limits<double>::quiet_NaN();
|
|
- }
|
|
-
|
|
- CloseHandle(hProcess);
|
|
+/*---------------------------------------------------------------------------------------------
|
|
+ * Copyright (c) Microsoft Corporation. All rights reserved.
|
|
+ * Licensed under the MIT License. See License.txt in the project root for license information.
|
|
+ *--------------------------------------------------------------------------------------------*/
|
|
+
|
|
+#include "process.h"
|
|
+#include "process_commandline.h"
|
|
+
|
|
+#include <tlhelp32.h>
|
|
+#include <psapi.h>
|
|
+#include <limits>
|
|
+
|
|
+uint32_t GetRawProcessList(std::vector<ProcessInfo>& process_info,
|
|
+ DWORD process_data_flags) {
|
|
+ // Fetch the PID and PPIDs
|
|
+ PROCESSENTRY32 process_entry = { 0 };
|
|
+ DWORD parent_pid = 0;
|
|
+ uint32_t process_count = 0;
|
|
+ HANDLE snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
+ process_entry.dwSize = sizeof(PROCESSENTRY32);
|
|
+ if (Process32First(snapshot_handle, &process_entry)) {
|
|
+ do {
|
|
+ if (process_entry.th32ProcessID != 0) {
|
|
+ // Value-initialize: `memory` is otherwise stack garbage when the flag is unset.
|
|
+ ProcessInfo pinfo{};
|
|
+ pinfo.pid = process_entry.th32ProcessID;
|
|
+ pinfo.ppid = process_entry.th32ParentProcessID;
|
|
+
|
|
+ if (MEMORY & process_data_flags) {
|
|
+ GetProcessMemoryUsage(pinfo);
|
|
+ }
|
|
+
|
|
+ if (COMMANDLINE & process_data_flags) {
|
|
+ GetProcessCommandLine(pinfo);
|
|
+ }
|
|
+
|
|
+ strcpy(pinfo.name, process_entry.szExeFile);
|
|
+ process_info.push_back(std::move(pinfo));
|
|
+ process_count++;
|
|
+ }
|
|
+ } while (Process32Next(snapshot_handle, &process_entry));
|
|
+ }
|
|
+
|
|
+ CloseHandle(snapshot_handle);
|
|
+ return process_count;
|
|
+}
|
|
+
|
|
+void GetProcessMemoryUsage(ProcessInfo& process_info) {
|
|
+ DWORD pid = process_info.pid;
|
|
+ HANDLE hProcess;
|
|
+ PROCESS_MEMORY_COUNTERS pmc;
|
|
+
|
|
+ // PROCESS_VM_READ is never used here -- GetProcessMemoryInfo reads counters the
|
|
+ // kernel keeps, not the address space -- and acquiring it is what EDR scores.
|
|
+ hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
|
+
|
|
=======
|
|
"AdditionalOptions": [
|
|
"/guard:cf",
|
|
"/sdl",
|
|
diff --git a/lib/index.js b/lib/index.js
|
|
index 9747a7402600cd252859144d32580ed45c8c93f7..8b1f467f2c18289f75419d30a43d6fa3a516c4cc 100644
|
|
--- a/lib/index.js
|
|
+++ b/lib/index.js
|
|
@@ -7,11 +7,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.getAllProcesses = exports.getProcessTree = exports.getProcessCpuUsage = exports.getProcessList = exports.filterProcessList = exports.buildProcessTree = exports.ProcessDataFlag = void 0;
|
|
const util_1 = require("util");
|
|
const native = process.platform === 'win32' ? require('../build/Release/windows_process_tree.node') : undefined;
|
|
+exports.supportedProcessDataFlags = native === undefined ? undefined : native.supportedProcessDataFlags;
|
|
var ProcessDataFlag;
|
|
(function (ProcessDataFlag) {
|
|
ProcessDataFlag[ProcessDataFlag["None"] = 0] = "None";
|
|
ProcessDataFlag[ProcessDataFlag["Memory"] = 1] = "Memory";
|
|
ProcessDataFlag[ProcessDataFlag["CommandLine"] = 2] = "CommandLine";
|
|
+ ProcessDataFlag[ProcessDataFlag["CreationTime"] = 4] = "CreationTime";
|
|
})(ProcessDataFlag = exports.ProcessDataFlag || (exports.ProcessDataFlag = {}));
|
|
// requestInProgress is used for any function that uses CreateToolhelp32Snapshot, as multiple calls
|
|
// to this cannot be done at the same time.
|
|
@@ -66,11 +68,12 @@ function buildProcessTree(rootPid, processList, maxDepth = MAX_FILTER_DEPTH) {
|
|
// • the properties are inlined/splatted
|
|
// • the 'ppid' field is omitted
|
|
// • the depth of the tree is limited by `maxDepth`
|
|
- const buildNode = ({ info: { pid, name, memory, commandLine }, children }, depth) => ({
|
|
+ const buildNode = ({ info: { pid, name, memory, commandLine, creationTimeMs }, children }, depth) => ({
|
|
pid,
|
|
name,
|
|
memory,
|
|
commandLine,
|
|
+ creationTimeMs,
|
|
children: depth > 0 ? children.map(c => buildNode(c, depth - 1)) : [],
|
|
});
|
|
return buildNode(root, maxDepth);
|
|
diff --git a/lib/index.ts b/lib/index.ts
|
|
index f9aa005d9ced9e42885b8a976de5eb5bd61899ee..270967c1d1192b37cb6b095457bf52b213f88e8d 100644
|
|
--- a/lib/index.ts
|
|
+++ b/lib/index.ts
|
|
@@ -6,12 +6,15 @@
|
|
import { promisify } from 'util';
|
|
|
|
const native = process.platform === 'win32' ? require('../build/Release/windows_process_tree.node') : undefined;
|
|
+/** The flag bits this compiled addon reports; undefined off win32. */
|
|
+export const supportedProcessDataFlags: number | undefined = native?.supportedProcessDataFlags;
|
|
import { IProcessInfo, IProcessTreeNode, IProcessCpuInfo } from '@vscode/windows-process-tree';
|
|
|
|
export enum ProcessDataFlag {
|
|
None = 0,
|
|
Memory = 1,
|
|
- CommandLine = 2
|
|
+ CommandLine = 2,
|
|
+ CreationTime = 4
|
|
}
|
|
|
|
type RequestCallback = (processList: IProcessInfo[]) => void;
|
|
@@ -81,11 +84,12 @@ export function buildProcessTree(rootPid: number, processList: Iterable<IProcess
|
|
// • the properties are inlined/splatted
|
|
// • the 'ppid' field is omitted
|
|
// • the depth of the tree is limited by `maxDepth`
|
|
- const buildNode = ({ info: { pid, name, memory, commandLine }, children }: IProcessInfoNode, depth: number): IProcessTreeNode => ({
|
|
+ const buildNode = ({ info: { pid, name, memory, commandLine, creationTimeMs }, children }: IProcessInfoNode, depth: number): IProcessTreeNode => ({
|
|
pid,
|
|
name,
|
|
memory,
|
|
commandLine,
|
|
+ creationTimeMs,
|
|
children: depth > 0 ? children.map(c => buildNode(c, depth - 1)) : [],
|
|
});
|
|
|
|
diff --git a/src/addon.cc b/src/addon.cc
|
|
index 9214aff281251e797a70ecb9f6e0b52932a0503f..722edd42ddb4740296bfc47582a181bd6d00c464 100644
|
|
--- a/src/addon.cc
|
|
+++ b/src/addon.cc
|
|
@@ -53,6 +53,10 @@ void GetProcessCpuUsage(const Napi::CallbackInfo& args) {
|
|
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
exports.Set("getProcessList", Napi::Function::New(env, GetProcessList));
|
|
exports.Set("getProcessCpuUsage", Napi::Function::New(env, GetProcessCpuUsage));
|
|
+ // Lets a caller prove THIS BINARY understands CREATIONTIME. The JS enum is
|
|
+ // patched source and says nothing about what the .node was compiled from.
|
|
+ exports.Set("supportedProcessDataFlags",
|
|
+ Napi::Number::New(env, MEMORY | COMMANDLINE | CREATIONTIME));
|
|
return exports;
|
|
}
|
|
|
|
diff --git a/src/process.cc b/src/process.cc
|
|
index 3eea92077c4d1d433119361d5c432881859131e9..c69442e2917b8474dbabaa1aa5b2c5ce746a56e7 100644
|
|
--- a/src/process.cc
|
|
+++ b/src/process.cc
|
|
@@ -21,7 +21,7 @@ uint32_t GetRawProcessList(std::vector<ProcessInfo>& process_info,
|
|
if (Process32First(snapshot_handle, &process_entry)) {
|
|
do {
|
|
if (process_entry.th32ProcessID != 0) {
|
|
- ProcessInfo pinfo;
|
|
+ ProcessInfo pinfo{};
|
|
pinfo.pid = process_entry.th32ProcessID;
|
|
pinfo.ppid = process_entry.th32ParentProcessID;
|
|
|
|
@@ -33,17 +33,43 @@ uint32_t GetRawProcessList(std::vector<ProcessInfo>& process_info,
|
|
GetProcessCommandLine(pinfo);
|
|
}
|
|
|
|
+ if (CREATIONTIME & process_data_flags) {
|
|
+ GetProcessCreationTime(pinfo);
|
|
+ }
|
|
+
|
|
strcpy(pinfo.name, process_entry.szExeFile);
|
|
process_info.push_back(std::move(pinfo));
|
|
process_count++;
|
|
}
|
|
- } while (process_count < 1024 && Process32Next(snapshot_handle, &process_entry));
|
|
+ } while (Process32Next(snapshot_handle, &process_entry));
|
|
}
|
|
|
|
CloseHandle(snapshot_handle);
|
|
return process_count;
|
|
}
|
|
|
|
+void GetProcessCreationTime(ProcessInfo& process_info) {
|
|
+ HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_info.pid);
|
|
>>>>>>> 41a13099e8 (fix(windows): make the compiled addon prove its own CreationTime support)
|
|
+ if (hProcess == NULL) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) {
|
|
+ process_info.memory = (DWORD)pmc.WorkingSetSize;
|
|
+ }
|
|
+
|
|
+ CloseHandle(hProcess);
|
|
+}
|
|
+
|
|
<<<<<<< HEAD
|
|
+// Per documentation, it is not recommended to add or subtract values from the FILETIME
|
|
+// structure, or to cast it to ULARGE_INTEGER as this can cause alignment faults on 64-bit Windows.
|
|
+// Copy the high and low part to a ULARGE_INTEGER and peform arithmetic on that instead.
|
|
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx
|
|
+ULONGLONG GetTotalTime(const FILETIME* kernelTime, const FILETIME* userTime) {
|
|
+ ULARGE_INTEGER kt, ut;
|
|
+ kt.LowPart = (*kernelTime).dwLowDateTime;
|
|
+ kt.HighPart = (*kernelTime).dwHighDateTime;
|
|
+
|
|
+ ut.LowPart = (*userTime).dwLowDateTime;
|
|
+ ut.HighPart = (*userTime).dwHighDateTime;
|
|
+
|
|
+ return kt.QuadPart + ut.QuadPart;
|
|
+}
|
|
+
|
|
+void GetCpuUsage(Cpu& cpu_info, bool first_pass) {
|
|
+ DWORD pid = cpu_info.pid;
|
|
+ HANDLE hProcess;
|
|
+
|
|
+ // GetProcessTimes needs no more than PROCESS_QUERY_LIMITED_INFORMATION.
|
|
+ hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
|
+
|
|
+ if (hProcess == NULL) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ FILETIME creationTime, exitTime, kernelTime, userTime;
|
|
+ FILETIME sysIdleTime, sysKernelTime, sysUserTime;
|
|
+ if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime)
|
|
+ && GetSystemTimes(&sysIdleTime, &sysKernelTime, &sysUserTime)) {
|
|
+ if (first_pass) {
|
|
+ cpu_info.initialProcRunTime = GetTotalTime(&kernelTime, &userTime);
|
|
+ cpu_info.initialSystemTime = GetTotalTime(&sysKernelTime, &sysUserTime);
|
|
+ } else {
|
|
+ ULONGLONG endProcTime = GetTotalTime(&kernelTime, &userTime);
|
|
+ ULONGLONG endSysTime = GetTotalTime(&sysKernelTime, &sysUserTime);
|
|
+
|
|
+ cpu_info.cpu = 100.0 * (endProcTime - cpu_info.initialProcRunTime) / (endSysTime - cpu_info.initialSystemTime);
|
|
=======
|
|
void GetProcessMemoryUsage(ProcessInfo& process_info) {
|
|
DWORD pid = process_info.pid;
|
|
HANDLE hProcess;
|
|
diff --git a/src/process.h b/src/process.h
|
|
index 82f8e4bcfa742551e5d874a7632736a7611d7aa7..087bf4ca81768918922a1e26836c70d7aec349ec 100644
|
|
--- a/src/process.h
|
|
+++ b/src/process.h
|
|
@@ -22,18 +22,22 @@ struct ProcessInfo {
|
|
DWORD ppid;
|
|
DWORD memory; // Reported in bytes
|
|
std::string commandLine;
|
|
+ ULONGLONG creationTimeMs;
|
|
};
|
|
|
|
enum ProcessDataFlags {
|
|
NONE = 0,
|
|
MEMORY = 1,
|
|
- COMMANDLINE = 2
|
|
+ COMMANDLINE = 2,
|
|
+ CREATIONTIME = 4
|
|
};
|
|
|
|
uint32_t GetRawProcessList(std::vector<ProcessInfo>& process_info, DWORD flags);
|
|
|
|
void GetProcessMemoryUsage(ProcessInfo& process_info);
|
|
|
|
+void GetProcessCreationTime(ProcessInfo& process_info);
|
|
+
|
|
void GetCpuUsage(Cpu& cpu_info, bool first_run);
|
|
|
|
#endif // SRC_PROCESS_H_
|
|
diff --git a/src/process_worker.cc b/src/process_worker.cc
|
|
index c9e3457a759c1acaa2644231a4917d45aed951f8..89f3339d5dc3b454397941c958b95965afed5788 100644
|
|
--- a/src/process_worker.cc
|
|
+++ b/src/process_worker.cc
|
|
@@ -43,6 +43,11 @@ void GetProcessesWorker::OnOK() {
|
|
Napi::String::New(env, pinfo.commandLine));
|
|
}
|
|
|
|
+ if ((CREATIONTIME & process_data_flags_) && pinfo.creationTimeMs != 0) {
|
|
+ object.Set("creationTimeMs",
|
|
+ Napi::Number::New(env, static_cast<double>(pinfo.creationTimeMs)));
|
|
>>>>>>> 41a13099e8 (fix(windows): make the compiled addon prove its own CreationTime support)
|
|
+ }
|
|
+ } else {
|
|
+ cpu_info.cpu = std::numeric_limits<double>::quiet_NaN();
|
|
+ }
|
|
+
|
|
<<<<<<< HEAD
|
|
+ CloseHandle(hProcess);
|
|
}
|
|
\ No newline at end of file
|
|
diff --git a/src/process_commandline.cc b/src/process_commandline.cc
|
|
index ea822b120e8038a4803e34647042f08f4aaf5ca1..25907c0bf542bed6c72b1b462b19bcf3210c3cfd 100644
|
|
--- a/src/process_commandline.cc
|
|
+++ b/src/process_commandline.cc
|
|
@@ -1,67 +1,125 @@
|
|
-/*---------------------------------------------------------------------------------------------
|
|
- * Copyright (c) Microsoft Corporation. All rights reserved.
|
|
- * Licensed under the MIT License. See License.txt in the project root for license information.
|
|
- *--------------------------------------------------------------------------------------------*/
|
|
-
|
|
-#include "process.h"
|
|
-#include "process_commandline.h"
|
|
-#include <windows.h>
|
|
-#include <winternl.h>
|
|
-#include <iostream>
|
|
-
|
|
-bool GetProcessCommandLine(ProcessInfo& process_info) {
|
|
- HINSTANCE ntdll = GetModuleHandleW(L"ntdll.dll");
|
|
- if (!ntdll) {
|
|
- return false;
|
|
- }
|
|
-
|
|
- decltype(NtQueryInformationProcess)* nt_query_information_process =
|
|
- reinterpret_cast<decltype(NtQueryInformationProcess)*>(
|
|
- GetProcAddress(ntdll, "NtQueryInformationProcess"));
|
|
-
|
|
- if (!nt_query_information_process) {
|
|
- return false;
|
|
- }
|
|
-
|
|
- PROCESS_BASIC_INFORMATION pbi{};
|
|
- PEB peb = {NULL};
|
|
- RTL_USER_PROCESS_PARAMETERS process_parameters = {NULL};
|
|
-
|
|
- // Get process handle
|
|
- DWORD pid = process_info.pid;
|
|
- HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
|
|
- if (hProcess == INVALID_HANDLE_VALUE) {
|
|
- return false;
|
|
- }
|
|
-
|
|
- // Get Process Environment Block (PEB)
|
|
- NTSTATUS status = nt_query_information_process(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr);
|
|
- if (NT_SUCCESS(status) && pbi.PebBaseAddress) {
|
|
- // Read PEB
|
|
- if (ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), nullptr)) {
|
|
- // Read the processs parameters
|
|
- if (ReadProcessMemory(hProcess, peb.ProcessParameters, &process_parameters, sizeof(RTL_USER_PROCESS_PARAMETERS), nullptr)) {
|
|
- if (process_parameters.CommandLine.Length > 0) {
|
|
- std::wstring buffer;
|
|
- buffer.resize(process_parameters.CommandLine.Length / sizeof(wchar_t));
|
|
- if (ReadProcessMemory(hProcess, process_parameters.CommandLine.Buffer, &buffer[0], process_parameters.CommandLine.Length, nullptr)) {
|
|
- int wide_length = static_cast<int>(buffer.length());
|
|
- int charcount = WideCharToMultiByte(CP_UTF8, 0, buffer.data(), wide_length,
|
|
- NULL, 0, NULL, NULL);
|
|
- if (charcount) {
|
|
- process_info.commandLine.resize(static_cast<size_t>(charcount));
|
|
- WideCharToMultiByte(CP_UTF8, 0, buffer.data(), wide_length,
|
|
- &process_info.commandLine[0], charcount,
|
|
- NULL, NULL);
|
|
- }
|
|
- CloseHandle(hProcess);
|
|
- return true;
|
|
- }
|
|
- }
|
|
- }
|
|
- }
|
|
- }
|
|
-
|
|
- CloseHandle(hProcess);
|
|
- return false;
|
|
-}
|
|
+/*---------------------------------------------------------------------------------------------
|
|
+ * Copyright (c) Microsoft Corporation. All rights reserved.
|
|
+ * Licensed under the MIT License. See License.txt in the project root for license information.
|
|
+ *--------------------------------------------------------------------------------------------*/
|
|
+
|
|
+#include "process.h"
|
|
+#include "process_commandline.h"
|
|
+#include <windows.h>
|
|
+#include <winternl.h>
|
|
+#include <vector>
|
|
+
|
|
+namespace {
|
|
+
|
|
+// Windows 8.1 and later hand back a process's command line as a UNICODE_STRING
|
|
+// the kernel builds, needing only PROCESS_QUERY_LIMITED_INFORMATION.
|
|
+//
|
|
+// There is deliberately no PEB fallback. Reading the command line out of the
|
|
+// target's address space -- opening it for VM reads and then chaining
|
|
+// memory reads across every pid on a timer -- is the credential-dumping
|
|
+// primitive this reader exists to not perform, so it is absent from the binary
|
|
+// rather than one anomalous NTSTATUS away. Electron's floor is Windows 10, so
|
|
+// every OS Orca supports has this class; if a hooked ntdll refuses it anyway,
|
|
+// the command line comes back empty, which callers already handle, instead of
|
|
+// silently reinstating the primitive on exactly the instrumented machines this
|
|
+// reader was written for.
|
|
+const ULONG kProcessCommandLineInformation = 60;
|
|
+
|
|
+const NTSTATUS kStatusInfoLengthMismatch = static_cast<NTSTATUS>(0xC0000004L);
|
|
+const NTSTATUS kStatusBufferTooSmall = static_cast<NTSTATUS>(0xC0000023L);
|
|
+
|
|
+// A command line is a UNICODE_STRING, whose Length is a USHORT, so the kernel
|
|
+// can never need more than the header plus 64 KiB. Refusing anything larger
|
|
+// keeps a bogus size from throwing bad_alloc out of a scan that has already
|
|
+// walked most of the table.
|
|
+const ULONG kMaxCommandLineBytes = sizeof(UNICODE_STRING) + 0xFFFF + sizeof(wchar_t);
|
|
+
|
|
+// winternl.h's PROCESSINFOCLASS does not name class 60 and its enumerator range
|
|
+// stops far short of it, so the class travels as a ULONG rather than a cast enum.
|
|
+typedef NTSTATUS(NTAPI* NtQueryInformationProcessFn)(HANDLE, ULONG, PVOID, ULONG, PULONG);
|
|
+
|
|
+// ntdll ships no import library for this entry point; it has to be resolved.
|
|
+NtQueryInformationProcessFn ResolveNtQueryInformationProcess() {
|
|
+ HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
|
|
+ if (!ntdll) {
|
|
+ return nullptr;
|
|
+ }
|
|
+ return reinterpret_cast<NtQueryInformationProcessFn>(
|
|
+ GetProcAddress(ntdll, "NtQueryInformationProcess"));
|
|
+}
|
|
+
|
|
+NtQueryInformationProcessFn NtQueryInformationProcessEntry() {
|
|
+ static NtQueryInformationProcessFn entry = ResolveNtQueryInformationProcess();
|
|
+ return entry;
|
|
+}
|
|
+
|
|
+bool StoreCommandLineUtf8(ProcessInfo& process_info, const wchar_t* data, size_t wide_length) {
|
|
+ if (wide_length == 0) {
|
|
+ return false;
|
|
+ }
|
|
+ int length = static_cast<int>(wide_length);
|
|
+ int charcount = WideCharToMultiByte(CP_UTF8, 0, data, length, NULL, 0, NULL, NULL);
|
|
+ if (!charcount) {
|
|
+ return false;
|
|
+ }
|
|
+ process_info.commandLine.resize(static_cast<size_t>(charcount));
|
|
+ WideCharToMultiByte(CP_UTF8, 0, data, length, &process_info.commandLine[0], charcount, NULL,
|
|
+ NULL);
|
|
+ return true;
|
|
+}
|
|
+
|
|
+} // namespace
|
|
+
|
|
+bool GetProcessCommandLine(ProcessInfo& process_info) {
|
|
+ NtQueryInformationProcessFn query = NtQueryInformationProcessEntry();
|
|
+ if (!query) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_info.pid);
|
|
+ if (process == NULL) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ ULONG size = 0;
|
|
+ NTSTATUS status = query(process, kProcessCommandLineInformation, nullptr, 0, &size);
|
|
+ if (NT_SUCCESS(status)) {
|
|
+ // Nothing was written, so there is no command line to read.
|
|
+ CloseHandle(process);
|
|
+ return false;
|
|
+ }
|
|
+ if (status != kStatusInfoLengthMismatch && status != kStatusBufferTooSmall) {
|
|
+ CloseHandle(process);
|
|
+ return false;
|
|
+ }
|
|
+ if (size < sizeof(UNICODE_STRING) || size > kMaxCommandLineBytes) {
|
|
+ CloseHandle(process);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ std::vector<unsigned char> buffer(size);
|
|
+ status = query(process, kProcessCommandLineInformation, &buffer[0], size, &size);
|
|
+ CloseHandle(process);
|
|
+ if (!NT_SUCCESS(status)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ // Header and characters arrive in one allocation, but treat the header as
|
|
+ // untrusted: a hooked ntdll is the case this reader is written for, and an
|
|
+ // unchecked Buffer/Length here would be an over-read encoded straight into JS.
|
|
+ // Bound against buffer.size(), never `size` -- the second query overwrote it.
|
|
+ const UNICODE_STRING* command_line = reinterpret_cast<const UNICODE_STRING*>(&buffer[0]);
|
|
+ const unsigned char* begin = &buffer[0];
|
|
+ const unsigned char* end = begin + buffer.size();
|
|
+ const unsigned char* chars = reinterpret_cast<const unsigned char*>(command_line->Buffer);
|
|
+ if (chars == nullptr || chars < begin + sizeof(UNICODE_STRING) || chars > end ||
|
|
+ command_line->Length > static_cast<ULONG>(end - chars)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ // True only when a command line was actually stored, so "empty" and "not
|
|
+ // recovered" stay the same answer they were before this reader replaced the
|
|
+ // PEB read. `src/process.cc` discards the result either way.
|
|
+ return StoreCommandLineUtf8(process_info, command_line->Buffer,
|
|
+ command_line->Length / sizeof(wchar_t));
|
|
+}
|
|
=======
|
|
result.Set(i, object);
|
|
}
|
|
|
|
diff --git a/typings/windows-process-tree.d.ts b/typings/windows-process-tree.d.ts
|
|
index 08bdac2fdc5ead6f0fcfb5ee5a021e2298c7d523..2c515242d78f9d8b8e63271d86f59d1024d71604 100644
|
|
--- a/typings/windows-process-tree.d.ts
|
|
+++ b/typings/windows-process-tree.d.ts
|
|
@@ -7,9 +7,17 @@ declare module '@vscode/windows-process-tree' {
|
|
export enum ProcessDataFlag {
|
|
None = 0,
|
|
Memory = 1,
|
|
- CommandLine = 2
|
|
+ CommandLine = 2,
|
|
+ CreationTime = 4
|
|
}
|
|
|
|
+ /**
|
|
+ * The flag bits the compiled addon actually understands, or undefined off
|
|
+ * win32. `ProcessDataFlag` above is source; this is what the binary reports,
|
|
+ * so it is the only way to tell a patched build from a stale prebuilt.
|
|
+ */
|
|
+ export const supportedProcessDataFlags: number | undefined;
|
|
+
|
|
export interface IProcessInfo {
|
|
pid: number;
|
|
ppid: number;
|
|
@@ -24,6 +32,9 @@ declare module '@vscode/windows-process-tree' {
|
|
* The string returned is at most 512 chars, strings exceeding this length are truncated.
|
|
*/
|
|
commandLine?: string;
|
|
+
|
|
+ /** Process creation time in Unix milliseconds. */
|
|
+ creationTimeMs?: number;
|
|
}
|
|
|
|
export interface IProcessCpuInfo extends IProcessInfo {
|
|
@@ -35,6 +46,7 @@ declare module '@vscode/windows-process-tree' {
|
|
name: string;
|
|
memory?: number;
|
|
commandLine?: string;
|
|
+ creationTimeMs?: number;
|
|
children: IProcessTreeNode[];
|
|
}
|
|
|
|
>>>>>>> 41a13099e8 (fix(windows): make the compiled addon prove its own CreationTime support)
|