Skip to content
API Documentation
BastionGuard™
dns_secure.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// =============================================================================
22// dns_secure.cpp
23//
24// Implementazione di reexec_inside_bwrap_with_custom_dns().
25// Questa unit è linkata sia da:
26// - BastionGuard-secure (CLI - apre l'URL banca/payment)
27// - BastionGuard-secure-gui (selettore grafico)
28// così che entrambi possano richiamare la stessa funzione di sandboxing.
29//
30// Definendola qui (anziché in BastionGuard-secure-cli.cpp) si evita di
31// forzare i due target a condividere il main() del CLI.
32// =============================================================================
33
34#include "../SafeProcess.hpp"
35#include "dns_secure.hpp"
36
37#include <unistd.h>
38#include <fcntl.h>
39#include <sys/stat.h>
40
41#include <cstdlib>
42#include <cstring>
43#include <cstdio>
44#include <iostream>
45#include <fstream>
46#include <vector>
47#include <string>
48
49#include <glib/gi18n.h>
50
51
52// -----------------------------------------------------------------------------
53// write_resolv_conf
54//
55// Scrive un /tmp/bastionguard-resolv.conf da bind-mountare dentro la sandbox
56// al posto del resolv.conf di sistema. Bypass del dnsmasq locale che
57// potrebbe interferire con il MITM CEF o con risoluzioni inattese.
58// -----------------------------------------------------------------------------
59static void write_resolv_conf(const std::string& path) {
60 std::ofstream o(path, std::ios::trunc);
61 if (!o) {
62 std::perror("[BastionGuard-secure] write resolv.conf");
63 return;
64 }
65 o << "nameserver 1.1.1.1\n";
66 o << "nameserver 9.9.9.9\n";
67 o << "nameserver 8.8.8.8\n";
68 o << "options timeout:2 attempts:3 rotate\n";
69 o.close();
70}
71
72
73// =============================================================================
74// Helper privati
75// =============================================================================
76namespace {
77
78// Builder argv-style per bwrap che possiede le stringhe (niente const_cast su
79// literal/temporanei): il vector di puntatori punta a strings stabili dentro
80// 'store'. Importante: chiamare to_argv() solo DOPO aver finito di add().
81struct BwrapArgs {
82 std::vector<std::string> store;
83 void add(const std::string& s) { store.push_back(s); }
84
85 std::vector<char*> to_argv() {
86 std::vector<char*> out;
87 out.reserve(store.size() + 1);
88 for (auto& s : store)
89 out.push_back(s.data());
90 out.push_back(nullptr);
91 return out;
92 }
93};
94
95// Un path è "pulito" abbastanza per essere passato a bwrap?
96static bool path_safe(const char* p) {
97 if (!p || !*p) return false;
98 if (p[0] != '/') return false;
99 if (std::strchr(p, '\n')) return false;
100 if (std::strchr(p, '\r')) return false;
101 return true;
102}
103
104static bool path_exists(const std::string& p) {
105 struct stat st{};
106 return ::stat(p.c_str(), &st) == 0;
107}
108
109} // namespace
110
111
112// =============================================================================
113// reexec_inside_bwrap_with_custom_dns
114//
115// Riesegue il processo corrente dentro un sandbox bubblewrap con:
116// - rootfs in read-only;
117// - /tmp tmpfs (privato, non vede nulla del /tmp dell'host);
118// - resolv.conf custom (bypass dnsmasq locale, force DoH-friendly DNS);
119// - bind del socket Wayland se sessione Wayland (passa via XDG_RUNTIME_DIR);
120// - bind del socket X11 (/tmp/.X11-unix), $XAUTHORITY e DISPLAY se X11;
121// - bind di ~/.cache/BastionGuard/cef_sandbox (cache CEF persistente);
122// - bind di ~/.config/BastionGuard (impostazioni: lettura).
123//
124// Funziona sia in sessione Wayland che X11, sia in sessione ibrida
125// (XWayland). Se bwrap non è disponibile la funzione ritorna senza
126// fare nulla (best effort).
127// =============================================================================
128void reexec_inside_bwrap_with_custom_dns(int argc, char** argv) {
129 if (std::getenv("BASTIONGUARD_IN_BWRAP")) return;
130 if ((bg_process::exists_on_path("bwrap") ? 0 : 1) != 0) return;
131
132 const std::string resolv = "/tmp/bastionguard-resolv.conf";
133 write_resolv_conf(resolv);
134
135 const char* home = std::getenv("HOME");
136 if (!home || !*home) {
137 std::cerr << _("[BastionGuard-secure] HOME non impostata.\n");
138 return;
139 }
140
141 const std::string host_cache = std::string(home) + "/.cache/BastionGuard/cef_sandbox";
142 const std::string host_config = std::string(home) + "/.config/BastionGuard";
143
144 // Assicurati che la cache esista (bwrap --bind richiede sorgente esistente).
145 {
146 ::mkdir((std::string(home) + "/.cache").c_str(), 0700);
147 ::mkdir((std::string(home) + "/.cache/BastionGuard").c_str(), 0700);
148 ::mkdir(host_cache.c_str(), 0700);
149 }
150
151 const char* xdg_rt = std::getenv("XDG_RUNTIME_DIR");
152 const char* wl_disp = std::getenv("WAYLAND_DISPLAY");
153 const char* x_disp = std::getenv("DISPLAY");
154 const char* xauth = std::getenv("XAUTHORITY");
155
156 const bool have_wayland = wl_disp && *wl_disp && xdg_rt && *xdg_rt;
157 const bool have_x11 = x_disp && *x_disp;
158
159 BwrapArgs a;
160 a.add("bwrap");
161 a.add("--new-session");
162 a.add("--die-with-parent");
163
164 // Hostname dedicato dentro la sandbox
165 a.add("--unshare-uts");
166 a.add("--hostname"); a.add("BastionGuard-secure");
167
168 // Root FS in RO
169 a.add("--ro-bind"); a.add("/"); a.add("/");
170
171 // /dev e /proc minimali
172 a.add("--dev"); a.add("/dev");
173 a.add("--proc"); a.add("/proc");
174
175 // /tmp privato + bind del file resolv.conf custom
176 a.add("--tmpfs"); a.add("/tmp");
177 a.add("--ro-bind"); a.add(resolv); a.add("/etc/resolv.conf");
178
179 // Cache CEF (rw)
180 if (path_safe(host_cache.c_str())) {
181 a.add("--bind"); a.add(host_cache); a.add(host_cache);
182 }
183
184 // Config BastionGuard (ro: legge whitelist, payments, ecc.)
185 if (path_safe(host_config.c_str()) && path_exists(host_config)) {
186 a.add("--ro-bind"); a.add(host_config); a.add(host_config);
187 }
188
189 // ---------------------------------------------------------------------
190 // Backend Wayland: serve XDG_RUNTIME_DIR (contiene il socket wayland-*)
191 // ---------------------------------------------------------------------
192 if (have_wayland) {
193 if (path_safe(xdg_rt))
194 a.add("--bind"), a.add(xdg_rt), a.add(xdg_rt);
195 a.add("--setenv"); a.add("WAYLAND_DISPLAY"); a.add(wl_disp);
196 std::cout << _("[BastionGuard-secure] bwrap: Wayland abilitato (")
197 << wl_disp << ")\n";
198 } else if (xdg_rt && *xdg_rt && path_safe(xdg_rt)) {
199 // Anche senza Wayland conviene avere XDG_RUNTIME_DIR (PulseAudio,
200 // PipeWire, dbus user) — ma non strettamente necessario.
201 a.add("--bind"); a.add(xdg_rt); a.add(xdg_rt);
202 }
203
204 // ---------------------------------------------------------------------
205 // Backend X11: /tmp/.X11-unix, $DISPLAY, $XAUTHORITY (cookie MIT-MAGIC)
206 // /tmp è già una tmpfs nuova qui dentro, quindi va re-bindata.
207 // ---------------------------------------------------------------------
208 if (have_x11) {
209 if (path_exists("/tmp/.X11-unix")) {
210 a.add("--ro-bind"); a.add("/tmp/.X11-unix"); a.add("/tmp/.X11-unix");
211 }
212
213 std::string xauth_path;
214 if (xauth && *xauth && path_safe(xauth) && path_exists(xauth)) {
215 xauth_path = xauth;
216 } else {
217 const std::string default_xauth = std::string(home) + "/.Xauthority";
218 if (path_exists(default_xauth)) xauth_path = default_xauth;
219 }
220 if (!xauth_path.empty()) {
221 a.add("--ro-bind"); a.add(xauth_path); a.add(xauth_path);
222 a.add("--setenv"); a.add("XAUTHORITY"); a.add(xauth_path);
223 }
224
225 a.add("--setenv"); a.add("DISPLAY"); a.add(x_disp);
226 std::cout << _("[BastionGuard-secure] bwrap: X11 abilitato (DISPLAY=")
227 << x_disp << ")\n";
228 }
229
230 if (!have_wayland && !have_x11) {
231 std::cerr << _("[BastionGuard-secure] ⚠ Nessun display Wayland/X11 rilevato; "
232 "il browser non riuscirà ad aprirsi.\n");
233 }
234
235 // Evita re-entry
236 a.add("--setenv"); a.add("BASTIONGUARD_IN_BWRAP"); a.add("1");
237
238 // Pass-through di un eventuale forced backend (utile per debug)
239 if (const char* fb = std::getenv("BastionGuard_CEF_BACKEND")) {
240 a.add("--setenv"); a.add("BastionGuard_CEF_BACKEND"); a.add(fb);
241 }
242
243 // Programma originale + suoi argv
244 if (argc < 1 || !argv[0]) {
245 std::cerr << _("[BastionGuard-secure] argv[0] mancante, abort bwrap.\n");
246 return;
247 }
248 a.add(argv[0]);
249 for (int i = 1; i < argc; ++i) {
250 if (argv[i]) a.add(argv[i]);
251 }
252
253 auto cargv = a.to_argv();
254 execvp("bwrap", cargv.data());
255 std::perror("execvp(bwrap)");
256}
#define _(s)
static void write_resolv_conf(const std::string &path)
void reexec_inside_bwrap_with_custom_dns(int argc, char **argv)
static bool path_safe(const char *p)
static bool path_exists(const std::string &p)
bool exists_on_path(const std::string &name)