Skip to content

Commit 5240723

Browse files
authored
Merge pull request #33 from hug-dev/socket-location
Change socket location and add checks
2 parents 881a615 + c967ad4 commit 5240723

File tree

3 files changed

+93
-2
lines changed

3 files changed

+93
-2
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ log = "0.4.8"
2020
derivative = "2.1.1"
2121
uuid = "0.7.4"
2222
zeroize = "1.1.0"
23+
users = "0.10.0"
2324

2425
[dev-dependencies]
2526
mockstream = "0.0.3"
2627

2728
[features]
28-
testing = ["parsec-interface/testing"]
29+
testing = ["parsec-interface/testing", "no-fs-permission-check"]
30+
no-fs-permission-check = []

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@
77
This repository contains a Rust client for consuming the API provided by the [Parsec service](https://github.com/parallaxsecond/parsec).
88
The low-level functionality that this library uses for IPC is implemented in the [interface crate](https://github.com/parallaxsecond/parsec-interface-rs).
99

10+
## Filesystem permission check
11+
12+
To make sure that the client is communicating with a trusted Parsec service, some permission checks
13+
are done on the socket location. Please see the
14+
[Recommendations for Secure Deployment](https://parallaxsecond.github.io/parsec-book/threat_model/secure_deployment.html)
15+
for more information.
16+
This feature is activated by default but, knowing the risks, you can remove it with:
17+
```
18+
cargo build --features no-fs-permission-check
19+
```
20+
It is also desactivated for testing.
21+
1022
## License
1123

1224
The software is provided under Apache-2.0. Contributions to this project are accepted under the same license.
@@ -18,6 +30,7 @@ This project uses the following third party crates:
1830
* derivative (MIT and Apache-2.0)
1931
* mockstream (MIT)
2032
* uuid (MIT and Apache-2.0)
33+
* users (MIT)
2134

2235
## Contributing
2336

src/core/ipc_handler/unix_socket.rs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
//! Handler for Unix domain sockets
44
use super::{Connect, ReadWrite};
55
use crate::error::{ClientErrorKind, Result};
6+
use log::error;
7+
use std::ffi::OsStr;
8+
use std::fs;
9+
use std::io::{Error, ErrorKind};
10+
use std::os::unix::fs::MetadataExt;
611
use std::os::unix::net::UnixStream;
712
use std::path::PathBuf;
813
use std::time::Duration;
914

10-
const DEFAULT_SOCKET_PATH: &str = "/tmp/security-daemon-socket";
15+
const DEFAULT_SOCKET_PATH: &str = "/tmp/parsec/parsec.sock";
1116
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(1);
1217

1318
/// IPC handler for Unix domain sockets
@@ -21,6 +26,9 @@ pub struct Handler {
2126

2227
impl Connect for Handler {
2328
fn connect(&self) -> Result<Box<dyn ReadWrite>> {
29+
#[cfg(not(no_fs_permission_check))]
30+
self.secure_parsec_socket_folder()?;
31+
2432
let stream = UnixStream::connect(self.path.clone()).map_err(ClientErrorKind::Ipc)?;
2533

2634
stream
@@ -43,6 +51,74 @@ impl Handler {
4351
pub fn new(path: PathBuf, timeout: Option<Duration>) -> Self {
4452
Handler { path, timeout }
4553
}
54+
55+
/// Checks if the socket is inside a folder with correct owners and permissions to make sure it
56+
/// is from the Parsec service.
57+
#[cfg(not(no_fs_permission_check))]
58+
fn secure_parsec_socket_folder(&self) -> Result<()> {
59+
let mut socket_dir = self.path.clone();
60+
if !socket_dir.pop() {
61+
return Err(ClientErrorKind::Ipc(Error::new(
62+
ErrorKind::Other,
63+
"Socket permission checks failed",
64+
))
65+
.into());
66+
}
67+
let meta = fs::metadata(socket_dir).map_err(ClientErrorKind::Ipc)?;
68+
69+
match users::get_user_by_uid(meta.uid()) {
70+
Some(user) => {
71+
if user.name() != OsStr::new("parsec") {
72+
error!("The socket directory must be owned by the parsec user.");
73+
return Err(ClientErrorKind::Ipc(Error::new(
74+
ErrorKind::Other,
75+
"Socket permission checks failed",
76+
))
77+
.into());
78+
}
79+
}
80+
None => {
81+
error!("Can not find socket directory user owner.");
82+
return Err(ClientErrorKind::Ipc(Error::new(
83+
ErrorKind::Other,
84+
"Socket permission checks failed",
85+
))
86+
.into());
87+
}
88+
}
89+
90+
match users::get_group_by_gid(meta.gid()) {
91+
Some(group) => {
92+
if group.name() != OsStr::new("parsec-clients") {
93+
error!("The socket directory must be owned by the parsec-clients group.");
94+
return Err(ClientErrorKind::Ipc(Error::new(
95+
ErrorKind::Other,
96+
"Socket permission checks failed",
97+
))
98+
.into());
99+
}
100+
}
101+
None => {
102+
error!("Can not find socket directory group owner.");
103+
return Err(ClientErrorKind::Ipc(Error::new(
104+
ErrorKind::Other,
105+
"Socket permission checks failed",
106+
))
107+
.into());
108+
}
109+
}
110+
111+
if (meta.mode() & 0o777) != 0o750 {
112+
error!("The permission bits of the folder containing the Parsec socket must be 750.");
113+
return Err(ClientErrorKind::Ipc(Error::new(
114+
ErrorKind::Other,
115+
"Socket permission checks failed",
116+
))
117+
.into());
118+
}
119+
120+
Ok(())
121+
}
46122
}
47123

48124
impl Default for Handler {

0 commit comments

Comments
 (0)