1
0
Fork 0
forked from wry/wry

compositor: set scheduler to SCHED_RR before dropping CAP_SYS_NICE

This commit is contained in:
Julian Orth 2025-05-10 22:41:40 +02:00
parent af6e868a78
commit 7a623006e2
8 changed files with 202 additions and 12 deletions

View file

@ -234,7 +234,9 @@ impl ValueEnum for &'static Format {
pub fn main() {
let cli = Jay::parse();
drop_all_pr_caps();
if not_matches!(cli.command, Cmd::Run(_)) {
drop_all_pr_caps();
}
match cli.command {
Cmd::Run(a) => start_compositor(cli.global, a),
Cmd::GenerateCompletion(g) => generate::main(g),

View file

@ -39,6 +39,7 @@ use {
logger::Logger,
output_schedule::OutputSchedule,
portal::{self, PortalStartup},
pr_caps::pr_caps,
scale::Scale,
sighand::{self, SighandError},
state::{ConnectorData, IdleState, ScreenlockState, State, XWaylandState},
@ -51,9 +52,17 @@ use {
},
user_session::import_environment,
utils::{
clone3::ensure_reaper, clonecell::CloneCell, errorfmt::ErrorFmt, fdcloser::FdCloser,
numcell::NumCell, oserror::OsError, queue::AsyncQueue, refcounted::RefCounted,
run_toplevel::RunToplevel, tri::Try,
clone3::ensure_reaper,
clonecell::CloneCell,
errorfmt::ErrorFmt,
fdcloser::FdCloser,
nice::{did_elevate_scheduler, elevate_scheduler},
numcell::NumCell,
oserror::OsError,
queue::AsyncQueue,
refcounted::RefCounted,
run_toplevel::RunToplevel,
tri::Try,
},
version::VERSION,
video::drm::wait_for_sync_obj::WaitForSyncObj,
@ -72,6 +81,11 @@ pub const MAX_EXTENTS: i32 = (1 << 22) - 1;
pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
sighand::reset_all();
let reaper_pid = ensure_reaper();
let caps = pr_caps().into_comp();
if caps.has_nice() {
elevate_scheduler();
}
drop(caps);
let forker = create_forker(reaper_pid);
let portal = portal::run_from_compositor(global.log_level.into());
enable_profiler();
@ -146,6 +160,9 @@ fn start_compositor2(
) -> Result<(), CompositorError> {
log::info!("pid = {}", uapi::getpid());
log::info!("version = {VERSION}");
if did_elevate_scheduler() {
log::info!("Running with elevated scheduler: SCHED_RR");
}
init_fd_limit();
leaks::init();
clientmem::init()?;
@ -680,7 +697,7 @@ fn create_dummy_output(state: &Rc<State>) {
state.dummy_output.set(Some(dummy_output));
}
fn config_dir() -> Option<String> {
pub fn config_dir() -> Option<String> {
if let Ok(xdg) = env::var("XDG_CONFIG_HOME") {
Some(format!("{}/jay", xdg))
} else if let Ok(home) = env::var("HOME") {

View file

@ -10,8 +10,13 @@ use {
state::State,
tree::ToplevelData,
utils::{
clonecell::CloneCell, numcell::NumCell, ptr_ext::PtrExt,
toplevel_identifier::ToplevelIdentifier, unlink_on_drop::UnlinkOnDrop, xrd::xrd,
clonecell::CloneCell,
nice::{JAY_NO_REALTIME, dont_allow_config_so},
numcell::NumCell,
ptr_ext::PtrExt,
toplevel_identifier::ToplevelIdentifier,
unlink_on_drop::UnlinkOnDrop,
xrd::xrd,
},
},
bincode::Options,
@ -26,7 +31,7 @@ use {
window::{self, TileState},
},
libloading::Library,
std::{cell::Cell, io, mem, ptr, rc::Rc},
std::{cell::Cell, io, mem, path::Path, ptr, rc::Rc},
thiserror::Error,
};
@ -42,6 +47,8 @@ pub enum ConfigError {
CopyConfigFile(#[source] io::Error),
#[error("XDG_RUNTIME_DIR is not set")]
XrdNotSet,
#[error("Custom config.so is not permitted")]
NotPermitted,
}
pub struct ConfigProxy {
@ -280,11 +287,24 @@ impl ConfigProxy {
}
pub fn from_config_dir(state: &Rc<State>) -> Result<Self, ConfigError> {
if dont_allow_config_so() {
if have_config_so(state.config_dir.as_deref()) {
log::warn!("Not loading config.so because");
log::warn!(" 1. Jay was started with CAP_SYS_NICE");
log::warn!(" 2. Jay was not started with {}=1", JAY_NO_REALTIME);
log::warn!(" 3. The scheduler was elevated to SCHED_RR");
log::warn!(
" 4. Jay was not compiled with {}=1",
jay_allow_realtime_config_so!(),
);
}
return Err(ConfigError::NotPermitted);
}
let dir = match state.config_dir.as_deref() {
Some(d) => d,
_ => return Err(ConfigError::ConfigDirNotSet),
};
let file = format!("{}/config.so", dir);
let file = format!("{}/{CONFIG_SO}", dir);
unsafe { Self::from_file(&file, state) }
}
@ -355,3 +375,15 @@ pub struct InvokedShortcut {
pub effective_mods: Modifiers,
pub sym: KeySym,
}
const CONFIG_SO: &str = "config.so";
pub fn have_config_so(config_dir: Option<&str>) -> bool {
let Some(dir) = config_dir else {
return false;
};
let mut dir = dir.to_owned();
dir.push_str("/");
dir.push_str(CONFIG_SO);
Path::new(&dir).exists()
}

View file

@ -760,3 +760,15 @@ macro_rules! client_wire_scale_to_logical {
}
};
}
macro_rules! not_matches {
($($tt:tt)*) => {
!matches!($($tt)*)
};
}
macro_rules! jay_allow_realtime_config_so {
() => {
"JAY_ALLOW_REALTIME_CONFIG_SO"
};
}

View file

@ -1,17 +1,52 @@
use {
crate::{
pr_caps::sys::{
_LINUX_CAPABILITY_U32S_3, _LINUX_CAPABILITY_VERSION_3, cap_user_data_t,
_LINUX_CAPABILITY_U32S_3, _LINUX_CAPABILITY_VERSION_3, CAP_SYS_NICE, cap_user_data_t,
cap_user_header_t,
},
utils::{errorfmt::ErrorFmt, oserror::OsError},
utils::{bitflags::BitflagsExt, errorfmt::ErrorFmt, oserror::OsError},
},
uapi::{
c::{SYS_capset, syscall},
c::{SYS_capget, SYS_capset, syscall},
map_err,
},
};
pub struct PrCaps {
effective: u64,
permitted: u64,
inheritable: u64,
}
pub struct PrCompCaps {
caps: PrCaps,
}
pub fn pr_caps() -> PrCaps {
let mut hdr = cap_user_header_t {
version: _LINUX_CAPABILITY_VERSION_3,
pid: 0,
};
let mut caps = [cap_user_data_t::default(); _LINUX_CAPABILITY_U32S_3];
let ret = unsafe { syscall(SYS_capget, &mut hdr, &mut caps) };
if let Err(e) = map_err!(ret) {
eprintln!(
"Could not get process capabilities: {}",
ErrorFmt(OsError(e.0))
);
return PrCaps {
effective: 0,
permitted: 0,
inheritable: 0,
};
}
PrCaps {
effective: caps[0].effective as u64 | ((caps[1].effective as u64) << 32),
permitted: caps[0].permitted as u64 | ((caps[1].permitted as u64) << 32),
inheritable: caps[0].inheritable as u64 | ((caps[1].inheritable as u64) << 32),
}
}
pub fn drop_all_pr_caps() {
let mut hdr = cap_user_header_t {
version: _LINUX_CAPABILITY_VERSION_3,
@ -27,6 +62,55 @@ pub fn drop_all_pr_caps() {
}
}
impl PrCaps {
pub fn into_comp(mut self) -> PrCompCaps {
let mut caps = 0;
macro_rules! add_cap {
($name:ident) => {
if self.permitted.contains(1 << $name) {
caps |= 1 << $name;
}
};
}
add_cap!(CAP_SYS_NICE);
let mut hdr = cap_user_header_t {
version: _LINUX_CAPABILITY_VERSION_3,
pid: 0,
};
let caps_hi = (caps >> 32) as u32;
let caps_lo = caps as u32;
let mut data = [cap_user_data_t::default(); _LINUX_CAPABILITY_U32S_3];
data[0].effective = caps_lo;
data[1].effective = caps_hi;
data[0].permitted = caps_lo;
data[1].permitted = caps_hi;
let ret = unsafe { syscall(SYS_capset, &mut hdr, &data) };
if let Err(e) = map_err!(ret) {
eprintln!(
"Could not get set compositor capabilities: {}",
ErrorFmt(OsError(e.0))
);
return PrCompCaps { caps: self };
}
self.effective = caps;
self.permitted = caps;
self.inheritable = 0;
PrCompCaps { caps: self }
}
}
impl PrCompCaps {
pub fn has_nice(&self) -> bool {
self.caps.effective.contains(1 << CAP_SYS_NICE)
}
}
impl Drop for PrCaps {
fn drop(&mut self) {
drop_all_pr_caps();
}
}
mod sys {
#![allow(dead_code)]

View file

@ -28,6 +28,7 @@ pub mod line_logger;
pub mod linkedlist;
pub mod log_on_drop;
pub mod mmap;
pub mod nice;
pub mod nonblock;
pub mod num_cpus;
pub mod numcell;

View file

@ -1,6 +1,7 @@
use {
crate::{
forker::ForkerError,
pr_caps::drop_all_pr_caps,
utils::{errorfmt::ErrorFmt, on_drop::OnDrop, process_name::set_process_name},
},
std::{env, mem::MaybeUninit, process, slice, str::FromStr},
@ -161,6 +162,7 @@ pub fn ensure_reaper() -> c::pid_t {
set_deathsig();
return reaper_pid;
};
drop_all_pr_caps();
set_process_name("jay reaper");
while let Ok((pid, status)) = uapi::wait() {
if pid == main_process_id {

40
src/utils/nice.rs Normal file
View file

@ -0,0 +1,40 @@
use {
crate::{compositor::config_dir, config::have_config_so},
c::sched_setscheduler,
std::{
env, mem,
sync::atomic::{AtomicBool, Ordering::Relaxed},
},
uapi::c::{self, SCHED_RESET_ON_FORK, SCHED_RR, sched_param},
};
static DID_ELEVATE_SCHEDULER: AtomicBool = AtomicBool::new(false);
pub const JAY_NO_REALTIME: &str = "JAY_NO_REALTIME";
pub fn elevate_scheduler() {
if env::var(JAY_NO_REALTIME).as_deref().unwrap_or_default() == "1" {
return;
}
if have_config_so(config_dir().as_deref()) && dont_allow_realtime_config_so() {
return;
}
let mut param = unsafe { mem::zeroed::<sched_param>() };
param.sched_priority = 1;
let res = unsafe { sched_setscheduler(0, SCHED_RR | SCHED_RESET_ON_FORK, &param) };
if res == 0 {
DID_ELEVATE_SCHEDULER.store(true, Relaxed);
}
}
pub fn did_elevate_scheduler() -> bool {
DID_ELEVATE_SCHEDULER.load(Relaxed)
}
fn dont_allow_realtime_config_so() -> bool {
option_env!(jay_allow_realtime_config_so!()).unwrap_or_default() != "1"
}
pub fn dont_allow_config_so() -> bool {
did_elevate_scheduler() && dont_allow_realtime_config_so()
}