Live data from Hacker News

There is no memory safety without thread safety

ralfj.de

211–220 of 517 posts

Re: There is no memory safety without thread safety

#211

Earlier quoted context omitted.

Why people with vastly more skill and experience programming and writing programming languages made the decisions they did.

I meant, which particular design decisions are you accusing people of having failed to comprehend the rationale for?

Whatever design decisions they consider to be weird. Because it probably just means they don't know what the trade offs and goals were.

Re: There is no memory safety without thread safety

#212
post #175

Earlier quoted context omitted.

As a general heuristic, a corrupted data structure in a network server results in RCE. This is common in languages like C and C++. On first glance, it looks like the bug can (at least) result in the server accessing a slice object where the various fields don’t all come from the same place. So the target server can end up accessing some object out of bounds (or as the wrong type or both), which can easily end up writ…

No, that doesn't work. Lots of (maybe even most) corrupted data structures aren't exploitable (past DOS). Where does the attacker-controlled data come from . What path does it take to get to where the attacker wants it to go . You have to be able to answer those two questions.

The Internet is full of nice articles of people bragging about their RCE exploits that start with single-byte overruns or seemingly-weak type confusions, etc.

> Where does the attacker-controlled data come from.

The example I gave was an HTTP server. Attackers can shove in as much attacker-controlled data as they want. They can likely do something like a heap by using many requests or many headers. Unless the runtime zeroes freed memory (and frees it immediately, which GC languages like Go often don’t do), then lots of attacker controlled data will stick around. And, for all I know, the slice that gets mixed up in this bug is fully attacker controlled!

In any event, I think this whole line of reasoning is backwards. Developers should assume that a memory safety error is game over unless there is a very strong reason to believe otherwise — assume full RCE, ability to read and write all in-process data, the ability to issue any syscall, and the ability to try to exploit side channels. Maybe very strong mitigations like hardware-assisted CFI will change this, and maybe not.

Re: There is no memory safety without thread safety

#213
post #203

Earlier quoted context omitted.

If you're stipulating deliberately inserted vulnerabilities then there are much easier ways, e.g., with a plausibly-deniable logic bug in code that calls os/exec or reflect (both of which can execute arbitrary code by design).

If you see `exec`, that's an obvious point where you want to pay extra attention. Compare to an innocent looking map operation, and it's not even in the same league.

What's the least suspicious-looking code that you think could facilitate remote code execution via data-race memory corruption?

Re: There is no memory safety without thread safety

#214
post #142

Every time this conversation comes up, I'm reminded of my team at Dropbox, where it was a rite of passage for new engineers to introduce a segfault in our Go server by not synchronizing writes to a data structure. Swift has (had?) the same issue and I had to write a program to illustrate that Swift is (was?) perfectly happy to segfault under shared access to data structures. Go has never been memory-safe (in the Rust…

Crashing on shared access is the safe thing to do

"Crashing" is a very positive spin. "The heap getting the corrupted until it was killed by the operating system" is another interpretation.

Re: There is no memory safety without thread safety

#215
post #23

Earlier quoted context omitted.

It took months to finally solve a data race in Go. No race detector would see anything. Nobody understood what was happening. It ultimately resulted in a loop counter overflowing, which recomputed the same thing a billion of time (but always the same!). So the visible effect was a request would randomly take 3 min instead of 100ms. I ended up using perf in production, which indirectly lead me to understand the data r…

> It ultimately resulted in a loop counter overflowing, which recomputed the same thing a billion of time (but always the same!). So the visible effect was a request would randomly take 3 min instead of 100ms. This means that multiple goroutines were writing to the same local variable. I've never worked on a Go team where code that is structured in such a way would be considered normal or pass code review without goo…

It happens all the time sadly.

It's not because people intentionally write this way. A function takes a parameter (a Go slice for example) and calls another function and so one. Deep down a function copies the pointer to the slice (via closure for example). And then a goroutine is spawned with this closure.

The most obvious mistakes are caught quickly. Buu sharing a memory address between two threads can happen very indirectly.

And somehow in Go, everybody feels incredibly comfortable spawning millions of coroutines/threads.

Re: There is no memory safety without thread safety

#216
post #80

Every time this conversation comes up, I'm reminded of my team at Dropbox, where it was a rite of passage for new engineers to introduce a segfault in our Go server by not synchronizing writes to a data structure. Swift has (had?) the same issue and I had to write a program to illustrate that Swift is (was?) perfectly happy to segfault under shared access to data structures. Go has never been memory-safe (in the Rust…

Safety isn't binary, so your comment makes no sense.

"Memory safety" has a specific, binary definition.

Re: There is no memory safety without thread safety

#217
post #180

Earlier quoted context omitted.

Where? Within, as I said, “the type of consumer OS or client-side application that typically gets attacked”. It has to be a component of either a big application or a big OS, or something with comparable scope. Otherwise it would not likely be targeted by real-world memory corruption attacks (that we hear about) no matter the language. At least that’s my impression.

I'm sure I could come up with a bunch of examples but the first thing that jumps into my head is the Docker ecosystem.

I would say that Go is common in command-line developer tooling, which is sort of client-side albeit a noncentral example of same (since it includes tools for running servers and suchlike), and rare in all other client-side domains that I can think of.

Re: There is no memory safety without thread safety

#218
post #171

I have never seen real Go code (i.e. not code written purposefully to be exploitable) that was exploitable due to a data race. This doesn’t prove a negative, but is probably a good hint that this risk is not something worth prioritizing for Go applications from a security point of view. Compare this with C/C++ where 60-75% of real world vulnerabilities are memory safety vulnerabilities. Memory safety is definitely a…

I have! What do i win?

Was it open source? Would be interested to know more.

Re: There is no memory safety without thread safety

#219
post #175

Earlier quoted context omitted.

What about this particular bug do you think makes it likely to be exploitable? I'm not asking you to write an RCE POC, just to tell a story of the sequence of events involving this bug that results in attacker-controlled code. What does the attacker control here, and how do they use that control to divert execution?

As a general heuristic, a corrupted data structure in a network server results in RCE. This is common in languages like C and C++. On first glance, it looks like the bug can (at least) result in the server accessing a slice object where the various fields don’t all come from the same place. So the target server can end up accessing some object out of bounds (or as the wrong type or both), which can easily end up writ…

I looked at the code, and unless I've misunderstood it, this bug can't corrupt the slice in the sense of allowing accesses outside the designated allocation or anything like that, because the slice variable is only written to once, when the writer is initialized, so there can't be racy accesses to it. The contents of the slice can potentially be corrupted, but that's just arbitrary bytes, so not a memory safety violation.

The line I'm not quite as sure about is https://go.googlesource.com/go/+/refs/tags/go1.13.1/src/bufi.... That assignment is to a variable of interface type, so in theory it could cause memory corruption if multiple goroutines executed it concurrently on the same receiver, which was possible until the bug was fixed. That said, I cannot immediately think of a way to exploit this; you can only write error values corresponding to errors that you can make occur while writing to the socket, and that's a much more constrained set of possible values than the arbitrary bytes that can occur in a buffer. And for that, you only get confusion among the types of those particular errors. It might be possible but it at least looks challenging.

Re: There is no memory safety without thread safety

#220
post #161

Earlier quoted context omitted.

The situation with numbers in basically every widely used programming language is kind of an indictment of our industry. Silent overflow for incorrect results, no convenient facilities for units, lossy casts everywhere. It's one of those things where standing in 1975 you'd think surely we'll spend some of the next 40 years of performance gains to give ourselves nice, correct numbers to work with, but we never did.

"nice, correct numbers" end somewhere between 1/3 and sqrt(2) so in reality, it's just "pick your own poison" to various degrees...

The square root of two is still a computable Real. We choose not to cope with that, but it's not actually impossible it was merely inconvenient. I've mentioned elsewhere that my Rust care realistic is quite happy to work with these numbers e.g. take the square root of ten, and the square root of forty, multiply them together and get the quite ordinary integer twenty.

The non computable reals are a huge problem because, as their name suggests, we can't compute them - and in the strict sense that's Almost All reals, but none of the ones you're thinking of are non-computable so you'll likely be fine.

For the merely rational numbers like a third, or sixteen hundred and five sevenths, it's even more so a matter of choosing not to address it rather than it being out of reach.

Post reply on HN