Skip to content

Commit f947828

Browse files
Fix soundness hole in join macros (#2649)
* fix soundness hole in join macros add a miri regression test update failing tests (join sizes increased due to fix) * fix `CI / cross test` by ignoring `join_size` and `try_join_size` tests on "non-64-bit pointer" targets (e.g. `i686-unknown-linux-gnu`) (this is the same fix that was also applied in PR #2447)
1 parent 930d3e0 commit f947828

File tree

3 files changed

+45
-10
lines changed

3 files changed

+45
-10
lines changed

futures-macro/src/join.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ fn bind_futures(fut_exprs: Vec<Expr>, span: Span) -> (Vec<TokenStream2>, Vec<Ide
3838
// Move future into a local so that it is pinned in one place and
3939
// is no longer accessible by the end user.
4040
let mut #name = __futures_crate::future::maybe_done(#expr);
41+
let mut #name = unsafe { __futures_crate::Pin::new_unchecked(&mut #name) };
4142
});
4243
name
4344
})
@@ -58,12 +59,12 @@ pub(crate) fn join(input: TokenStream) -> TokenStream {
5859
let poll_futures = future_names.iter().map(|fut| {
5960
quote! {
6061
__all_done &= __futures_crate::future::Future::poll(
61-
unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }, __cx).is_ready();
62+
#fut.as_mut(), __cx).is_ready();
6263
}
6364
});
6465
let take_outputs = future_names.iter().map(|fut| {
6566
quote! {
66-
unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }.take_output().unwrap(),
67+
#fut.as_mut().take_output().unwrap(),
6768
}
6869
});
6970

@@ -96,17 +97,17 @@ pub(crate) fn try_join(input: TokenStream) -> TokenStream {
9697
let poll_futures = future_names.iter().map(|fut| {
9798
quote! {
9899
if __futures_crate::future::Future::poll(
99-
unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }, __cx).is_pending()
100+
#fut.as_mut(), __cx).is_pending()
100101
{
101102
__all_done = false;
102-
} else if unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }.output_mut().unwrap().is_err() {
103+
} else if #fut.as_mut().output_mut().unwrap().is_err() {
103104
// `.err().unwrap()` rather than `.unwrap_err()` so that we don't introduce
104105
// a `T: Debug` bound.
105106
// Also, for an error type of ! any code after `err().unwrap()` is unreachable.
106107
#[allow(unreachable_code)]
107108
return __futures_crate::task::Poll::Ready(
108109
__futures_crate::Err(
109-
unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }.take_output().unwrap().err().unwrap()
110+
#fut.as_mut().take_output().unwrap().err().unwrap()
110111
)
111112
);
112113
}
@@ -118,7 +119,7 @@ pub(crate) fn try_join(input: TokenStream) -> TokenStream {
118119
// an `E: Debug` bound.
119120
// Also, for an ok type of ! any code after `ok().unwrap()` is unreachable.
120121
#[allow(unreachable_code)]
121-
unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }.take_output().unwrap().ok().unwrap(),
122+
#fut.as_mut().take_output().unwrap().ok().unwrap(),
122123
}
123124
});
124125

futures/tests/async_await_macros.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -346,36 +346,38 @@ fn stream_select() {
346346
});
347347
}
348348

349+
#[cfg_attr(not(target_pointer_width = "64"), ignore)]
349350
#[test]
350351
fn join_size() {
351352
let fut = async {
352353
let ready = future::ready(0i32);
353354
join!(ready)
354355
};
355-
assert_eq!(mem::size_of_val(&fut), 12);
356+
assert_eq!(mem::size_of_val(&fut), 24);
356357

357358
let fut = async {
358359
let ready1 = future::ready(0i32);
359360
let ready2 = future::ready(0i32);
360361
join!(ready1, ready2)
361362
};
362-
assert_eq!(mem::size_of_val(&fut), 20);
363+
assert_eq!(mem::size_of_val(&fut), 40);
363364
}
364365

366+
#[cfg_attr(not(target_pointer_width = "64"), ignore)]
365367
#[test]
366368
fn try_join_size() {
367369
let fut = async {
368370
let ready = future::ready(Ok::<i32, i32>(0));
369371
try_join!(ready)
370372
};
371-
assert_eq!(mem::size_of_val(&fut), 16);
373+
assert_eq!(mem::size_of_val(&fut), 24);
372374

373375
let fut = async {
374376
let ready1 = future::ready(Ok::<i32, i32>(0));
375377
let ready2 = future::ready(Ok::<i32, i32>(0));
376378
try_join!(ready1, ready2)
377379
};
378-
assert_eq!(mem::size_of_val(&fut), 28);
380+
assert_eq!(mem::size_of_val(&fut), 48);
379381
}
380382

381383
#[test]

futures/tests/future_join.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use futures::executor::block_on;
2+
use futures::future::Future;
3+
use std::task::Poll;
4+
5+
/// This tests verifies (through miri) that self-referencing
6+
/// futures are not invalidated when joining them.
7+
#[test]
8+
fn futures_join_macro_self_referential() {
9+
block_on(async { futures::join!(yield_now(), trouble()) });
10+
}
11+
12+
async fn trouble() {
13+
let lucky_number = 42;
14+
let problematic_variable = &lucky_number;
15+
16+
yield_now().await;
17+
18+
// problematic dereference
19+
let _ = { *problematic_variable };
20+
}
21+
22+
fn yield_now() -> impl Future<Output = ()> {
23+
let mut yielded = false;
24+
std::future::poll_fn(move |cx| {
25+
if core::mem::replace(&mut yielded, true) {
26+
Poll::Ready(())
27+
} else {
28+
cx.waker().wake_by_ref();
29+
Poll::Pending
30+
}
31+
})
32+
}

0 commit comments

Comments
 (0)