Skip to content

Commit 8af1909

Browse files
authored
Harden against potential safety violation (#4).
2 parents d031cfc + 2d26855 commit 8af1909

File tree

2 files changed

+88
-14
lines changed

2 files changed

+88
-14
lines changed

crates/fleetspeak/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@ fleetspeak-proto = { path = "../fleetspeak-proto", version = "0.4.0" }
1919
lazy_static = { version = "1.4.0" }
2020
log = { version = "0.4.19" }
2121
protobuf = { workspace = true }
22+
23+
[target.'cfg(target_family = "unix")'.dependencies]
24+
libc = { version = "0.2.147" }
25+
26+
[target.'cfg(target_family = "windows")'.dependencies]
27+
windows-sys = { version = "0.48.0", features = ["Win32_Foundation", "Win32_Storage_FileSystem"] }

crates/fleetspeak/src/lib.rs

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -213,16 +213,16 @@ pub fn receive_with_heartbeat(rate: Duration) -> Message {
213213
/// sending heartbeat signals) when another thread might be busy with reading
214214
/// messages.
215215
struct Connection {
216-
input: Mutex<std::fs::File>,
217-
output: Mutex<std::fs::File>,
216+
input: Mutex<&'static mut std::fs::File>,
217+
output: Mutex<&'static mut std::fs::File>,
218218
}
219219

220220
lazy_static! {
221221
static ref CONNECTION: Connection = {
222-
let mut input = file_from_env_var("FLEETSPEAK_COMMS_CHANNEL_INFD");
223-
let mut output = file_from_env_var("FLEETSPEAK_COMMS_CHANNEL_OUTFD");
222+
let input = file_from_env_var("FLEETSPEAK_COMMS_CHANNEL_INFD");
223+
let output = file_from_env_var("FLEETSPEAK_COMMS_CHANNEL_OUTFD");
224224

225-
crate::io::handshake(&mut input, &mut output)
225+
crate::io::handshake(input, output)
226226
.expect("handshake failure");
227227

228228
log::info!(target: "fleetspeak", "handshake successful");
@@ -243,7 +243,7 @@ lazy_static! {
243243
///
244244
/// Any I/O error returned by the executed function indicates a fatal connection
245245
/// failure and ends with a panic.
246-
fn execute<F, T>(mutex: &Mutex<std::fs::File>, f: F) -> T
246+
fn execute<F, T>(mutex: &Mutex<&mut std::fs::File>, f: F) -> T
247247
where
248248
F: FnOnce(&mut std::fs::File) -> std::io::Result<T>,
249249
{
@@ -260,23 +260,91 @@ where
260260
/// a valid file descriptor (in which case the library cannot be initialized and
261261
/// the service is unlikely to work anyway).
262262
///
263+
/// This function returns a static mutable reference to ensure that the file is
264+
/// never dropped.
265+
///
263266
/// [`File`]: std::fs::File
264-
fn file_from_env_var(var: &str) -> std::fs::File {
267+
fn file_from_env_var(var: &str) -> &'static mut std::fs::File {
265268
let fd = std::env::var(var)
266269
.expect(&format!("invalid variable `{}`", var))
267270
.parse()
268271
.expect(&format!("failed to parse file descriptor"));
269272

273+
// We run inside a critical section to guarantee that between verifying the
274+
// descriptor and using `std::File::from_raw_*` functions (see the comments
275+
// below) nothing closes it inbetween.
276+
let mutex = Mutex::new(());
277+
let guard = mutex.lock()
278+
.unwrap();
279+
280+
// SAFETY: `std::fs::File::from_raw_fd` requires the file descriptor to be
281+
// valid and open. We verify this through `fcntl` and panic if the check
282+
// fails. Because we run within a critical section we are sure that the file
283+
// was not closed at the moment we wrap the raw descriptor.
284+
//
285+
// Note that the whole issue is more subtle than this. While we uphold the
286+
// safety requirements of `from_raw_fd`, we cannot guarantee that we are
287+
// exclusive owner of the descriptor or that the descriptor remains open
288+
// throughout the entirety of the process lifetime which might lead to other
289+
// kinds of undefined behaviour. As an additional safety measure we return
290+
// a static mutable reference to ensure that the file destructor is never
291+
// called. See the discussion in [1].
292+
//
293+
// [1]: https://github.com/rust-lang/unsafe-code-guidelines/issues/434
270294
#[cfg(target_family = "unix")]
271-
unsafe {
295+
let file = unsafe {
296+
if libc::fcntl(fd, libc::F_GETFD) == -1 {
297+
let error = std::io::Error::last_os_error();
298+
panic!("invalid file descriptor '{fd}': {error}");
299+
}
300+
272301
std::os::unix::io::FromRawFd::from_raw_fd(fd)
273-
}
302+
};
274303

304+
// SAFETY: `std::fs::File::from_raw_handle` requires the file handle to be
305+
// valid, open and closable via `CloseHandle`. We verify this through a call
306+
// to `GetFileType`: if the call fails or returns an unexpected file type,
307+
// we panic. We expect the type to be a named pipe: this is what Fleetspeak
308+
// should pass as and it is closable via `CloseHandle` [1] as required. We
309+
// run within a critical section we are sure that the file was not closed at
310+
// the moment we wrap the raw handle.
311+
//
312+
// See also remarks in the comment for the Unix branch of this method.
313+
//
314+
// [1]: https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle#remarks
275315
#[cfg(target_family = "windows")]
276-
unsafe {
277-
// We use `identity` to specify the type for the `parse` call above and
278-
// then cast it to an appropriate Windows-specific pointer type.
279-
let handle = std::convert::identity::<usize>(fd) as _;
316+
let file = unsafe {
317+
use windows_sys::Win32::{
318+
Foundation::*,
319+
Storage::FileSystem::*,
320+
};
321+
322+
// We use `identity` to specify the type for the `parse` call above.
323+
let handle = std::convert::identity::<HANDLE>(fd);
324+
325+
// We fail both in case there is something wrong with the handle (in
326+
// which case the call to `GetFileType` should return unknown file type
327+
// and set the last error value) or the file type is not as expected.
328+
let file_type = GetFileType(handle);
329+
if file_type != FILE_TYPE_PIPE {
330+
let code = GetLastError();
331+
if code != NO_ERROR {
332+
let error = std::io::Error::from_raw_os_error(code as i32);
333+
panic!("invalid file descriptor '{handle}': {error}");
334+
} else {
335+
panic!("wrong type of file descriptor '{handle}': {file_type}");
336+
}
337+
}
338+
339+
// `HANDLE` type from `windows-sys` and from the standard library are
340+
// different (one is a pointer and one is `isize`), so we have to cast
341+
// between them.
342+
let handle = handle as std::os::windows::raw::HANDLE;
343+
280344
std::os::windows::io::FromRawHandle::from_raw_handle(handle)
281-
}
345+
};
346+
347+
drop(guard);
348+
349+
Box::leak(Box::new(file))
282350
}

0 commit comments

Comments
 (0)