Il lun 17 giu 2024, 23:45 Manos Pitsidianakis
<manos.pitsidiana...@linaro.org> ha scritto:
> Secondly, are you implying that these callbacks are not operated under
> the BQL?

No, I'm implying that if you had the following nested calls:

 unsafe read callback receives the opaque point
   -> cast to &mut to call safe read callback
     -> chardev function accessing the opaque value
       -> unsafe chardev callback receives the opaque pointer
         -> cast to & or &mut to call safe chardev callback

Then you would violate the Rust invariant that there can only be one
active reference at a single time if it is &mut. The only exception is
that you can create an &mut UnsafeCell<T> for an active
&UnsafeCell<T>.

It doesn't matter if this cannot happen because of invariants in the
QEMU code. If you are writing safe Rust, every cast to &mut requires
very strong justification. The way you do it instead is to use
RefCell, and borrow_mut() instead of casting to &mut. This way, at
least, the invariant is checked at runtime.

And in fact this _can_ happen. I hadn't checked until now because I
was mostly worried about the conceptual problem, not any specific way
undefined behavior can happen. But here it is:

PL011State::read takes &mut
  -> qemu_chr_fe_accept_input
    -> mux_chr_accept_input
      -> pl011_can_receive
        -> creates & which is undefined behavior

You probably can write a 20 lines test case in pure Rust that miri
complains about.

Should we make it a requirement for v3, that all callback methods in
PL011State (read, write, can_receive, receive, event) take a &self and
achieve interior mutability through RefCell? Probably not, a comment
is enough even though technically this is _not_ valid Rust. But it is
a high priority task after the initial commit.

> Just say this directly instead of writing all these amounts of text

I said in the first review that the state should be behind a RefCell.

https://lore.kernel.org/qemu-devel/CABgObfY8BS0yCw2CxgDQTBA4np9BZgGJF3N=t6eoBcdACAE=n...@mail.gmail.com/

> Finally, this is Rust code. You cannot turn off the type system, you
> cannot turn off the borrow checker, you can only go around creating new
> types and references out of raw memory addresses and tell the compiler
> 'trust me on this'

I am sorry if this sounds condescending. But this is _exactly_ what
I'm complaining about: that there is too much trust placed in the
programmer.

Converting from * to & does not turn off the borrow checker, but still
you cannot trust anymore what the borrow checker says; and that's why
you do it inside an abstraction, not in each and every callback of
each and every device. This is what Marc-André did for QAPI; and he
probably erred in the other direction for a PoC, but we _must_ agree
that something as complete as his work is the direction that we _have_
to take.

Again: I am sorry that you feel this way about my remark, because I
thought I had only praised your work. I didn't think I was being
condescending or dismissing. But I am worried that without the proper
abstractions this is going to be a technical debt magnet with only
marginal benefits over C.

And frankly I am saying this from experience. Check out CVE-2019-18960
which was an out of bounds access in Firecracker caused by not using
proper abstractions for DMA. And that's in a 100% Rust code base.
Since we're starting from and interfacing with C it's only going to be
worse; skimping on abstractions is simply something that we cannot
afford.

It's also the way Linux is introducing Rust in the code base. Whenever
something needs access to C functionality they introduce bindings to
it. It's slower, but it's sustainable.

> Ignoring that misses the entire point of Rust. The
> statement 'this is not Rust code but as good as C' is dishonest and
> misguided. Check for example the source code of the nix crate, which
> exposes libc and various POSIX/*nix APIs. Is it the same as C and not
> Rust code?

That's exactly my point. Libc provides mostly unsafe functions, which
is on the level of what bindgen generates. Other crates on top provide
*safe abstractions* such as nix's Dir
(https://docs.rs/nix/0.29.0/nix/dir/struct.Dir.html). If possible,
leaf crates use safe Rust (nix), and avoid unsafe (libc) as much as
possible.

Instead you're using the unsafe functions in the device itself. It's
missing an equivalent of nix.

> If you have specific scenarios in mind where such things exist in the
> code and end up doing invalid behavior please be kind and write them
> down explicitly and demonstrate them on code review.

It doesn't matter whether they exist or not. The point of Rust is that
the type system ensures that these invalid behaviors are caught either
at compile time or (with RefCell) at run time. As things stand, your
code cannot catch them and the language provides a false sense of
security.

On the other hand, I want to be clear agin that this is not a problem
at all—but only as long as we agree that it's not the desired final
state

> This approach of
> 'yes but no' is not constructive because it is not addressing any
> specific problems directly. Instead it comes out as vague dismissive FUD
> and I'm sure that is not what you or anyone else wants.
>
> Please take some time to understand my POV here, it'd help both of us
> immensely.

I can ask the same though. Please take the time to understand that I
am not being dismissive! My position is exactly "yes but no". Yes,
this is a great way to introduce Rust in the code base. No, this is
not a sustainable way to mix Rust and C—but I am willing to help.

Paolo

>
> Sincerely thank you in advance,
> Manos
>


Reply via email to