Make compile-time feature for SUID/SGID mode
All checks were successful
Build / check (push) Successful in 1m27s
Build / build (push) Successful in 2m46s
Build / docs (push) Successful in 6m33s

This commit is contained in:
2025-11-11 00:27:27 +09:00
parent 0feb959199
commit 30d02775c2
3 changed files with 37 additions and 20 deletions

View File

@@ -51,6 +51,7 @@ uuid = { version = "1.18.1", features = ["v4"] }
default = ["mysql-admutils-compatibility"] default = ["mysql-admutils-compatibility"]
tui = ["dep:ratatui"] tui = ["dep:ratatui"]
mysql-admutils-compatibility = [] mysql-admutils-compatibility = []
suid-sgid-mode = []
[[bin]] [[bin]]
name = "mysqladm" name = "mysqladm"

View File

@@ -19,12 +19,14 @@ use crate::{
/// If neither is feasible, an error is returned. /// If neither is feasible, an error is returned.
fn will_connect_to_external_server( fn will_connect_to_external_server(
server_socket_path: Option<&PathBuf>, server_socket_path: Option<&PathBuf>,
config_path: Option<&PathBuf>, // This parameter is only used in suid-sgid-mode
#[allow(unused_variables)] config_path: Option<&PathBuf>,
) -> anyhow::Result<bool> { ) -> anyhow::Result<bool> {
if server_socket_path.is_some() { if server_socket_path.is_some() {
return Ok(true); return Ok(true);
} }
#[cfg(feature = "suid-sgid-mode")]
if config_path.is_some() { if config_path.is_some() {
return Ok(false); return Ok(false);
} }
@@ -33,11 +35,16 @@ fn will_connect_to_external_server(
return Ok(true); return Ok(true);
} }
#[cfg(feature = "suid-sgid-mode")]
if fs::metadata(DEFAULT_CONFIG_PATH).is_ok() { if fs::metadata(DEFAULT_CONFIG_PATH).is_ok() {
return Ok(false); return Ok(false);
} }
#[cfg(feature = "suid-sgid-mode")]
anyhow::bail!("No socket path or config path provided, and no default socket or config found"); anyhow::bail!("No socket path or config path provided, and no default socket or config found");
#[cfg(not(feature = "suid-sgid-mode"))]
anyhow::bail!("No socket path provided, and no default socket found");
} }
/// This function is used to bootstrap the connection to the server. /// This function is used to bootstrap the connection to the server.
@@ -77,7 +84,7 @@ pub fn bootstrap_server_connection_and_drop_privileges(
.init(); .init();
connect_to_external_server(server_socket_path) connect_to_external_server(server_socket_path)
} else { } else if cfg!(feature = "suid-sgid-mode") {
// NOTE: We need to be really careful with the code up until this point, // NOTE: We need to be really careful with the code up until this point,
// as we might be running with elevated privileges. // as we might be running with elevated privileges.
let server_connection = bootstrap_internal_server_and_drop_privs(config)?; let server_connection = bootstrap_internal_server_and_drop_privs(config)?;
@@ -87,6 +94,8 @@ pub fn bootstrap_server_connection_and_drop_privileges(
.init(); .init();
Ok(server_connection) Ok(server_connection)
} else {
anyhow::bail!("SUID/SGID support is not enabled, cannot start internal server");
} }
} }

View File

@@ -1,6 +1,5 @@
use anyhow::Context; use anyhow::Context;
use nix::unistd::{Group as LibcGroup, User as LibcUser}; use nix::unistd::{Group as LibcGroup, User as LibcUser};
use std::{fs, os::unix::fs::PermissionsExt};
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
use std::ffi::CString; use std::ffi::CString;
@@ -21,23 +20,6 @@ fn get_unix_groups(_user: &LibcUser) -> anyhow::Result<Vec<LibcGroup>> {
Ok(vec![]) Ok(vec![])
} }
/// Check if the current executable is SUID or SGID.
///
/// If the check fails, an error is returned.
pub fn executable_is_suid_or_sgid() -> anyhow::Result<bool> {
let result = std::env::current_exe()
.context("Failed to get current executable path")
.and_then(|executable| {
fs::metadata(executable).context("Failed to get executable metadata")
})
.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(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
fn get_unix_groups(user: &LibcUser) -> anyhow::Result<Vec<LibcGroup>> { fn get_unix_groups(user: &LibcUser) -> anyhow::Result<Vec<LibcGroup>> {
let user_cstr = let user_cstr =
@@ -61,6 +43,31 @@ fn get_unix_groups(user: &LibcUser) -> anyhow::Result<Vec<LibcGroup>> {
Ok(groups) Ok(groups)
} }
/// Check if the current executable is SUID or SGID.
///
/// If the check fails, an error is returned.
#[cfg(feature = "suid-sgid-mode")]
pub fn executable_is_suid_or_sgid() -> anyhow::Result<bool> {
use std::{fs, os::unix::fs::PermissionsExt};
let result = std::env::current_exe()
.context("Failed to get current executable path")
.and_then(|executable| {
fs::metadata(executable).context("Failed to get executable metadata")
})
.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"))]
#[inline]
pub fn executable_is_suid_or_sgid() -> anyhow::Result<bool> {
Ok(false)
}
impl UnixUser { impl UnixUser {
pub fn from_uid(uid: u32) -> anyhow::Result<Self> { pub fn from_uid(uid: u32) -> anyhow::Result<Self> {
let libc_uid = nix::unistd::Uid::from_raw(uid); let libc_uid = nix::unistd::Uid::from_raw(uid);