Skip to content
API Documentation
BastionGuard™
AurScan.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 * BastionGuard™ is a trademark of Calogero Scarnà.
18 * The BastionGuard™ name and branding are not licensed under the GPL.
19 */
20
21#include "AurScan.hpp"
22#include <fstream>
23#include <filesystem>
24#include <giomm.h>
25#include <iostream>
26#include <unordered_set>
27#include <glibmm/i18n.h>
28
29namespace AurScan {
30
31namespace fs = std::filesystem;
32
33static std::function<void(const std::string&)> logFunc;
34static std::function<void(const std::string&)> pkgbuildDetectedFunc;
35
36static std::vector<Glib::RefPtr<Gio::FileMonitor>> aurMonitors;
37
38
39
41 std::ifstream f("/etc/os-release");
42 if (!f.is_open()) return false;
43
44 std::string line;
45 while (std::getline(f, line)) {
46 if (line.find("ID=") == 0 || line.find("ID_LIKE=") == 0) {
47 if (line.find("arch") != std::string::npos ||
48 line.find("manjaro") != std::string::npos ||
49 line.find("endeavouros") != std::string::npos ||
50 line.find("arco") != std::string::npos)
51 return true;
52 }
53 }
54 return false;
55}
56
57
59 std::function<void(const std::string&)> logCallback,
60 std::function<void(const std::string&)> pkgbuildDetectedCallback)
61{
62 logFunc = logCallback;
63 pkgbuildDetectedFunc = pkgbuildDetectedCallback;
64
65 if (logFunc)
66 logFunc(_("📦 AurScan inizializzato"));
67}
68
69
70static bool ends_with(const std::string& s, const std::string& suf) {
71 return s.size() >= suf.size() &&
72 s.compare(s.size() - suf.size(), suf.size(), suf) == 0;
73}
74
75
76static std::unordered_set<std::string> recentDetections;
77
78static bool isRecentlyDetected(const std::string& path) {
79 if (recentDetections.count(path))
80 return true;
81
82 recentDetections.insert(path);
83
84 // Rimuove dopo 1 secondo
85 Glib::signal_timeout().connect_once([path]() {
86 recentDetections.erase(path);
87 }, 1000);
88
89 return false;
90}
91
92
93static void addMonitorRecursively(const std::string& base)
94{
95 if (!fs::exists(base)) return;
96
97
98 const std::vector<std::string> skipDirs = {
99 ".git", ".svn", ".hg", "pkg", "src"
100 };
101
102 auto basename = fs::path(base).filename().string();
103 for (const auto& skip : skipDirs) {
104 if (basename == skip)
105 return;
106 }
107
108
109 auto gfile = Gio::File::create_for_path(base);
110 auto monitor = gfile->monitor_directory(
111 Gio::FileMonitorFlags::WATCH_MOVES |
112 Gio::FileMonitorFlags::SEND_MOVED
113 );
114
115 monitor->signal_changed().connect(
116 [base](const Glib::RefPtr<Gio::File>& file,
117 const Glib::RefPtr<Gio::File>&,
118 Gio::FileMonitor::Event e)
119 {
120 std::string path = file->get_path();
121 if (path.empty()) return;
122
123 // Eventi rilevanti
124 if (e != Gio::FileMonitor::Event::CREATED &&
125 e != Gio::FileMonitor::Event::MOVED_IN &&
126 e != Gio::FileMonitor::Event::CHANGES_DONE_HINT)
127 return;
128
129
130 if (ends_with(path, "PKGBUILD")) {
131
132 //
133 if (isRecentlyDetected(path))
134 return;
135
136 if (logFunc)
137 logFunc(_("📦 PKGBUILD rilevato: ") + path);
138
141 }
142 });
143
144 aurMonitors.push_back(monitor);
145
146 if (logFunc)
147 logFunc(_("📁 Monitor AUR attivo su: ") + base);
148
149
150 try {
151 for (auto it = fs::directory_iterator(
152 base,
153 fs::directory_options::skip_permission_denied);
154 it != fs::directory_iterator();
155 ++it)
156 {
157 try {
158 if (it->is_directory())
159 addMonitorRecursively(it->path().string());
160 }
161 catch (...) {
162
163 continue;
164 }
165 }
166 }
167 catch (...) {
168 }
169}
170
171
172
173static void addDynamicRootMonitor(const std::string& root)
174{
175 if (!fs::exists(root)) return;
176
177 auto gfile = Gio::File::create_for_path(root);
178 auto monitor = gfile->monitor_directory(Gio::FileMonitorFlags::WATCH_MOVES);
179
180 monitor->signal_changed().connect(
181 [root](const Glib::RefPtr<Gio::File>& file,
182 const Glib::RefPtr<Gio::File>&,
183 Gio::FileMonitor::Event e)
184 {
185 std::string path = file->get_path();
186 if (path.empty()) return;
187
188
189 if (e == Gio::FileMonitor::Event::CREATED ||
190 e == Gio::FileMonitor::Event::MOVED_IN)
191 {
192 try {
193 if (fs::is_directory(path)) {
194
195 if (logFunc)
196 logFunc(_("🆕 Nuova directory AUR trovata: ") + path);
197
198
200 }
201 }
202 catch (...) {
203 }
204 }
205 });
206
207 aurMonitors.push_back(monitor);
208
209 if (logFunc)
210 logFunc(_("🌱 Monitor root AUR attivo su: ") + root);
211}
212
214 if (!isArchBased()) {
215 if (logFunc)
216 logFunc(_("🐧 Sistema non Arch-based — AUR disattivato"));
217 return;
218 }
219
220 if (logFunc)
221 logFunc(_("📡 Avvio monitor AUR…"));
222
223 std::vector<std::string> targets = {
224 Glib::get_home_dir() + "/.cache/yay",
225 Glib::get_home_dir() + "/.cache/paru/clone",
226 Glib::get_home_dir() + "/.cache/trizen",
227 Glib::get_home_dir() + "/.cache/pikaur/build"
228 };
229
230 for (auto& d : targets) {
231 if (!fs::exists(d)) continue;
232
233
235
236
238 }
239
240 if (logFunc)
241 logFunc(_("✅ AurScan avviato"));
242}
243
244
245
247 for (auto& m : aurMonitors) {
248 try { m->cancel(); }
249 catch (...) {}
250 }
251 aurMonitors.clear();
252
253 if (logFunc)
254 logFunc(_("🛑 AurScan disattivato"));
255}
256
257
258void scanPKGBUILD(const std::string& path) {
259 if (!fs::exists(path)) {
260 if (logFunc) logFunc(_("❌ PKGBUILD non trovato: ") + path);
261 return;
262 }
263
264 std::ifstream file(path);
265 if (!file.is_open()) {
266 if (logFunc) logFunc(_("❌ Impossibile leggere PKGBUILD: ") + path);
267 return;
268 }
269
270 if (logFunc)
271 logFunc(_("🔎 Analisi sicurezza PKGBUILD: ") + path);
272
273 std::string line;
274 int lineNum = 0;
275 bool suspicious = false;
276
277
278 auto isDependencyLine = [](const std::string& l) {
279 return l.find("depends=(") != std::string::npos ||
280 l.find("makedepends=(") != std::string::npos ||
281 l.find("optdepends=(") != std::string::npos ||
282 l.find("checkdepends=(") != std::string::npos ||
283 l.find("provides=(") != std::string::npos ||
284 l.find("conflicts=(") != std::string::npos;
285 };
286
287
288 const std::vector<std::pair<std::string, std::string>> patterns = {
289 {"curl ", _("Uso di curl come comando")},
290 {"curl|", _("Curl pipato verso shell — RCE")},
291 {"curl |", _("Curl pipato verso shell — RCE")},
292 {"wget ", _("Uso di wget come comando")},
293 {"wget|", _("Wget pipato verso shell — RCE")},
294 {"wget |", _("Wget pipato verso shell — RCE")},
295 {"bash -c", _("Esecuzione dinamica tramite bash -c")},
296 {"chmod 777", _("Permessi 777 pericolosi")},
297 {"rm -rf", _("Comando distruttivo rm -rf")},
298 {"sudo ", _("Uso improprio di sudo nel PKGBUILD")},
299 {"| sh", _("Pipe verso sh — RCE")},
300 {"| bash", _("Pipe verso bash — RCE")},
301 {"base64 -d", _("Possibile payload codificato e decodificato")},
302 {"install -m 777", _("Install con permessi insicuri")},
303 {"/etc/", _("Scrittura sospetta in /etc")},
304 {"/usr/bin", _("Scrittura diretta in /usr/bin")},
305 {"python3 -c", _("Exec dinamico con python")},
306 {"System.exec", _("Exec arbitrario")}
307 };
308
309
310 while (std::getline(file, line)) {
311 lineNum++;
312
313 if (isDependencyLine(line))
314 continue;
315
316 for (const auto& [needle, desc] : patterns) {
317 if (line.find(needle) != std::string::npos) {
318 suspicious = true;
319 if (logFunc) {
320 logFunc("⚠️ [" + std::to_string(lineNum) + "] " + desc);
321 logFunc(" → " + line);
322 }
323 }
324 }
325 }
326
327 if (suspicious) {
328 if (logFunc)
329 logFunc(_("🚨 Minacce individuate nel PKGBUILD!"));
330
332 pkgbuildDetectedFunc("INFECTED://" + path);
333
334 } else {
335 if (logFunc)
336 logFunc(_("✅ PKGBUILD pulito."));
337 }
338}
339
340
341
342} // namespace AurScan
#define _(s)
void startMonitoring()
Definition AurScan.cpp:213
static std::function< void(const std::string &)> logFunc
Definition AurScan.cpp:33
static bool isRecentlyDetected(const std::string &path)
Definition AurScan.cpp:78
static std::function< void(const std::string &)> pkgbuildDetectedFunc
Definition AurScan.cpp:34
static std::vector< Glib::RefPtr< Gio::FileMonitor > > aurMonitors
Definition AurScan.cpp:36
static void addDynamicRootMonitor(const std::string &root)
Definition AurScan.cpp:173
static std::unordered_set< std::string > recentDetections
Definition AurScan.cpp:76
void scanPKGBUILD(const std::string &path)
Definition AurScan.cpp:258
void initialize(std::function< void(const std::string &)> logCallback, std::function< void(const std::string &)> pkgbuildDetectedCallback)
Definition AurScan.cpp:58
bool isArchBased()
Definition AurScan.cpp:40
static bool ends_with(const std::string &s, const std::string &suf)
Definition AurScan.cpp:70
static void addMonitorRecursively(const std::string &base)
Definition AurScan.cpp:93
void stopMonitoring()
Definition AurScan.cpp:246