Skip to content
API Documentation
BastionGuard™
ProcMon.cpp
Go to the documentation of this file.
2#include <algorithm>
3#include <cctype>
4#include <filesystem>
5#include <fstream>
6#include <openssl/md5.h>
7#include <openssl/sha.h>
8#include <sstream>
9#include <unistd.h>
10
11namespace bsc::proc {
12namespace fs = std::filesystem;
13namespace {
14constexpr auto kFreshCacheTtl = std::chrono::seconds(3);
15constexpr auto kRecentExitTtl = std::chrono::seconds(2);
16
17std::string slurp(const fs::path& p, bool binary=false) {
18 std::ifstream in(p, binary ? std::ios::binary : std::ios::in);
19 if (!in) return {};
20 std::ostringstream ss; ss << in.rdbuf(); return ss.str();
21}
22std::vector<std::string> split_nulls(const std::string& s) {
23 std::vector<std::string> out; std::string cur;
24 for (char c : s) {
25 if (c == '\0') { if (!cur.empty()) out.push_back(cur); cur.clear(); }
26 else cur.push_back(c);
27 }
28 if (!cur.empty()) out.push_back(cur);
29 return out;
30}
31std::string md5_file(const fs::path& p) {
32 std::ifstream in(p, std::ios::binary); if (!in) return {};
33 MD5_CTX ctx; MD5_Init(&ctx);
34 char buf[8192];
35 while (in.read(buf, sizeof(buf)) || in.gcount() > 0) MD5_Update(&ctx, buf, static_cast<size_t>(in.gcount()));
36 unsigned char out[MD5_DIGEST_LENGTH]; MD5_Final(out, &ctx);
37 static const char* hex = "0123456789abcdef";
38 std::string s; s.reserve(MD5_DIGEST_LENGTH * 2);
39 for (unsigned char b : out) { s.push_back(hex[b >> 4]); s.push_back(hex[b & 0xF]); }
40 return s;
41}
42std::string sha1_file(const fs::path& p) {
43 std::ifstream in(p, std::ios::binary); if (!in) return {};
44 SHA_CTX ctx; SHA1_Init(&ctx);
45 char buf[8192];
46 while (in.read(buf, sizeof(buf)) || in.gcount() > 0) SHA1_Update(&ctx, buf, static_cast<size_t>(in.gcount()));
47 unsigned char out[SHA_DIGEST_LENGTH]; SHA1_Final(out, &ctx);
48 static const char* hex = "0123456789abcdef";
49 std::string s; s.reserve(SHA_DIGEST_LENGTH * 2);
50 for (unsigned char b : out) { s.push_back(hex[b >> 4]); s.push_back(hex[b & 0xF]); }
51 return s;
52}
53std::string sha256_file(const fs::path& p) {
54 std::ifstream in(p, std::ios::binary); if (!in) return {};
55 SHA256_CTX ctx; SHA256_Init(&ctx);
56 char buf[8192];
57 while (in.read(buf, sizeof(buf)) || in.gcount() > 0) SHA256_Update(&ctx, buf, static_cast<size_t>(in.gcount()));
58 unsigned char out[SHA256_DIGEST_LENGTH]; SHA256_Final(out, &ctx);
59 static const char* hex = "0123456789abcdef";
60 std::string s; s.reserve(SHA256_DIGEST_LENGTH * 2);
61 for (unsigned char b : out) { s.push_back(hex[b >> 4]); s.push_back(hex[b & 0xF]); }
62 return s;
63}
64}
65
66std::optional<bsc::model::ProcessInfo> ProcMon::inspect_procfs(std::uint32_t pid, bool checksums) {
67 fs::path base = fs::path("/proc") / std::to_string(pid);
68 if (!fs::exists(base)) return std::nullopt;
70 std::error_code ec;
71 p.path = fs::read_symlink(base / "exe", ec).string();
72 ec.clear(); p.cwd = fs::read_symlink(base / "cwd", ec).string();
73 p.args = split_nulls(slurp(base / "cmdline", true));
74 auto env = split_nulls(slurp(base / "environ", true));
75 for (const auto& e : env) { auto pos=e.find('='); if (pos!=std::string::npos) p.env[e.substr(0,pos)] = e.substr(pos+1); }
76
77 // /proc/<pid>/comm contiene il nome del processo terminato da \n.
78 // Piu' affidabile del parsing di /proc/<pid>/status (separatore variabile).
79 {
80 std::ifstream comm_file(base / "comm");
81 if (comm_file) {
82 std::string comm_line;
83 if (std::getline(comm_file, comm_line)) {
84 while (!comm_line.empty() &&
85 (comm_line.back() == '\n' || comm_line.back() == '\r' ||
86 std::isspace(static_cast<unsigned char>(comm_line.back()))))
87 comm_line.pop_back();
88 p.comm = std::move(comm_line);
89 }
90 }
91 }
92
93 std::ifstream status(base / "status");
94 std::string line;
95 while (std::getline(status, line)) {
96 // Fallback per p.comm se /proc/<pid>/comm non era leggibile
97 if (p.comm.empty() && line.rfind("Name:", 0) == 0) {
98 p.comm = line.substr(5);
99 while (!p.comm.empty() && std::isspace(static_cast<unsigned char>(p.comm.front()))) p.comm.erase(p.comm.begin());
100 while (!p.comm.empty() && std::isspace(static_cast<unsigned char>(p.comm.back()))) p.comm.pop_back();
101 }
102 else if (line.rfind("PPid:", 0) == 0) p.ppid = std::stoull(line.substr(6));
103 else if (line.rfind("Uid:", 0) == 0) { std::istringstream iss(line.substr(5)); iss >> p.uid; }
104 }
105 for (auto current = p.pid; current > 0;) {
106 fs::path status_path = fs::path("/proc") / std::to_string(current) / "status";
107 std::ifstream st(status_path);
108 if (!st) break;
109 std::string name; std::uint64_t ppid=0;
110 while (std::getline(st, line)) {
111 if (line.rfind("Name:",0)==0) { name = line.substr(6); while (!name.empty() && std::isspace(static_cast<unsigned char>(name.front()))) name.erase(name.begin()); }
112 else if (line.rfind("PPid:",0)==0) ppid = std::stoull(line.substr(6));
113 }
114 p.process_tree.emplace_back(name, static_cast<std::uint32_t>(current));
115 if (ppid == current) break;
116 current = ppid;
117 }
118 if (checksums && !p.path.empty()) { p.checksums["md5"] = md5_file(p.path); p.checksums["sha1"] = sha1_file(p.path); p.checksums["sha256"] = sha256_file(p.path); }
119 return p;
120}
121
122std::optional<bsc::model::ProcessInfo> ProcMon::cached(std::uint32_t pid, bool allow_recent_exit) const {
123 const auto now = std::chrono::steady_clock::now();
124 std::lock_guard lock(cache_mutex_);
125 if (const auto it = cache_.find(pid); it != cache_.end()) return it->second.info;
126 if (allow_recent_exit) {
127 if (const auto it = exited_.find(pid); it != exited_.end()) {
128 if (now - it->second.updated_at <= kRecentExitTtl) return it->second.info;
129 exited_.erase(it);
130 }
131 }
132 return std::nullopt;
133}
134
135std::vector<bsc::model::ProcessInfo> ProcMon::recent_candidates(std::uint32_t uid,
136 std::chrono::milliseconds max_age,
137 std::size_t limit) const {
138 const auto now = std::chrono::steady_clock::now();
139 std::vector<std::pair<std::chrono::steady_clock::time_point, bsc::model::ProcessInfo>> tmp;
140
141 std::lock_guard lock(cache_mutex_);
142 auto consider = [&](const CacheEntry& entry) {
143 if (entry.info.uid != uid) return;
144 if (entry.info.comm.empty() && entry.info.path.empty()) return;
145 if (max_age.count() > 0 && now - entry.updated_at > max_age) return;
146 tmp.emplace_back(entry.updated_at, entry.info);
147 };
148 for (const auto& [pid, entry] : cache_) consider(entry);
149 for (const auto& [pid, entry] : exited_) consider(entry);
150
151 std::sort(tmp.begin(), tmp.end(), [](const auto& a, const auto& b) {
152 return a.first > b.first;
153 });
154
155 std::vector<bsc::model::ProcessInfo> out;
156 out.reserve(std::min(limit, tmp.size()));
157 for (const auto& [ts, info] : tmp) {
158 out.push_back(info);
159 if (out.size() >= limit) break;
160 }
161 return out;
162}
163
164
165std::vector<bsc::model::ProcessInfo> ProcMon::recent_candidates_any(std::chrono::milliseconds max_age,
166 std::size_t limit) const {
167 const auto now = std::chrono::steady_clock::now();
168 std::vector<std::pair<std::chrono::steady_clock::time_point, bsc::model::ProcessInfo>> tmp;
169
170 std::lock_guard lock(cache_mutex_);
171 auto consider = [&](const CacheEntry& entry) {
172 if (entry.info.comm.empty() && entry.info.path.empty()) return;
173 if (max_age.count() > 0 && now - entry.updated_at > max_age) return;
174 tmp.emplace_back(entry.updated_at, entry.info);
175 };
176 for (const auto& [pid, entry] : cache_) consider(entry);
177 for (const auto& [pid, entry] : exited_) consider(entry);
178
179 std::sort(tmp.begin(), tmp.end(), [](const auto& a, const auto& b) {
180 return a.first > b.first;
181 });
182
183 std::vector<bsc::model::ProcessInfo> out;
184 out.reserve(std::min(limit, tmp.size()));
185 for (const auto& [ts, info] : tmp) {
186 out.push_back(info);
187 if (out.size() >= limit) break;
188 }
189 return out;
190}
191
192std::optional<bsc::model::ProcessInfo> ProcMon::inspect(std::uint32_t pid, bool checksums) const {
193 const auto now = std::chrono::steady_clock::now();
194 {
195 std::lock_guard lock(cache_mutex_);
196 if (const auto it = cache_.find(pid); it != cache_.end()) {
197 const auto age = now - it->second.updated_at;
198 const auto fresh = age <= kFreshCacheTtl;
199 const auto checksum_ok = !checksums || it->second.has_checksums;
200 if (fresh && checksum_ok) return it->second.info;
201 }
202 if (const auto it = exited_.find(pid); it != exited_.end()) {
203 const auto age = now - it->second.updated_at;
204 const auto checksum_ok = !checksums || it->second.has_checksums;
205 if (age <= kRecentExitTtl && checksum_ok) return it->second.info;
206 if (age > kRecentExitTtl) exited_.erase(it);
207 }
208 }
209
210 auto info = inspect_procfs(pid, checksums);
211 if (!info) {
212 if (auto stale = cached(pid, true)) return stale;
213 invalidate(pid);
214 return std::nullopt;
215 }
216
217 CacheEntry entry;
218 entry.info = *info;
219 entry.has_checksums = !info->checksums.empty();
220 entry.updated_at = now;
221 std::lock_guard lock(cache_mutex_);
222 exited_.erase(pid);
223 cache_[pid] = std::move(entry);
224 return info;
225}
226
228 std::lock_guard lock(cache_mutex_);
229 auto& entry = cache_[static_cast<std::uint32_t>(info.pid)];
230 entry.info = info;
231 entry.has_checksums = !info.checksums.empty();
232 entry.updated_at = std::chrono::steady_clock::now();
233 exited_.erase(static_cast<std::uint32_t>(info.pid));
234}
235
236void ProcMon::invalidate(std::uint32_t pid) const {
237 std::lock_guard lock(cache_mutex_);
238 cache_.erase(pid);
239 exited_.erase(pid);
240}
241
242void ProcMon::note_fork(std::uint32_t child_pid, std::uint32_t parent_pid) const {
243 std::lock_guard lock(cache_mutex_);
244 if (const auto it = cache_.find(parent_pid); it != cache_.end()) {
245 auto child = it->second;
246 child.info.pid = child_pid;
247 child.info.ppid = parent_pid;
248 child.info.checksums.clear();
249 child.has_checksums = false;
250 child.updated_at = std::chrono::steady_clock::now();
251 child.info.process_tree.clear();
252 cache_[child_pid] = std::move(child);
253 exited_.erase(child_pid);
254 }
255}
256
257void ProcMon::note_exec(std::uint32_t pid, bool checksums) const {
258 if (auto info = inspect_procfs(pid, checksums)) {
259 cache_process(*info);
260 }
261}
262
263void ProcMon::note_exit(std::uint32_t pid) const {
264 std::lock_guard lock(cache_mutex_);
265 if (const auto it = cache_.find(pid); it != cache_.end()) {
266 auto entry = it->second;
267 entry.updated_at = std::chrono::steady_clock::now();
268 exited_[pid] = std::move(entry);
269 cache_.erase(it);
270 }
271}
272
273} // namespace bsc::proc
std::string sha256_file(const std::string &path)
std::vector< bsc::model::ProcessInfo > recent_candidates_any(std::chrono::milliseconds max_age=std::chrono::milliseconds(1500), std::size_t limit=8) const
Definition ProcMon.cpp:165
std::unordered_map< std::uint32_t, CacheEntry > exited_
Definition ProcMon.hpp:39
std::mutex cache_mutex_
Definition ProcMon.hpp:37
void note_exit(std::uint32_t pid) const
Definition ProcMon.cpp:263
static std::optional< bsc::model::ProcessInfo > inspect_procfs(std::uint32_t pid, bool checksums)
Definition ProcMon.cpp:66
std::vector< bsc::model::ProcessInfo > recent_candidates(std::uint32_t uid, std::chrono::milliseconds max_age=std::chrono::milliseconds(5000), std::size_t limit=8) const
Definition ProcMon.cpp:135
void cache_process(const bsc::model::ProcessInfo &info) const
Definition ProcMon.cpp:227
std::optional< bsc::model::ProcessInfo > cached(std::uint32_t pid, bool allow_recent_exit=true) const
Definition ProcMon.cpp:122
void note_fork(std::uint32_t child_pid, std::uint32_t parent_pid) const
Definition ProcMon.cpp:242
std::unordered_map< std::uint32_t, CacheEntry > cache_
Definition ProcMon.hpp:38
void invalidate(std::uint32_t pid) const
Definition ProcMon.cpp:236
void note_exec(std::uint32_t pid, bool checksums=false) const
Definition ProcMon.cpp:257
std::optional< bsc::model::ProcessInfo > inspect(std::uint32_t pid, bool checksums=false) const
Definition ProcMon.cpp:192
std::string md5_file(const fs::path &p)
Definition ProcMon.cpp:31
std::string slurp(const fs::path &p, bool binary=false)
Definition ProcMon.cpp:17
std::string sha1_file(const fs::path &p)
Definition ProcMon.cpp:42
std::vector< std::string > split_nulls(const std::string &s)
Definition ProcMon.cpp:22
std::string sha256_file(const fs::path &p)
Definition ProcMon.cpp:53
std::map< std::string, std::string > env
std::map< std::string, std::string > checksums
std::vector< std::pair< std::string, std::uint32_t > > process_tree
std::vector< std::string > args
bsc::model::ProcessInfo info
Definition ProcMon.hpp:30
std::chrono::steady_clock::time_point updated_at
Definition ProcMon.hpp:32