Comment by tialaramex

Comment by tialaramex 10 days ago

17 replies

The fact Go has UB under data races has practical implications for sufficiently complex concurrent software. If you can induce a race on a non-trivial object, that's UB instantly - you can probably blow up the Go runtime and all bets are off.

I would not characterise this fact, which is a design choice in Go, as similar to say a Rust soundness bug, which will sooner or later just get fixed. They aren't going to somehow magically fix this problem in Go, it's part of the design.

adonovan 10 days ago

> They aren't going to somehow magically fix this problem in Go, it's part of the design.

I wouldn't be entirely pessimistic.

Russ's post https://research.swtch.com/gorace mentions a conservative representation for Go's data structures (essentially: more indirection) that would make it possible to implement them in a way that was robust to races, at an obvious large performance cost.

More recently others have been investigating the possibility of using 128-bit atomic writes (on ARM and x86) to reduce the cost. Go's strings and interfaces are both 2-word structures. Slices are three words but by changing the field order atomicity can be achieved with 2-word writes. Of course it would break a lot of code that assumes the representation or the ABI.

  • wbl 10 days ago

    That code is usually internal.

  • kaba0 9 days ago

    I mean, C is also memory safe when run within valgrind.. at an obvious large performance cost.

    • tialaramex 9 days ago

      To make this work you need to re-define "memory safety" to the point where it loses any value in a discussion about programming

      Valgrind has no way to detect trivial global or local array bounds misses so long as they don't stray out of the defined memory. It can't spot this because the resulting executable (the only thing Valgrind sees) is not doing anything that's prohibited - it's nonsense because you used a non-MSL, but the actual executable has some defined behaviour.

tptacek 10 days ago

My point has nothing to do with whether the language will achieve "soundness". It's that this is behavior that has not over the last 15 years produced exploitable vulnerabilities, despite extremely high incentives for those vulnerabilities to be unearthed.

  • pkolaczk 10 days ago

    You don’t need to blow up the runtime to cause a vulnerability due to a data race in Go:

    https://security.snyk.io/vuln/SNYK-DEBIAN13-GOLANGGITHUBGORE...

    • arp242 10 days ago

      That's a completely different type of vulnerability than the UB that's being talked about.

      > The call to sync.Pool.Get will then return a bytes.Buffer that hasn't had bytes.Buffer.Reset called on it. This dirty buffer will contain the HTTP request body from an unrelated request.

      This is just a good ol' logic error, that just so happens to also be a race.

      • unscaled 10 days ago

        These type of UB bugs in Go are a bit of a red herring, since most race conditions arise from improper use of shared mutability, and would still be a problem even in the presence of full memory safety, for instance:

        https://github.com/golang/go/issues/37669

        https://github.com/golang/go/issues/48340

        These types of race conditions cannot happen in Rust. Not because Rust does not have UB, but because Rust does not allow multiple writable pointers ("mutable borrows") to the same memory region. If you want shared AND mutable access to memory, you must use a thread-safe construct such as Mutex or Cell — or drop into unsafe code.

        Rust does not prevent all types of errors of course. Dirty buffer reuse (as in the GP example) is still possible in Rust. You could still have situations where a buffer is returned to a pool without resetting it. But this could only be a pure logic error where you've forgot to reset the buffer and it would occur consistently and thus would be easy to reproduce and debug. In addition, with idiomatic Rust, you could enforce proper buffer cleanup in Rust by wrapping the Buffer with a type that implements Drop.

        More specifically, the vulnerability mentioned in GP is not possible in Rust. The description is a bit misleading, but the issue was not that the buffer was returned to the pool without being reset, but rather that the same buffer was returned to the pool TWICE under certain conditions, due to a data race. This is not possible in Rust. You cannot put the same owned buffer twice in a pool, due to Rust's move semantics (affine types). And if we want to be completely honest, you'd probably won't need to pool buffers in Rust to begin with, since you don't need to avoid garbage collection (there is none). In most cases, malloc is going to work good enough as your "pool".

        We have a serious problem as an industry, where there is a popular conception of memory safety and type safety as a binary property: a language is either safe or unsafe, either sound or unsound. But it's more of a spectrum, and not even a contiguous one at that. This comments thread is split between people who say that large size atomicity UB is not a major issue in practice and people willing to completely rule off Go's memory safety based on that. But we could just say Go sits near the safe end of the spectrum of memory safety — it certainly does far better than C. My security concerns with Go, after nearly 9 years of using are mostly about race conditions, memory leaks and lack of better mechanisms to enforce type safety (such as sum types and affine types).

arp242 10 days ago

I think these are "if a tree falls in a forest and no one is around to hear it, does it make a sound?"-type musings.

Whatever the case, it doesn't really affect anyone and it doesn't really matter.

  • pkolaczk 10 days ago

    It’s a matter of time. Spectre / meltdown were also considered extremely hard to use in practice. Yet they are considered vulnerabilities.

    In Golang case the data race window to corrupt memory is extremely narrow, so it makes it very hard to trigger it. That together with Go being still quite a niche language results in the fact we see no exploits… yet.

    • vacuity 10 days ago

      I note that of the Spectre/Meltdown and similar hardware vulns, even the hard-to-swallow kinds of mitigations for Spectre primarily prevent user-to-kernel hijacking only, which is the most important single property but doesn't cover inter-process hijacking. We can more or less patch these vulns completely, but there is a (huge) performance penalty to be weighed as a drawback. I do not know enough to say whether the Go data race bugs are an acceptable risk. Although, not everyone may accept it, namely if it strikes them just once.

    • arp242 10 days ago

      Even if some sort of security bug is discovered tomorrow, then we're talking about one issue every 15 years or so. Whoop die doo. That barely even registers in the noise.

      That it "may" lead to a problem and that it's not "sound" is basically just meaningless.

      • pkolaczk 9 days ago

        How many spectre / meltdown related vulnerabilities were detected between 1990 and 2010? Zero. So those chip vendors must be paranoid they patch them - were talking about one issue per 20 years xD Similarly how many hashmap collision attacks existed prior to 2010? Zero, but once people learned they are not just a theoretical problem, suddenly plenty of vulnerabilities were found.

        Seriously, it doesn’t work like that. It’s not linear. During the first half of those 15 years almost no one heard about Go, and forget about using it in critical systems where vulnerabilities would matter. Even at Google it was (still is?) very niche compared to Java, Python and C++ and is used mostly for userspace clis and orchestration, not the core stuff. There is simply very little incentive to attack systems written Go, when there exist 100x more less secure networked systems written in C or C++.

        Considering this memory unsafety thing in Go is fortunately very hard to exploit, there is no doubt why attackers don’t target this weakness and it has been so far only a technical curiosity. Also data races in Go are easy to make and can lead to vulnerabilities in a much more direct way, without corrupting the heap. I bet those are exploited first (and there exist CVEs caused by races in Go).