Skip to content

RawError conversion is non-exhaustive #295

Open
@Wtoll

Description

@Wtoll

In debugging another issue I discovered that when Softdevice::enable calls the unsafe enable function on the other side of FFI and gets back a raw error code, it immediately attempts type conversion into RawError from the primitive but does so non-exhaustively. Because the raw error is coming from the other side of FFI there's no guarantee for it to actually be a valid error code, and because the type conversion is non-exhaustive, there's an opportunity to panic that won't generate an actual Result::Err to bubble up.

// softdevice.rs
...
impl Softdevice {
    pub fn enable(config: &Config) -> &'static mut Softdevice {
        if ENABLED
            .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
            .is_err()
        {
            panic!("nrf_softdevice::enable() called multiple times.")
        }

        let p_clock_lf_cfg = config.clock.as_ref().map(|x| x as _).unwrap_or(ptr::null());
        // Raw error comes straight out of an unsafe block...
        let ret = unsafe { raw::sd_softdevice_enable(p_clock_lf_cfg, Some(fault_handler)) };
        // ...into a non-exhaustive type conversion, without any prior validation.
        match RawError::convert(ret) {
            Ok(()) => {}
            // Subsequently, this branch becomes unreachable, so the panic is never actually printed.
            Err(err) => panic!("sd_softdevice_enable err {:?}", err),
        }
        ...
    }
}
...

In raw_error.rs I suggest changing the derive macro on RawError to derive TryFromPrimitive instead, and use that in the RawError::convert helper function. An invalid type conversion result could then just be matched to the RawError::Unknown type so that the program doesn't simply halt without an error message.

// raw_error.rs

#[rustfmt::skip]
#[repr(u32)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
- #[derive(Debug, PartialEq, Eq, Clone, Copy, IntoPrimitive, FromPrimitive)]
+ #[derive(Debug, PartialEq, Eq, Clone, Copy, IntoPrimitive, TryFromPrimitive)]
pub enum RawError {
    ...
}

impl RawError {
    pub fn convert(ret: u32) -> Result<(), RawError> {
        if ret == raw::NRF_SUCCESS {
            Ok(())
        } else {
-           Err(RawError::from(ret))
+           Err(RawError::try_from(ret).unwrap_or(RawError::Unknown))
        }
    }
}

Separately, if there was perchance some known error that's causing my code to be receiving a 0x20022D91 raw error code that would be super cool to know about, but I imagine there isn't, it's just a memory issue, and that I'll have to wipe everything to troubleshoot it 😩.

Thanks everyone for writing such an amazing library! It's really been a life saver and otherwise such a pleasure to use.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions