Skip to content

Commit

Permalink
Merge pull request #398 from jbaublitz/panics-in-tests
Browse files Browse the repository at this point in the history
Handle panics in test infrastructure
jbaublitz authored Jan 27, 2025
2 parents ba5ac9a + 1ca5d3f commit 3c97c48
Showing 6 changed files with 247 additions and 147 deletions.
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -96,6 +96,13 @@ mod test {
tests::encrypt::test_encrypt_by_password();
}

#[ignore]
#[test]
#[cfg(cryptsetup24supported)]
fn test_reencrypt_by_password() {
tests::reencrypt::test_reencrypt_by_password();
}

#[ignore]
#[test]
fn test_encrypt_by_keyfile() {
222 changes: 112 additions & 110 deletions src/tests/encrypt.rs
Original file line number Diff line number Diff line change
@@ -29,96 +29,112 @@ use rand::random;
/// and unencrypted devices.
const WINDOW_SIZE: usize = 1024 * 1024;

fn init(dev_path: &Path, passphrase: &str) -> Result<c_uint, LibcryptErr> {
let mut dev = CryptInit::init(dev_path)?;
dev.context_handle().format::<()>(
EncryptionFormat::Luks2,
("aes", "xts-plain"),
None,
Either::Right(512 / 8),
None,
)?;
fn init(dev_path: &Path, passphrase: &str) -> c_uint {
let mut dev = CryptInit::init(dev_path).unwrap();
dev.context_handle()
.format::<()>(
EncryptionFormat::Luks2,
("aes", "xts-plain"),
None,
Either::Right(512 / 8),
None,
)
.unwrap();
dev.keyslot_handle()
.add_by_key(None, None, passphrase.as_bytes(), CryptVolumeKey::empty())
.unwrap()
}

/// This method initializes the device with no encryption as a way to test
/// that the plaintext can be read vs. the plaintext not being found due to
/// proper encryption in the other tests.
fn init_null_cipher(dev_path: &Path) -> Result<c_uint, LibcryptErr> {
let mut dev = CryptInit::init(dev_path)?;
dev.context_handle().format::<()>(
EncryptionFormat::Luks1,
("cipher_null", "ecb"),
None,
Either::Right(32),
None,
)?;
dev.keyslot_handle().add_by_passphrase(None, b"", b"")
fn init_null_cipher(dev_path: &Path) -> c_uint {
let mut dev = CryptInit::init(dev_path).unwrap();
dev.context_handle()
.format::<()>(
EncryptionFormat::Luks1,
("cipher_null", "ecb"),
None,
Either::Right(32),
None,
)
.unwrap();
dev.keyslot_handle()
.add_by_passphrase(None, b"", b"")
.unwrap()
}

fn init_by_keyfile(dev_path: &Path, keyfile_path: &Path) -> Result<c_uint, LibcryptErr> {
let mut dev = CryptInit::init(dev_path)?;
dev.context_handle().format::<()>(
EncryptionFormat::Luks2,
("aes", "xts-plain"),
None,
Either::Right(512 / 8),
None,
)?;
fn init_by_keyfile(dev_path: &Path, keyfile_path: &Path) -> c_uint {
let mut dev = CryptInit::init(dev_path).unwrap();
dev.context_handle()
.format::<()>(
EncryptionFormat::Luks2,
("aes", "xts-plain"),
None,
Either::Right(512 / 8),
None,
)
.unwrap();
let keyfile_contents = {
let mut kf_handle = dev.keyfile_handle();
kf_handle.device_read(keyfile_path, 0, None, CryptKeyfile::empty())?
kf_handle
.device_read(keyfile_path, 0, None, CryptKeyfile::empty())
.unwrap()
};
dev.keyslot_handle().add_by_key(
None,
None,
keyfile_contents.as_ref(),
CryptVolumeKey::empty(),
)
dev.keyslot_handle()
.add_by_key(
None,
None,
keyfile_contents.as_ref(),
CryptVolumeKey::empty(),
)
.unwrap()
}

fn activate_without_explicit_format(
dev_path: &Path,
device_name: &'static str,
keyslot: c_uint,
passphrase: &'static str,
) -> Result<(), LibcryptErr> {
let mut dev = CryptInit::init(dev_path)?;
dev.context_handle().load::<()>(None, None)?;
dev.activate_handle().activate_by_passphrase(
Some(device_name),
Some(keyslot),
passphrase.as_bytes(),
CryptActivate::empty(),
)?;
Ok(())
) {
let mut dev = CryptInit::init(dev_path).unwrap();
dev.context_handle().load::<()>(None, None).unwrap();
dev.activate_handle()
.activate_by_passphrase(
Some(device_name),
Some(keyslot),
passphrase.as_bytes(),
CryptActivate::empty(),
)
.unwrap();
}

fn activate_by_passphrase(
dev_path: &Path,
device_name: &'static str,
keyslot: c_uint,
passphrase: &'static str,
) -> Result<(), LibcryptErr> {
let mut dev = CryptInit::init(dev_path)?;
) {
let mut dev = CryptInit::init(dev_path).unwrap();
dev.context_handle()
.load::<()>(Some(EncryptionFormat::Luks2), None)?;
dev.activate_handle().activate_by_passphrase(
Some(device_name),
Some(keyslot),
passphrase.as_bytes(),
CryptActivate::empty(),
)?;
Ok(())
.load::<()>(Some(EncryptionFormat::Luks2), None)
.unwrap();
dev.activate_handle()
.activate_by_passphrase(
Some(device_name),
Some(keyslot),
passphrase.as_bytes(),
CryptActivate::empty(),
)
.unwrap();
}

fn create_keyfile(loopback_file_path: &Path) -> Result<PathBuf, LibcryptErr> {
fn create_keyfile(loopback_file_path: &Path) -> PathBuf {
let path = PathBuf::from(format!("{}-key", loopback_file_path.display()));
let mut f = File::create(&path).map_err(LibcryptErr::IOError)?;
let mut f = File::create(&path).unwrap();
let random: Vec<_> = (0..4096).map(|_| random::<u8>()).collect();
f.write(&random).map_err(LibcryptErr::IOError)?;
Ok(path)
f.write(&random).unwrap();
path
}

fn activate_by_keyfile(
@@ -127,31 +143,29 @@ fn activate_by_keyfile(
keyslot: c_uint,
keyfile_path: &Path,
keyfile_size: Option<crate::size_t>,
) -> Result<(), LibcryptErr> {
let mut dev = CryptInit::init(dev_path)?;
) {
let mut dev = CryptInit::init(dev_path).unwrap();
dev.context_handle()
.load::<()>(Some(EncryptionFormat::Luks2), None)?;
dev.activate_handle().activate_by_keyfile_device_offset(
Some(device_name),
Some(keyslot),
keyfile_path,
keyfile_size,
0,
CryptActivate::empty(),
)?;
Ok(())
.load::<()>(Some(EncryptionFormat::Luks2), None)
.unwrap();
dev.activate_handle()
.activate_by_keyfile_device_offset(
Some(device_name),
Some(keyslot),
keyfile_path,
keyfile_size,
0,
CryptActivate::empty(),
)
.unwrap();
}

fn activate_null_cipher(dev_path: &Path, device_name: &'static str) -> Result<(), LibcryptErr> {
let mut dev = CryptInit::init(dev_path)?;
dev.context_handle().load::<()>(None, None)?;
dev.activate_handle().activate_by_passphrase(
Some(device_name),
None,
b"",
CryptActivate::empty(),
)?;
Ok(())
fn activate_null_cipher(dev_path: &Path, device_name: &'static str) {
let mut dev = CryptInit::init(dev_path).unwrap();
dev.context_handle().load::<()>(None, None).unwrap();
dev.activate_handle()
.activate_by_passphrase(Some(device_name), None, b"", CryptActivate::empty())
.unwrap();
}

fn write_random(device_name: &str) -> Result<Box<[u8]>, io::Error> {
@@ -238,16 +252,13 @@ pub fn test_encrypt_by_password() {
let device_name = "test-device";
let passphrase = "abadpassphrase";

let keyslot = init(dev_path, passphrase)?;
activate_by_passphrase(dev_path, device_name, keyslot, passphrase)?;
if run_plaintext_test(file_path, device_name)? {
return Err(LibcryptErr::Other("Should not find plaintext".to_string()));
let keyslot = init(dev_path, passphrase);
activate_by_passphrase(dev_path, device_name, keyslot, passphrase);
if run_plaintext_test(file_path, device_name).unwrap() {
panic!("Should not find plaintext");
}

Ok(())
},
)
.expect("Should succeed");
}

pub fn test_encrypt_by_keyfile() {
@@ -258,17 +269,14 @@ pub fn test_encrypt_by_keyfile() {
|dev_path, file_path| {
let device_name = "test-device";

let keyfile_path = create_keyfile(file_path)?;
let keyslot = init_by_keyfile(dev_path, keyfile_path.as_path())?;
activate_by_keyfile(dev_path, device_name, keyslot, keyfile_path.as_path(), None)?;
if run_plaintext_test(file_path, device_name)? {
return Err(LibcryptErr::Other("Should not find plaintext".to_string()));
let keyfile_path = create_keyfile(file_path);
let keyslot = init_by_keyfile(dev_path, keyfile_path.as_path());
activate_by_keyfile(dev_path, device_name, keyslot, keyfile_path.as_path(), None);
if run_plaintext_test(file_path, device_name).unwrap() {
panic!("Should not find plaintext");
}

Ok(())
},
)
.expect("Should succeed");
}

pub fn test_encrypt_by_password_without_explicit_format() {
@@ -280,16 +288,13 @@ pub fn test_encrypt_by_password_without_explicit_format() {
let device_name = "test-device";
let passphrase = "abadpassphrase";

let keyslot = init(dev_path, passphrase)?;
activate_without_explicit_format(dev_path, device_name, keyslot, passphrase)?;
if run_plaintext_test(file_path, device_name)? {
return Err(LibcryptErr::Other("Should not find plaintext".to_string()));
let keyslot = init(dev_path, passphrase);
activate_without_explicit_format(dev_path, device_name, keyslot, passphrase);
if run_plaintext_test(file_path, device_name).unwrap() {
panic!("Should not find plaintext");
}

Ok(())
},
)
.expect("Should succeed");
}

pub fn test_unencrypted() {
@@ -300,14 +305,11 @@ pub fn test_unencrypted() {
|dev_path, file_path| {
let device_name = "test-device";

init_null_cipher(dev_path)?;
activate_null_cipher(dev_path, device_name)?;
if !run_plaintext_test(file_path, device_name)? {
return Err(LibcryptErr::Other("Should find plaintext".to_string()));
init_null_cipher(dev_path);
activate_null_cipher(dev_path, device_name);
if !run_plaintext_test(file_path, device_name).unwrap() {
panic!("Should find plaintext");
}

Ok(())
},
)
.expect("Should succeed");
}
16 changes: 6 additions & 10 deletions src/tests/keyfile.rs
Original file line number Diff line number Diff line change
@@ -2,28 +2,27 @@ use std::{env, fs::File, io::Write, path::PathBuf};

use super::loopback;

use crate::{consts::flags::CryptKeyfile, CryptInit, LibcryptErr};
use crate::{consts::flags::CryptKeyfile, CryptInit};

pub fn test_keyfile_cleanup() {
loopback::use_loopback(
50 * 1024 * 1024,
super::format_with_zeros(),
super::do_cleanup(),
|dev_path, _file_path| {
let mut device = CryptInit::init(dev_path)?;
let mut device = CryptInit::init(dev_path).unwrap();
let mut key_path =
PathBuf::from(env::var("TEST_DIR").unwrap_or_else(|_| "/tmp".to_string()));
key_path.push("safe-free-test-keyfile");
let mut f = File::create(&key_path).map_err(LibcryptErr::IOError)?;
f.write(b"this is a test password")
.map_err(LibcryptErr::IOError)?;
let mut f = File::create(&key_path).unwrap();
f.write(b"this is a test password").unwrap();
let keyfile_contents =
device
.keyfile_handle()
.device_read(&key_path, 0, None, CryptKeyfile::empty());
std::fs::remove_file(&key_path).map_err(LibcryptErr::IOError)?;
std::fs::remove_file(&key_path).unwrap();
let (keyfile_ptr, keyfile_len) = {
let keyfile_contents = keyfile_contents?;
let keyfile_contents = keyfile_contents.unwrap();

let keyfile_ref = keyfile_contents.as_ref();
assert_eq!(keyfile_ref, b"this is a test password" as &[u8]);
@@ -36,9 +35,6 @@ pub fn test_keyfile_cleanup() {
if dangling_buffer == b"this is a test password" {
panic!("Key was not cleaned up!");
}

Ok(())
},
)
.expect("Should succeed");
}
42 changes: 15 additions & 27 deletions src/tests/loopback.rs
Original file line number Diff line number Diff line change
@@ -6,15 +6,14 @@ use std::{
env,
fs::{remove_file, File},
io::{self, Write},
panic::{self, RefUnwindSafe},
path::{Path, PathBuf},
};

use base64::Engine;
use loopdev::LoopControl;
use rand::random;

use crate::err::LibcryptErr;

fn setup_backing_file(size_in_bytes: usize, with_zeros: bool) -> Result<PathBuf, io::Error> {
let mut i = 0;

@@ -43,36 +42,25 @@ fn setup_backing_file(size_in_bytes: usize, with_zeros: bool) -> Result<PathBuf,
Ok(file_path)
}

pub fn use_loopback<F>(
file_size: usize,
with_zeros: bool,
cleanup: bool,
func: F,
) -> Result<(), LibcryptErr>
pub fn use_loopback<F>(file_size: usize, with_zeros: bool, cleanup: bool, func: F)
where
F: Fn(&Path, &Path) -> Result<(), LibcryptErr>,
F: Fn(&Path, &Path) + RefUnwindSafe,
{
if !nix::unistd::Uid::effective().is_root() {
panic!("Must be root to run tests");
}
let ctrl = LoopControl::open();
let dev = ctrl
.and_then(|ref c| c.next_free())
.map_err(LibcryptErr::IOError)?;
let dev = ctrl.and_then(|ref c| c.next_free()).unwrap();

let path = setup_backing_file(file_size, with_zeros).map_err(LibcryptErr::IOError)?;
let attach_result = dev.attach_file(&path);
let test_result = attach_result
.map_err(LibcryptErr::IOError)
.and_then(|_| match dev.path() {
Some(ref d) => func(d, &path),
_ => Err(LibcryptErr::IOError(io::Error::from(
io::ErrorKind::NotFound,
))),
});
let detach_result = if cleanup { dev.detach() } else { Ok(()) };
detach_result
.and_then(|_| if cleanup { remove_file(&path) } else { Ok(()) })
.map_err(LibcryptErr::IOError)
.and(test_result)
let path = setup_backing_file(file_size, with_zeros).unwrap();
dev.attach_file(&path).unwrap();
let test_result = panic::catch_unwind(|| match dev.path() {
Some(ref d) => func(d, &path),
None => panic!("No path for loopback device"),
});
if cleanup {
dev.detach().unwrap();
remove_file(&path).unwrap();
}
test_result.unwrap()
}
2 changes: 2 additions & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -7,6 +7,8 @@ use std::env::var;
pub mod encrypt;
pub mod keyfile;
pub mod loopback;
#[cfg(cryptsetup24supported)]
pub mod reencrypt;

fn format_with_zeros() -> bool {
var("FORMAT_WITH_ZEROS")
105 changes: 105 additions & 0 deletions src/tests/reencrypt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use crate::{
consts::{
flags::{CryptActivate, CryptDeactivate, CryptReencrypt, CryptVolumeKey},
vals::{CryptReencryptDirectionInfo, CryptReencryptModeInfo, EncryptionFormat},
},
device::CryptInit,
get_sector_size,
tests::loopback,
CryptParamsLuks2, CryptParamsReencrypt, Either,
};

pub fn test_reencrypt_by_password() {
loopback::use_loopback(
50 * 1024 * 1024,
super::format_with_zeros(),
super::do_cleanup(),
|dev_path, _file_path| {
let mut dev = CryptInit::init(dev_path).unwrap();
dev.context_handle()
.format::<()>(
EncryptionFormat::Luks2,
("aes", "xts-plain"),
None,
Either::Right(512 / 8),
None,
)
.unwrap();

dev.keyslot_handle()
.add_by_key(
None,
None,
"thisisatest".as_bytes(),
CryptVolumeKey::empty(),
)
.unwrap();

let new_keyslot = dev
.keyslot_handle()
.add_by_key(
None,
Some(Either::Right(512 / 8)),
"thisisatest".as_bytes(),
CryptVolumeKey::NO_SEGMENT,
)
.unwrap();

dev.activate_handle()
.activate_by_passphrase(
Some("test-device"),
None,
"thisisatest".as_bytes(),
CryptActivate::empty(),
)
.unwrap();

let size = match get_sector_size(Some(&mut dev)) {
i if i < 0 => panic!("Received error: {i:?}"),
i => i as u32,
};
let cipher = dev.status_handle().get_cipher().unwrap();
let cipher_mode = dev.status_handle().get_cipher_mode().unwrap();

dev.reencrypt_handle()
.reencrypt_init_by_passphrase(
Some("test-device"),
"thisisatest".as_bytes(),
None,
Some(new_keyslot),
Some((&cipher, &cipher_mode)),
CryptParamsReencrypt {
mode: CryptReencryptModeInfo::Reencrypt,
direction: CryptReencryptDirectionInfo::Forward,
resilience: "checksum".to_string(),
hash: "sha256".to_string(),
data_shift: 0,
max_hotzone_size: 0,
device_size: 0,
luks2: CryptParamsLuks2 {
data_alignment: 0,
data_device: None,
integrity: None,
integrity_params: None,
pbkdf: None,
label: None,
sector_size: size,
subsystem: None,
},
flags: CryptReencrypt::empty(),
},
)
.unwrap();

dev.reencrypt_handle().reencrypt2::<()>(None, None).unwrap();

dev.activate_handle()
.deactivate("test-device", CryptDeactivate::empty())
.unwrap();
},
)
}

0 comments on commit 3c97c48

Please sign in to comment.