//! USB Storage Control Module //! //! Este modulo implementa o controle de dispositivos de armazenamento USB no Windows. //! Utiliza duas abordagens complementares: //! 1. Removable Storage Access Policy (via registro do Windows) //! 2. USBSTOR driver control (como fallback/reforco) //! //! IMPORTANTE: Requer privilegios de administrador para funcionar. use serde::{Deserialize, Serialize}; use std::io; use thiserror::Error; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum UsbPolicy { Allow, BlockAll, Readonly, } impl UsbPolicy { pub fn from_str(s: &str) -> Option { match s.to_uppercase().as_str() { "ALLOW" => Some(Self::Allow), "BLOCK_ALL" => Some(Self::BlockAll), "READONLY" => Some(Self::Readonly), _ => None, } } pub fn as_str(&self) -> &'static str { match self { Self::Allow => "ALLOW", Self::BlockAll => "BLOCK_ALL", Self::Readonly => "READONLY", } } } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UsbPolicyResult { pub success: bool, pub policy: String, pub error: Option, pub applied_at: Option, } #[derive(Error, Debug)] #[allow(dead_code)] pub enum UsbControlError { #[error("Politica USB invalida: {0}")] InvalidPolicy(String), #[error("Erro de registro do Windows: {0}")] RegistryError(String), #[error("Permissao negada - requer privilegios de administrador")] PermissionDenied, #[error("Sistema operacional nao suportado")] UnsupportedOs, #[error("Erro de I/O: {0}")] Io(#[from] io::Error), } #[cfg(target_os = "windows")] mod windows_impl { use super::*; use std::fs; use std::path::PathBuf; use std::process::Command; use winreg::enums::*; use winreg::RegKey; // GUID para Removable Storage Devices (Disk) const REMOVABLE_STORAGE_GUID: &str = "{53f56307-b6bf-11d0-94f2-00a0c91efb8b}"; // Chaves de registro const REMOVABLE_STORAGE_PATH: &str = r"Software\Policies\Microsoft\Windows\RemovableStorageDevices"; const USBSTOR_PATH: &str = r"SYSTEM\CurrentControlSet\Services\USBSTOR"; const STORAGE_POLICY_PATH: &str = r"SYSTEM\CurrentControlSet\Control\StorageDevicePolicies"; pub fn apply_usb_policy(policy: UsbPolicy) -> Result { let now = chrono::Utc::now().timestamp_millis(); let direct_result = try_apply_policy_direct(policy); match direct_result { Ok(()) => Ok(UsbPolicyResult { success: true, policy: policy.as_str().to_string(), error: None, applied_at: Some(now), }), Err(err) => { // Se faltou permissão, retorna erro - o serviço deve ser usado // Não fazemos elevação aqui para evitar UAC adicional if is_permission_error(&err) { return Err(UsbControlError::PermissionDenied); } Err(err) } } } fn try_apply_policy_direct(policy: UsbPolicy) -> Result<(), UsbControlError> { // 1. Aplicar Removable Storage Access Policy apply_removable_storage_policy(policy)?; // 2. Aplicar USBSTOR como reforco apply_usbstor_policy(policy)?; // 3. Aplicar WriteProtect se necessario if policy == UsbPolicy::Readonly { apply_write_protect(true)?; } else { apply_write_protect(false)?; } Ok(()) } fn apply_removable_storage_policy(policy: UsbPolicy) -> Result<(), UsbControlError> { let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); let full_path = format!(r"{}\{}", REMOVABLE_STORAGE_PATH, REMOVABLE_STORAGE_GUID); match policy { UsbPolicy::Allow => { // Tenta remover as restricoes, se existirem if let Ok(key) = hklm.open_subkey_with_flags(&full_path, KEY_ALL_ACCESS) { let _ = key.delete_value("Deny_Read"); let _ = key.delete_value("Deny_Write"); let _ = key.delete_value("Deny_Execute"); } // Tenta remover a chave inteira se estiver vazia let _ = hklm.delete_subkey(&full_path); } UsbPolicy::BlockAll => { let (key, _) = hklm .create_subkey(&full_path) .map_err(map_winreg_error)?; key.set_value("Deny_Read", &1u32) .map_err(map_winreg_error)?; key.set_value("Deny_Write", &1u32) .map_err(map_winreg_error)?; key.set_value("Deny_Execute", &1u32) .map_err(map_winreg_error)?; } UsbPolicy::Readonly => { let (key, _) = hklm .create_subkey(&full_path) .map_err(map_winreg_error)?; // Permite leitura, bloqueia escrita key.set_value("Deny_Read", &0u32) .map_err(map_winreg_error)?; key.set_value("Deny_Write", &1u32) .map_err(map_winreg_error)?; key.set_value("Deny_Execute", &0u32) .map_err(map_winreg_error)?; } } Ok(()) } fn apply_usbstor_policy(policy: UsbPolicy) -> Result<(), UsbControlError> { let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); let key = hklm .open_subkey_with_flags(USBSTOR_PATH, KEY_ALL_ACCESS) .map_err(map_winreg_error)?; match policy { UsbPolicy::Allow => { // Start = 3 habilita o driver key.set_value("Start", &3u32) .map_err(map_winreg_error)?; } UsbPolicy::BlockAll | UsbPolicy::Readonly => { // Start = 4 desabilita o driver // Nota: Para Readonly, mantemos o driver ativo mas com WriteProtect // Porem, como fallback de seguranca, desabilitamos para BlockAll if policy == UsbPolicy::BlockAll { key.set_value("Start", &4u32) .map_err(map_winreg_error)?; } else { // Readonly mantem driver ativo key.set_value("Start", &3u32) .map_err(map_winreg_error)?; } } } Ok(()) } fn apply_write_protect(enable: bool) -> Result<(), UsbControlError> { let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); if enable { let (key, _) = hklm .create_subkey(STORAGE_POLICY_PATH) .map_err(map_winreg_error)?; key.set_value("WriteProtect", &1u32) .map_err(map_winreg_error)?; } else if let Ok(key) = hklm.open_subkey_with_flags(STORAGE_POLICY_PATH, KEY_ALL_ACCESS) { let _ = key.set_value("WriteProtect", &0u32); } Ok(()) } pub fn get_current_policy() -> Result { let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); // Verifica Removable Storage Policy primeiro let full_path = format!(r"{}\{}", REMOVABLE_STORAGE_PATH, REMOVABLE_STORAGE_GUID); if let Ok(key) = hklm.open_subkey_with_flags(&full_path, KEY_READ) { let deny_read: u32 = key.get_value("Deny_Read").unwrap_or(0); let deny_write: u32 = key.get_value("Deny_Write").unwrap_or(0); if deny_read == 1 && deny_write == 1 { return Ok(UsbPolicy::BlockAll); } if deny_read == 0 && deny_write == 1 { return Ok(UsbPolicy::Readonly); } } // Verifica USBSTOR como fallback if let Ok(key) = hklm.open_subkey_with_flags(USBSTOR_PATH, KEY_READ) { let start: u32 = key.get_value("Start").unwrap_or(3); if start == 4 { return Ok(UsbPolicy::BlockAll); } } Ok(UsbPolicy::Allow) } fn is_permission_error(error: &UsbControlError) -> bool { match error { UsbControlError::PermissionDenied => true, UsbControlError::RegistryError(msg) => { let lower = msg.to_lowercase(); lower.contains("access is denied") || lower.contains("acesso negado") || lower.contains("5") } _ => false, } } #[allow(dead_code)] fn apply_policy_with_elevation(policy: UsbPolicy) -> Result<(), UsbControlError> { // Cria script temporário para aplicar as chaves via PowerShell elevado let temp_dir = std::env::temp_dir(); let script_path: PathBuf = temp_dir.join("raven_usb_policy.ps1"); let policy_str = policy.as_str(); let script = format!( r#"$ErrorActionPreference = 'Stop' $guid = '{guid}' $policy = '{policy}' function Set-Allow {{ reg delete 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /f 2>$null reg delete 'HKLM\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies' /f 2>$null reg add 'HKLM\SYSTEM\CurrentControlSet\Services\USBSTOR' /v Start /t REG_DWORD /d 3 /f | Out-Null }} function Set-BlockAll {{ reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /f | Out-Null reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /v Deny_Read /t REG_DWORD /d 1 /f | Out-Null reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /v Deny_Write /t REG_DWORD /d 1 /f | Out-Null reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /v Deny_Execute /t REG_DWORD /d 1 /f | Out-Null reg add 'HKLM\SYSTEM\CurrentControlSet\Services\USBSTOR' /v Start /t REG_DWORD /d 4 /f | Out-Null reg add 'HKLM\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies' /f | Out-Null reg add 'HKLM\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies' /v WriteProtect /t REG_DWORD /d 0 /f | Out-Null }} function Set-Readonly {{ reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /f | Out-Null reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /v Deny_Read /t REG_DWORD /d 0 /f | Out-Null reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /v Deny_Write /t REG_DWORD /d 1 /f | Out-Null reg add 'HKLM\Software\Policies\Microsoft\Windows\RemovableStorageDevices\{guid}' /v Deny_Execute /t REG_DWORD /d 0 /f | Out-Null reg add 'HKLM\SYSTEM\CurrentControlSet\Services\USBSTOR' /v Start /t REG_DWORD /d 3 /f | Out-Null reg add 'HKLM\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies' /f | Out-Null reg add 'HKLM\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies' /v WriteProtect /t REG_DWORD /d 1 /f | Out-Null }} switch ($policy) {{ 'ALLOW' {{ Set-Allow }} 'BLOCK_ALL' {{ Set-BlockAll }} 'READONLY' {{ Set-Readonly }} default {{ throw 'Politica invalida' }} }} try {{ gpupdate /target:computer /force | Out-Null }} catch {{}} "#, guid = REMOVABLE_STORAGE_GUID, policy = policy_str ); fs::write(&script_path, script).map_err(UsbControlError::Io)?; // Start-Process com RunAs para acionar UAC let arg = format!( "Start-Process -WindowStyle Hidden -FilePath powershell -Verb RunAs -Wait -ArgumentList '-ExecutionPolicy Bypass -File \"{}\"'", script_path.display() ); let status = Command::new("powershell") .arg("-Command") .arg(arg) .status() .map_err(UsbControlError::Io)?; if !status.success() { return Err(UsbControlError::PermissionDenied); } Ok(()) } fn map_winreg_error(error: io::Error) -> UsbControlError { if let Some(code) = error.raw_os_error() { if code == 5 { return UsbControlError::PermissionDenied; } } UsbControlError::RegistryError(error.to_string()) } pub fn refresh_group_policy() -> Result<(), UsbControlError> { use std::os::windows::process::CommandExt; use std::process::Command; const CREATE_NO_WINDOW: u32 = 0x08000000; // Executa gpupdate para forcar atualizacao das politicas let output = Command::new("gpupdate") .args(["/target:computer", "/force"]) .creation_flags(CREATE_NO_WINDOW) .output() .map_err(UsbControlError::Io)?; if !output.status.success() { // Nao e critico se falhar, apenas log eprintln!( "[usb_control] gpupdate retornou erro: {}", String::from_utf8_lossy(&output.stderr) ); } Ok(()) } } #[cfg(not(target_os = "windows"))] mod fallback_impl { use super::*; pub fn apply_usb_policy(_policy: UsbPolicy) -> Result { Err(UsbControlError::UnsupportedOs) } pub fn get_current_policy() -> Result { Err(UsbControlError::UnsupportedOs) } pub fn refresh_group_policy() -> Result<(), UsbControlError> { Err(UsbControlError::UnsupportedOs) } } #[cfg(target_os = "windows")] pub use windows_impl::*; #[cfg(not(target_os = "windows"))] pub use fallback_impl::*; #[cfg(test)] mod tests { use super::*; #[test] fn test_policy_from_str() { assert_eq!(UsbPolicy::from_str("ALLOW"), Some(UsbPolicy::Allow)); assert_eq!(UsbPolicy::from_str("BLOCK_ALL"), Some(UsbPolicy::BlockAll)); assert_eq!(UsbPolicy::from_str("READONLY"), Some(UsbPolicy::Readonly)); assert_eq!(UsbPolicy::from_str("allow"), Some(UsbPolicy::Allow)); assert_eq!(UsbPolicy::from_str("invalid"), None); } #[test] fn test_policy_as_str() { assert_eq!(UsbPolicy::Allow.as_str(), "ALLOW"); assert_eq!(UsbPolicy::BlockAll.as_str(), "BLOCK_ALL"); assert_eq!(UsbPolicy::Readonly.as_str(), "READONLY"); } }