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

Borrowck allows two mutable borrows when using match ergonomics #51117

Closed
Arnavion opened this issue May 27, 2018 · 17 comments
Closed

Borrowck allows two mutable borrows when using match ergonomics #51117

Arnavion opened this issue May 27, 2018 · 17 comments
Assignees
Labels
A-borrow-checker Area: The borrow checker C-bug Category: This is a bug. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness regression-from-stable-to-stable Performance or correctness regression from one stable version to another. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@Arnavion
Copy link

Arnavion commented May 27, 2018

... which can be used to trigger a UAF in safe code.

rustc 1.26.0 (a77568041 2018-05-07)
binary: rustc
commit-hash: a7756804103447ea4e68a71ccf071e7ad8f7a03e
commit-date: 2018-05-07
host: x86_64-pc-windows-msvc
release: 1.26.0
LLVM version: 6.0
fn main() {
    let mut foo = Some("foo".to_string());
    let bar = &mut foo;
    match bar {
        Some(baz) => {
            bar.take();
            println!("{:?}", baz); // UAF, segfaults or prints garbage
        },
        None => unreachable!(),
    }
}

Original repro:

enum CacheFuture {
    Get(Option<String>)
}

fn main() {
    let mut f = CacheFuture::Get(Some("foo".to_string()));
    match &mut f {
        CacheFuture::Get(get) => match get {
            Some(mod_name) => {
                get.take();
                println!("{:?}", mod_name); // UAF, segfaults or prints garbage
            },
            None => unreachable!(),
        },
    }
}

Inserting explicit annotations to disable match ergonomics leads to borrowck correctly complaining about two mutable borrows, which leads me to believe this is only because of match ergonomics.

enum CacheFuture {
    Get(Option<String>)
}

fn main() {
    let mut f = CacheFuture::Get(Some("foo".to_string()));
    match *(&mut f) {
        CacheFuture::Get(ref mut get) => match *get {
            Some(ref mut mod_name) => {
                get.take();
                println!("{:?}", mod_name);
            },
            None => unreachable!(),
        },
    }
}
9  |             Some(ref mut mod_name) => {
   |                  ---------------- first mutable borrow occurs here
10 |                 get.take();
   |                 ^^^ second mutable borrow occurs here
...
14 |         },
   |         - first borrow ends here
@est31
Copy link
Member

est31 commented May 28, 2018

NLL fixes this. Adding #![feature(nll)] gives me this error:


error[E0499]: cannot borrow `*get` as mutable more than once at a time
  --> src/main.rs:12:17
   |
11 |             Some(mod_name) => {
   |                  -------- first mutable borrow occurs here
12 |                 get.take();
   |                 ^^^ second mutable borrow occurs here
13 |                 println!("{:?}", mod_name);
   |                                  -------- borrow later used here

@ishitatsuyuki ishitatsuyuki added A-borrow-checker Area: The borrow checker I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness NLL-fixed-by-NLL Bugs fixed, but only when NLL is enabled. labels May 28, 2018
@Arnavion
Copy link
Author

#rust wasn't sure if this regression-from-stable-to-stable-worthy or not. On the one hand match ergonomics is new in 1.26, so it's not strictly a regression from 1.25 since the bad code would not have compiled there. On the other hand, 1.26 makes it easy to write code that hits this bug, so it would be nice to fix this in 1.26.2.

@Arnavion
Copy link
Author

Now that 1.26.1 has been released, can someone confirm that this will be fixed in 1.26.2, or at least disable match ergonomics entirely? It isn't feasible to expect users to download a nightly and enable feature(nll) just to be sure they don't accidentally write code with this bug.

@novacrazy
Copy link

I for one would love the option to disable match ergonomics for crates. It's been nothing but distracting so far.

@pnkfelix pnkfelix added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. I-nominated labels May 30, 2018
@pietroalbini pietroalbini added the regression-from-stable-to-stable Performance or correctness regression from one stable version to another. label May 30, 2018
@pietroalbini
Copy link
Member

Now that 1.26.1 has been released, can someone confirm that this will be fixed in 1.26.2, or at least disable match ergonomics entirely?

cc @Mark-Simulacrum

@Mark-Simulacrum
Copy link
Member

I doubt that we will be disabling match ergonomics at this point due to their likely wide use and stabilization. It's possible we'd make another patch release but rather unlikely; we'll see what the results of compiler discussion are (I believe their meeting is Thursday). I do believe we'll backport the fix to beta so that it makes it for 1.27 if we have it by then.

@Arnavion
Copy link
Author

It's possible we'd make another patch release but rather unlikely... I do believe we'll backport the fix to beta so that it makes it for 1.27 if we have it by then.

I figured compiling code to access garbage memory silently would be more serious than that. I guess users should stay with 1.25 then.

@aturon
Copy link
Member

aturon commented May 30, 2018

@novacrazy This issue isn't the place to argue about the feature itself (which has already gone through several open comment periods prior to stabilization). I'd encourage you to instead open a thread on https://internals.rust-lang.org/ to talk about your experiences in detail, if you think there's something actionable for the lang team.

@nikomatsakis nikomatsakis self-assigned this May 30, 2018
@pietroalbini pietroalbini added the C-bug Category: This is a bug. label May 30, 2018
@nikomatsakis
Copy link
Contributor

This problem is in no way specific to match-default-bindings, apparently. The following program is also accepted (!):

fn main() {
    let mut foo = Some("foo".to_string());
    let bar = &mut foo;
    match bar {
        Some(ref mut baz) => {
            bar.take();
            drop(baz);
        },
        None => unreachable!(),
    }
}

@est31
Copy link
Member

est31 commented May 30, 2018

This problem is in no way specific to match-default-bindings, apparently. The following program is also accepted (!):

@nikomatsakis prepending &mut to the Some and None patterns in your example does give an error though, on both stable and nightly.

@Arnavion
Copy link
Author

Arnavion commented May 30, 2018

@nikomatsakis let bar = &mut foo; match bar { Some(...) is using match ergonomics. Without match ergonomics you would have had to write match *bar or &mut Some (edit: ... both of which correctly flag the double mutable borrow.)

@nikomatsakis
Copy link
Contributor

I see, I see. Yes, of course. That helps! thanks.

@nikomatsakis
Copy link
Contributor

Found the bug. "Implicit" derefs were accidentally being considered distinct from "explicit" derefs, leading borrowck to conclude that there are two distinct paths involved. Fix coming shortly.

bors added a commit that referenced this issue May 31, 2018
…ef, r=eddyb

remove notion of Implicit derefs from mem-cat

`PointerKind` is included in `LoanPath` and hence forms part of the equality check; this led to having two unequal paths that both represent `*x`, depending on whether the `*` was inserted automatically or explicitly. Bad mojo.

Fixes #51117

r? @eddyb
@Mark-Simulacrum
Copy link
Member

Core team just discussed this and we've decided to backport the fix and release 1.26.2.

@pietroalbini pietroalbini reopened this May 31, 2018
bors added a commit that referenced this issue Jun 1, 2018
1.26.2 release

This includes a backport of #51235 which fixes #51117 on stable. It has not been tested.

r? @nikomatsakis since the backport was not clean.
cc @rust-lang/core @rust-lang/release
@bstrie
Copy link
Contributor

bstrie commented Jun 2, 2018

Can this be closed?

@Mark-Simulacrum
Copy link
Member

Yes, I think so.

remexre added a commit to remexre/evaltrees that referenced this issue Jun 3, 2018
Arnavion pushed a commit to Arnavion/fac-rs that referenced this issue Jun 4, 2018
The fix for rust-lang/rust#51117 exposed some other
double mutable borrows, which are difficult to work around without enabling NLL.
@pnkfelix
Copy link
Member

Since this was fixed on AST-borrowck by #51235, removing NLL-fixed-by-NLL.

@pnkfelix pnkfelix removed the NLL-fixed-by-NLL Bugs fixed, but only when NLL is enabled. label Jun 27, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-borrow-checker Area: The borrow checker C-bug Category: This is a bug. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness regression-from-stable-to-stable Performance or correctness regression from one stable version to another. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

10 participants