#pragma once #ifndef _WIN32 #include #include #endif #include #include #include #include #include // 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 ` 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 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(std::strtoul(sudo_uid, nullptr, 10)); gid_t target_gid = static_cast(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