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

cmd/compile: incorrect assignment to uint64 via pointer converted to *uint16 (new in 1.7) #16769

Closed
kelseyfrancis opened this issue Aug 17, 2016 · 23 comments

Comments

@kelseyfrancis
Copy link

What version of Go are you using (go version)?

go version go1.7 darwin/amd64 and go version go1.7 linux/amd64.

This issue is not present in go1.6.2.

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"

and

GOARCH="amd64"
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"

What did you do?

Assign a uint16 (or uint32) to a previously unused uint64 via converted pointer on a little-endian architecture. go run the following program:

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    var x uint64
    *(*uint16)(unsafe.Pointer(&x)) = 0xffee
    fmt.Printf("%x\n", x)

    var y uint64
    fmt.Printf("%x\n", y)
    *(*uint16)(unsafe.Pointer(&y)) = 0xffee
    fmt.Printf("%x\n", y)
}

What did you expect to see?

ffee
0
ffee

What did you see instead?

c42000ffee
0
ffee

A test that passes with Go 1.6 but not with Go 1.7:

// +build amd64

package main

import (
    "fmt"
    "unsafe"
)

func ExampleUint16NoAccessBeforeAssignment() {
    var x uint64
    *(*uint16)(unsafe.Pointer(&x)) = 0xffee
    fmt.Printf("%x\n", x)
    // Output:
    // ffee
}

func ExampleUint16AccessBeforeAssignment() {
    var x uint64
    fmt.Printf("%x\n", x)
    *(*uint16)(unsafe.Pointer(&x)) = 0xffee
    fmt.Printf("%x\n", x)
    // Output:
    // 0
    // ffee
}

func ExampleUint32NoAccessBeforeAssignment() {
    var x uint64
    *(*uint32)(unsafe.Pointer(&x)) = 0xffeeddcc
    fmt.Printf("%x\n", x)
    // Output:
    // ffeeddcc
}

func ExampleUint32AccessBeforeAssignment() {
    var x uint64
    fmt.Printf("%x\n", x)
    *(*uint32)(unsafe.Pointer(&x)) = 0xffeeddcc
    fmt.Printf("%x\n", x)
    // Output:
    // 0
    // ffeeddcc
}
@dr2chase
Copy link
Contributor

Unsafe code, not a compiler bug.

It would be a minor bit of a work to fix this, and a larger bit of work to fix this in a way that did not leave useless local-zeroing in the code for correctly-written unsafe code.

@mdempsky
Copy link
Member

@dr2chase Can you clarify what's incorrect about @kelseyfrancis's code? It looks valid to me.

@bradfitz
Copy link
Contributor

@mdempsky:

    var x uint64
    *(*uint16)(unsafe.Pointer(&x)) = 0xffee

That's treating a uint64 as a uint16.

@mdempsky
Copy link
Member

Right, I see that. What's invalid about that? I don't recall package unsafe ever saying type punning like that isn't allowed in Go.

@bradfitz
Copy link
Contributor

Package unsafe has also never promised it is valid. As @randall77 has said elsewhere, we're allowed to change the unsafe behavior between releases because because it makes no promises.

https://golang.org/doc/go1compat even says:

Use of package unsafe. Packages that import unsafe may depend on internal properties of the Go implementation. We reserve the right to make changes to the implementation that may break such programs.

@kelseyfrancis
Copy link
Author

Fair enough. Incidentally, explicit initialization of x produces the same result:

    var x uint64 = 0x1020304050607080
    *(*uint16)(unsafe.Pointer(&x)) = 0xffee
    fmt.Printf("%x\n", x)  // prints c42000ffee rather than 102030405060ffee

@mdempsky
Copy link
Member

https://golang.org/pkg/unsafe/#Pointer says:

(1) Conversion of a *T1 to Pointer to *T2.

Provided that T2 is no larger than T1 and that the two share an equivalent memory layout, this conversion allows reinterpreting data of one type as data of another type.

My interpretation of "equivalent memory layout" is referring to having the same pointer slots. Since uint16 is no larger than uint64 and neither contain pointers, it seems explicitly documented to be valid.

If not, we should clarify the documentation to define "memory layout". That term isn't used in the Go language spec or memory model.

@mdempsky
Copy link
Member

Reopening because at the very least I think there's a documentation issue here.

@mdempsky mdempsky reopened this Aug 17, 2016
@randall77
Copy link
Contributor

We should fix this. The failure mode is bad - I believe we're exposing uninitialized stack memory this way. That's a (very minor, but still) security bug.

@dr2chase
Copy link
Contributor

Workaround is to assign the local to a global sink before the unsafe use; this will force the zeroing to occur.

@dr2chase
Copy link
Contributor

dr2chase commented Aug 17, 2016

Just treat unsafe stores as not real stores, and thus no redundant store elimination?
Would only hurt code that ought not use unsafe anyway, since it has to be locals anyhow.

Keith, how can use of unsafe not be a security violation in general?
What prevents taking address of auto, casting to *byte[100000], and happily iterating over it?

@mdempsky mdempsky added this to the Go1.7.1 milestone Aug 17, 2016
@cherrymui
Copy link
Member

+1 on @dr2chase's point on security.

Anyway, I send CL https://go-review.googlesource.com/c/27320/.

@gopherbot
Copy link

CL https://golang.org/cl/27320 mentions this issue.

@dr2chase
Copy link
Contributor

If we're revising the documentation for unsafe, can we make it a lot scarier and with even fewer things that look like promises?

@dgryski
Copy link
Contributor

dgryski commented Aug 18, 2016

@dr2chase I thought the only promise was "We promise to occasionally break your use of unsafe."

@dr2chase
Copy link
Contributor

dr2chase commented Aug 18, 2016

occasionally
and it was already broken, you were just deluded into thinking it wasn't by some spuriously passing test.

@mdempsky
Copy link
Member

Keith, how can use of unsafe not be a security violation in general?

Package unsafe documents several patterns that users are allowed to assume will remain safe, one of which includes (at least by my best interpretation) converting *uint64 to *uint16. If there were no safe uses of package unsafe, then there wouldn't be any point in providing it.

What prevents taking address of auto, casting to *byte[100000], and happily iterating over it?

That's not one of the patterns that are documented to be safe. Conversion of *T1 to *T2 is only safe if Sizeof(T2) <= Sizeof(T1), which won't be true in the scenario you're asking about (since we don't allocate 100,000+ byte objects on the stack).

and it was already broken,

Do you mean that the pre-SSA compiler didn't support conversions from *uint64 to *uint16 either? Or you think the user code was broken for relying on patterns that you don't agree should be valid?

@dr2chase
Copy link
Contributor

I think that we should not be bound by any particular artifacts of the previous compiler's treatment of unsafe code, because the full set of those artifacts is not specified. I also think it was a mistake to "document" this behavior for end-user use, because people will write unsafe code that happens to pass tests with a particular Go release and believe that therefore they got it right. We don't have a test suite for what unsafe behavior we support except for the runtime itself, and compiler writers aren't good at imaging the crazy things that people might do with unsafe code. And lacking such a test suite, for unsafe, I think it's fine for compiler-writers to push back hard on "bugs" involving unsafe code.

Unsafe code is generally a security violation, because no matter what we say in documentation, we don't check it mechanically. It's pretty much the whole point of actual security violations that they do an end-run on documented restrictions. Or as I asked, "what prevents?"

@mdempsky
Copy link
Member

mdempsky commented Aug 18, 2016

Reopening to track backporting to Go 1.7.1. IMO this is a regression from 1.6, and if we're going to fix for 1.8, we should backport to 1.7.1 too. /cc @broady

@dr2chase I'll followup on golang-dev. I think this discussion is worth having, just it shouldn't be here. :)

@mdempsky
Copy link
Member

Closing because this is fixed on master. Per new release policy, a separate issue will track cherry-picking for Go 1.7.1.

@joegrasse
Copy link

@mdempsky, just curious what is the new issue number or has it not been created yet?

@bradfitz
Copy link
Contributor

bradfitz commented Sep 7, 2016

@randall77, we're getting stuck cherry-picking this to the Go 1.7 branch. It seems to depend on some earlier stuff.

Rather than @broady and I botch it, can you prepare a version of the fix for the 1.7 release branch?

@randall77
Copy link
Contributor

https://go-review.googlesource.com/28670 (manual cherrypick of 27320 into release-branch.go1.7) out for review.

@golang golang locked and limited conversation to collaborators Sep 7, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

9 participants