From 7ce6de17aa19bd33a0ba750c0a7016a098980a1f Mon Sep 17 00:00:00 2001 From: vikingowl Date: Thu, 26 Mar 2026 16:29:47 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20soundness=20=E2=80=94=20OnceLock=20for?= =?UTF-8?q?=20HOST=5FAPI,=20IPC=20size=20limits,=20mutex=20poisoning=20rec?= =?UTF-8?q?overy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/owlry-core/src/server.rs | 40 ++++++++++++++++++++++-------- crates/owlry-plugin-api/src/lib.rs | 12 ++++----- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/crates/owlry-core/src/server.rs b/crates/owlry-core/src/server.rs index f345379..ed9f69e 100644 --- a/crates/owlry-core/src/server.rs +++ b/crates/owlry-core/src/server.rs @@ -4,6 +4,9 @@ use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; 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 crate::config::Config; @@ -94,11 +97,28 @@ impl Server { frecency: Arc>, config: Arc, ) -> io::Result<()> { - let reader = BufReader::new(stream.try_clone()?); + let mut reader = BufReader::new(stream.try_clone()?); let mut writer = stream; - for line in reader.lines() { - let line = line?; + loop { + 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(); if trimmed.is_empty() { continue; @@ -139,8 +159,8 @@ impl Server { let max = config.general.max_results; let weight = config.providers.frecency_weight; - let pm_guard = pm.lock().unwrap(); - let frecency_guard = frecency.lock().unwrap(); + let pm_guard = pm.lock().unwrap_or_else(|e| e.into_inner()); + let frecency_guard = frecency.lock().unwrap_or_else(|e| e.into_inner()); let results = pm_guard.search_with_frecency( text, max, @@ -162,13 +182,13 @@ impl Server { item_id, 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); Response::Ack } 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(); Response::Providers { list: descs.into_iter().map(descriptor_to_desc).collect(), @@ -176,7 +196,7 @@ impl Server { } 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); Response::Ack } @@ -187,7 +207,7 @@ impl Server { } 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) { Some((_name, actions)) => Response::SubmenuItems { items: actions @@ -202,7 +222,7 @@ impl Server { } 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) { Response::Ack } else { diff --git a/crates/owlry-plugin-api/src/lib.rs b/crates/owlry-plugin-api/src/lib.rs index 01b8930..01ad883 100644 --- a/crates/owlry-plugin-api/src/lib.rs +++ b/crates/owlry-plugin-api/src/lib.rs @@ -297,26 +297,24 @@ pub struct HostAPI { pub log_error: extern "C" fn(message: RStr<'_>), } +use std::sync::OnceLock; + // 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) /// /// # Safety /// Must only be called once by the host before any plugins use the API pub unsafe fn init_host_api(api: &'static HostAPI) { - // SAFETY: Caller guarantees this is called once before any plugins use the API - unsafe { - HOST_API = Some(api); - } + let _ = HOST_API.set(api); } /// Get the host API /// /// Returns None if the host hasn't initialized the API yet pub fn host_api() -> Option<&'static HostAPI> { - // SAFETY: We only read the pointer, and it's set once at startup - unsafe { HOST_API } + HOST_API.get().copied() } // ============================================================================