summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorEmīls Piņķis <emils@mullvad.net>2018-06-18 11:39:06 +0100
committerEmīls Piņķis <emils@mullvad.net>2018-06-18 11:39:06 +0100
commite1aacb16667a5fdce94a5126961359d511d19821 (patch)
tree9a28840ba8c528fbaa792987aa0f60aebb623e12
parentf64fa3eac2df89fa5ebaaa52763c966c0f381f6f (diff)
parent1119d817c8ad7d48109b5c9e2800d3c805689cfa (diff)
downloadmullvadvpn-e1aacb16667a5fdce94a5126961359d511d19821.tar.xz
mullvadvpn-e1aacb16667a5fdce94a5126961359d511d19821.zip
Merge branch 'add-system-state'
-rw-r--r--Cargo.lock1
-rw-r--r--talpid-core/Cargo.toml3
-rw-r--r--talpid-core/src/firewall/mod.rs2
-rw-r--r--talpid-core/src/firewall/system_state.rs163
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);
+ }
+}