Skip to content

prevent deref coercions in pin!#153457

Merged
rust-bors[bot] merged 2 commits intorust-lang:mainfrom
dianne:no-coercing-in-pin-macro
Apr 26, 2026
Merged

prevent deref coercions in pin!#153457
rust-bors[bot] merged 2 commits intorust-lang:mainfrom
dianne:no-coercing-in-pin-macro

Conversation

@dianne
Copy link
Copy Markdown
Contributor

@dianne dianne commented Mar 5, 2026

View all comments

Mitigates #153438 using a (hopefully temporary!) typed macro idiom to ensure that when pin! produces a Pin<&mut T>, its argument is of type T. See #153438 (comment) for my ideas on how this could be changed in the future.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Mar 5, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Mar 5, 2026

r? @jieyouxu

rustbot has assigned @jieyouxu.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 69 candidates
  • Random selection from 15 candidates

@dianne
Copy link
Copy Markdown
Contributor Author

dianne commented Mar 5, 2026

I expect this needs crater, so: @bors try

@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Mar 5, 2026
@dianne dianne force-pushed the no-coercing-in-pin-macro branch from 092ecb8 to 8f93fc9 Compare March 5, 2026 19:25
@dianne
Copy link
Copy Markdown
Contributor Author

dianne commented Mar 5, 2026

Just realized I could format things more cleanly (diff). This shouldn't affect the correctness of the try build.

@eggyal
Copy link
Copy Markdown
Contributor

eggyal commented Mar 5, 2026

A more explicit approach could be to do something like:

super let mut pinned = $value;

#[allow(unreachable_code)]
'p: {
    fn unreachable_type_constraint<'a, T>(_: T) -> Pin<&'a mut T> {
        unreachable!()
    }

    break 'p unsafe { Pin::new_unchecked(&mut pinned) };
    unreachable_type_constraint(pinned)
}

@dianne
Copy link
Copy Markdown
Contributor Author

dianne commented Mar 5, 2026

So that's how to add types to macros! Funky idiom, but I do prefer the explicitness of actually having types, yeah. I think that'd be less likely to break inference too if anything's started relying on it; iirc type expectations are propagated from the block to the break expression (via the block's coercion target type). But it's fine because we'll encounter an error instead of silently adding a coercion if things don't match up.. I think.

Would you prefer to make your own PR @eggyal, or should I just work that into this one? I'm not a library reviewer so I can't say which would be preferable from that perspective, but from the perspective I do have, I think adding a type constraint is a better fix.

@eggyal
Copy link
Copy Markdown
Contributor

eggyal commented Mar 5, 2026

By all means work it into this one. This idiom was originally suggested to me by lcnr some years ago, so I claim no credit for it!

@Kivooeo
Copy link
Copy Markdown
Member

Kivooeo commented Mar 5, 2026

this change requires someone from libs team to be approved, so i'm reassigning

r? libs

@rustbot rustbot assigned Mark-Simulacrum and unassigned jieyouxu Mar 5, 2026
@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented Mar 5, 2026

☀️ Try build successful (CI)
Build commit: 048f484 (048f484e31141edc1fa71e20406300e92a9c16ba, parent: 64b72a1fa5449d928d5f553b01a596b78ee255d2)

@dianne dianne force-pushed the no-coercing-in-pin-macro branch from 8f93fc9 to 524b11d Compare March 5, 2026 22:22
@dianne
Copy link
Copy Markdown
Contributor Author

dianne commented Mar 5, 2026

Went ahead with the unreachable type constraint version of this (diff). There's unfortunately a bit of a diagnostics regression due to the extra type checking. I still think it's worth the lower likelihood of breakage, but it's a tradeoff. I'll be firing off a fresh try build, but if the diagnostics are a sticking point, maybe it'd be worth comparing crater results for both approaches?

@dianne
Copy link
Copy Markdown
Contributor Author

dianne commented Mar 5, 2026

@bors try

@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Mar 5, 2026
@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented Mar 6, 2026

☀️ Try build successful (CI)
Build commit: 5d96fa0 (5d96fa0e954d77528204a1ba3b8847ec083c779b, parent: 64b72a1fa5449d928d5f553b01a596b78ee255d2)

@dianne
Copy link
Copy Markdown
Contributor Author

dianne commented Mar 6, 2026

I'll crater the type constraint approach first since that's my preferred quick fix at the moment.
@craterbot check

Also going to try re-running pr-check-2. It never restarted after being canceled it looks like?

@craterbot
Copy link
Copy Markdown
Collaborator

👌 Experiment pr-153457 created and queued.
🤖 Automatically detected try build 5d96fa0
🔍 You can check out the queue and this experiment's details.

ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

@craterbot craterbot added S-waiting-on-crater Status: Waiting on a crater run to be completed. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 6, 2026
@theemathas
Copy link
Copy Markdown
Contributor

theemathas commented Mar 6, 2026

I'm not yet convinced that this approach is 100% sound. Although I think it would be a good idea to do this fix even if it's not 100% sound, since any soundness exploit of this would be significantly more convoluted than the current exploit.

Given a value x of type Src, it might be possible with this PR for the expression pin!(x) to create a value of type Pin<&mut Dst>, where Src and Dst are different types. This can happen and cause unsoundness if:

  • Src can be coerced to Dst (this coercion would be done in the parameter of unreachable_type_constraint); and
  • &mut Src can be coerced to &mut Dst.(this coercion would be done in the parameter of Pin::new_unchecked); and
  • Src and Dst are different types and have no subtyping relationship with each other; and
  • Dst does not implement Unpin

I am not sure whether there exist types Src and Dst such that these two coercions can happen.

Edit: Added two more bullet points

Edit 2: Changed a requirement into the Unpin requirement.

@eggyal

This comment has been minimized.

@rustbot rustbot added the T-types Relevant to the types team, which will review and decide on the PR/issue. label Mar 19, 2026
@rustbot rustbot assigned jackh726 and unassigned Mark-Simulacrum Mar 19, 2026
@dianne
Copy link
Copy Markdown
Contributor Author

dianne commented Mar 19, 2026

@rustbot review

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Mar 19, 2026
@jackh726
Copy link
Copy Markdown
Member

Okay, so reading over the issue and this thread, I think the current approach is okay.

I'm not sure @theemathas's comment is applicable. I think there is an additional requisite that the pin!(...) call must actually constrain Dst (i.e. let x: Pin<&mut NotUnpin> = pin!(...);.

I was attempting to actually create a reproducer. It is definitely possible to have Src -> Dst and &mut Src -> &mut Dst both be possible - unsizing "just works" for example. Whether it's possible and exploitable I was trying to work on - and ran into a wall, and now I need to pause.

Some info on my experimentation

I have the start of a reproducer at https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=a3589e147b0b337b259a8f01da53fd79

I'm not entirely sure why we're getting a mismatched types error. I expected that the output would coerce first and then the arguments, so not sure what's going on.


All in all though, I think what we can do is fcp merge this for types. I don't know that I'd quite be happy to close the issue (because it still may be possible to exploit this), but this gets us much closer to soundness at the very least.

@theemathas
Copy link
Copy Markdown
Contributor

It is definitely possible to have Src -> Dst and &mut Src -> &mut Dst both be possible - unsizing "just works" for example.

@jackh726 This is not true. If &mut Src could be coerced into a &mut Dst via an unsize coercion, then this implies that Dst does not implement Sized. Therefore, it is impossible for Src to be coerced into Dst, since coercion can't produce a non-Sized value.

@jackh726
Copy link
Copy Markdown
Member

If &mut Src could be coerced into a &mut Dst via an unsize coercion, then this implies that Dst does not implement Sized.

This is true.

Therefore, it is impossible for Src to be coerced into Dst, since coercion can't produce a non-Sized value.

This is not.

This playground gets similarly close, just differently. In this, both &mut Src -> &mut Dst and Src -> Dst happen through unsizing: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=66147a3f63c3f584f5b02cf6fb2f4818


Actually, I think the most constrained rule to satisfy is the Src -> Dst coercion:

  • As said, this can be done through unsizing trivially, but I do not think that there is any way that this could result in a Dst that is !Unpin but Src is, because both Unpin impls and unsizing must be fully generic and there exists no Unsize step that changes Unpin-ness
  • That means that Src -> Dst must coerce through deref":

For a deref coercion to happen, Src and Dst must effectively be Src = &T and Dst = &U where T: Deref<Target = U>. Then, we now have an "updated" constraint that &mut &T coerces to &mut &U. We know that this coercion can't happen through deref (because there exists the &T: Deref<Target = T> impl. And, the unsize impl on &mut X requires X: Unsize, and &T: Unsize does not hold.


I'm not sure if I've missed something, but I am feeling fairly confident that the change in this PR is always sound w.r.t. "possible" coercions. I think this analyses does lead to constraints, which is that:

  • we can never let users write an non-fully-generic Unpin impl - otherwise they could write impl !Unpin for Wrapper<[u8]> {} (with Wrapper<[u8; N]> holding), for example
  • *we can never let users write "user-defined" unsizing - CoerceUnsized could never to be "non-generic" over the unsized data

@jackh726
Copy link
Copy Markdown
Member

Alright, to move this forward:

I think we should just merge this. I'm a little on the fence of whether or not this needs an FCP. My gut feeling is "typically, seems like something we'd FCP" - that's probably on libs? I think types is probably a better reviewer - which I have (and I think @theemathas has thought through this a bunch too), but ultimately this is lib's territory.

However, that being said, this feels more like a "bug fix" for unintended behavior, than anything. So I'm more inclined to just merge without FCP (especially given that crater is clean).

As far as #153438, technically that could be closed once this is merged, since the reported unsoundness is gone. However, given the theoretical concern raised by @theemathas, my instinct is to leave it open even after merging this PR. Then, to ensure that issue doesn't stall open, I think a useful process here might be to fcp close types on that - with a checkbox essentially saying "I've thought about this enough that I don't think there's a theoretical issue".

This lets us merge this and fix at least the known unsoundness - and puts us on a concrete path to close the issue itself.

@dianne does this seem reasonable to you? If so, r=me in a day or so (to give people time to respond).

@dianne dianne force-pushed the no-coercing-in-pin-macro branch from 524b11d to 5276fcd Compare April 24, 2026 03:49
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Apr 24, 2026

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@dianne
Copy link
Copy Markdown
Contributor Author

dianne commented Apr 24, 2026

I'm not familiar with T-libs or T-libs-api procedure, so I couldn't say there. The process I'm used to for language bug fixes is to get team approval via fcp merge, sometimes waiving the 10-day wait (though waiving the comment period might just be for beta regressions?).

Leaving #153438 open here and closing it via types FCP sounds reasonable to me! I've changed the description so it shouldn't automatically be closed.

Looking over the diagnostics again, I'm still not happy with how adding the extra function call affected those, but I think I've convinced myself that those are issues with the diagnostics, rather than the macro idiom. I can try and open a separate PR to make note_wrong_return_ty_due_to_generic_arg not leak implementation details of external macros. It already doesn't fire on calls from desugarings.

One last change, also: I've finally moved the function with the type signature of pin! into a separate helper so that it's not recreated for each pin! invocation. I'll once again hand this over to libs review to make sure I did that right. r? @Mark-Simulacrum since you've already reviewed this

@rustbot rustbot assigned Mark-Simulacrum and unassigned jackh726 Apr 24, 2026
Copy link
Copy Markdown
Member

@Mark-Simulacrum Mark-Simulacrum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bors r=Mark-Simulacrum,jackh726 rollup=iffy

View changes since this review

@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented Apr 26, 2026

📌 Commit 5276fcd has been approved by Mark-Simulacrum,jackh726

It is now in the queue for this repository.

@rust-bors rust-bors Bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 26, 2026
@theemathas theemathas added the relnotes Marks issues that should be documented in the release notes of the next release. label Apr 26, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Apr 26, 2026
…=Mark-Simulacrum,jackh726

prevent deref coercions in `pin!`

Mitigates rust-lang#153438 using a (hopefully temporary!) typed macro idiom to ensure that when `pin!` produces a `Pin<&mut T>`, its argument is of type `T`. See rust-lang#153438 (comment) for my ideas on how this could be changed in the future.
rust-bors Bot pushed a commit that referenced this pull request Apr 26, 2026
…uwer

Rollup of 15 pull requests

Successful merges:

 - #152995 (ACP Implementation of PermissionsExt for Windows )
 - #153457 (prevent deref coercions in `pin!`)
 - #155250 (Windows: Cache the pipe filesystem handle)
 - #155574 (Move `std::io::RawOsError` to `core::io`)
 - #155757 (macro_metavar_expr_concat: explain why idents are invalid)
 - #155823 (miri subtree update)
 - #155693 (Suggest enclosing format string with `""` under special cases)
 - #155707 (Fix minor panic-unsoundness in CString::clone_into)
 - #155719 (Suggest `.iter()` for shared projections)
 - #155779 (ssa_range_prop: use `if let` guards)
 - #155789 (Cleanups to `AttributeExt`)
 - #155805 (Mention `DEPRECATED_LLVM_INTRINSIC` lint for internal use)
 - #155806 (Remove the incomplete marker from `impl` restrictions)
 - #155820 (Avoid improper spans when `...` or `..=` is recovered from non-ASCII)
 - #155822 (Add default field values to diagnostic FormatArgs)
@rust-bors rust-bors Bot merged commit 691ab1b into rust-lang:main Apr 26, 2026
11 checks passed
@rustbot rustbot added this to the 1.97.0 milestone Apr 26, 2026
rust-timer added a commit that referenced this pull request Apr 26, 2026
Rollup merge of #153457 - dianne:no-coercing-in-pin-macro, r=Mark-Simulacrum,jackh726

prevent deref coercions in `pin!`

Mitigates #153438 using a (hopefully temporary!) typed macro idiom to ensure that when `pin!` produces a `Pin<&mut T>`, its argument is of type `T`. See #153438 (comment) for my ideas on how this could be changed in the future.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

relnotes Marks issues that should be documented in the release notes of the next release. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.