diff options
Diffstat (limited to 'include/wireframe/privileges.hpp')
| -rw-r--r-- | include/wireframe/privileges.hpp | 87 |
1 files changed, 0 insertions, 87 deletions
diff --git a/include/wireframe/privileges.hpp b/include/wireframe/privileges.hpp deleted file mode 100644 index 69df725..0000000 --- a/include/wireframe/privileges.hpp +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#ifndef _WIN32 -#include <grp.h> -#include <unistd.h> -#endif - -#include <cerrno> -#include <cstdlib> -#include <cstring> -#include <optional> -#include <string> - -// After pcap_open_live() succeeds, the process has gotten everything -// CAP_NET_RAW exists for - running the rest of the program (decoding -// untrusted packet bytes, an interactive TUI/GUI event loop) as root -// from that point on is unnecessary exposure, and PLAN.md says as much -// directly: "Drop privileges immediately after opening the capture -// handle; use CAP_NET_RAW via file capabilities instead of running as -// root." -// -// The recommended path doesn't need this file at all: run -// `sudo setcap cap_net_raw+ep <binary>` once, then invoke the binary -// directly, unprivileged, forever after - CAP_NET_RAW alone is enough -// for pcap_open_live(), no root required at any point. This exists for -// the case someone still runs the binary via sudo (out of habit, or -// because setcap isn't available/permitted in some environments): drop -// straight back to the invoking user immediately, so the rest of the -// process's lifetime - including any -w output file, which then ends -// up owned by that user instead of root - runs unprivileged either way. -namespace wireframe { - -// Drops from root to the user who actually invoked the program, via -// sudo's SUDO_UID/SUDO_GID (which sudo always sets). A no-op if not -// currently root, or if SUDO_UID isn't set (e.g. a genuine root login, -// not sudo - there's no "real" user to drop to in that case). -// -// setuid() to a nonzero UID also clears the process's Linux capability -// sets as a kernel-level side effect, so this covers both "running as -// root via sudo" and "root's own CAP_NET_RAW" the same way, without a -// separate libcap dependency. -// -// Returns an error message on failure. The drop is safety-critical: a -// failure here should be treated as fatal by the caller, not silently -// ignored while the process keeps running as root. -inline std::optional<std::string> drop_privileges_if_root() { -#ifdef _WIN32 - return std::nullopt; // no POSIX privilege model to drop from -#else - if (geteuid() != 0) return std::nullopt; // already unprivileged - - const char* sudo_uid = std::getenv("SUDO_UID"); - const char* sudo_gid = std::getenv("SUDO_GID"); - if (sudo_uid == nullptr || sudo_gid == nullptr) { - return std::nullopt; // no safe target to drop to - } - - uid_t target_uid = static_cast<uid_t>(std::strtoul(sudo_uid, nullptr, 10)); - gid_t target_gid = static_cast<gid_t>(std::strtoul(sudo_gid, nullptr, 10)); - - // Order matters: groups and GID need root to change, so they must - // be dropped before UID - once UID is gone, so is the privilege - // to change the others. - if (setgroups(1, &target_gid) == -1) { - return "setgroups failed: " + std::string(std::strerror(errno)); - } - if (setgid(target_gid) == -1) { - return "setgid failed: " + std::string(std::strerror(errno)); - } - if (setuid(target_uid) == -1) { - return "setuid failed: " + std::string(std::strerror(errno)); - } - - // Defense in depth (standard advice from setuid-privilege-drop - // write-ups): confirm root can't be reclaimed. If the saved-UID - // was somehow left at 0, this would succeed and silently undo the - // drop - so a *successful* setuid(0) here means something is - // wrong, and is treated as the failure case. - if (setuid(0) != -1) { - return "failed to permanently drop root (setuid(0) unexpectedly succeeded)"; - } - - return std::nullopt; -#endif -} - -} // namespace wireframe |