#![cfg(feature = "landlock")]
use std::path::Path;
use std::io::{Read, Write};
use std::fs::{create_dir, read_dir, remove_dir, remove_file, File};
use extrasafe::builtins::SystemIO;
fn can_read_file(path: &Path, expected_data: &str) {
let res = File::open(path);
assert!(res.is_ok(), "Failed to open allowed file: {:?}", res.unwrap_err());
let mut f = res.unwrap();
let mut file_contents = String::new();
let res = f.read_to_string(&mut file_contents);
assert!(res.is_ok(), "Failed to read string: {:?}", res.unwrap_err());
assert_eq!(expected_data, file_contents);
}
fn can_write_file(path: &Path, write_data: &str) {
let res = File::options().append(true).open(path);
assert!(res.is_ok(), "Failed to open allowed file: {:?}", res.unwrap_err());
let mut f = res.unwrap();
let res = f.write_all(write_data.as_bytes());
assert!(res.is_ok(), "failed to write to file: {:?}", res.unwrap_err());
drop(f);
can_read_file(path, write_data);
}
fn can_not_open_file(path: &Path) {
let res = File::open(path);
assert!(res.is_err(), "Incorrectly succeeded in opening file for reading");
}
fn can_not_remove_dir(path: &Path) {
let res = remove_dir(path);
assert!(res.is_err(), "Incorrectly succeeded in removing dir");
}
#[test]
fn test_landlock_read_file() {
let dir = tempfile::tempdir().unwrap();
let allowed_file = dir.path().join("allowed.txt");
let denied_file = dir.path().join("denied.txt");
let mut f = File::create(&allowed_file).unwrap();
f.write_all(b"test allowed").unwrap();
drop(f);
let mut f = File::create(&denied_file).unwrap();
f.write_all(b"test denied").unwrap(); drop(f);
extrasafe::SafetyContext::new()
.enable(
SystemIO::nothing()
.allow_read_path(&allowed_file)
).unwrap()
.apply_to_current_thread().unwrap();
can_read_file(&allowed_file, "test allowed");
can_not_open_file(&denied_file);
}
#[test]
fn test_landlock_write_file() {
let dir = tempfile::tempdir().unwrap();
let allowed_file = dir.path().join("allowed.txt");
let f = File::create(&allowed_file).unwrap();
drop(f);
extrasafe::SafetyContext::new()
.enable(
SystemIO::nothing()
.allow_write_file(&dir)
.allow_read_path(&dir)
).unwrap()
.apply_to_current_thread().unwrap();
can_write_file(&allowed_file, "test data");
let denied_file = dir.path().join("denied.txt");
let res = File::create(denied_file);
assert!(res.is_err(), "Incorrectly succeeded in creating file");
}
#[test]
fn test_landlock_create_file_in_path() {
let dir_allowed = tempfile::tempdir().unwrap();
let dir_denied = tempfile::tempdir().unwrap();
let allowed_file = dir_allowed.path().join("allowed.txt");
let denied_file = dir_denied.path().join("denied.txt");
extrasafe::SafetyContext::new()
.enable(
SystemIO::nothing()
.allow_create_in_dir(&dir_allowed)
).unwrap()
.apply_to_current_thread().unwrap();
let res = File::create(allowed_file);
assert!(res.is_ok(), "Failed to create file in allowed directory: {:?}", res.unwrap_err());
let res = File::create(denied_file);
assert!(res.is_err(), "Incorrectly suceeded in creating file in directory we did not allow");
}
#[test]
fn test_landlock_delete_file() {
let dir_allowed = tempfile::tempdir().unwrap();
let dir_denied = tempfile::tempdir().unwrap();
let allowed_file = dir_allowed.path().join("allowed.txt");
let denied_file = dir_denied.path().join("denied.txt");
let f = File::create(&allowed_file).unwrap();
drop(f);
let f = File::create(&denied_file).unwrap();
drop(f);
extrasafe::SafetyContext::new()
.enable(
SystemIO::nothing()
.allow_remove_file(&dir_allowed)
.allow_list_dir(&dir_allowed)
).unwrap()
.apply_to_current_thread().unwrap();
let res = remove_file(&allowed_file);
assert!(res.is_ok(), "Failed to remove file: {}", res.unwrap_err());
let dir = read_dir(&dir_allowed).unwrap();
assert_eq!(dir.collect::<Vec<_>>().len(), 0);
let res = remove_file(&denied_file);
assert!(res.is_err(), "Incorrectly succeeded in removing file that was not allowed");
}
#[test]
fn test_landlock_read_dir() {
let dir = tempfile::tempdir().unwrap();
let allowed_subdir = dir.path().join("allowed");
create_dir(&allowed_subdir).unwrap();
let allowed_file = allowed_subdir.as_path().join("allowed.txt");
let denied_file = dir.path().join("denied.txt");
let mut f = File::create(&allowed_file).unwrap();
f.write_all(b"test allowed").unwrap();
drop(f);
let mut f = File::create(&denied_file).unwrap();
f.write_all(b"test denied").unwrap(); drop(f);
extrasafe::SafetyContext::new()
.enable(
SystemIO::nothing()
.allow_read_path(allowed_subdir)
).unwrap()
.apply_to_current_thread().unwrap();
can_read_file(&allowed_file, "test allowed");
can_not_open_file(&denied_file);
}
#[test]
fn test_landlock_create_dir() {
let dir_allowed = tempfile::tempdir().unwrap();
let dir_denied = tempfile::tempdir().unwrap();
extrasafe::SafetyContext::new()
.enable(
SystemIO::nothing()
.allow_create_dir(&dir_allowed)
).unwrap()
.apply_to_current_thread().unwrap();
let allowed_subdir = dir_allowed.path().join("test_allowed");
let res = create_dir(allowed_subdir);
assert!(res.is_ok(), "Failed to create dir: {:?}", res.unwrap_err());
let denied_subdir = dir_denied.path().join("test_denied");
let res = create_dir(denied_subdir);
assert!(res.is_err(), "Incorrectly succeeded in creating directory");
}
#[test]
fn test_landlock_list_dir() {
let dir_allowed = tempfile::tempdir().unwrap();
let dir_denied = tempfile::tempdir().unwrap();
let allowed_file = dir_allowed.path().join("allowed.txt");
let f = File::create(allowed_file).unwrap();
drop(f);
extrasafe::SafetyContext::new()
.enable(
SystemIO::nothing()
.allow_list_dir(&dir_allowed)
).unwrap()
.apply_to_current_thread().unwrap();
let res = read_dir(&dir_allowed);
assert!(res.is_ok(), "Failed to list directory: {}", res.unwrap_err());
let dir = res.unwrap();
assert_eq!(dir.collect::<Vec<_>>().len(), 1);
let res = read_dir(&dir_denied);
assert!(res.is_err(), "Incorrectly succeeded in reading directory that was not allowed");
}
#[test]
fn test_landlock_delete_dir() {
let dir_allowed = tempfile::tempdir().unwrap();
let dir_denied = tempfile::tempdir().unwrap();
let allowed_subdir = dir_allowed.path().join("allowed");
let denied_subdir = dir_denied.path().join("denied");
create_dir(&allowed_subdir).unwrap();
create_dir(&denied_subdir).unwrap();
extrasafe::SafetyContext::new()
.enable(
SystemIO::nothing()
.allow_remove_dir(&dir_allowed)
.allow_list_dir(&dir_allowed)
).unwrap()
.apply_to_current_thread().unwrap();
let res = remove_dir(&allowed_subdir);
assert!(res.is_ok(), "Failed to remove directory: {}", res.unwrap_err());
let res = remove_dir(&denied_subdir);
assert!(res.is_err(), "Incorrectly succeeded in removing directory that was not allowed");
let mut dir = read_dir(&dir_allowed).unwrap();
assert!(dir.next().is_none());
}
#[test]
fn test_landlock_one_ruleset() {
let dir_allowed = tempfile::tempdir().unwrap();
let dir_denied = tempfile::tempdir().unwrap();
let allowed_subdir = dir_allowed.path().join("allowed_ro");
let allowed_subdir_write = dir_allowed.path().join("allowed_write");
let denied_subdir = dir_denied.path().join("denied");
create_dir(&allowed_subdir).unwrap();
create_dir(&allowed_subdir_write).unwrap();
create_dir(&denied_subdir).unwrap();
extrasafe::SafetyContext::new()
.enable(
SystemIO::nothing()
.allow_read_path(&allowed_subdir)
.allow_list_dir(&allowed_subdir)
.allow_create_in_dir(&allowed_subdir_write)
.allow_list_dir(&allowed_subdir_write)
.allow_read_path(&allowed_subdir_write)
.allow_write_file(&allowed_subdir_write)
.allow_remove_dir(&dir_allowed)
).unwrap()
.apply_to_current_thread().unwrap();
let allowed_file = allowed_subdir_write.as_path().join("allowed.txt");
let mut f = File::create(&allowed_file).unwrap();
f.write_all(b"test allowed write directory").unwrap();
drop(f);
let res = read_dir(&allowed_subdir);
assert!(res.is_ok(), "Failed to list ro directory: {}", res.unwrap_err());
let mut dir = res.unwrap();
assert!(dir.next().is_none());
let res = read_dir(&allowed_subdir_write);
assert!(res.is_ok(), "Failed to list rw directory: {}", res.unwrap_err());
let dir = res.unwrap();
assert_eq!(dir.collect::<Vec<_>>().len(), 1);
let res = remove_dir(&allowed_subdir);
assert!(res.is_ok(), "Failed to remove ro directory: {}", res.unwrap_err());
can_read_file(&allowed_file, "test allowed write directory");
can_not_remove_dir(&denied_subdir);
}
#[test]
fn test_landlock_different_rulesets() {
let dir_allowed = tempfile::tempdir().unwrap();
let dir_denied = tempfile::tempdir().unwrap();
let allowed_subdir = dir_allowed.path().join("allowed_ro");
let allowed_subdir_write = dir_allowed.path().join("allowed_write");
let denied_subdir = dir_denied.path().join("denied");
create_dir(&allowed_subdir).unwrap();
create_dir(&allowed_subdir_write).unwrap();
create_dir(&denied_subdir).unwrap();
extrasafe::SafetyContext::new()
.enable(
SystemIO::nothing()
.allow_list_dir(&allowed_subdir)
.allow_read_path(&allowed_subdir)
.allow_remove_dir(&dir_allowed)
.allow_list_dir(&dir_allowed)
).unwrap()
.enable(
SystemIO::nothing()
.allow_create_in_dir(&allowed_subdir_write)
.allow_read_path(&allowed_subdir_write)
.allow_write_file(&allowed_subdir_write)
).unwrap()
.apply_to_current_thread().unwrap();
let allowed_file = allowed_subdir_write.as_path().join("allowed.txt");
let mut f = File::create(&allowed_file).unwrap();
f.write_all(b"test allowed write directory").unwrap();
drop(f);
let res = read_dir(&allowed_subdir);
assert!(res.is_ok(), "Failed to list ro directory: {}", res.unwrap_err());
let mut dir = res.unwrap();
assert!(dir.next().is_none());
let res = read_dir(&allowed_subdir_write);
assert!(res.is_ok(), "Failed to list rw directory: {}", res.unwrap_err());
let dir = res.unwrap();
assert_eq!(dir.collect::<Vec<_>>().len(), 1);
let res = remove_dir(&allowed_subdir);
assert!(res.is_ok(), "Failed to remove ro directory: {}", res.unwrap_err());
can_read_file(&allowed_file, "test allowed write directory");
can_not_remove_dir(&denied_subdir);
}
#[test]
fn test_nonexistant_file_no_error() {
let dir = tempfile::tempdir().unwrap();
let nonexistant = dir.path().join("bad");
let res = extrasafe::SafetyContext::new()
.enable(
SystemIO::nothing()
.allow_create_in_dir(nonexistant)
).unwrap()
.landlock_only()
.apply_to_current_thread();
assert!(res.is_ok(), "Errored when passing nonexistant file to landlock rule: {:?} ", res.unwrap_err());
}
#[test]
fn test_duplicate_path() {
let dir = tempfile::tempdir().unwrap();
let res = extrasafe::SafetyContext::new()
.enable(
SystemIO::nothing()
.allow_create_in_dir(&dir)
).unwrap()
.enable(
SystemIO::nothing()
.allow_create_in_dir(&dir)
);
assert!(res.is_err(), "Did not error on passing same dir in multiple rulesets");
let err = res.unwrap_err();
assert!(err.to_string().contains("The same path"));
assert!(err.to_string().contains("was used in two different landlock rules."));
}