All of lore.kernel.org
 help / color / mirror / Atom feed
From: Zhao Liu <zhao1.liu@intel.com>
To: Paolo Bonzini <pbonzini@redhat.com>
Cc: qemu-devel@nongnu.org, qemu-rust@nongnu.org
Subject: Re: [PATCH 04/12] rust: timer: wrap QEMUTimer with Opaque<> and express pinning requirements
Date: Tue, 4 Mar 2025 17:13:27 +0800	[thread overview]
Message-ID: <Z8bEN0HFbfMJ5rmm@intel.com> (raw)
In-Reply-To: <7b09b4e3-3c1f-4c94-ad3a-054eaf74f24c@redhat.com>

On Mon, Mar 03, 2025 at 04:58:57PM +0100, Paolo Bonzini wrote:
> Date: Mon, 3 Mar 2025 16:58:57 +0100
> From: Paolo Bonzini <pbonzini@redhat.com>
> Subject: Re: [PATCH 04/12] rust: timer: wrap QEMUTimer with Opaque<> and
>  express pinning requirements
> 
> On 3/3/25 14:48, Zhao Liu wrote:
> > > @@ -156,7 +157,7 @@ pub struct HPETTimer {
> > >       /// timer N index within the timer block (`HPETState`)
> > >       #[doc(alias = "tn")]
> > >       index: usize,
> > > -    qemu_timer: Option<Box<Timer>>,
> > > +    qemu_timer: Option<Pin<Box<Timer>>>,
> > 
> > I'm removing this Option<> wrapper in migration series. This is because
> > Option<> can't be treated as pointer as you mentioned in [*].
> > 
> > So for this reason, does this mean that VMStateField cannot accept
> > Option<>? I realize that all the current VMStateFlags don't seem
> > compatible with Option<> unless a new flag is introduced.
> > 
> > [*]: https://lore.kernel.org/qemu-devel/9a0389fa-765c-443b-ac2f-7c99ed862982@redhat.com/
> 
> Ok, so let's get rid of the option.  I didn't really like it anyway...
> 
> If the Timer is embedded in the HPETTimer, there needs to be some
> "unsafe" in order to make sure that the pinning is observed, and also
> because an uninitialized Timer is bad and can cause a NULL pointer
> dereference in modify()... i.e. Timer shouldn't have implemented
> Default!

Yes! Good point.

> However, the lifetime checks in init_full() are preserved, so overall
> this is better---at least for now.  Linux also had unsafe initialization
> for quite some time, so I'm okay with it.

The overall design is okay for me too.

> The replacements for this patch are below.

I have comments about current Opaque<> implemation below...
  
> From 2d74bdf176b2fbeb6205396d0021f68a9e72bde1 Mon Sep 17 00:00:00 2001
> From: Paolo Bonzini <pbonzini@redhat.com>
> Date: Mon, 3 Mar 2025 16:27:08 +0100
> Subject: [PATCH 1/2] rust: hpet: embed Timer without the Option and Box
>  indirection
> 
> This simplifies things for migration, since Option<Box<QEMUTimer>> does not
> implement VMState.
> 
> This also shows a soundness issue because Timer::new() will leave a NULL
> timer list pointer, which can then be dereferenced by Timer::modify().  It
> will be fixed shortly.

Good catch!

> Suggested-by: Zhao Liu <zhao1.liu@intel.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  rust/hw/timer/hpet/src/hpet.rs | 59 ++++++++++++++++------------------
>  1 file changed, 28 insertions(+), 31 deletions(-)

Thanks. This cleanup is fine for me!

...

> From 276020645786b6537c50bb37795f281b5d630f27 Mon Sep 17 00:00:00 2001
> From: Paolo Bonzini <pbonzini@redhat.com>
> Date: Fri, 14 Feb 2025 12:06:13 +0100
> Subject: [PATCH 2/2] rust: timer: wrap QEMUTimer with Opaque<> and express
>  pinning requirements
> 
> Timers must be pinned in memory, because modify() stores a pointer to them
> in the TimerList.  To express this requirement, change init_full() to take
> a pinned reference.  Because the only way to obtain a Timer is through
> Timer::new(), which is unsafe, modify() can assume that the timer it got
> was later initialized; and because the initialization takes a Pin<&mut
> Timer> modify() can assume that the timer is pinned.  In the future the
> pinning requirement will be expressed through the pin_init crate instead.
> 
> Note that Timer is a bit different from other users of Opaque, in that
> it is created in Rust code rather than C code.  This is why it has to
> use the unsafe constructors provided by Opaque; and in fact Timer::new()
> is also unsafe, because it leaves it to the caller to invoke init_full()
> before modify().  Without a call to init_full(), modify() will cause a
> NULL pointer dereference.
> 
> An alternative could be to combine new() + init_full() by returning a
> pinned box; however, using a reference makes it easier to express
> the requirement that the opaque outlives the timer.
> 
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  meson.build                    |  7 -----
>  rust/hw/timer/hpet/src/hpet.rs | 10 ++++++--
>  rust/qemu-api/src/timer.rs     | 47 ++++++++++++++++++++++++++--------
>  3 files changed, 44 insertions(+), 20 deletions(-)

...

>  impl Timer {
>      pub const MS: u32 = bindings::SCALE_MS;
>      pub const US: u32 = bindings::SCALE_US;
>      pub const NS: u32 = bindings::SCALE_NS;
> -    pub fn new() -> Self {
> -        Default::default()
> -    }
> -
> -    const fn as_mut_ptr(&self) -> *mut Self {
> -        self as *const Timer as *mut _
> +    /// Create a `Timer` struct without initializing it.
> +    ///
> +    /// # Safety
> +    ///
> +    /// The timer must be initialized before it is armed with
> +    /// [`modify`](Self::modify).
> +    pub unsafe fn new() -> Self {
> +        // SAFETY: requirements relayed to callers of Timer::new
> +        Self(unsafe { Opaque::zeroed() })

We should use Opaque::uninit()? Because MaybeUninit::<bindings::QEMUTimer>::zeroed()
marks the timer as initialized, which disables MaybeUninit's ability to check for
initialization. e.g.,

// No compiling error or runtime panic
let t: MaybeUninit<bindings::QEMUTimer> = MaybeUninit::zeroed();
let _t = unsafe { t.assume_init() };

Further more, I spent some time trying to figure out if MaybeUninit in
Opaque<> could help identify UB caused by uninitialized Timer, but I found
it doesn't work. :-(

There're 2 cases:

// No compiling error or runtime panic
let mut v: UnsafeCell<MaybeUninit<bindings::QEMUTimer>> = UnsafeCell::new(MaybeUninit::uninit());
let _v = unsafe { v.get_mut().assume_init() };

// Runtime panic: Illegal instruction
let t: MaybeUninit<bindings::QEMUTimer> = MaybeUninit::uninit();
let _t = unsafe { t.assume_init() };

I understand that the outer UnsafeCell wrapper makes MaybeUninit's
checks not work.

But when I adjust MaybeUninit as the outer wrapper, the UB check can
work:

// Runtime panic: Illegal instruction
let v: MaybeUninit<UnsafeCell<bindings::QEMUTimer>> = MaybeUninit::uninit();
let _v = unsafe { v.assume_init() };

And there's another example:

https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html#method.raw_get

Compared with linux's Opaque, it also puts MaybeUninit on the outermost
layer.

Emm, I guess now we have UnsafeCell<MaybeUninit<>> because interior
mutability is expected... but this layout breaks MaybeUninit's functionality.

> +    /// Create a new timer with the given attributes.
>      pub fn init_full<'timer, 'opaque: 'timer, T, F>(
> -        &'timer mut self,
> +        self: Pin<&'timer mut Self>,
>          timer_list_group: Option<&TimerListGroup>,
>          clk_type: ClockType,
>          scale: u32,
> @@ -51,7 +71,7 @@ pub fn init_full<'timer, 'opaque: 'timer, T, F>(
>          // SAFETY: the opaque outlives the timer
>          unsafe {
>              timer_init_full(
> -                self,
> +                self.as_mut_ptr(),
>                  if let Some(g) = timer_list_group {
>                      g as *const TimerListGroup as *mut _
>                  } else {
> @@ -67,14 +87,19 @@ pub fn init_full<'timer, 'opaque: 'timer, T, F>(
>      }
>      pub fn modify(&self, expire_time: u64) {
> +        // SAFETY: the only way to obtain a Timer safely is via methods that
> +        // take a Pin<&mut Self>, therefore the timer is pinned

The SAFETY should also be ensured by MaybeUninit, I think. But I haven't
verified if MaybeUninit<UnsafeCell<>> can work on FFI case...

>          unsafe { timer_mod(self.as_mut_ptr(), expire_time as i64) }
>      }
>      pub fn delete(&self) {
> +        // SAFETY: the only way to obtain a Timer safely is via methods that
> +        // take a Pin<&mut Self>, therefore the timer is pinned
>          unsafe { timer_del(self.as_mut_ptr()) }
>      }
>  }
> +// FIXME: use something like PinnedDrop from the pinned_init crate
>  impl Drop for Timer {
>      fn drop(&mut self) {
>          self.delete()
> -- 
> 2.48.1
> 
> 
> 


  reply	other threads:[~2025-03-04  8:53 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-02-27 14:22 [PATCH v2 00/12] rust: wrap all C types exposed through qemu_api Paolo Bonzini
2025-02-27 14:22 ` [PATCH 01/12] rust: cell: add wrapper for FFI types Paolo Bonzini
2025-02-27 14:22 ` [PATCH 02/12] rust: qemu_api_macros: add Wrapper derive macro Paolo Bonzini
2025-02-27 14:22 ` [PATCH 03/12] rust: vmstate: add std::pin::Pin as transparent wrapper Paolo Bonzini
2025-03-03 13:25   ` Zhao Liu
2025-02-27 14:22 ` [PATCH 04/12] rust: timer: wrap QEMUTimer with Opaque<> and express pinning requirements Paolo Bonzini
2025-03-03 13:48   ` Zhao Liu
2025-03-03 15:58     ` Paolo Bonzini
2025-03-04  9:13       ` Zhao Liu [this message]
2025-03-06 10:45         ` Paolo Bonzini
2025-03-06 11:35           ` Zhao Liu
2025-03-03 14:28   ` Zhao Liu
2025-03-03 14:51     ` Paolo Bonzini
2025-03-03 16:15       ` Zhao Liu
2025-02-27 14:22 ` [PATCH 05/12] rust: irq: wrap IRQState with Opaque<> Paolo Bonzini
2025-03-03 15:07   ` Zhao Liu
2025-02-27 14:22 ` [PATCH 06/12] rust: qom: wrap Object " Paolo Bonzini
2025-02-27 14:22 ` [PATCH 07/12] rust: qdev: wrap Clock and DeviceState " Paolo Bonzini
2025-02-27 14:22 ` [PATCH 08/12] rust: hpet: do not access fields of SysBusDevice Paolo Bonzini
2025-03-03 15:09   ` Zhao Liu
2025-02-27 14:22 ` [PATCH 09/12] rust: sysbus: wrap SysBusDevice with Opaque<> Paolo Bonzini
2025-03-03 15:19   ` Zhao Liu
2025-02-27 14:22 ` [PATCH 10/12] rust: memory: wrap MemoryRegion " Paolo Bonzini
2025-03-03 15:25   ` Zhao Liu
2025-03-05  7:09   ` Zhao Liu
2025-02-27 14:22 ` [PATCH 11/12] rust: chardev: wrap Chardev " Paolo Bonzini
2025-02-27 14:22 ` [PATCH 12/12] rust: bindings: remove more unnecessary Send/Sync impls Paolo Bonzini

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=Z8bEN0HFbfMJ5rmm@intel.com \
    --to=zhao1.liu@intel.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=qemu-rust@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.