tegra-rcm 0.7.1

A library to help exploit the bootROM exploit for the Tegra X1's RCM mode
Documentation
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;

// our manual ioctl read
ioctl_read!(usb_submit_urb, IOCTL_MAGIC, IOCTL_TYPE, UsbUrb);

/// The C data structure that is used for Usb Urb requests
#[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"),
            // rest don't need to be touched
            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")
        };

        // We should have a success
        Ok(())
    }

    fn validate_environment(&self) -> crate::Result<()> {
        if !self.validate_environment() {
            return Err(SwitchError::LinuxEnv);
        }
        Ok(())
    }
}

impl SwitchHandle {
    /// We can only inject giant control requests on devices that are backed
    /// by certain usb controllers-- typically, the xhci_hcd on most PCs.
    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
    }

    /// Checks to see if the given sysfs node matches our given device.
    /// Can be used to check if an xhci_hcd controller subnode reflects a given device.
    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
    }
}

/// Reads a numeric value from a sysfs file that contains only a number.
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()
}