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

Soundness hole: transmuting item type using yield in a nested async block awaited in the wrong place #106

Open
steffahn opened this issue Apr 22, 2024 · 0 comments

Comments

@steffahn
Copy link

steffahn commented Apr 22, 2024

It seems (me looking at macro expansions) that yield usage in an async {} block is not expanded by this macro, and that’s good - in principle - because it can be unsound to await that in the wrong place with the way that thread-locals are used.

However, this is a syntactic analysis, and yield in macros are expanded, so well, that can be a problem if a macro introduces an async block:

use async_stream::stream;
use futures::StreamExt;

use std::pin::pin;

macro_rules! asynk {
    ($e:expr) => {
        async { $e }
    };
}

#[tokio::main]

async fn main() {
    pin!(stream! {
        let yield_42 = asynk!(yield 42_usize);
        let s = stream! {
            yield Box::new(12345);
            yield_42.await; // yield 42 -- wait that's not a Box!?
        };
        for await (n, i) in s.enumerate() {
            println!("Item at index {n}:\n    {i}");
            // Item at index 0:
            //     12345
            // Item at index 1:
            // Segmentation fault
        }
    })
    .next()
    .await;
}

(run on rustexplorer.com)

This kind of abuse could be better prevented with a trick I came up with in dureuill/nolife#8 (comment), the idea is to make the whole stream! invocation create a labelled block, and have yield include a if false guarded break from the same label; this will successfully prevent compilation in case the yield is anywhere where control-flow cannot directly jump back to the top-level of stream! (which should correspond to anywhere where the .await of the yield expansion does not correspond to the outer async block from the stream! expansion), hence preventing accidental use of yield in a nested async block.

(For nolife, the macro involved a macro_rules macro, where the default hygiene rules would successfully prevent the labels not matching up properly; I don't quite remember off the top of my head how far support for doing the same in proc-macros is nowadays.)

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

1 participant