diff options
| author | Emīls Piņķis <emils@mullvad.net> | 2018-06-18 11:39:06 +0100 |
|---|---|---|
| committer | Emīls Piņķis <emils@mullvad.net> | 2018-06-18 11:39:06 +0100 |
| commit | e1aacb16667a5fdce94a5126961359d511d19821 (patch) | |
| tree | 9a28840ba8c528fbaa792987aa0f60aebb623e12 | |
| parent | f64fa3eac2df89fa5ebaaa52763c966c0f381f6f (diff) | |
| parent | 1119d817c8ad7d48109b5c9e2800d3c805689cfa (diff) | |
| download | mullvadvpn-e1aacb16667a5fdce94a5126961359d511d19821.tar.xz mullvadvpn-e1aacb16667a5fdce94a5126961359d511d19821.zip | |
Merge branch 'add-system-state'
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 3 | ||||
| -rw-r--r-- | talpid-core/src/firewall/mod.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/firewall/system_state.rs | 163 |
4 files changed, 169 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock index 2d0866e3aa..94f2e6b375 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1391,6 +1391,7 @@ dependencies = [ "system-configuration 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "talpid-ipc 0.1.0", "talpid-types 0.1.0", + "tempfile 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "widestring 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index ba17a9993a..1a906ae6f3 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -33,3 +33,6 @@ tokio-core = "0.1" [target.'cfg(windows)'.dependencies] widestring = "0.3" + +[dev-dependencies] +tempfile = "3.0" diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs index cec1a66d94..6b2a073d40 100644 --- a/talpid-core/src/firewall/mod.rs +++ b/talpid-core/src/firewall/mod.rs @@ -1,5 +1,7 @@ use talpid_types::net::Endpoint; +mod system_state; + /// A enum that describes firewall rules strategy #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/talpid-core/src/firewall/system_state.rs b/talpid-core/src/firewall/system_state.rs new file mode 100644 index 0000000000..18782d91cf --- /dev/null +++ b/talpid-core/src/firewall/system_state.rs @@ -0,0 +1,163 @@ +//! A writer for a blob that would persistently store the system state. Useful +//! for when the application of a secuirty policy proves to be persistent across +//! reboots +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; +use std::slice; + +const STATE_BACKUP_FILENAME: &str = "system_state_backup"; + +/// This struct is responsible for saving a binary blob to disk. The binary blob is intended to +/// store system state that should be resotred when the security policy is reset. +pub struct SystemStateWriter { + /// Full path to the system state backup file + pub backup_path: Box<Path>, +} + +impl SystemStateWriter { + /// Creates a new SystemStateWriter which will use a file in the cache directory to store system + /// state that has to be restored. + pub fn new<P: AsRef<Path>>(cache_dir: P) -> Self { + let backup_path = cache_dir + .as_ref() + .join(STATE_BACKUP_FILENAME) + .into_boxed_path(); + Self { backup_path } + } + + /// Writes a binary blob representing the system state to the backup location before any + /// security policies are applied. + pub fn write_backup(&self, data: &[u8]) -> io::Result<()> { + fs::write(&self.backup_path, &data) + } + + /// Tries to read a previously saved backup and deletes it after reading it if it exists. + pub fn consume_state_backup(&self) -> io::Result<Option<Vec<u8>>> { + match fs::read(&self.backup_path) { + Ok(blob) => { + if let Err(e) = self.remove_state_file() { + error!("Failed to remove system state backup: {}", e) + }; + Ok(Some(blob)) + } + Err(e) => match e.kind() { + io::ErrorKind::NotFound => Ok(None), + _ => Err(e), + }, + } + } + + /// Removes a previously created state backup if it exists. + pub fn remove_state_file(&self) -> io::Result<()> { + match fs::remove_file(&self.backup_path) { + Err(e) => { + if e.kind() != io::ErrorKind::NotFound { + Err(e) + } else { + Ok(()) + } + } + _ => Ok(()), + } + } +} + +#[cfg(test)] +mod tests { + extern crate tempfile; + use super::*; + + #[test] + fn can_create_backup() { + let temp_dir = tempfile::tempdir().expect("failed to crate temp dir"); + + let mock_system_state: Vec<_> = b"8.8.8.8\n8.8.4.4\n".to_vec(); + let writer = SystemStateWriter::new(&temp_dir); + writer + .write_backup(&mock_system_state) + .expect("failed to write system state"); + + let backup = writer + .consume_state_backup() + .expect("error when reading system state backup") + .expect("expected to read system state backup"); + assert_eq!(backup, mock_system_state); + + let empty_read = writer + .consume_state_backup() + .expect("error when reading system state backup"); + assert_eq!(empty_read, None); + } + + #[test] + fn can_succeed_without_backup() { + let temp_dir = tempfile::tempdir().expect("failed to crate temp dir"); + + let writer = SystemStateWriter::new(&temp_dir); + let backup = writer + .consume_state_backup() + .expect("error when reading system state backup"); + assert_eq!(backup, None); + } + + #[cfg(unix)] + #[test] + fn cant_read_without_access() { + let temp_dir = PathBuf::from("/dev/null"); + + let writer = SystemStateWriter::new(&temp_dir); + let mock_system_state: Vec<_> = b"8.8.8.8\n8.8.4.4\n".to_vec(); + + let failure = writer + .write_backup(&mock_system_state) + .expect_err("successfully wrote backup file to a directory in /dev/null"); + assert_eq!(failure.kind(), io::ErrorKind::Other); + + let recovery_failure = writer + .consume_state_backup() + .expect_err("successfully read backup file in /dev/null"); + assert_eq!(recovery_failure.kind(), io::ErrorKind::Other); + } + + #[test] + fn can_remove_when_no_backup_exists() { + let temp_dir = tempfile::tempdir().expect("failed to crate temp dir"); + + let writer = SystemStateWriter::new(&temp_dir); + writer.remove_state_file().expect( + "Encountered IO error when running remove_state_file when no state file exists", + ); + } + + #[test] + fn can_remove_backup() { + let temp_dir = tempfile::tempdir().expect("failed to crate temp dir"); + let writer = SystemStateWriter::new(&temp_dir); + let mock_system_state = b"8.8.8.8\n8.8.4.4\n".to_vec(); + + writer + .write_backup(&mock_system_state) + .expect("Failed to write backup"); + writer + .remove_state_file() + .expect("Failed to remove state file"); + + let empty_backup = writer + .consume_state_backup() + .expect("Encountered IO error when no backup file exists"); + assert_eq!(empty_backup, None); + } + + #[cfg(unix)] + #[test] + fn cant_remove_backup_with_io_error() { + let temp_dir = PathBuf::from("/dev/null"); + + let writer = SystemStateWriter::new(&temp_dir); + let removal_failure = writer + .remove_state_file() + .expect_err("successfully removed state file in /dev/null"); + assert_eq!(removal_failure.kind(), io::ErrorKind::Other); + } +} |
