diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index 8aebff4143..70e7e90ebe 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -129,6 +129,7 @@ fn generate_micropython_bindings() { .allowlist_function("trezor_obj_get_ll_checked") .allowlist_function("trezor_obj_get_ull_checked") .allowlist_function("trezor_obj_str_from_rom_text") + .allowlist_var("mp_const_empty_tuple_obj") // buffer .allowlist_function("mp_get_buffer") .allowlist_var("MP_BUFFER_READ") diff --git a/core/embed/rust/librust.h b/core/embed/rust/librust.h index a3d943e202..375923e321 100644 --- a/core/embed/rust/librust.h +++ b/core/embed/rust/librust.h @@ -1,5 +1,7 @@ #include "librust_qstr.h" +mp_obj_t io_usb_start(mp_obj_t serial_number); + mp_obj_t protobuf_type_for_name(mp_obj_t name); mp_obj_t protobuf_type_for_wire(mp_obj_t wire_id); mp_obj_t protobuf_decode(mp_obj_t buf, mp_obj_t def, @@ -16,4 +18,4 @@ extern mp_obj_module_t mp_module_trezorui2; #ifdef TREZOR_EMULATOR mp_obj_t ui_debug_layout_type(); -#endif +#endif \ No newline at end of file diff --git a/core/embed/rust/src/io.rs b/core/embed/rust/src/io.rs index cb3dc1b5bc..7538d075b1 100644 --- a/core/embed/rust/src/io.rs +++ b/core/embed/rust/src/io.rs @@ -1,7 +1,16 @@ +use core::ops::RangeFrom; + +use cstr_core::{cstr, CStr}; + use crate::{ error::Error, + micropython::{buffer::Buffer, obj::Obj}, time::{Duration, Instant}, - trezorhal::usb::{usb_is_ready_to_read, usb_is_ready_to_write, IfaceTicket}, + trezorhal::usb::{ + usb_is_ready_to_read, usb_is_ready_to_write, usb_open, IfaceTicket, UsbConfig, UsbError, + WebUsbConfig, + }, + util, util::wait_in_busy_loop, }; @@ -87,3 +96,190 @@ pub fn poll_io(resources: &[Resource], timeout: Duration) -> Result Obj { + #[cfg(feature = "ui_debug")] + use crate::trezorhal::usb::VcpConfig; + + #[cfg(not(feature = "bitcoin_only"))] + use crate::trezorhal::usb::HidConfig; + + // SAFETY: + // In this function we often take mut refs to static mut buffers. This requires + // an unsafe block. Safety rationale: + // - We are in a single-threaded context. + // - `io_usb_start` is required to be called with USB turned off. + // - Underlying USB stack is required to manage the buffers on its own, + // guarding i.e. against escaping of stale buffer content. + + const UDP_PORT: u16 = 0; + const WIRE_PORT_OFFSET: u16 = 0; + const DEBUGLINK_PORT_OFFSET: u16 = 1; + const WEBAUTHN_PORT_OFFSET: u16 = 2; + const VCP_PORT_OFFSET: u16 = 3; + + fn usb_config(serial_number: &CStr) -> UsbConfig { + UsbConfig { + vendor_id: 0x1209, + product_id: 0x53C1, + release_num: 0x0200, + manufacturer: cstr!("SatoshiLabs"), + product: cstr!("TREZOR"), + serial_number: set_global_serial_number(serial_number), + interface: cstr!("TREZOR Interface"), + usb21_landing: false, + ..UsbConfig::default() + } + } + + fn create_wire_iface(ids: &mut RangeFrom) -> WebUsbConfig { + static mut WIRE_RX_BUFFER: [u8; 64] = [0; 64]; + + let id = ids.next().unwrap(); + WebUsbConfig { + rx_buffer: unsafe { &mut WIRE_RX_BUFFER }, + iface_num: id, + ep_in: 0x81 + id, + ep_out: 0x01 + id, + emu_port: UDP_PORT + WIRE_PORT_OFFSET, + } + } + + #[cfg(feature = "ui_debug")] + fn create_debug_iface(ids: &mut RangeFrom) -> WebUsbConfig { + static mut DEBUG_RX_BUFFER: [u8; 64] = [0; 64]; + + let id = ids.next().unwrap(); + WebUsbConfig { + rx_buffer: unsafe { &mut DEBUG_RX_BUFFER }, + iface_num: id, + ep_in: 0x81 + id, + ep_out: 0x01 + id, + emu_port: UDP_PORT + DEBUGLINK_PORT_OFFSET, + } + } + + #[cfg(not(feature = "bitcoin_only"))] + fn create_webauthn_iface(ids: &mut RangeFrom) -> HidConfig { + static mut WEBAUTHN_RX_BUFFER: [u8; 64] = [0; 64]; + + let id = ids.next().unwrap(); + HidConfig { + report_desc: &[ + 0x06, 0xd0, 0xf1, // USAGE_PAGE (FIDO Alliance) + 0x09, 0x01, // USAGE (U2F HID Authenticator Device) + 0xa1, 0x01, // COLLECTION (Application) + 0x09, 0x20, // USAGE (Input Report Data) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x40, // REPORT_COUNT (64) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x09, 0x21, // USAGE (Output Report Data) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x40, // REPORT_COUNT (64) + 0x91, 0x02, // OUTPUT (Data,Var,Abs) + 0xc0, // END_COLLECTION + ], + rx_buffer: unsafe { &mut WEBAUTHN_RX_BUFFER }, + iface_num: id, + ep_in: 0x81 + id, + ep_out: 0x01 + id, + emu_port: UDP_PORT + WEBAUTHN_PORT_OFFSET, + } + } + + #[cfg(feature = "ui_debug")] + fn create_vcp_iface(ids: &mut RangeFrom) -> VcpConfig { + static mut VCP_RX_BUFFER: [u8; 1024] = [0; 1024]; + static mut VCP_TX_BUFFER: [u8; 1024] = [0; 1024]; + static mut VCP_RX_PACKET: [u8; 64] = [0; 64]; + static mut VCP_TX_PACKET: [u8; 64] = [0; 64]; + + let id = ids.next().unwrap(); + let id_data = ids.next().unwrap(); + VcpConfig { + rx_buffer: unsafe { &mut VCP_RX_BUFFER }, + tx_buffer: unsafe { &mut VCP_TX_BUFFER }, + rx_packet: unsafe { &mut VCP_RX_PACKET }, + tx_packet: unsafe { &mut VCP_TX_PACKET }, + rx_intr_fn: None, // Use pendsv_kbd_intr here. + rx_intr_byte: 3, // Ctrl-C + iface_num: id, + data_iface_num: id_data, + ep_in: 0x81 + id, + ep_out: 0x01 + id, + ep_cmd: 0x81 + id_data, + emu_port: UDP_PORT + VCP_PORT_OFFSET, + } + } + + fn set_global_serial_number(sn: &CStr) -> &'static CStr { + static mut GLOBAL_BUFFER: [u8; 64] = [0; 64]; + + // SAFETY: We are in a single threaded context, so the only possible race on + // `GLOBAL_BUFFER` is with the USB stack. We should take care to only call + // `set_global_serial_number` with the USB stopped. + unsafe { + let sn_nul = sn.to_bytes_with_nul(); + + // Panics if `sn_nul` is bigger then `GLOBAL_BUFFER`. + GLOBAL_BUFFER[..sn_nul.len()].copy_from_slice(sn_nul); + + CStr::from_bytes_with_nul_unchecked(&GLOBAL_BUFFER[..sn_nul.len()]) + } + } + + let block = || { + let serial_number: Buffer = serial_number.try_into()?; + let serial_number = + CStr::from_bytes_with_nul(&serial_number).map_err(|_| Error::TypeError)?; + + let mut ids = 0u8..; // Iterator of interface IDS. + let mut usb = usb_config(serial_number); + + // Create the interfaces we use and add them to the config. Note that the order + // matters. + + let wire_id = create_wire_iface(&mut ids).add(&mut usb)?.iface_num(); + + #[cfg(feature = "ui_debug")] + let debug_id = Some(create_debug_iface(&mut ids).add(&mut usb)?.iface_num()); + #[cfg(not(feature = "ui_debug"))] + let debug_id: Option = None; + + #[cfg(feature = "bitcoin_only")] + let webauthn_id: Option = None; + #[cfg(not(feature = "bitcoin_only"))] + let webauthn_id = Some(create_webauthn_iface(&mut ids).add(&mut usb)?.iface_num()); + + #[cfg(feature = "ui_debug")] + { + create_vcp_iface(&mut ids).add(&mut usb)?; + // TODO: Enable MicroPython VCP support. + } + + // Convert used interface IDs to MicroPython objects. + let wire_id_obj = Obj::try_from(wire_id as u32)?; + let debug_id_obj = Obj::from(debug_id.map(Obj::try_from).transpose()?); + let webauthn_id_obj = Obj::from(webauthn_id.map(Obj::try_from).transpose()?); + let tuple = Obj::try_from((wire_id_obj, debug_id_obj, webauthn_id_obj))?; + + // Initialize the USB and start the configured interfaces. + usb_open(usb)?; + + Ok(tuple) + }; + unsafe { util::try_or_raise(block) } +} + +impl From for Error { + fn from(usb: UsbError) -> Self { + match usb { + UsbError::FailedToAddInterface => Error::ValueError(cstr!("Failed to add interface")), + UsbError::InterfaceNotFound => Error::ValueError(cstr!("Interface not found")), + } + } +} diff --git a/core/embed/rust/src/lib.rs b/core/embed/rust/src/lib.rs index bb395da97f..a825dc7b07 100644 --- a/core/embed/rust/src/lib.rs +++ b/core/embed/rust/src/lib.rs @@ -22,7 +22,7 @@ mod util; #[cfg(not(feature = "test"))] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { - use cstr_core::CStr; + use cstr_core::cstr; // Although it would be ideal to use the original error message, ignoring it // lets us avoid the `fmt` machinery and its code size and is also important for @@ -30,9 +30,8 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { // should also avoid printing "panic" or "rust" on the user screen to avoid any // confusion. - // SAFETY: Safe because we are passing in \0-terminated literals. - let empty = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") }; - let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"rs\0") }; + let empty = cstr!(""); + let msg = cstr!("rs"); // TODO: Ideally we would take the file and line info out of // `PanicInfo::location()`. diff --git a/core/embed/rust/src/micropython/ffi.rs b/core/embed/rust/src/micropython/ffi.rs index d268afd305..7d568ca0a9 100644 --- a/core/embed/rust/src/micropython/ffi.rs +++ b/core/embed/rust/src/micropython/ffi.rs @@ -1,5 +1,6 @@ +#![allow(dead_code)] #![allow(non_camel_case_types)] #![allow(non_upper_case_globals)] -#![allow(dead_code)] +#![allow(unsafe_op_in_unsafe_fn)] include!(concat!(env!("OUT_DIR"), "/micropython.rs")); diff --git a/core/embed/rust/src/micropython/obj.rs b/core/embed/rust/src/micropython/obj.rs index 5d66d670c6..5d685747e4 100644 --- a/core/embed/rust/src/micropython/obj.rs +++ b/core/embed/rust/src/micropython/obj.rs @@ -305,6 +305,48 @@ impl TryFrom<(Obj, Obj)> for Obj { } } +impl From<()> for Obj { + fn from(_value: ()) -> Self { + // micropython/py/obj.h + // #define mp_const_empty_tuple (MP_OBJ_FROM_PTR(&mp_const_empty_tuple_obj)) + unsafe { + let empty_tuple_ptr = &ffi::mp_const_empty_tuple_obj as *const _ as *mut _; + Obj::from_ptr(empty_tuple_ptr) + } + } +} + +// TODO: WARNING: while rebasing I chose randomly to ignore this implementation +// and use the abovementioned + +// impl TryFrom<(Obj, Obj)> for Obj { +// type Error = Error; + +// fn try_from(value: (Obj, Obj)) -> Result { +// // SAFETY: `mp_obj_new_tuple` does not retain pointer to `items`, +// expects to // find `items.len()` elements and does not modify them. +// // EXCEPTION: Will raise if allocation fails. +// catch_exception(|| unsafe { +// let items = [value.0, value.1]; +// ffi::mp_obj_new_tuple(items.len(), items.as_ptr()) +// }) +// } +// } + +impl TryFrom<(Obj, Obj, Obj)> for Obj { + type Error = Error; + + fn try_from(value: (Obj, Obj, Obj)) -> Result { + // SAFETY: `mp_obj_new_tuple` does not retain pointer to `items`, expects to + // find `items.len()` elements and does not modify them. + // EXCEPTION: Will raise if allocation fails. + catch_exception(|| unsafe { + let items = [value.0, value.1, value.2]; + ffi::mp_obj_new_tuple(items.len(), items.as_ptr()) + }) + } +} + // // # Additional conversions based on the methods above. // diff --git a/core/embed/rust/src/protobuf/error.rs b/core/embed/rust/src/protobuf/error.rs index 68d8950d32..a579b461b4 100644 --- a/core/embed/rust/src/protobuf/error.rs +++ b/core/embed/rust/src/protobuf/error.rs @@ -1,31 +1,30 @@ -use cstr_core::CStr; +use cstr_core::cstr; use crate::{error::Error, micropython::qstr::Qstr}; // XXX const version of `from_bytes_with_nul_unchecked` is nightly-only. pub fn experimental_not_enabled() -> Error { - let msg = - unsafe { CStr::from_bytes_with_nul_unchecked(b"Experimental features are disabled.\0") }; + let msg = cstr!("Experimental features are disabled"); Error::ValueError(msg) } pub fn unknown_field_type() -> Error { - let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"Unknown field type.\0") }; + let msg = cstr!("Unknown field type"); Error::ValueError(msg) } pub fn missing_required_field(field: Qstr) -> Error { - let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"Missing required field\0") }; + let msg = cstr!("Missing required field"); Error::ValueErrorParam(msg, field.into()) } pub fn invalid_value(field: Qstr) -> Error { - let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"Invalid value for field\0") }; + let msg = cstr!("Invalid value for field"); Error::ValueErrorParam(msg, field.into()) } pub fn end_of_buffer() -> Error { - let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"End of buffer.\0") }; + let msg = cstr!("End of buffer"); Error::ValueError(msg) } diff --git a/core/embed/rust/src/trezorhal/usb.rs b/core/embed/rust/src/trezorhal/usb.rs index 97514379a9..4e9bbc8251 100644 --- a/core/embed/rust/src/trezorhal/usb.rs +++ b/core/embed/rust/src/trezorhal/usb.rs @@ -107,19 +107,19 @@ pub fn usb_close() { const MAX_INTERFACE_COUNT: usize = 4; #[derive(Default)] -pub struct UsbConfig<'a> { +pub struct UsbConfig { pub vendor_id: u16, pub product_id: u16, pub release_num: u16, pub manufacturer: &'static CStr, pub product: &'static CStr, pub interface: &'static CStr, - pub serial_number: &'a CStr, + pub serial_number: &'static CStr, pub usb21_landing: bool, pub interfaces: Vec, } -impl UsbConfig<'_> { +impl UsbConfig { fn as_dev_info(&self) -> ffi::usb_dev_info_t { ffi::usb_dev_info_t { device_class: 0, @@ -131,8 +131,7 @@ impl UsbConfig<'_> { manufacturer: self.manufacturer.as_ptr(), product: self.product.as_ptr(), interface: self.interface.as_ptr(), - // Because we set the SN dynamically, we need to copy it to the static storage first. - serial_number: set_global_serial_number(self.serial_number).as_ptr(), + serial_number: self.serial_number.as_ptr(), usb21_enabled: secbool::TRUE, usb21_landing: if self.usb21_landing { secbool::TRUE @@ -151,6 +150,15 @@ pub enum IfaceTicket { WebUsb(u8), } +impl IfaceTicket { + pub fn iface_num(&self) -> u8 { + match self { + IfaceTicket::Hid(iface_num) => *iface_num, + IfaceTicket::WebUsb(iface_num) => *iface_num, + } + } +} + pub enum IfaceConfig { WebUsb(WebUsbConfig), Hid(HidConfig), @@ -383,130 +391,3 @@ impl VcpConfig { } } } - -#[cfg(test)] -mod tests { - use cstr_core::cstr; - - use super::*; - - #[test] - fn test_usb() { - // Example port of usb.py. - - const UDP_PORT: u16 = 0; - const WIRE_PORT_OFFSET: u16 = 0; - const DEBUGLINK_PORT_OFFSET: u16 = 1; - const WEBAUTHN_PORT_OFFSET: u16 = 2; - const VCP_PORT_OFFSET: u16 = 3; - - let mut iface_iter = 0u8..; - - const ENABLE_IFACE_DEBUG: bool = true; - const ENABLE_IFACE_WEBAUTHN: bool = true; - const ENABLE_IFACE_VCP: bool = true; - - let mut config = UsbConfig { - vendor_id: 0x1209, - product_id: 0x53C1, - release_num: 0x0200, - manufacturer: cstr!("SatoshiLabs"), - product: cstr!("TREZOR"), - serial_number: cstr!(""), - interface: cstr!("TREZOR Interface"), - usb21_landing: false, - ..UsbConfig::default() - }; - - let id_wire = iface_iter.next().unwrap(); - let iface_wire = WebUsbConfig { - rx_buffer: &mut [0; 64], - iface_num: id_wire, - ep_in: 0x81 + id_wire, - ep_out: 0x01 + id_wire, - emu_port: UDP_PORT + WIRE_PORT_OFFSET, - }; - let ticket_wire = iface_wire.add(&mut config).unwrap(); - - if ENABLE_IFACE_DEBUG { - let id_debug = iface_iter.next().unwrap(); - let iface_debug = WebUsbConfig { - rx_buffer: &mut [0; 64], - iface_num: id_debug, - ep_in: 0x81 + id_debug, - ep_out: 0x01 + id_debug, - emu_port: UDP_PORT + DEBUGLINK_PORT_OFFSET, - }; - let ticket_debug = iface_debug.add(&mut config).unwrap(); - } - - if ENABLE_IFACE_WEBAUTHN { - let id_webauthn = iface_iter.next().unwrap(); - let iface_webauthn = HidConfig { - report_desc: &[ - 0x06, 0xd0, 0xf1, // USAGE_PAGE (FIDO Alliance) - 0x09, 0x01, // USAGE (U2F HID Authenticator Device) - 0xa1, 0x01, // COLLECTION (Application) - 0x09, 0x20, // USAGE (Input Report Data) - 0x15, 0x00, // LOGICAL_MINIMUM (0) - 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) - 0x75, 0x08, // REPORT_SIZE (8) - 0x95, 0x40, // REPORT_COUNT (64) - 0x81, 0x02, // INPUT (Data,Var,Abs) - 0x09, 0x21, // USAGE (Output Report Data) - 0x15, 0x00, // LOGICAL_MINIMUM (0) - 0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255) - 0x75, 0x08, // REPORT_SIZE (8) - 0x95, 0x40, // REPORT_COUNT (64) - 0x91, 0x02, // OUTPUT (Data,Var,Abs) - 0xc0, // END_COLLECTION - ], - rx_buffer: &mut [0; 64], - iface_num: id_webauthn, - ep_in: 0x81 + id_webauthn, - ep_out: 0x01 + id_webauthn, - emu_port: UDP_PORT + WEBAUTHN_PORT_OFFSET, - }; - let ticket_webauthn = iface_webauthn.add(&mut config).unwrap(); - } - - if ENABLE_IFACE_VCP { - let id_vcp = iface_iter.next().unwrap(); - let id_vcp_data = iface_iter.next().unwrap(); - let iface_vcp = VcpConfig { - rx_buffer: &mut [0; 1024], - tx_buffer: &mut [0; 1024], - rx_packet: &mut [0; 64], - tx_packet: &mut [0; 64], - rx_intr_fn: None, // Use pendsv_kbd_intr here. - rx_intr_byte: 3, // Ctrl-C - iface_num: id_vcp, - data_iface_num: id_vcp_data, - ep_in: 0x81 + id_vcp, - ep_out: 0x01 + id_vcp, - ep_cmd: 0x81 + id_vcp_data, - emu_port: UDP_PORT + VCP_PORT_OFFSET, - }; - iface_vcp.add(&mut config).unwrap(); - } - - usb_open(config).unwrap(); - usb_close(); - } -} - -fn set_global_serial_number(sn: &CStr) -> &'static CStr { - static mut GLOBAL_BUFFER: [u8; 64] = [0; 64]; - - // SAFETY: We are in a single threaded context, so the only possible race on - // `GLOBAL_BUFFER` is with the USB stack. We should take care to only call - // `set_global_serial_number` with the USB stopped. - unsafe { - let sn_nul = sn.to_bytes_with_nul(); - - // Panics if `sn_nul` is bigger then `GLOBAL_BUFFER`. - GLOBAL_BUFFER[..sn_nul.len()].copy_from_slice(sn_nul); - - CStr::from_bytes_with_nul_unchecked(&GLOBAL_BUFFER[..sn_nul.len()]) - } -} diff --git a/core/embed/rust/src/util.rs b/core/embed/rust/src/util.rs index 39abd7ba05..ab3e66e58a 100644 --- a/core/embed/rust/src/util.rs +++ b/core/embed/rust/src/util.rs @@ -1,14 +1,14 @@ use core::slice; -use crate::micropython::time; -use crate::time::Duration; use crate::{ error::Error, micropython::{ map::{Map, MapElem}, obj::Obj, runtime::raise_exception, + time, }, + time::Duration, }; /// Wait an unspecified short amount of time. To be used in busy loops.