core: check suid/sgid dynamically instead of checking file
All checks were successful
Build and test / check (push) Successful in 1m41s
Build and test / test (push) Successful in 3m5s
Build and test / check-license (push) Successful in 5m56s
Build and test / docs (push) Successful in 5m29s
Build and test / build (push) Successful in 3m9s

This commit is contained in:
2025-12-04 20:29:44 +09:00
parent eeef8bd546
commit 222941509d
4 changed files with 17 additions and 25 deletions

View File

@@ -90,7 +90,7 @@ nixpkgs.lib.nixosSystem {
defaultEditor = true; defaultEditor = true;
}; };
environment.systemPackages = with pkgs; [ jq ]; environment.systemPackages = with pkgs; [ jq pkgs.muscl ];
}) })
]; ];
} }

View File

@@ -10,7 +10,7 @@ use tracing_subscriber::prelude::*;
use crate::{ use crate::{
core::common::{ core::common::{
DEFAULT_CONFIG_PATH, DEFAULT_SOCKET_PATH, UnixUser, executable_is_suid_or_sgid, DEFAULT_CONFIG_PATH, DEFAULT_SOCKET_PATH, UnixUser, executing_in_suid_sgid_mode,
}, },
server::{ server::{
config::{MysqlConfig, ServerConfig}, config::{MysqlConfig, ServerConfig},
@@ -81,7 +81,7 @@ pub fn bootstrap_server_connection_and_drop_privileges(
) -> anyhow::Result<StdUnixStream> { ) -> anyhow::Result<StdUnixStream> {
if will_connect_to_external_server(server_socket_path.as_ref(), config.as_ref())? { if will_connect_to_external_server(server_socket_path.as_ref(), config.as_ref())? {
assert!( assert!(
!executable_is_suid_or_sgid()?, !executing_in_suid_sgid_mode()?,
"The executable should not be SUID or SGID when connecting to an external server" "The executable should not be SUID or SGID when connecting to an external server"
); );
@@ -178,7 +178,7 @@ fn bootstrap_internal_server_and_drop_privs(
config_path: Option<PathBuf>, config_path: Option<PathBuf>,
) -> anyhow::Result<StdUnixStream> { ) -> anyhow::Result<StdUnixStream> {
if let Some(config_path) = config_path { if let Some(config_path) = config_path {
if !executable_is_suid_or_sgid()? { if !executing_in_suid_sgid_mode()? {
anyhow::bail!("Executable is not SUID/SGID - refusing to start internal sever"); anyhow::bail!("Executable is not SUID/SGID - refusing to start internal sever");
} }
@@ -195,7 +195,7 @@ fn bootstrap_internal_server_and_drop_privs(
let config_path = PathBuf::from(DEFAULT_CONFIG_PATH); let config_path = PathBuf::from(DEFAULT_CONFIG_PATH);
if fs::metadata(&config_path).is_ok() { if fs::metadata(&config_path).is_ok() {
if !executable_is_suid_or_sgid()? { if !executing_in_suid_sgid_mode()? {
anyhow::bail!("Executable is not SUID/SGID - refusing to start internal sever"); anyhow::bail!("Executable is not SUID/SGID - refusing to start internal sever");
} }
tracing::debug!("Starting server with default config at {:?}", config_path); tracing::debug!("Starting server with default config at {:?}", config_path);

View File

@@ -67,28 +67,20 @@ fn get_unix_groups(user: &LibcUser) -> anyhow::Result<Vec<LibcGroup>> {
Ok(groups) Ok(groups)
} }
/// Check if the current executable is SUID or SGID. /// Check if the current executable is running in SUID/SGID mode
///
/// If the check fails, an error is returned.
#[cfg(feature = "suid-sgid-mode")] #[cfg(feature = "suid-sgid-mode")]
pub fn executable_is_suid_or_sgid() -> anyhow::Result<bool> { pub fn executing_in_suid_sgid_mode() -> anyhow::Result<bool> {
use std::{fs, os::unix::fs::PermissionsExt}; let euid = nix::unistd::geteuid();
let result = std::env::current_exe() let uid = nix::unistd::getuid();
.context("Failed to get current executable path") let egid = nix::unistd::getegid();
.and_then(|executable| { let gid = nix::unistd::getgid();
fs::metadata(executable).context("Failed to get executable metadata")
}) Ok(euid != uid || egid != gid)
.context("Failed to check SUID/SGID bits on executable")
.map(|metadata| {
let mode = metadata.permissions().mode();
mode & 0o4000 != 0 || mode & 0o2000 != 0
})?;
Ok(result)
} }
#[cfg(not(feature = "suid-sgid-mode"))] #[cfg(not(feature = "suid-sgid-mode"))]
#[inline] #[inline]
pub fn executable_is_suid_or_sgid() -> anyhow::Result<bool> { pub fn executing_in_suid_sgid_mode() -> anyhow::Result<bool> {
Ok(false) Ok(false)
} }

View File

@@ -16,7 +16,7 @@ use futures_util::StreamExt;
use crate::{ use crate::{
core::{ core::{
bootstrap::bootstrap_server_connection_and_drop_privileges, bootstrap::bootstrap_server_connection_and_drop_privileges,
common::{ASCII_BANNER, KIND_REGARDS, executable_is_suid_or_sgid}, common::{ASCII_BANNER, KIND_REGARDS, executing_in_suid_sgid_mode},
protocol::{Response, create_client_to_server_message_stream}, protocol::{Response, create_client_to_server_message_stream},
}, },
server::{command::ServerArgs, landlock::landlock_restrict_server}, server::{command::ServerArgs, landlock::landlock_restrict_server},
@@ -151,7 +151,7 @@ fn main() -> anyhow::Result<()> {
fn handle_dynamic_completion() -> anyhow::Result<Option<()>> { fn handle_dynamic_completion() -> anyhow::Result<Option<()>> {
if std::env::var_os("COMPLETE").is_some() { if std::env::var_os("COMPLETE").is_some() {
#[cfg(feature = "suid-sgid-mode")] #[cfg(feature = "suid-sgid-mode")]
if executable_is_suid_or_sgid()? { if executing_in_suid_sgid_mode()? {
use crate::core::bootstrap::drop_privs; use crate::core::bootstrap::drop_privs;
drop_privs()? drop_privs()?
} }
@@ -202,7 +202,7 @@ fn handle_server_command(args: &Args) -> anyhow::Result<Option<()>> {
match args.command { match args.command {
Command::Server(ref command) => { Command::Server(ref command) => {
assert!( assert!(
!executable_is_suid_or_sgid()?, !executing_in_suid_sgid_mode()?,
"The executable should not be SUID or SGID when running the server manually" "The executable should not be SUID or SGID when running the server manually"
); );