Skip to content

Commit

Permalink
Telemetry: defend against parent process loops (#1101)
Browse files Browse the repository at this point in the history
  • Loading branch information
BillyONeal authored Jun 15, 2023
1 parent 133f8fc commit 8c254a5
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 42 deletions.
3 changes: 2 additions & 1 deletion include/vcpkg/base/system.process.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <vcpkg/base/fwd/files.h>
#include <vcpkg/base/fwd/optional.h>
#include <vcpkg/base/fwd/system.process.h>

Expand Down Expand Up @@ -167,7 +168,7 @@ namespace vcpkg
std::string executable_name;
};

Optional<ProcessStat> try_parse_process_stat_file(StringView text, StringView origin);
Optional<ProcessStat> try_parse_process_stat_file(const FileContents& contents);
void get_parent_process_list(std::vector<std::string>& ret);

bool succeeded(const ExpectedL<int>& maybe_exit) noexcept;
Expand Down
15 changes: 8 additions & 7 deletions src/vcpkg-test/cgroup-parser.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <catch2/catch.hpp>

#include <vcpkg/base/files.h>
#include <vcpkg/base/optional.h>
#include <vcpkg/base/stringview.h>
#include <vcpkg/base/system.process.h>
Expand Down Expand Up @@ -74,7 +75,7 @@ TEST_CASE ("parse proc/pid/stat file", "[cgroup-parser]")
std::string contents =
R"(4281 (cpptools-srv) S 4099 1676 1676 0 -1 1077936384 51165 303 472 0 81 25 0 0 20 0 10 0 829158 4924583936 39830 18446744073709551615 4194304 14147733 140725993620736 0 0 0 0 16781312 16386 0 0 0 17 1 0 0 5 0 0 16247120 16519160 29999104 140725993622792 140725993622920 140725993622920 140725993627556 0)";

auto maybe_stat = try_parse_process_stat_file(contents, "test");
auto maybe_stat = try_parse_process_stat_file({contents, "test"});
REQUIRE(maybe_stat.has_value());
auto stat = maybe_stat.value_or_exit(VCPKG_LINE_INFO);
CHECK(stat.ppid == 4099);
Expand All @@ -86,7 +87,7 @@ TEST_CASE ("parse proc/pid/stat file", "[cgroup-parser]")
std::string contents =
R"(4281 () S 4099 1676 1676 0 -1 1077936384 51165 303 472 0 81 25 0 0 20 0 10 0 829158 4924583936 39830 18446744073709551615 4194304 14147733 140725993620736 0 0 0 0 16781312 16386 0 0 0 17 1 0 0 5 0 0 16247120 16519160 29999104 140725993622792 140725993622920 140725993622920 140725993627556 0)";

auto maybe_stat = try_parse_process_stat_file(contents, "test");
auto maybe_stat = try_parse_process_stat_file({contents, "test"});
REQUIRE(maybe_stat.has_value());
auto stat = maybe_stat.value_or_exit(VCPKG_LINE_INFO);
CHECK(stat.ppid == 4099);
Expand All @@ -98,7 +99,7 @@ TEST_CASE ("parse proc/pid/stat file", "[cgroup-parser]")
std::string contents =
R"(4281 (<(' '<)(> ' ')>) S 4099 1676 1676 0 -1 1077936384 51165 303 472 0 81 25 0 0 20 0 10 0 829158 4924583936 39830 18446744073709551615 4194304 14147733 140725993620736 0 0 0 0 16781312 16386 0 0 0 17 1 0 0 5 0 0 16247120 16519160 29999104 140725993622792 140725993622920 140725993622920 140725993627556 0)";

auto maybe_stat = try_parse_process_stat_file(contents, "test");
auto maybe_stat = try_parse_process_stat_file({contents, "test"});
REQUIRE(maybe_stat.has_value());
auto stat = maybe_stat.value_or_exit(VCPKG_LINE_INFO);
CHECK(stat.ppid == 4099);
Expand All @@ -110,7 +111,7 @@ TEST_CASE ("parse proc/pid/stat file", "[cgroup-parser]")
std::string contents =
R"(4281 (0123456789abcdef) S 4099 1676 1676 0 -1 1077936384 51165 303 472 0 81 25 0 0 20 0 10 0 829158 4924583936 39830 18446744073709551615 4194304 14147733 140725993620736 0 0 0 0 16781312 16386 0 0 0 17 1 0 0 5 0 0 16247120 16519160 29999104 140725993622792 140725993622920 140725993622920 140725993627556 0)";

auto maybe_stat = try_parse_process_stat_file(contents, "test");
auto maybe_stat = try_parse_process_stat_file({contents, "test"});
REQUIRE(maybe_stat.has_value());
auto stat = maybe_stat.value_or_exit(VCPKG_LINE_INFO);
CHECK(stat.ppid == 4099);
Expand All @@ -122,7 +123,7 @@ TEST_CASE ("parse proc/pid/stat file", "[cgroup-parser]")
std::string contents =
R"(4281 (()()()()()()()()) S 4099 1676 1676 0 -1 1077936384 51165 303 472 0 81 25 0 0 20 0 10 0 829158 4924583936 39830 18446744073709551615 4194304 14147733 140725993620736 0 0 0 0 16781312 16386 0 0 0 17 1 0 0 5 0 0 16247120 16519160 29999104 140725993622792 140725993622920 140725993622920 140725993627556 0)";

auto maybe_stat = try_parse_process_stat_file(contents, "test");
auto maybe_stat = try_parse_process_stat_file({contents, "test"});
REQUIRE(maybe_stat.has_value());
auto stat = maybe_stat.value_or_exit(VCPKG_LINE_INFO);
CHECK(stat.ppid == 4099);
Expand All @@ -134,7 +135,7 @@ TEST_CASE ("parse proc/pid/stat file", "[cgroup-parser]")
std::string contents =
R"(4281 (0123456789abcdefg) S 4099 1676 1676 0 -1 1077936384 51165 303 472 0 81 25 0 0 20 0 10 0 829158 4924583936 39830 18446744073709551615 4194304 14147733 140725993620736 0 0 0 0 16781312 16386 0 0 0 17 1 0 0 5 0 0 16247120 16519160 29999104 140725993622792 140725993622920 140725993622920 140725993627556 0)";

auto maybe_stat = try_parse_process_stat_file(contents, "test");
auto maybe_stat = try_parse_process_stat_file({contents, "test"});
REQUIRE(!maybe_stat.has_value());
}
}
}
123 changes: 89 additions & 34 deletions src/vcpkg/base/system.process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <ctime>
#include <future>
#include <set>

#if defined(__APPLE__)
extern char** environ;
Expand Down Expand Up @@ -249,9 +250,9 @@ namespace vcpkg
#endif
}

Optional<ProcessStat> try_parse_process_stat_file(StringView text, StringView origin)
Optional<ProcessStat> try_parse_process_stat_file(const FileContents& contents)
{
ParserBase p(text, origin);
ParserBase p(contents.content, contents.origin);

p.match_while(ParserBase::is_ascii_digit); // pid %d (ignored)

Expand Down Expand Up @@ -293,61 +294,115 @@ namespace vcpkg
}
return nullopt;
}
} // namespace vcpkg

namespace
{
#if defined(_WIN32)
struct ToolHelpProcessSnapshot
{
ToolHelpProcessSnapshot() noexcept : snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)) { }
ToolHelpProcessSnapshot(const ToolHelpProcessSnapshot&) = delete;
ToolHelpProcessSnapshot& operator=(const ToolHelpProcessSnapshot&) = delete;
~ToolHelpProcessSnapshot()
{
if (snapshot != INVALID_HANDLE_VALUE)
{
CloseHandle(snapshot);
}
}
explicit operator bool() const noexcept { return snapshot != INVALID_HANDLE_VALUE; }

BOOL Process32First(PPROCESSENTRY32W entry) const noexcept { return Process32FirstW(snapshot, entry); }
BOOL Process32Next(PPROCESSENTRY32W entry) const noexcept { return Process32NextW(snapshot, entry); }

private:
HANDLE snapshot;
};
#elif defined(__linux__)
Optional<ProcessStat> try_get_process_stat_by_pid(int pid)
{
auto filepath = fmt::format("/proc/{}/stat", pid);
auto maybe_contents = real_filesystem.try_read_contents(filepath);
if (auto contents = maybe_contents.get())
{
return try_parse_process_stat_file(*contents);
}

return nullopt;
}
#endif // ^^^ __linux__
} // unnamed namespace

namespace vcpkg
{
void get_parent_process_list(std::vector<std::string>& ret)
{
ret.clear();
#if defined(_WIN32)
// Enumerate all processes in the system snapshot.
auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) return;

std::map<DWORD, DWORD> pid_ppid_map;
std::map<DWORD, std::string> pid_exe_path_map;
std::set<DWORD> seen_pids;

PROCESSENTRY32W entry;
memset(&entry, 0, sizeof(entry));
PROCESSENTRY32W entry{};
entry.dwSize = sizeof(entry);
if (Process32FirstW(snapshot, &entry))
{
do
ToolHelpProcessSnapshot snapshot;
if (!snapshot)
{
pid_ppid_map.emplace(entry.th32ProcessID, entry.th32ParentProcessID);
pid_exe_path_map.emplace(entry.th32ProcessID, Strings::to_utf8(entry.szExeFile));
} while (Process32NextW(snapshot, &entry));
}
CloseHandle(snapshot);
return;
}

if (snapshot.Process32First(&entry))
{
do
{
pid_ppid_map.emplace(entry.th32ProcessID, entry.th32ParentProcessID);
pid_exe_path_map.emplace(entry.th32ProcessID, Strings::to_utf8(entry.szExeFile));
} while (snapshot.Process32Next(&entry));
}
} // destroy snapshot

// Find hierarchy of current process
auto it = pid_ppid_map.find(GetCurrentProcessId());
if (it == pid_ppid_map.end()) return;
while (true)

for (DWORD next_parent = GetCurrentProcessId();;)
{
it = pid_ppid_map.find(it->second);
if (it == pid_ppid_map.end()) break;
if (Util::Sets::contains(seen_pids, next_parent))
{
// parent graph loops, for example if a parent terminates and the PID is reused by a child launch
break;
}

seen_pids.insert(next_parent);
auto it = pid_ppid_map.find(next_parent);
if (it == pid_ppid_map.end())
{
break;
}

ret.push_back(pid_exe_path_map[it->first]);
next_parent = it->second;
}
#elif defined(__linux__)
std::error_code ec;
auto vcpkg_stat_filepath = fmt::format("/proc/{}/stat", getpid());
auto vcpkg_stat_contents = real_filesystem.read_contents(vcpkg_stat_filepath, ec);
if (ec) return;

auto maybe_vcpkg_stat = try_parse_process_stat_file(vcpkg_stat_contents, vcpkg_stat_filepath);
std::set<int> seen_pids;
auto maybe_vcpkg_stat = try_get_process_stat_by_pid(getpid());
if (auto vcpkg_stat = maybe_vcpkg_stat.get())
{
auto pid = vcpkg_stat->ppid;
while (pid != 0)
for (auto next_parent = vcpkg_stat->ppid; next_parent != 0;)
{
auto stat_filepath = fmt::format("/proc/{}/stat", pid);
auto contents = real_filesystem.read_contents(stat_filepath, ec);
if (ec) break;
if (Util::Sets::contains(seen_pids, next_parent))
{
// parent graph loops, for example if a parent terminates and the PID is reused by a child launch
break;
}

auto maybe_stat = try_parse_process_stat_file(contents, stat_filepath);
if (auto stat = maybe_stat.get())
seen_pids.insert(next_parent);
auto maybe_next_parent_stat = try_get_process_stat_by_pid(next_parent);
if (auto next_parent_stat = maybe_next_parent_stat.get())
{
ret.push_back(stat->executable_name);
pid = stat->ppid;
ret.push_back(next_parent_stat->executable_name);
next_parent = next_parent_stat->ppid;
}
else
{
Expand Down

0 comments on commit 8c254a5

Please sign in to comment.