Skip to content
API Documentation
BastionGuard™
anonymous_namespace{bastionguard-firewall.cpp} Namespace Reference

Classes

struct  SecureInput

Functions

std::optional< uid_t > parse_uid (const char *value)
uid_t original_caller_uid ()
bool allowed_input_path (const std::string &path)
std::optional< SecureInput > open_input_securely (const std::string &path, uid_t caller_uid)
std::string trim (std::string value)
bool is_valid_ip (const std::string &value)
std::vector< std::string > load_and_validate_ips (int fd)
std::optional< std::string > find_program (std::initializer_list< const char * > candidates)
bool run_program (const std::string &executable, const std::vector< std::string > &arguments, bool silence_stderr=false)
bool apply_ufw (const std::vector< std::string > &ips)
bool apply_firewalld (const std::vector< std::string > &ips)
void clear_firewall ()

Variables

constexpr std::string_view kInputPrefix = "/tmp/bastionguard-firewall-ips-"
constexpr off_t kMaxInputBytes = 16 * 1024 * 1024
constexpr std::size_t kMaxEntries = 250000
constexpr std::size_t kMaxLineLength = 128

Function Documentation

◆ allowed_input_path()

bool anonymous_namespace{bastionguard-firewall.cpp}::allowed_input_path ( const std::string & path)

Definition at line 98 of file bastionguard-firewall.cpp.

98 {
99 if (!path.starts_with(kInputPrefix)) return false;
100
101 const std::string_view suffix(path.data() + kInputPrefix.size(),
102 path.size() - kInputPrefix.size());
103 return !suffix.empty() && suffix.find('/') == std::string_view::npos;
104}

References allowed_input_path(), and kInputPrefix.

Referenced by allowed_input_path(), and open_input_securely().

◆ apply_firewalld()

bool anonymous_namespace{bastionguard-firewall.cpp}::apply_firewalld ( const std::vector< std::string > & ips)

Definition at line 286 of file bastionguard-firewall.cpp.

286 {
287 const auto firewall_cmd =
288 find_program({"/usr/bin/firewall-cmd", "/usr/sbin/firewall-cmd"});
289 if (!firewall_cmd) return false;
290
291 // These operations are idempotent from BastionGuard's perspective. An
292 // already existing ipset/source association is not fatal.
293 run_program(*firewall_cmd,
294 {"--permanent", "--new-ipset=bastionguard-blacklist",
295 "--type=hash:ip"},
296 true);
297 run_program(*firewall_cmd,
298 {"--permanent", "--zone=public",
299 "--add-source-ipset=bastionguard-blacklist"},
300 true);
301
302 bool success = true;
303 for (const auto& ip : ips) {
304 if (!run_program(*firewall_cmd,
305 {"--permanent",
306 "--ipset=bastionguard-blacklist",
307 "--add-entry=" + ip},
308 true)) {
309 // Duplicate entries may return a non-zero status. Continue and let
310 // reload determine whether firewalld is operational.
311 success = false;
312 }
313 }
314
315 const bool reloaded = run_program(*firewall_cmd, {"--reload"});
316 return reloaded && (success || !ips.empty());
317}
std::optional< std::string > find_program(std::initializer_list< const char * > candidates)
bool run_program(const std::string &executable, const std::vector< std::string > &arguments, bool silence_stderr=false)

References apply_firewalld(), find_program(), and run_program().

Referenced by apply_firewalld(), and main().

◆ apply_ufw()

bool anonymous_namespace{bastionguard-firewall.cpp}::apply_ufw ( const std::vector< std::string > & ips)

Definition at line 273 of file bastionguard-firewall.cpp.

273 {
274 const auto ufw = find_program({"/usr/sbin/ufw", "/usr/bin/ufw"});
275 if (!ufw) return false;
276
277 if (!run_program(*ufw, {"--force", "enable"})) return false;
278
279 bool success = true;
280 for (const auto& ip : ips) {
281 if (!run_program(*ufw, {"deny", "from", ip})) success = false;
282 }
283 return success;
284}

References apply_ufw(), find_program(), and run_program().

Referenced by apply_ufw(), and main().

◆ clear_firewall()

void anonymous_namespace{bastionguard-firewall.cpp}::clear_firewall ( )

Definition at line 319 of file bastionguard-firewall.cpp.

319 {
320 if (const auto ufw = find_program({"/usr/sbin/ufw", "/usr/bin/ufw"})) {
321 run_program(*ufw, {"--force", "reset"});
322 }
323
324 if (const auto firewall_cmd =
325 find_program({"/usr/bin/firewall-cmd", "/usr/sbin/firewall-cmd"})) {
326 run_program(*firewall_cmd,
327 {"--permanent", "--delete-ipset=bastionguard-blacklist"},
328 true);
329 run_program(*firewall_cmd, {"--reload"});
330 }
331}

References clear_firewall(), find_program(), and run_program().

Referenced by clear_firewall(), and main().

◆ find_program()

std::optional< std::string > anonymous_namespace{bastionguard-firewall.cpp}::find_program ( std::initializer_list< const char * > candidates)

Definition at line 227 of file bastionguard-firewall.cpp.

228 {
229 for (const char* candidate : candidates) {
230 if (::access(candidate, X_OK) == 0) return std::string(candidate);
231 }
232 return std::nullopt;
233}

References find_program().

Referenced by apply_firewalld(), apply_ufw(), clear_firewall(), and find_program().

◆ is_valid_ip()

bool anonymous_namespace{bastionguard-firewall.cpp}::is_valid_ip ( const std::string & value)

Definition at line 162 of file bastionguard-firewall.cpp.

162 {
163 if (value.empty() || value.size() > kMaxLineLength) return false;
164
165 in_addr ipv4 {};
166 if (::inet_pton(AF_INET, value.c_str(), &ipv4) == 1) return true;
167
168 in6_addr ipv6 {};
169 return ::inet_pton(AF_INET6, value.c_str(), &ipv6) == 1;
170}
#define AF_INET
Definition bsc_ebpf.c:56
#define AF_INET6
Definition bsc_ebpf.c:57

References AF_INET, AF_INET6, is_valid_ip(), and kMaxLineLength.

Referenced by is_valid_ip(), and load_and_validate_ips().

◆ load_and_validate_ips()

std::vector< std::string > anonymous_namespace{bastionguard-firewall.cpp}::load_and_validate_ips ( int fd)

Definition at line 172 of file bastionguard-firewall.cpp.

172 {
173 std::string data;
174 data.reserve(64 * 1024);
175
176 char buffer[8192];
177 while (true) {
178 const ssize_t count = ::read(fd, buffer, sizeof(buffer));
179 if (count == 0) break;
180 if (count < 0) {
181 if (errno == EINTR) continue;
182 std::cerr << _("[Firewall] Errore lettura file input: ")
183 << std::strerror(errno) << "\n";
184 return {};
185 }
186
187 if (data.size() + static_cast<std::size_t>(count) >
188 static_cast<std::size_t>(kMaxInputBytes)) {
189 std::cerr << _("[Firewall] File input troppo grande.\n");
190 return {};
191 }
192 data.append(buffer, static_cast<std::size_t>(count));
193 }
194
195 std::vector<std::string> ips;
196 std::unordered_set<std::string> seen;
197 std::size_t start = 0;
198
199 while (start <= data.size()) {
200 const auto end = data.find('\n', start);
201 std::string line = trim(data.substr(
202 start,
203 end == std::string::npos ? std::string::npos : end - start));
204
205 if (!line.empty() && line.front() != '#') {
206 if (!is_valid_ip(line)) {
207 std::cerr << _("[Firewall] Voce IP rifiutata: ") << line << "\n";
208 return {};
209 }
210
211 if (seen.insert(line).second) {
212 ips.push_back(std::move(line));
213 if (ips.size() > kMaxEntries) {
214 std::cerr << _("[Firewall] Troppe voci nel file input.\n");
215 return {};
216 }
217 }
218 }
219
220 if (end == std::string::npos) break;
221 start = end + 1;
222 }
223
224 return ips;
225}
#define _(s)

References _, is_valid_ip(), kMaxEntries, kMaxInputBytes, load_and_validate_ips(), and trim().

Referenced by load_and_validate_ips(), and main().

◆ open_input_securely()

std::optional< SecureInput > anonymous_namespace{bastionguard-firewall.cpp}::open_input_securely ( const std::string & path,
uid_t caller_uid )

Definition at line 106 of file bastionguard-firewall.cpp.

107 {
108 if (!allowed_input_path(path)) {
109 std::cerr << _("[Firewall] Percorso input non autorizzato.\n");
110 return std::nullopt;
111 }
112
113 const int fd = ::open(path.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
114 if (fd < 0) {
115 std::cerr << _("[Firewall] Impossibile aprire il file input: ")
116 << std::strerror(errno) << "\n";
117 return std::nullopt;
118 }
119
120 struct stat st {};
121 if (::fstat(fd, &st) != 0) {
122 std::cerr << _("[Firewall] fstat fallita: ")
123 << std::strerror(errno) << "\n";
124 ::close(fd);
125 return std::nullopt;
126 }
127
128 const bool ownership_ok =
129 (caller_uid == 0 && st.st_uid == 0) ||
130 (caller_uid != 0 && st.st_uid == caller_uid);
131
132 if (!S_ISREG(st.st_mode) ||
133 st.st_nlink != 1 ||
134 !ownership_ok ||
135 (st.st_mode & (S_IWGRP | S_IWOTH)) != 0 ||
136 st.st_size <= 0 ||
137 st.st_size > kMaxInputBytes) {
138 std::cerr << _("[Firewall] File input non sicuro o non valido.\n");
139 ::close(fd);
140 return std::nullopt;
141 }
142
143 // The descriptor remains valid after unlink and cannot be replaced through
144 // the pathname while the privileged process is reading it.
145 if (::unlink(path.c_str()) != 0) {
146 std::cerr << _("[Firewall] Impossibile rimuovere il file temporaneo: ")
147 << std::strerror(errno) << "\n";
148 ::close(fd);
149 return std::nullopt;
150 }
151
152 return SecureInput(fd, path);
153}
#define S_IWOTH
#define S_IWGRP

References _, allowed_input_path(), kMaxInputBytes, open_input_securely(), S_IWGRP, S_IWOTH, and anonymous_namespace{bastionguard-firewall.cpp}::SecureInput::SecureInput().

Referenced by main(), and open_input_securely().

◆ original_caller_uid()

uid_t anonymous_namespace{bastionguard-firewall.cpp}::original_caller_uid ( )

Definition at line 91 of file bastionguard-firewall.cpp.

91 {
92 if (const auto uid = parse_uid(std::getenv("PKEXEC_UID"))) {
93 return *uid;
94 }
95 return ::getuid();
96}
std::optional< uid_t > parse_uid(const char *value)

References original_caller_uid(), and parse_uid().

Referenced by main(), and original_caller_uid().

◆ parse_uid()

std::optional< uid_t > anonymous_namespace{bastionguard-firewall.cpp}::parse_uid ( const char * value)

Definition at line 78 of file bastionguard-firewall.cpp.

78 {
79 if (!value || !*value) return std::nullopt;
80
81 unsigned long long parsed = 0;
82 for (const unsigned char ch : std::string(value)) {
83 if (!std::isdigit(ch)) return std::nullopt;
84 parsed = parsed * 10 + static_cast<unsigned long long>(ch - '0');
85 if (parsed > std::numeric_limits<uid_t>::max()) return std::nullopt;
86 }
87
88 return static_cast<uid_t>(parsed);
89}

References parse_uid().

Referenced by original_caller_uid(), and parse_uid().

◆ run_program()

bool anonymous_namespace{bastionguard-firewall.cpp}::run_program ( const std::string & executable,
const std::vector< std::string > & arguments,
bool silence_stderr = false )

Definition at line 235 of file bastionguard-firewall.cpp.

237 {
238 std::vector<std::string> storage;
239 storage.reserve(arguments.size() + 1);
240 storage.push_back(executable);
241 storage.insert(storage.end(), arguments.begin(), arguments.end());
242
243 std::vector<char*> argv;
244 argv.reserve(storage.size() + 1);
245 for (auto& value : storage) argv.push_back(value.data());
246 argv.push_back(nullptr);
247
248 const pid_t pid = ::fork();
249 if (pid < 0) return false;
250
251 if (pid == 0) {
252 if (silence_stderr) {
253 const int null_fd = ::open("/dev/null", O_WRONLY | O_CLOEXEC);
254 if (null_fd >= 0) {
255 ::dup2(null_fd, STDERR_FILENO);
256 if (null_fd != STDERR_FILENO) ::close(null_fd);
257 }
258 }
259
260 ::execv(executable.c_str(), argv.data());
261 _exit(127);
262 }
263
264 int status = 0;
265 while (::waitpid(pid, &status, 0) < 0) {
266 if (errno == EINTR) continue;
267 return false;
268 }
269
270 return WIFEXITED(status) && WEXITSTATUS(status) == 0;
271}

References run_program().

Referenced by apply_firewalld(), apply_ufw(), clear_firewall(), and run_program().

◆ trim()

std::string anonymous_namespace{bastionguard-firewall.cpp}::trim ( std::string value)

Definition at line 155 of file bastionguard-firewall.cpp.

155 {
156 const auto first = value.find_first_not_of(" \t\r\n");
157 if (first == std::string::npos) return {};
158 const auto last = value.find_last_not_of(" \t\r\n");
159 return value.substr(first, last - first + 1);
160}

References trim().

Referenced by load_and_validate_ips(), and trim().

Variable Documentation

◆ kInputPrefix

std::string_view anonymous_namespace{bastionguard-firewall.cpp}::kInputPrefix = "/tmp/bastionguard-firewall-ips-"
constexpr

Definition at line 42 of file bastionguard-firewall.cpp.

Referenced by allowed_input_path().

◆ kMaxEntries

std::size_t anonymous_namespace{bastionguard-firewall.cpp}::kMaxEntries = 250000
constexpr

Definition at line 44 of file bastionguard-firewall.cpp.

Referenced by load_and_validate_ips().

◆ kMaxInputBytes

off_t anonymous_namespace{bastionguard-firewall.cpp}::kMaxInputBytes = 16 * 1024 * 1024
constexpr

Definition at line 43 of file bastionguard-firewall.cpp.

Referenced by load_and_validate_ips(), and open_input_securely().

◆ kMaxLineLength

std::size_t anonymous_namespace{bastionguard-firewall.cpp}::kMaxLineLength = 128
constexpr

Definition at line 45 of file bastionguard-firewall.cpp.

Referenced by is_valid_ip().