Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rtt_init* macros are unsound #3

Open
japaric opened this issue May 12, 2020 · 5 comments
Open

rtt_init* macros are unsound #3

japaric opened this issue May 12, 2020 · 5 comments

Comments

@japaric
Copy link

japaric commented May 12, 2020

rtt_init!

The rtt_init! macro lets you effectively alias a mutable reference to memory. Example below:

#[entry]
fn main() -> ! {
    let c0 = alias();

    // use `c0`
}

#[exception]
fn SysTick() {
    let c0 = alias();
    // use `c0`
}

fn alias() -> UpChannel {
    let cs = rtt_init! {
        up: {
            0: {
                size: 1024
                mode: NoBlockSkip
            }
        }
    };
    cs.up.0
}

Both c0 variables have mutable access to the same chunk of memory and each variable can be accessed from a different priority level (SysTick can preempt main): this alone is a data race and UB in paper (in practice, you may need to call write to make the code explode)

Suggested fix: either make rtt_init! unsafe to call OR make rtt_init! return Some only once. The latter is the owned singleton pattern (described in its zero-sized reference form in this blog post). Basically you want something like this:

fn once() -> Option<Something> {
    static ONCE: AtomicBool = AtomicBool::new(false);
    
    if ONCE
        .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
        .is_ok() {
        // this branch will run at most *once*
        // ..
        Some(something)
    } else {
        None
    }
}

rtt_init_default!

This is unsound for the same reason rtt_init! is unsound: you should not be able to alias Channels in safe code.

Fix: fixing rtt_init! fixes this -- just don't make rtt_init unsafe to call but rtt_init_default safe to call; both should be unsafe OR both should return an Option

rtt_init_print!

This one also lets you run into data races in safe code. See below:

#[entry]
fn main() -> ! {
    init();

    loop {
        rprintln!("Hello, world!");
    }
}

#[exception]
fn SysTick() {
    init();
}

fn init() {
    rtt_init_print!();
}

Here the exception (SysTick) can preempt rprintln! and re-initialize the channel (overwrite static variables) used by rprintln!. The main issue here is the unsafe code in rtt_init! running more than once and/or running in parallel / concurrently to the execution of rprintln!.

Suggested fix: this macro should panic if called more than once OR should be no-op if called more than once

cc @Yatekii

@Yatekii
Copy link
Member

Yatekii commented May 12, 2020

Great catch, thanks for reporting :)

Should be easy enough to fix thanks to your suggestions.

@mvirkkunen
Copy link
Contributor

mvirkkunen commented May 12, 2020

This one is a bit annoying to fix due to the fact we can't rely on the availability of atomics on all platforms. I suppose I could live with atomics by default and a platform feature flag (is it just me or does like every crate have a "cortex-m" flag for instance?) like "no-atomics" that disables atomics and makes everything unsafe instead.

I'd rather not have a requirement for everybody to use "unsafe".

@Yatekii
Copy link
Member

Yatekii commented May 12, 2020

Yeah, this won't work on M0 targets indeed. But I think having a sane default is a good trade-off with a flag to disable it :)

@xobs
Copy link

xobs commented Jul 13, 2022

As an update, Rust now has #[cfg(target_has_atomic = 32)] which can be used to omit such code on those platforms.

@tgross35
Copy link
Contributor

tgross35 commented Mar 6, 2023

Somewhat tangential, but using the OnceLock pattern would probably also allow panic-rtt-target to work without first needing to call rtt_init_default, which would be convenient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants