fix: soundness — OnceLock for HOST_API, IPC size limits, mutex poisoning recovery

This commit is contained in:
2026-03-26 16:29:47 +01:00
parent 91da177f46
commit 7ce6de17aa
2 changed files with 35 additions and 17 deletions

View File

@@ -4,6 +4,9 @@ use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::thread; use std::thread;
/// Maximum allowed size for a single IPC request line (1 MiB).
const MAX_REQUEST_SIZE: usize = 1_048_576;
use log::{error, info, warn}; use log::{error, info, warn};
use crate::config::Config; use crate::config::Config;
@@ -94,11 +97,28 @@ impl Server {
frecency: Arc<Mutex<FrecencyStore>>, frecency: Arc<Mutex<FrecencyStore>>,
config: Arc<Config>, config: Arc<Config>,
) -> io::Result<()> { ) -> io::Result<()> {
let reader = BufReader::new(stream.try_clone()?); let mut reader = BufReader::new(stream.try_clone()?);
let mut writer = stream; let mut writer = stream;
for line in reader.lines() { loop {
let line = line?; let mut line = String::new();
let bytes_read = reader.read_line(&mut line)?;
if bytes_read == 0 {
break;
}
if line.len() > MAX_REQUEST_SIZE {
let resp = Response::Error {
message: format!(
"request too large ({} bytes, max {})",
line.len(),
MAX_REQUEST_SIZE
),
};
write_response(&mut writer, &resp)?;
break;
}
let trimmed = line.trim(); let trimmed = line.trim();
if trimmed.is_empty() { if trimmed.is_empty() {
continue; continue;
@@ -139,8 +159,8 @@ impl Server {
let max = config.general.max_results; let max = config.general.max_results;
let weight = config.providers.frecency_weight; let weight = config.providers.frecency_weight;
let pm_guard = pm.lock().unwrap(); let pm_guard = pm.lock().unwrap_or_else(|e| e.into_inner());
let frecency_guard = frecency.lock().unwrap(); let frecency_guard = frecency.lock().unwrap_or_else(|e| e.into_inner());
let results = pm_guard.search_with_frecency( let results = pm_guard.search_with_frecency(
text, text,
max, max,
@@ -162,13 +182,13 @@ impl Server {
item_id, item_id,
provider: _, provider: _,
} => { } => {
let mut frecency_guard = frecency.lock().unwrap(); let mut frecency_guard = frecency.lock().unwrap_or_else(|e| e.into_inner());
frecency_guard.record_launch(item_id); frecency_guard.record_launch(item_id);
Response::Ack Response::Ack
} }
Request::Providers => { Request::Providers => {
let pm_guard = pm.lock().unwrap(); let pm_guard = pm.lock().unwrap_or_else(|e| e.into_inner());
let descs = pm_guard.available_providers(); let descs = pm_guard.available_providers();
Response::Providers { Response::Providers {
list: descs.into_iter().map(descriptor_to_desc).collect(), list: descs.into_iter().map(descriptor_to_desc).collect(),
@@ -176,7 +196,7 @@ impl Server {
} }
Request::Refresh { provider } => { Request::Refresh { provider } => {
let mut pm_guard = pm.lock().unwrap(); let mut pm_guard = pm.lock().unwrap_or_else(|e| e.into_inner());
pm_guard.refresh_provider(provider); pm_guard.refresh_provider(provider);
Response::Ack Response::Ack
} }
@@ -187,7 +207,7 @@ impl Server {
} }
Request::Submenu { plugin_id, data } => { Request::Submenu { plugin_id, data } => {
let pm_guard = pm.lock().unwrap(); let pm_guard = pm.lock().unwrap_or_else(|e| e.into_inner());
match pm_guard.query_submenu_actions(plugin_id, data, plugin_id) { match pm_guard.query_submenu_actions(plugin_id, data, plugin_id) {
Some((_name, actions)) => Response::SubmenuItems { Some((_name, actions)) => Response::SubmenuItems {
items: actions items: actions
@@ -202,7 +222,7 @@ impl Server {
} }
Request::PluginAction { command } => { Request::PluginAction { command } => {
let pm_guard = pm.lock().unwrap(); let pm_guard = pm.lock().unwrap_or_else(|e| e.into_inner());
if pm_guard.execute_plugin_action(command) { if pm_guard.execute_plugin_action(command) {
Response::Ack Response::Ack
} else { } else {

View File

@@ -297,26 +297,24 @@ pub struct HostAPI {
pub log_error: extern "C" fn(message: RStr<'_>), pub log_error: extern "C" fn(message: RStr<'_>),
} }
use std::sync::OnceLock;
// Global host API pointer - set by the host when loading plugins // Global host API pointer - set by the host when loading plugins
static mut HOST_API: Option<&'static HostAPI> = None; static HOST_API: OnceLock<&'static HostAPI> = OnceLock::new();
/// Initialize the host API (called by the host) /// Initialize the host API (called by the host)
/// ///
/// # Safety /// # Safety
/// Must only be called once by the host before any plugins use the API /// Must only be called once by the host before any plugins use the API
pub unsafe fn init_host_api(api: &'static HostAPI) { pub unsafe fn init_host_api(api: &'static HostAPI) {
// SAFETY: Caller guarantees this is called once before any plugins use the API let _ = HOST_API.set(api);
unsafe {
HOST_API = Some(api);
}
} }
/// Get the host API /// Get the host API
/// ///
/// Returns None if the host hasn't initialized the API yet /// Returns None if the host hasn't initialized the API yet
pub fn host_api() -> Option<&'static HostAPI> { pub fn host_api() -> Option<&'static HostAPI> {
// SAFETY: We only read the pointer, and it's set once at startup HOST_API.get().copied()
unsafe { HOST_API }
} }
// ============================================================================ // ============================================================================