1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-04 10:43:47 +00:00

feat: add hash to process info

This commit is contained in:
Andreas Coroiu
2025-10-20 14:53:15 +02:00
parent bf1ac80e65
commit 9f9429de00
3 changed files with 128 additions and 7 deletions

View File

@@ -6,7 +6,12 @@ use std::{
use futures::{SinkExt, StreamExt, TryFutureExt};
use anyhow::Result;
// Non-Unix uses interprocess local sockets
#[cfg(not(unix))]
use interprocess::local_socket::{tokio::prelude::*, GenericFilePath, ListenerOptions};
// Unix uses tokio's UnixListener to access peer credentials
#[cfg(unix)]
use tokio::net::UnixListener;
use tokio::{
io::{AsyncRead, AsyncWrite},
sync::{broadcast, mpsc},
@@ -54,9 +59,15 @@ impl Server {
let _ = std::fs::remove_file(path);
}
let name = path.as_os_str().to_fs_name::<GenericFilePath>()?;
let opts = ListenerOptions::new().name(name);
let listener = opts.create_tokio()?;
#[cfg(unix)]
let listener = UnixListener::bind(path)?;
#[cfg(not(unix))]
let listener = {
let name = path.as_os_str().to_fs_name::<GenericFilePath>()?;
let opts = ListenerOptions::new().name(name);
opts.create_tokio()?
};
// This broadcast channel is used for sending messages to all connected clients, and so the sender
// will be stored in the server while the receiver will be cloned and passed to each client handler.
@@ -74,7 +85,15 @@ impl Server {
cancel_token: cancel_token.clone(),
server_to_clients_send,
};
tokio::spawn(listen_incoming(
#[cfg(unix)]
tokio::spawn(listen_incoming_unix(
listener,
client_to_server_send,
server_to_clients_recv,
cancel_token,
));
#[cfg(not(unix))]
tokio::spawn(listen_incoming_non_unix(
listener,
client_to_server_send,
server_to_clients_recv,
@@ -108,8 +127,78 @@ impl Drop for Server {
}
}
async fn listen_incoming(
listener: LocalSocketListener,
#[cfg(unix)]
async fn listen_incoming_unix(
listener: UnixListener,
client_to_server_send: mpsc::Sender<Message>,
server_to_clients_recv: broadcast::Receiver<String>,
cancel_token: CancellationToken,
) {
// We use a simple incrementing ID for each client
let mut next_client_id = 1_u32;
loop {
use crate::ssh_agent::peerinfo::gather::get_peer_info;
tokio::select! {
_ = cancel_token.cancelled() => {
info!("IPC server cancelled.");
break;
},
// A new client connection has been established
msg = listener.accept() => {
match msg {
Ok((client_stream, _addr)) => {
let client_id = next_client_id;
next_client_id += 1;
// Try to log peer credentials
match client_stream.peer_cred() {
Ok(peer) => {
if let Some(pid) = peer.pid() {
let peer_info = match get_peer_info(pid as u32) {
Ok(info) => info,
Err(_) => crate::ssh_agent::peerinfo::models::PeerInfo::unknown(),
};
info!(client_id, pid, uid = peer.uid(), gid = peer.gid(), peer_info = ?peer_info, "IPC client connected (peer credentials)");
} else {
info!(client_id, uid = peer.uid(), gid = peer.gid(), "IPC client connected (peer credentials, no pid)");
}
},
Err(e) => {
error!(client_id, error = %e, "Failed to get peer credentials");
}
}
let future = handle_connection(
client_stream,
client_to_server_send.clone(),
// We resubscribe to the receiver here so this task can have it's own copy
// Note that this copy will only receive messages sent after this point,
// but that is okay, realistically we don't want any messages before we get a chance
// to send the connected message to the client, which is done inside [`handle_connection`]
server_to_clients_recv.resubscribe(),
cancel_token.clone(),
client_id
);
tokio::spawn(future.map_err(|e| {
error!(error = %e, "Error handling connection")
}));
},
Err(e) => {
error!(error = %e, "Error accepting connection");
break;
},
}
}
}
}
}
#[cfg(not(unix))]
async fn listen_incoming_non_unix(
listener: interprocess::local_socket::LocalSocketListener,
client_to_server_send: mpsc::Sender<Message>,
server_to_clients_recv: broadcast::Receiver<String>,
cancel_token: CancellationToken,

View File

@@ -1,3 +1,5 @@
use sha2::{Digest, Sha256};
use std::{fs::File, io::Read, path::Path};
use sysinfo::{Pid, System};
use super::models::PeerInfo;
@@ -12,12 +14,35 @@ pub fn get_peer_info(peer_pid: u32) -> Result<PeerInfo, String> {
}
};
let exe_hash = process.exe().and_then(|p| hash_file_if_exists(p));
return Ok(PeerInfo::new(
peer_pid,
process.pid().as_u32(),
peer_process_name,
exe_hash,
));
}
Err("Failed to get process".to_string())
}
fn hash_file_if_exists(path: &Path) -> Option<String> {
if path.as_os_str().is_empty() || !path.exists() {
return None;
}
let mut f = File::open(path).ok()?;
let mut hasher = Sha256::new();
let mut buf = [0u8; 64 * 1024];
loop {
let n = match f.read(&mut buf) {
Ok(n) => n,
Err(_) => return None,
};
if n == 0 {
break;
}
hasher.update(&buf[..n]);
}
Some(format!("{:x}", hasher.finalize()))
}

View File

@@ -9,16 +9,18 @@ pub struct PeerInfo {
uid: u32,
pid: u32,
process_name: String,
exe_hash: Option<String>,
is_forwarding: Arc<AtomicBool>,
host_key: Arc<Mutex<Vec<u8>>>,
}
impl PeerInfo {
pub fn new(uid: u32, pid: u32, process_name: String) -> Self {
pub fn new(uid: u32, pid: u32, process_name: String, exe_hash: Option<String>) -> Self {
Self {
uid,
pid,
process_name,
exe_hash,
is_forwarding: Arc::new(AtomicBool::new(false)),
host_key: Arc::new(Mutex::new(Vec::new())),
}
@@ -29,6 +31,7 @@ impl PeerInfo {
uid: 0,
pid: 0,
process_name: "Unknown application".to_string(),
exe_hash: None,
is_forwarding: Arc::new(AtomicBool::new(false)),
host_key: Arc::new(Mutex::new(Vec::new())),
}
@@ -46,6 +49,10 @@ impl PeerInfo {
&self.process_name
}
pub fn exe_hash(&self) -> Option<&str> {
self.exe_hash.as_deref()
}
pub fn is_forwarding(&self) -> bool {
self.is_forwarding
.load(std::sync::atomic::Ordering::Relaxed)