fix: soundness — OnceLock for HOST_API, IPC size limits, mutex poisoning recovery
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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 }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user