use nix::ioctl_read;
use std::fs;
use std::os::unix::prelude::AsRawFd;
use std::path::Path;
use super::Vulnerability;
use crate::SwitchError;
use crate::SwitchHandle;
const IOCTL_MAGIC: u8 = b'U';
const IOCTL_TYPE: u8 = 10;
ioctl_read!(usb_submit_urb, IOCTL_MAGIC, IOCTL_TYPE, UsbUrb);
#[derive(Debug)]
#[repr(C)]
pub struct UsbUrb {
r#type: u8,
endpoint: u8,
status: i32,
flags: u32,
buffer: *mut u8,
buffer_length: i32,
actual_length: i32,
start_frame: i32,
stream_id: u32,
error_count: i32,
signr: u32,
usercontext: *mut u8,
}
impl UsbUrb {
fn new(buf: &mut [u8]) -> Self {
const URB_CONTROL_REQUEST: u8 = 2;
const ENDPOINT: u8 = 0;
Self {
r#type: URB_CONTROL_REQUEST,
endpoint: ENDPOINT,
buffer: buf.as_mut_ptr(),
buffer_length: buf
.len()
.try_into()
.expect("Buffer length should not exceed i32"),
status: 0,
flags: 0,
actual_length: 0,
start_frame: 0,
stream_id: 0,
error_count: 0,
signr: 0,
usercontext: std::ptr::null_mut(),
}
}
}
impl Vulnerability for SwitchHandle {
fn backend_name() -> &'static str {
"linux"
}
fn trigger(&self, length: usize) -> Result<(), SwitchError> {
const GET_STATUS: u8 = 0x0;
const STANDARD_REQUEST_DEVICE_TO_HOST_TO_ENDPOINT: u8 = 0x82;
let file_path = format!(
"/dev/bus/usb/{:03}/{:03}",
self.handle.device().bus_number(),
self.handle.device().address()
);
let Ok(file) = fs::File::options().read(true).write(true).open(file_path) else {
return Err(SwitchError::LinuxEnv);
};
let fd = file.as_raw_fd();
let mut setup_packet = Vec::with_capacity(length + 8);
setup_packet.extend(STANDARD_REQUEST_DEVICE_TO_HOST_TO_ENDPOINT.to_le_bytes());
setup_packet.extend(GET_STATUS.to_le_bytes());
setup_packet.extend(0u16.to_le_bytes());
setup_packet.extend(0u16.to_le_bytes());
setup_packet.extend((length as u16).to_le_bytes());
setup_packet.resize(setup_packet.len() + length, b'\0');
let mut usb_urb = UsbUrb::new(&mut setup_packet);
unsafe {
usb_submit_urb(fd, &mut usb_urb).expect("The manual ioctl should have succeeded")
};
Ok(())
}
fn validate_environment(&self) -> crate::Result<()> {
if !self.validate_environment() {
return Err(SwitchError::LinuxEnv);
}
Ok(())
}
}
impl SwitchHandle {
fn validate_environment(&self) -> bool {
const SUPPORTED_USB_CONTROLLERS: [&str; 2] =
["pci/drivers/xhci_hcd", "platform/drivers/dwc_otg"];
for hci_name in SUPPORTED_USB_CONTROLLERS {
let glob = glob::glob(&format!("/sys/bus/{hci_name}/*/usb*")).unwrap();
for path in glob {
if self.node_matches_our_device(&path.unwrap()) {
return true;
}
}
}
false
}
fn node_matches_our_device(&self, path: &Path) -> bool {
let bus_path = path.join("busnum");
if !bus_path.exists() {
return false;
}
let Some(num) = read_num_file(&bus_path) else {
return false;
};
if self.handle.device().bus_number() != num {
return false;
}
true
}
}
fn read_num_file(path: &Path) -> Option<u8> {
let Ok(data) = fs::read(path) else {
return None;
};
String::from_utf8_lossy(&data).trim().parse().ok()
}