use crate::comm::dbus::validate_device_handle;
use crate::error::FpgadError;
use crate::platforms::platform::{platform_for_known_platform, platform_from_compat_or_device};
use crate::platforms::xilinx_sys::xilinx_sys_write_handler;
#[cfg(feature = "xilinx-dfx-mgr")]
use crate::softeners::xilinx_dfx_mgr::xilinx_dfx_mgr_helpers::run_dfx_mgr;
use log::{info, trace};
use std::path::Path;
use std::sync::Arc;
use tokio::sync::{Mutex, MutexGuard, OnceCell};
use zbus::{fdo, interface};
static WRITE_LOCK: OnceCell<Arc<Mutex<()>>> = OnceCell::const_new();
async fn get_write_lock_guard() -> MutexGuard<'static, ()> {
let lock = WRITE_LOCK
.get_or_init(|| async { Arc::new(Mutex::new(())) })
.await;
lock.lock().await
}
pub struct ControlInterface {}
#[interface(name = "com.canonical.fpgad.control")]
impl ControlInterface {
async fn write_bitstream_direct(
&self,
platform_string: &str,
device_handle: &str,
bitstream_path_str: &str,
firmware_lookup_path: &str,
) -> Result<String, fdo::Error> {
info!("load_firmware called with name: {device_handle} and path_str: {bitstream_path_str}");
validate_device_handle(device_handle)?;
let path = Path::new(bitstream_path_str);
let lookup = Path::new(firmware_lookup_path);
let _guard = get_write_lock_guard().await;
trace!("Got write lock.");
let platform = platform_from_compat_or_device(platform_string, device_handle)?;
Ok(platform.fpga(device_handle)?.load_firmware(path, lookup)?)
}
async fn apply_overlay(
&self,
platform_string: &str,
overlay_handle: &str,
overlay_source_path: &str,
firmware_lookup_path: &str,
) -> Result<String, fdo::Error> {
info!(
"apply_overlay called with platform_string: {platform_string}, overlay_handle: \
{overlay_handle} and overlay_path: {overlay_source_path}",
);
let _guard = get_write_lock_guard().await;
trace!("Got write lock.");
let platform = platform_for_known_platform(platform_string)?;
let overlay_handler = platform.overlay_handler(overlay_handle)?;
Ok(overlay_handler.apply_overlay(
Path::new(overlay_source_path),
Path::new(firmware_lookup_path),
)?)
}
async fn remove_overlay(
&self,
platform_string: &str,
overlay_handle: &str,
) -> Result<String, fdo::Error> {
info!(
"remove_overlay called with platform_string: {platform_string} and overlay_handle:\
{overlay_handle}"
);
let platform = platform_for_known_platform(platform_string)?;
let overlay_handler = platform.overlay_handler(overlay_handle)?;
let handle = match overlay_handle {
"" => None,
_ => Some(overlay_handle),
};
Ok(overlay_handler.remove_overlay(handle)?)
}
async fn remove_bitstream(
&self,
platform_string: &str,
device_handle: &str,
bitstream_handle: &str,
) -> Result<String, fdo::Error> {
info!(
"remove_bitstream called with platform_string: {platform_string}, device_handle:\
{device_handle} and bitstream_handle: {bitstream_handle}"
);
let platform = platform_from_compat_or_device(platform_string, device_handle)?;
let fpga = platform.fpga(device_handle)?;
let handle = match bitstream_handle {
"" => None,
_ => Some(bitstream_handle),
};
Ok(fpga.remove_firmware(handle)?)
}
async fn xlnx_sys(
&self,
sub_cmd: &str,
path_str: &str,
value_str: &str,
) -> Result<String, fdo::Error> {
info!("xlnx_sys (write) called with sub_cmd: {sub_cmd}, path_str: {path_str}");
xilinx_sys_write_handler(sub_cmd, path_str, value_str)
}
async fn dfx_mgr(&self, cmd_string: &str) -> Result<String, fdo::Error> {
#[cfg(feature = "xilinx-dfx-mgr")]
{
use tokio::task;
let cmd_owned = cmd_string.to_string();
let res = task::spawn_blocking(move || {
let args: Vec<&str> = cmd_owned.split_whitespace().collect();
if let Err(e) = validate_dfx_mgr_args(&args) {
return Err(FpgadError::Argument(format!(
"Invalid dfx-mgr arguments: {}",
e
)));
}
run_dfx_mgr(&args).map_err(|e| e.into())
})
.await
.map_err(|e| FpgadError::Internal(format!("dfx-mgr-client subprocess failed: {e}")))?;
match res {
Ok(output) => {
info!("dfx-mgr command ran successfully!");
Ok(output)
}
Err(e) => {
info!("dfx-mgr command failed: {}", e);
Err(e.into())
}
}
}
#[cfg(not(feature = "xilinx-dfx-mgr"))]
{
let _ = cmd_string;
Err(FpgadError::Feature(
"Cannot use DfxMgr method - FPGAd was compiled without xilinx-dfx-mgr feature"
.into(),
)
.into())
}
}
}
#[cfg(feature = "xilinx-dfx-mgr")]
fn validate_dfx_mgr_args(args: &[&str]) -> Result<(), String> {
const DANGEROUS_CHARS: &[char] = &[
';', '&', '|', '$', '`', '\n', '\r', '<', '>', '(', ')', '{', '}', '[', ']', '\\', '\'',
'"', '*', '?',
];
for (index, arg) in args.iter().enumerate() {
if let Some(dangerous_char) = arg.chars().find(|c| DANGEROUS_CHARS.contains(c)) {
return Err(format!(
"Argument at position {} contains dangerous character '{}': \"{}\". \
Shell metacharacters and special characters are not allowed in dfx-mgr commands.",
index, dangerous_char, arg
));
}
if arg.len() > 1024 {
return Err(format!(
"Argument at position {} is too long ({} characters). Maximum length is 1024 characters.",
index,
arg.len()
));
}
}
Ok(())
}
#[cfg(test)]
mod test_get_write_lock_guard {
use crate::comm::dbus::control_interface::get_write_lock_guard;
#[tokio::test]
async fn test_get_write_lock_guard() {
let _guard = get_write_lock_guard().await;
}
}
#[cfg(test)]
#[cfg(feature = "xilinx-dfx-mgr")]
mod test_validate_dfx_mgr_args {
use super::validate_dfx_mgr_args;
use googletest::prelude::*;
#[gtest]
fn test_validate_valid_commands() {
assert_that!(validate_dfx_mgr_args(&["-listPackage"]), ok(anything()));
assert_that!(validate_dfx_mgr_args(&["-listSlot"]), ok(anything()));
assert_that!(
validate_dfx_mgr_args(&["-load", "0", "my_design"]),
ok(anything())
);
assert_that!(validate_dfx_mgr_args(&["-remove", "0"]), ok(anything()));
assert_that!(
validate_dfx_mgr_args(&["-load", "1", "my-package_v2.0"]),
ok(anything())
);
assert_that!(
validate_dfx_mgr_args(&["-load", "0", "/path/to/package"]),
ok(anything())
);
assert_that!(validate_dfx_mgr_args(&["-list_Package"]), ok(anything()));
assert_that!(validate_dfx_mgr_args(&["-loadPackage123"]), ok(anything()));
}
#[gtest]
fn test_validate_shell_injection_attempts() {
let result = validate_dfx_mgr_args(&["-listPackage", ";", "rm", "-rf", "/"]);
assert_that!(result, err(contains_substring("dangerous character")));
let result = validate_dfx_mgr_args(&["-listPackage", "&", "sudo", "rm", "-rf", "/"]);
assert_that!(result, err(contains_substring("dangerous character")));
let result = validate_dfx_mgr_args(&["-listPackage", "|", "grep", "secret"]);
assert_that!(result, err(contains_substring("dangerous character")));
let result = validate_dfx_mgr_args(&["-load", "$HOME"]);
assert_that!(result, err(contains_substring("dangerous character")));
let result = validate_dfx_mgr_args(&["`whoami`"]);
assert_that!(result, err(contains_substring("dangerous character")));
let result = validate_dfx_mgr_args(&["-listPackage", ">", "/tmp/output"]);
assert_that!(result, err(contains_substring("dangerous character")));
let result = validate_dfx_mgr_args(&["-listPackage", "<", "/etc/passwd"]);
assert_that!(result, err(contains_substring("dangerous character")));
let result = validate_dfx_mgr_args(&["-load", "*"]);
assert_that!(result, err(contains_substring("dangerous character")));
let result = validate_dfx_mgr_args(&["\"malicious\""]);
assert_that!(result, err(contains_substring("dangerous character")));
let result = validate_dfx_mgr_args(&["'malicious'"]);
assert_that!(result, err(contains_substring("dangerous character")));
}
#[gtest]
fn test_validate_allows_special_characters() {
assert_that!(
validate_dfx_mgr_args(&["-load", "0", "package@version"]),
ok(anything())
);
assert_that!(
validate_dfx_mgr_args(&["-load", "0", "package+variant"]),
ok(anything())
);
assert_that!(
validate_dfx_mgr_args(&["-load", "0", "design#1"]),
ok(anything())
);
}
#[gtest]
fn test_validate_allows_unicode() {
assert_that!(
validate_dfx_mgr_args(&["-load", "0", "设计文件"]),
ok(anything())
); assert_that!(
validate_dfx_mgr_args(&["-load", "0", "ファイル"]),
ok(anything())
); assert_that!(
validate_dfx_mgr_args(&["-load", "0", "файл"]),
ok(anything())
); }
#[gtest]
fn test_validate_long_arguments() {
let long_arg = "a".repeat(1025);
let result = validate_dfx_mgr_args(&["-load", "0", &long_arg]);
assert_that!(result, err(contains_substring("too long")));
let boundary_arg = "a".repeat(1024);
assert_that!(
validate_dfx_mgr_args(&["-load", "0", &boundary_arg]),
ok(anything())
);
}
#[gtest]
fn test_validate_newlines_and_control_chars() {
let result = validate_dfx_mgr_args(&["-list\nPackage"]);
assert_that!(result, err(contains_substring("dangerous character")));
let result = validate_dfx_mgr_args(&["-list\rPackage"]);
assert_that!(result, err(contains_substring("dangerous character")));
}
#[gtest]
fn test_validate_parentheses_and_brackets() {
let result = validate_dfx_mgr_args(&["$(whoami)"]);
assert_that!(result, err(contains_substring("dangerous character")));
let result = validate_dfx_mgr_args(&["(ls)"]);
assert_that!(result, err(contains_substring("dangerous character")));
let result = validate_dfx_mgr_args(&["[test]"]);
assert_that!(result, err(contains_substring("dangerous character")));
}
}