use std::path::PathBuf;
use tokio::net::{UnixListener, UnixStream};
use crate::error::ServiceError;
pub type TransportListener = UnixListener;
pub type TransportStream = UnixStream;
pub type TransportReadHalf = tokio::io::ReadHalf<TransportStream>;
pub type TransportWriteHalf = tokio::io::WriteHalf<TransportStream>;
pub fn bind(path: PathBuf) -> Result<TransportListener, ServiceError> {
match std::fs::symlink_metadata(&path) {
Ok(metadata) => {
if metadata.file_type().is_symlink() {
return Err(ServiceError::Ipc(
"refusing to bind: socket path is a symlink".into(),
));
}
std::fs::remove_file(&path)
.map_err(|e| ServiceError::Ipc(format!("remove stale socket: {e}")))?;
}
Err(_) => {
}
}
UnixListener::bind(&path).map_err(|e| ServiceError::Ipc(format!("bind failed: {e}")))
}
pub async fn connect(path: &PathBuf) -> Result<TransportStream, ServiceError> {
UnixStream::connect(path)
.await
.map_err(|e| ServiceError::Ipc(format!("connect failed: {e}")))
}
fn peer_uid_allowed(peer_uid: libc::uid_t, my_uid: libc::uid_t) -> bool {
peer_uid == my_uid
}
pub fn peer_cred_check(stream: &TransportStream) -> bool {
#[cfg(target_os = "linux")]
{
use std::os::unix::io::AsRawFd;
let peer_uid = unsafe {
let mut creds: libc::ucred = std::mem::zeroed();
let mut len = std::mem::size_of::<libc::ucred>() as libc::socklen_t;
let ret = libc::getsockopt(
stream.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_PEERCRED,
&mut creds as *mut _ as *mut _,
&mut len,
);
if ret == -1 {
log::warn!("IPC: failed to get peer credentials, rejecting connection");
return false;
}
creds.uid
};
let my_uid = unsafe { libc::getuid() };
if !peer_uid_allowed(peer_uid, my_uid) {
log::warn!("IPC connection rejected: uid mismatch ({peer_uid} != {my_uid})");
return false;
}
}
#[cfg(target_os = "macos")]
{
use std::os::unix::io::AsRawFd;
let mut peer_uid: libc::uid_t = 0;
let mut peer_gid: libc::gid_t = 0;
if unsafe { libc::getpeereid(stream.as_raw_fd(), &mut peer_uid, &mut peer_gid) } != 0 {
log::warn!("IPC: failed to get peer credentials via getpeereid, rejecting connection");
return false;
}
let my_uid = unsafe { libc::getuid() };
if !peer_uid_allowed(peer_uid, my_uid) {
log::warn!("IPC connection rejected: uid mismatch ({peer_uid} != {my_uid})");
return false;
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
let _ = stream;
log::warn!("IPC: no peer credential check available on this platform");
}
true
}
pub fn cleanup(path: &PathBuf) {
let _ = std::fs::remove_file(path);
}
pub async fn accept(listener: &mut TransportListener) -> Result<TransportStream, std::io::Error> {
let (stream, _addr) = listener.accept().await?;
Ok(stream)
}
pub fn split(stream: TransportStream) -> (TransportReadHalf, TransportWriteHalf) {
tokio::io::split(stream)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bgs29_control_socket_rejects_foreign_uid() {
let my_uid: libc::uid_t = unsafe { libc::getuid() };
assert!(
peer_uid_allowed(my_uid, my_uid),
"same-UID peer must be allowed"
);
assert!(
!peer_uid_allowed(my_uid.wrapping_add(1), my_uid),
"foreign UID (my_uid + 1) must be rejected"
);
assert!(
!peer_uid_allowed(my_uid.wrapping_sub(1), my_uid),
"foreign UID (my_uid - 1) must be rejected"
);
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[tokio::test]
async fn desk06_same_uid_loopback_passes_peer_cred_check() {
use std::os::unix::net::UnixStream;
use tokio::net::UnixListener;
let dir = std::env::temp_dir();
let path = dir.join(format!(
"tauri-bg-desk06-{}-{}.sock",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
let _ = std::fs::remove_file(&path);
let listener = UnixListener::bind(&path).expect("bind UnixListener");
let _client = UnixStream::connect(&path).expect("loopback connect");
let (server_stream, _addr) = listener.accept().await.expect("accept");
let transport_stream: TransportStream = server_stream;
let allowed = peer_cred_check(&transport_stream);
assert!(
allowed,
"same-UID loopback connection must pass peer_cred_check"
);
let _ = std::fs::remove_file(&path);
}
}