🔒 [security fix] Handle serialization failures in daemon commands and socket communication. (#16)

- Replaced `.unwrap()` with proper error handling during JSON serialization in `GetStateCommand`, `GetTracksCommand`, and `GetFullStateCommand`.
- Added error handling for malformed client requests in the daemon's main loop.
- Ensured the daemon stays running even if serialization or deserialization fails.
- Handled potential errors from `get_all_devices()`.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
Tarasov Aleksandr
2026-03-06 23:02:07 +03:00
committed by GitHub
parent 80a8b1a45f
commit 89ce111542
2 changed files with 43 additions and 8 deletions
+22 -2
View File
@@ -95,7 +95,21 @@ async fn commands_loop(listener: UnixListener) -> Result<(), Box<dyn Error>> {
return; return;
} }
let request: Request = serde_json::from_slice(&buffer).unwrap(); let request: Request = match serde_json::from_slice(&buffer) {
Ok(req) => req,
Err(err) => {
let response =
Response::new(false, format!("Failed to parse request: {}", err));
let response_data = match serde_json::to_vec(&response) {
Ok(data) => data,
Err(_) => return, // Should not happen with this simple Response
};
let response_len = response_data.len() as u32;
let _ = stream.write_all(&response_len.to_le_bytes()).await;
let _ = stream.write_all(&response_data).await;
return;
}
};
// ---------- Read request (end) ---------- // ---------- Read request (end) ----------
// ---------- Generate response (start) ---------- // ---------- Generate response (start) ----------
@@ -109,7 +123,13 @@ async fn commands_loop(listener: UnixListener) -> Result<(), Box<dyn Error>> {
// ---------- Generate response (end) ---------- // ---------- Generate response (end) ----------
// ---------- Send response (start) ---------- // ---------- Send response (start) ----------
let response_data = serde_json::to_vec(&response).unwrap(); let response_data = match serde_json::to_vec(&response) {
Ok(data) => data,
Err(err) => {
eprintln!("Failed to serialize response: {}", err);
return;
}
};
let response_len = response_data.len() as u32; let response_len = response_data.len() as u32;
if stream.write_all(&response_len.to_le_bytes()).await.is_err() { if stream.write_all(&response_len.to_le_bytes()).await.is_err() {
+21 -6
View File
@@ -183,7 +183,10 @@ impl Executable for GetStateCommand {
async fn execute(&self) -> Response { async fn execute(&self) -> Response {
let audio_player = get_audio_player().await.lock().await; let audio_player = get_audio_player().await.lock().await;
let state = audio_player.get_state(); let state = audio_player.get_state();
Response::new(true, serde_json::to_string(&state).unwrap()) match serde_json::to_string(&state) {
Ok(json) => Response::new(true, json),
Err(err) => Response::new(false, format!("Failed to serialize state: {}", err)),
}
} }
} }
@@ -272,7 +275,10 @@ impl Executable for GetTracksCommand {
async fn execute(&self) -> Response { async fn execute(&self) -> Response {
let audio_player = get_audio_player().await.lock().await; let audio_player = get_audio_player().await.lock().await;
let tracks = audio_player.get_tracks(); let tracks = audio_player.get_tracks();
Response::new(true, serde_json::to_string(&tracks).unwrap()) match serde_json::to_string(&tracks) {
Ok(json) => Response::new(true, json),
Err(err) => Response::new(false, format!("Failed to serialize tracks: {}", err)),
}
} }
} }
@@ -298,7 +304,10 @@ impl Executable for GetCurrentInputCommand {
#[async_trait] #[async_trait]
impl Executable for GetAllInputsCommand { impl Executable for GetAllInputsCommand {
async fn execute(&self) -> Response { async fn execute(&self) -> Response {
let (input_devices, _output_devices) = get_all_devices().await.unwrap(); let (input_devices, _output_devices) = match get_all_devices().await {
Ok(devices) => devices,
Err(err) => return Response::new(false, format!("Failed to get devices: {}", err)),
};
let mut input_devices_strings = vec![]; let mut input_devices_strings = vec![];
for device in input_devices { for device in input_devices {
if device.name == "pwsp-virtual-mic" { if device.name == "pwsp-virtual-mic" {
@@ -375,7 +384,10 @@ impl Executable for GetDaemonVersionCommand {
#[async_trait] #[async_trait]
impl Executable for GetFullStateCommand { impl Executable for GetFullStateCommand {
async fn execute(&self) -> Response { async fn execute(&self) -> Response {
let (input_devices, _output_devices) = get_all_devices().await.unwrap(); let (input_devices, _output_devices) = match get_all_devices().await {
Ok(devices) => devices,
Err(err) => return Response::new(false, format!("Failed to get devices: {}", err)),
};
let mut all_inputs = HashMap::new(); let mut all_inputs = HashMap::new();
let mut current_input_nick = String::new(); let mut current_input_nick = String::new();
@@ -400,9 +412,12 @@ impl Executable for GetFullStateCommand {
tracks: audio_player.get_tracks(), tracks: audio_player.get_tracks(),
volume: audio_player.volume, volume: audio_player.volume,
current_input: current_input_nick, current_input: current_input_nick,
all_inputs: all_inputs, all_inputs,
}; };
Response::new(true, serde_json::to_string(&full_state).unwrap()) match serde_json::to_string(&full_state) {
Ok(json) => Response::new(true, json),
Err(err) => Response::new(false, format!("Failed to serialize full state: {}", err)),
}
} }
} }