-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
image-rs: add image block device dm-verity and mount
Signed-off-by: ChengyuZhu6 <[email protected]>
- Loading branch information
ChengyuZhu6
committed
Jul 10, 2023
1 parent
af35c06
commit f777e8e
Showing
4 changed files
with
264 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,3 +21,4 @@ pub mod signature; | |
pub mod snapshots; | ||
pub mod stream; | ||
pub mod unpack; | ||
pub mod verity; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
use anyhow::{anyhow,bail, Result}; | ||
use base64; | ||
use regex::Regex; | ||
use serde::{Deserialize, Serialize}; | ||
use serde_json; | ||
use std::fs; | ||
use std::os::unix::io::AsRawFd; | ||
use std::path::Path; | ||
use std::process::Command; | ||
|
||
const LOOP_SET_FD: u64 = 0x4C00; | ||
pub const VERITYSETUP_PATH: &[&str] = &["/sbin/veritysetup", "/usr/sbin/veritysetup"]; | ||
|
||
#[derive(Debug, Deserialize, Serialize)] | ||
pub struct DmVerityOption { | ||
/// Hash algorithm for dm-verity. | ||
pub hashtype: String, | ||
/// Used block size for the data device. | ||
pub blocksize: String, | ||
/// Used block size for the hash device. | ||
pub hashsize: String, | ||
/// Size of data device used in verification. | ||
pub blocknum: String, | ||
/// Offset of hash area/superblock on hash_device. | ||
pub offset: String, | ||
/// Root hash for device verification or activation. | ||
pub hash: String, | ||
} | ||
|
||
/// Creates a mapping with <name> backed by data_device <source_device_path> | ||
/// and using hash_device for in-kernel verification. | ||
/// It will return the verity block device Path "/dev/mapper/<name>" | ||
/// Notes: the data device and the hash device are the same one. | ||
pub fn create_verity_device( | ||
verity_option: &DmVerityOption, | ||
source_device_path: &str, | ||
) -> Result<String> { | ||
let veritysetup_path = veritysetup_exists()?; | ||
|
||
let output = Command::new(veritysetup_path) | ||
.args(&[ | ||
"open", | ||
"--no-superblock", | ||
"--format=1", | ||
"-s", | ||
"", | ||
&format!("--hash={}", verity_option.hashtype), | ||
&format!("--data-block-size={}", verity_option.blocksize), | ||
&format!("--hash-block-size={}", verity_option.hashsize), | ||
"--data-blocks", | ||
verity_option.blocknum.as_str(), | ||
"--hash-offset", | ||
verity_option.offset.as_str(), | ||
source_device_path, | ||
verity_option.hash.as_str(), | ||
source_device_path, | ||
verity_option.hash.as_str(), | ||
]) | ||
.output()?; | ||
if output.status.success() { | ||
return Ok(format!("{}{}", "/dev/mapper/", verity_option.hash.as_str())); | ||
} else { | ||
let error_message = String::from_utf8_lossy(&output.stderr); | ||
bail!("Failed to create dm-verity device: {}", error_message); | ||
} | ||
} | ||
pub fn decode_verity_options(verity_options: &str) -> Result<DmVerityOption> { | ||
let decoded = base64::decode(verity_options)?; | ||
let parsed_data = serde_json::from_slice::<DmVerityOption>(&decoded)?; | ||
Ok(parsed_data) | ||
} | ||
pub fn find_unused_loop_device() -> Result<String> { | ||
let output = Command::new("losetup").arg("-f").output()?; | ||
|
||
if output.status.success() { | ||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) | ||
} else { | ||
bail!("failed to find loop device"); | ||
} | ||
} | ||
pub fn attach_data_to_loop_device(data_path: &str, loop_device: &str) -> Result<()> { | ||
let loop_file = fs::OpenOptions::new() | ||
.read(true) | ||
.write(true) | ||
.open(loop_device)?; | ||
let loop_fd = loop_file.as_raw_fd(); | ||
let blob_file = fs::OpenOptions::new() | ||
.read(true) | ||
.write(true) | ||
.open(data_path)?; | ||
let blob_fd = blob_file.as_raw_fd(); | ||
unsafe { | ||
if libc::ioctl(loop_fd, LOOP_SET_FD, blob_fd) == -1 { | ||
bail!("can not attach data to a loop device"); | ||
} | ||
} | ||
Ok(()) | ||
} | ||
/// Calculates and permanently stores hash verification data for data_device. | ||
/// It will return the root hash. | ||
pub fn create_hash_device( | ||
verity_options: &mut DmVerityOption, | ||
data_device_path: &str, | ||
) -> Result<String> { | ||
let veritysetup_path: &str = veritysetup_exists()?; | ||
let output = Command::new(veritysetup_path) | ||
.args(&[ | ||
"format", | ||
"--no-superblock", | ||
"--format=1", | ||
"-s", | ||
"", | ||
&format!("--hash={}", verity_options.hashtype), | ||
&format!("--data-block-size={}", verity_options.blocksize), | ||
&format!("--hash-block-size={}", verity_options.hashsize), | ||
"--data-blocks", | ||
verity_options.blocknum.as_str(), | ||
"--hash-offset", | ||
verity_options.offset.as_str(), | ||
data_device_path, | ||
data_device_path, | ||
]) | ||
.output()?; | ||
if output.status.success() { | ||
let stdout_string = String::from_utf8_lossy(&output.stdout).to_string(); | ||
let re = Regex::new(r"Root hash:\s+([a-f0-9]+)") | ||
.map_err(|err| anyhow::anyhow!("Failed to create regex: {}", err))?; | ||
if let Some(captures) = re.captures(stdout_string.as_str()) { | ||
if let Some(root_hash) = captures.get(1).map(|m| m.as_str()) { | ||
return Ok(root_hash.to_string()); | ||
} | ||
} | ||
bail!("Failed to find root hash"); | ||
} else { | ||
let error_message = String::from_utf8_lossy(&output.stderr); | ||
bail!("Failed to create hash device: {}", error_message); | ||
} | ||
} | ||
pub fn close_verity_device(verity_device_path: &Path) -> Result<()> { | ||
let veritysetup_path: &str = veritysetup_exists()?; | ||
let file_name = verity_device_path | ||
.file_name() | ||
.and_then(|name| name.to_str()) | ||
.ok_or_else(|| anyhow::anyhow!("Failed to extract file name"))?; | ||
let output = Command::new(veritysetup_path) | ||
.args(&["close", file_name]) | ||
.output()?; | ||
|
||
if output.status.success() { | ||
Ok(()) | ||
} else { | ||
let error_message = String::from_utf8_lossy(&output.stderr); | ||
bail!("Failed to close verity device: {}", error_message); | ||
} | ||
} | ||
pub fn veritysetup_exists() -> Result<&'static str> { | ||
VERITYSETUP_PATH | ||
.iter() | ||
.find(|&path| Path::new(path).exists()) | ||
.copied() | ||
.ok_or_else(|| anyhow!("Veritysetup path not found")) | ||
} |