fix(linux): security: drop CAP_SYS_ADMIN when possible, retain CAP_SYS_NICE (#5075)

This commit is contained in:
Conn O'Griofa
2026-05-06 01:05:21 +01:00
committed by GitHub
parent dd30d0555f
commit ecba5c3c2e
4 changed files with 37 additions and 28 deletions
+6 -4
View File
@@ -895,13 +895,15 @@ namespace platf {
/**
* @brief Check is the current process is running with elevated privileges (e.g. system admin/etc.)
* @return True if system admin capabilities are present.
* @param all_caps Bool that specifies whether to check all caps or only CAP_SYS_ADMIN
* @return True if capabilities specified to be checked are present.
*/
bool has_elevated_privileges();
bool has_elevated_privileges(bool all_caps);
/**
* @brief Drop elevated privileges (e.g. system admin/etc.)
* @brief Drop elevated privileges (e.g. system admin/nice etc.)
* @param all_caps Bool that specifies whether to drop all caps or only CAP_SYS_ADMIN
*/
void drop_elevated_privileges();
void drop_elevated_privileges(bool all_caps);
} // namespace platf
+1 -6
View File
@@ -674,11 +674,6 @@ namespace platf {
return nullptr;
}
// Drop CAP_SYS_ADMIN so KWin's permission check (if active) can see and match the executable
if (!kwin::screencast_permission_helper_t::is_permission_system_deactivated() && has_elevated_privileges()) {
drop_elevated_privileges();
}
auto display = std::make_shared<kwin::kwin_t>();
if (display->init(hwdevice_type, display_name, config)) {
return nullptr;
@@ -688,7 +683,7 @@ namespace platf {
}
std::vector<std::string> kwin_display_names() {
if (!kwin::screencast_permission_helper_t::is_permission_system_deactivated() && has_elevated_privileges()) {
if (has_elevated_privileges(false)) {
// We're still in the probing phase of Sunshine startup. Dropping portal security early will break KMS.
// Just return a dummy screen for now. Display re-enumeration after encoder probing will yield full result.
std::vector<std::string> display_names;
+26 -14
View File
@@ -1076,6 +1076,19 @@ namespace platf {
}
std::shared_ptr<display_t> display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) {
// Keep KMS as first element to check before dropping CAP_SYS_ADMIN
#ifdef SUNSHINE_BUILD_DRM
if (sources[source::KMS]) {
BOOST_LOG(info) << "Screencasting with KMS"sv;
return kms_display(hwdevice_type, display_name, config);
}
#endif
// KMS capture was passed; drop CAP_SYS_ADMIN only.
if (has_elevated_privileges(false)) {
drop_elevated_privileges(false);
}
#ifdef SUNSHINE_BUILD_CUDA
if (sources[source::NVFBC] && hwdevice_type == mem_type_e::cuda) {
BOOST_LOG(info) << "Screencasting with NvFBC"sv;
@@ -1088,12 +1101,6 @@ namespace platf {
return wl_display(hwdevice_type, display_name, config);
}
#endif
#ifdef SUNSHINE_BUILD_DRM
if (sources[source::KMS]) {
BOOST_LOG(info) << "Screencasting with KMS"sv;
return kms_display(hwdevice_type, display_name, config);
}
#endif
#ifdef SUNSHINE_BUILD_X11
if (sources[source::X11]) {
BOOST_LOG(info) << "Screencasting with X11"sv;
@@ -1261,18 +1268,22 @@ namespace platf {
}
#if !defined(__FreeBSD__)
constexpr std::array<cap_value_t, 2> ELEVATED_PRIVILEGES_EFFECTIVE {CAP_SYS_ADMIN, CAP_SYS_NICE};
constexpr std::array<cap_value_t, 2> ELEVATED_PRIVILEGES_PERMITTED {CAP_SYS_ADMIN, CAP_SYS_NICE};
static constexpr cap_value_t FULL_CAPS[] = {CAP_SYS_ADMIN, CAP_SYS_NICE};
static constexpr cap_value_t ADMIN_CAPS[] = {CAP_SYS_ADMIN};
constexpr std::span<const cap_value_t> ELEVATED_PRIVILEGES_FULL {FULL_CAPS};
constexpr std::span<const cap_value_t> ELEVATED_PRIVILEGES_ADMIN {ADMIN_CAPS};
#endif
bool has_elevated_privileges() {
bool has_elevated_privileges(bool all_caps) {
#if !defined(__FreeBSD__)
const auto caps_to_check = all_caps ? ELEVATED_PRIVILEGES_FULL : ELEVATED_PRIVILEGES_ADMIN;
const cap_t caps = cap_get_proc();
if (!caps) {
BOOST_LOG(error) << "[misc] has_elevated_privileges failed to get process capabilities."sv;
return false;
}
for (const auto c : ELEVATED_PRIVILEGES_EFFECTIVE) {
for (const auto c : caps_to_check) {
cap_flag_value_t cap_flags_value;
cap_get_flag(caps, c, CAP_EFFECTIVE, &cap_flags_value);
if (cap_flags_value == CAP_SET) {
@@ -1280,7 +1291,7 @@ namespace platf {
return true;
}
}
for (const auto c : ELEVATED_PRIVILEGES_PERMITTED) {
for (const auto c : caps_to_check) {
cap_flag_value_t cap_flags_value;
cap_get_flag(caps, c, CAP_PERMITTED, &cap_flags_value);
if (cap_flags_value == CAP_SET) {
@@ -1293,17 +1304,18 @@ namespace platf {
return false;
}
void drop_elevated_privileges() {
void drop_elevated_privileges(bool all_caps) {
#if !defined(__FreeBSD__)
bool failed = false;
const auto caps_to_drop = all_caps ? ELEVATED_PRIVILEGES_FULL : ELEVATED_PRIVILEGES_ADMIN;
const cap_t caps = cap_get_proc();
if (!caps) {
BOOST_LOG(error) << "[misc] drop_elevated_privileges failed to get process capabilities"sv;
return;
}
cap_set_flag(caps, CAP_EFFECTIVE, ELEVATED_PRIVILEGES_EFFECTIVE.size(), ELEVATED_PRIVILEGES_EFFECTIVE.data(), CAP_CLEAR);
cap_set_flag(caps, CAP_PERMITTED, ELEVATED_PRIVILEGES_PERMITTED.size(), ELEVATED_PRIVILEGES_PERMITTED.data(), CAP_CLEAR);
cap_set_flag(caps, CAP_EFFECTIVE, caps_to_drop.size(), caps_to_drop.data(), CAP_CLEAR);
cap_set_flag(caps, CAP_PERMITTED, caps_to_drop.size(), caps_to_drop.data(), CAP_CLEAR);
if (cap_set_proc(caps) != 0) {
BOOST_LOG(error) << "[misc] drop_elevated_privileges failed to prune capabilities: "sv << std::strerror(errno);
+4 -4
View File
@@ -705,9 +705,9 @@ namespace platf {
return nullptr;
}
// Drop CAP_SYS_ADMIN and set DUMPABLE flag to allow XDG /root access
if (has_elevated_privileges()) {
drop_elevated_privileges();
// Drop CAP_SYS_ADMIN, CAP_SYS_NICE and set DUMPABLE flag to allow XDG /root access
if (has_elevated_privileges(true)) {
drop_elevated_privileges(true);
}
auto portal = std::make_shared<portal::portal_t>();
@@ -727,7 +727,7 @@ namespace platf {
return {};
}
if (has_elevated_privileges()) {
if (has_elevated_privileges(true)) {
// We're still in the probing phase of Sunshine startup. Dropping portal security early will break KMS.
// Just return a dummy screen for now. Display re-enumeration after encoder probing will yield full result.
display_names.emplace_back("init");