Stream: t-lang/wg-unsafe-code-guidelines

Topic: unknown code Rust AM boundary


gnzlbg (Oct 12 2019 at 20:29, on Zulip):

@Hadrien Grasland

Using volatile loads and stores to engage in a data race on a *mut u8 which is only accessed via volatiles loads and stores and has no Rust references to it is okay, because this memory location does not interact with the Rust abstract machine and memory operations affecting it are therefore not subjected to the rules of said abstract machine.

I'm not sure what you mean by "this memory location does not interact with the Rust abstract machine".

gnzlbg (Oct 12 2019 at 20:30, on Zulip):

E.g., if the memory behind the *mut u8 is "owned" by Rust, e.g., because it was allocated by it, then accesses through the pointer do alter the state of the abstract machine

gnzlbg (Oct 12 2019 at 20:31, on Zulip):

OTOH if the pointer was obtained by Rust from some unknown code, and Rust never accesses it, then that sounds reasonable

Hadrien Grasland (Oct 12 2019 at 20:49, on Zulip):

What I was trying to express is that under these conditions, hardware stores cannot cause any observable harm.

Hadrien Grasland (Oct 12 2019 at 20:51, on Zulip):

But I need some more spec-friendly language to spell it out ;)

centril (Oct 12 2019 at 20:52, on Zulip):

This reminds me of the notion of non-interference (from lang-sec / information-flow control)

Hadrien Grasland (Oct 12 2019 at 20:55, on Zulip):

If I try to go into more details...

  1. Only hardware stores may harm Rust. Hardware loads can safely be considered not to be observable by Rust (even if that's technically not 100% true).
  2. A hardware store may only cause harm by writing an invalid value in memory or violating Rust's data access protocol (e.g. aliasing rules).
Hadrien Grasland (Oct 12 2019 at 20:57, on Zulip):

If the data has no validity invariant besides initialized-ness, then any value that hardware can write is fine.

Hadrien Grasland (Oct 12 2019 at 20:58, on Zulip):

If the data is only accessed -- and accessible, beware noalias and dereferenceable -- via hardware loads (volatile or assembly), then Rust's memory access semantics never come into play.

Hadrien Grasland (Oct 12 2019 at 20:59, on Zulip):

No reference nor non-volatile pointer accesses = no aliasing assumptions and no caching/dereferenceable.

Hadrien Grasland (Oct 12 2019 at 21:02, on Zulip):

At least that's my theory. Now I need to do a postdoc in @RalfJ's team to see how well it holds :P

RalfJ (Oct 12 2019 at 21:32, on Zulip):

lol^^

RalfJ (Oct 12 2019 at 21:33, on Zulip):

hardware loads and stores have their own semantic of a data race which "just" produces garbage data, which is fine by Rust as long as there's no data validity invariant to be upheld.

unfortunately LLVM devs refuse to guarantee things like "volatile loads can never return undef"

RalfJ (Oct 12 2019 at 21:34, on Zulip):

so, while I could certainly imagine a spec of volatile where you are right, I don't dare say that that's what LLVM implements :(

RalfJ (Oct 12 2019 at 21:37, on Zulip):

(also: oh no, now that long long volatile thread that lay dormant for some time is alive again and will be even harder to summarize, if/when some dares to take on that task^^)

Hadrien Grasland (Oct 12 2019 at 21:43, on Zulip):

Meh. Nasty LLVM devs. You have to admit, it would be super-tempting to be able to process inline assembly, FFI and volatile under a single unified "hardware memory model and its interactions with Rust's memory model" framework.

Three difficult memory model problems addressed for the price of one!

gnzlbg (Oct 12 2019 at 21:44, on Zulip):

If we only allow unknown code to do what sound Rust code would be able to do, then we are already there

gnzlbg (Oct 12 2019 at 21:45, on Zulip):

The problem is people wanting to call unknown code that can do things that Rust cannot do :laughter_tears:

Hadrien Grasland (Oct 12 2019 at 21:45, on Zulip):

Yeah, or writing an OS kernel in Rust.

Hadrien Grasland (Oct 12 2019 at 21:46, on Zulip):

Low-level hardware generally doesn't care much about what languages think of as the right way to access memory.

gnzlbg (Oct 12 2019 at 21:46, on Zulip):

Unless your hardware is "valgrind"

gnzlbg (Oct 12 2019 at 21:46, on Zulip):

or something similar

Hadrien Grasland (Oct 12 2019 at 21:47, on Zulip):

Truly, it would be nice if every CPU were like valgrind... but without the 30x slowdown.

gnzlbg (Oct 12 2019 at 21:48, on Zulip):

Jokes aside, I think that Rust can guarantee that volatile loads do not generate undef even if LLVM doesn't

gnzlbg (Oct 12 2019 at 21:48, on Zulip):

I'd be fine to defer fixing that LLVM bug until somebody hits it

RalfJ (Oct 12 2019 at 21:49, on Zulip):

volatile and inline assembly are related but not the same though ;)

Hadrien Grasland (Oct 12 2019 at 21:49, on Zulip):

In a hardware-specific way, that's trivial: just write a one-line inline assembly snippet with your favorite LOAD instruction.

RalfJ (Oct 12 2019 at 21:49, on Zulip):

and FFI... together with xLTO I'm afraid this framework is not going to be any good for that

Hadrien Grasland (Oct 12 2019 at 21:49, on Zulip):

In a portable way, that may be harder

RalfJ (Oct 12 2019 at 21:50, on Zulip):

but yes, the better I (think I) understand volatile the more I think a variant of that can also be used for inline assembly

RalfJ (Oct 12 2019 at 21:50, on Zulip):

and the framework for that even exists already, it's called "syscalls" or "externally observable events"

gnzlbg (Oct 12 2019 at 21:51, on Zulip):

I think we should group them all under that framework

gnzlbg (Oct 12 2019 at 21:52, on Zulip):

The question is still: what is unknown code allowed to do? I don't think the possibility of xLTO happening should affect the answer

RalfJ (Oct 12 2019 at 21:52, on Zulip):

the hard part is bounding what the effect of a volatile / inline assembly can be in terms of the Rust Abstract Machine in a way that's actually both sound and useful

gnzlbg (Oct 12 2019 at 21:52, on Zulip):

I asked whether an asm! statement with a memory clobber is a data-race, and I wasn't joking there

RalfJ (Oct 12 2019 at 21:52, on Zulip):

I'd be fine to defer fixing that LLVM bug until somebody hits it

I am doubtful we'd ever realize that this is the cause of the bug. It'll just be someone's Heisenbug and make their life miserable.

RalfJ (Oct 12 2019 at 21:53, on Zulip):

The question is still: what is unknown code allowed to do? I don't think the possibility of xLTO happening should affect the answer

I'd like to keep FFI and xLTO out of the equation until we solved the "easier" cases^^

RalfJ (Oct 12 2019 at 21:53, on Zulip):

I asked whether an asm! statement with a memory clobber is a data-race, and I wasn't joking there

yeah it's a good question ;)

RalfJ (Oct 12 2019 at 21:54, on Zulip):

it goes back to another issue I have with the C++ memory model: it's definition of a data race is utterly broken

RalfJ (Oct 12 2019 at 21:54, on Zulip):

I think we need an operational model to make sense of all of that

Hadrien Grasland (Oct 13 2019 at 08:23, on Zulip):

So... I have another question on this topic.

Hadrien Grasland (Oct 13 2019 at 08:23, on Zulip):

What if we defined Rust's volatile to compile down to LLVM's atomic volatile with Relaxed or Unordered ordering?

Hadrien Grasland (Oct 13 2019 at 08:25, on Zulip):

As far as I know, this would change almost nothing from a codegen point of view (since volatile loads and stores already cannot be eliminated or reordered).

Hadrien Grasland (Oct 13 2019 at 08:26, on Zulip):

But it would have the interesting side-effect of making data races of volatile accesses on the Rust side well-defined.

Hadrien Grasland (Oct 13 2019 at 08:27, on Zulip):

(Since they wouldn't be data races anymore from LLVM's point of view, and rustc itself never cared either way)

Hadrien Grasland (Oct 13 2019 at 08:31, on Zulip):

As for the use case of sharing memory with untrusted code, data races of atomic reads with non-atomic writes would remain UB... but on any platform that I am aware of, this UB would have no observable consequence, as long as the compiler never does LTO between the untrusted code and the trusted code (and doing that could go wrong in plenty of other way)

Hadrien Grasland (Oct 13 2019 at 08:33, on Zulip):

I guess my question is, if volatile is meant to mean "defer to hardware semantics", and the semantics of all known hardware guarantees Relaxed atomics-like semantics on loads and store... do we really gain anything by using non-atomic volatile accesses?

Hadrien Grasland (Oct 13 2019 at 08:34, on Zulip):

Given volatile's restrictions, is there really any optimization that LLVM can perform on non-atomic volatile accesses, that it cannot perform on atomic volatile accesses?

Hadrien Grasland (Oct 13 2019 at 08:35, on Zulip):

Do we really care about LLVM's weird notion of racey volatile accesses, or could we just do without by telling LLVM to shut up and treat volatile as atomic?

Hadrien Grasland (Oct 13 2019 at 08:36, on Zulip):

There is admittedly one backwards compatibility wrinkle: Making volatile atomic would break compilation of volatile accesses larger than pointer-sized. Many would argue that this is actually desirable. But breaking backwards compatibility is never okay.

Hadrien Grasland (Oct 13 2019 at 08:38, on Zulip):

To address this, we would probably need to add ptr::read_atomic_volatile(), as opposed to changing ptr::read_volatile()'s semantics. Which would, as an extra benefit, allow us to add an Ordering parameter.

Hadrien Grasland (Oct 13 2019 at 08:39, on Zulip):

After that, if everyone agrees with me that non-atomic volatile is a footgun, we could _consider_ deprecating it... But we don't really have two, we can just add a note on non-atomic volatile's documentation suggesting use of atomic volatile in concurrent scenarios.

Hadrien Grasland (Oct 13 2019 at 08:41, on Zulip):

(OTOH, defaulting to relaxed/unordered volatile semantics would be the right choice in the case of inline assembly, where there's no room for an atomic ordering parameter in the syntax and hardware doesn't allow non-native-width load/store anyhow)

comex (Oct 13 2019 at 08:45, on Zulip):

One issue might be if there were some very hypothetical platform where (a) relaxed atomics use a different instruction from normal accesses and (b) MMIO accesses want the normal instruction.

Hadrien Grasland (Oct 13 2019 at 08:47, on Zulip):

That would mean a multiprocessor platform without guaranteed cache coherence... ew, poor devs.

Hadrien Grasland (Oct 13 2019 at 08:48, on Zulip):

But such a use case could be served by not deprecating read_volatile/write_volatile, and instead warning people that these are aimed at very niche scenarios and should not be used as their everyday tool, even for MMIO.

comex (Oct 13 2019 at 08:52, on Zulip):

condition A might be satisfied by VMs such as WebAssembly... I took a quick look but I can't figure out exactly what they're guaranteeing about data races

comex (Oct 13 2019 at 08:52, on Zulip):

but then WebAssembly has no MMIO

Hadrien Grasland (Oct 13 2019 at 08:55, on Zulip):

It does have I/O with the host platform, that's what hsivonen had in mind when starting this topic many months ago... but that form of MMIO is not picky about the exact choice of hardware instruction ;)

comex (Oct 13 2019 at 08:57, on Zulip):

by the way, one fun thing about volatile with memory orderings

comex (Oct 13 2019 at 08:57, on Zulip):

on some platforms, there is more than one way to implement atomics in terms of barriers https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html

comex (Oct 13 2019 at 08:58, on Zulip):

if you're using it for IPC in shared memory... in theory the process you're communicating with could be using a different choice

comex (Oct 13 2019 at 08:58, on Zulip):

(it probably won't be, since it's part of the ABI and other processes are probably not using a different ABI, but you never know shrug)

Hadrien Grasland (Oct 13 2019 at 08:59, on Zulip):

You're right! In that case, it would probably make more sense to only allow relaxed and unordered ordering on atomic volatile.

comex (Oct 13 2019 at 08:59, on Zulip):

...that said, I have actual use cases for atomics in shared memory with non-relaxed orderings

comex (Oct 13 2019 at 09:00, on Zulip):

so I have no idea what the best policy is :p

Hadrien Grasland (Oct 13 2019 at 09:00, on Zulip):

Probably start with Relaxed, and consider adding other memory barriers as a later step.

comex (Oct 13 2019 at 09:01, on Zulip):

perhaps

Hadrien Grasland (Oct 13 2019 at 09:01, on Zulip):

We don't really need to solve the problem of defining an ABI for atomic memory barriers urgently, I think.

Hadrien Grasland (Oct 13 2019 at 09:02, on Zulip):

And that would likely entail quite a bit of extra work, e.g. getting it engraved in the SYSV ABI for example.

comex (Oct 13 2019 at 09:02, on Zulip):

it's already part of the ABI, isn't it? you can link together multiple C programs that use C11 atomics

comex (Oct 13 2019 at 09:02, on Zulip):

although I don't know if it's literally specified in the document

Hadrien Grasland (Oct 13 2019 at 09:03, on Zulip):

Actually, that's an excellent point. I have no idea how that works.

comex (Oct 13 2019 at 09:03, on Zulip):

it's just that in general, a process you're communicating with over IPC may not be something you could link with

comex (Oct 13 2019 at 09:03, on Zulip):

that said, this is usually an issue only for SeqCst (although I can't say it couldn't theoretically apply to other orderings)

Hadrien Grasland (Oct 13 2019 at 09:07, on Zulip):

One potentially interesting middle ground could be to treat non-Relaxed atomics in shared memory like any other repr(Rust) concern: we only guarantee that it works when linking two binaries produced by the same version of rustc and in the same configuration.

Hadrien Grasland (Oct 13 2019 at 09:07, on Zulip):

Not sure how that idea translates into standardese, tho

gnzlbg (Oct 13 2019 at 10:23, on Zulip):

If we map Rust atomics to C11 atomics we can just provide the guarantees when libc is linked

gnzlbg (Oct 13 2019 at 10:24, on Zulip):

e.g. "if all processes involved in the IPC use the same ABI"/"libc", then...

Hadrien Grasland (Oct 13 2019 at 10:45, on Zulip):

I posted some report of this discussion at https://github.com/rust-lang/unsafe-code-guidelines/issues/152 with a link back here for extra visibility.

Last update: Nov 20 2019 at 11:25UTC