Re: C aggregate passing (Rust kernel policy)

Linus Torvalds
2025-02-26T21:14:54Z

On Wed, 26 Feb 2025 at 12:00, Martin Uecker <uecker@tugraz.at> wrote:
>
> The model is exactly the same as in C. One defines "observable
> behavior" (to use C terminology) and compiler can do whatever it
> wants as long as it preserves this.

The problem really is that memory accesses (outside of volatile, which
is defined to be a side effect) aren't actually defined to be
observable.

Yes, yes, the standard _allows_ that behavior, and even hass language
to that effect ("The keyword volatile would then be redundant"), but
nobody ever does that (and honestly, treating all memory accesses as
volatile would be insane).

> As Ralf said, the difference is that Rust makes it much harder to
> accidentally trigger UB.

Yes, but "accidental" is easy - unless the compiler warns about it.

That's why I basically asked for "either warn about UB, or define the
UB do the 'naive' thing".

So this is literally the problem I'm trying to bring up: "aliasing" is
defined to be UD _and_ the memory accesses are not defined to be
observable in themselves, so a C compiler can take those two things
and then say "you get random output".

THAT is what I am asking you to consider.

Pointing to the C standard doesn't help. The C standard GOT THIS WRONG.

And yes, part of getting it wrong is that the standard was written at
a time when threading wasn't a prime thing. So it was somewhat
reasonable to claim that memory accesses weren't "observable".

But dammit, doing things like "read the same variable twice even
though the programmer only read it once" *IS* observable! It's
observable as an actual security issue when it causes TOCTOU behavior
that was introduced into the program by the compiler.

So I claimed above that treating all memory accesses as volatile would
be insane. But I do claim that all memory accesses should be treated
as "USE the value of a read or write AT MOST as many times as the
source code said".

IOW, doing CSE on reads - and combining writes - when there aren't any
aliasing issues (or when there aren't any memory ordering issues)
should absolutely be considered ok.

And doing speculative reads - even if you then don't use the value -
is also entirely fine. You didn't introduce any observable behavior
difference (we'll agree to dismiss cache footprint issues).

But if the source code has sa single write, implementing it as two
writes (overwriting the first one) IS A BUG. It damn well is visible
behavior, and even the C standards committee has agreed on that
eventually.

Similarly, if the source code has a single read, the compiler had
better not turn that into two reads (because of some register pressure
issue). That would *ALSO* be a bug, because of the whole TOCTOU issue
(ie the source code may have had one single access, done sanity
testing on the value before using it, and if the compiler turned it
all into "read+sanity test" and "read+use", the compiler is
introducing behavioral differences).

That "single read done as multiple reads" is sadly still accepted by
the C standard, as far as I can tell. Because the standard still
considers it "unobservable" unless I've missed some update.

Please do better than that.

Linus