Linux PCI subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Danilo Krummrich" <dakr@kernel.org>
Cc: ojeda@kernel.org, linux-pci@vger.kernel.org
Subject: Re: [PATCH] rust: irq: make Registration compatible with lifetime-bound drivers
Date: Fri, 03 Jul 2026 21:19:01 +0000	[thread overview]
Message-ID: <20260703211901.A777F1F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260703210936.1128698-1-dakr@kernel.org>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [Medium] The doctests for `Registration` and `ThreadedRegistration` demonstrate wrapping the `unsafe` constructors in safe functions, violating soundness requirements.
- [Low] Missing `// INVARIANT:` comments when constructing structs with `# Invariants` documentation.

Pre-existing issues:
- [High] Creating a Rust reference to `Registration` or `ThreadedRegistration` before its `request` field is fully initialized causes Undefined Behavior during IRQ registration.
- [Low] Missing `#[inline]` annotations on small/forwarding functions.
--

commit 8e263235d0a19c8fc9b9f8c688ce7dc1f63f4165
Author: Danilo Krummrich <dakr@kernel.org>

rust: irq: make Registration compatible with lifetime-bound drivers

This commit adapts the IRQ registration to support Higher-Ranked Lifetime
Types (HRT) device drivers by removing the static bound from IRQ handlers.
It replaces Devres indirection with direct request_irq() and free_irq()
calls during initialization and drop, tying the registration directly to
the device binding scope.

> diff --git a/rust/kernel/irq/request.rs b/rust/kernel/irq/request.rs
> --- a/rust/kernel/irq/request.rs
> +++ b/rust/kernel/irq/request.rs
[ ... ]
> @@ -163,12 +126,21 @@ pub fn irq(&self) -> u32 {
>  /// //
>  /// // This runs in process context and assumes `request` was previously acquired from a device.
>  /// fn register_irq(
> -///     handler: impl PinInit<Data, Error>,
>  ///     request: IrqRequest<'_>,
> -/// ) -> Result<Arc<Registration<Data>>> {
> -///     let registration = Registration::new(request, Flags::SHARED, c"my_device", handler);
> +/// ) -> Result<Pin<KBox<Registration<'_, Data>>>> {
> +///     // SAFETY: The returned Registration is not leaked.
> +///     let registration = unsafe {
> +///         Registration::new(
> +///             request,
> +///             Flags::SHARED,
> +///             c"my_device",
> +///             try_pin_init!(Data {
> +///                 completion <- Completion::new(),
> +///             }? Error),
> +///         )
> +///     };

[Severity: Medium]
Is it safe to expose this leak-sensitive object from a safe function?

The Registration::new() constructor is unsafe because callers must not
leak the returned registration to guarantee proper drop semantics.
Since register_irq() is a safe function returning a Pin<KBox<...>>,
could a safe caller leak the registration and potentially trigger a 
use-after-free after the driver unbinds?

Should this example function be marked unsafe fn?

[ ... ]
> @@ -194,43 +165,44 @@ pub struct Registration<T: Handler> {
[ ... ]
> +    pub unsafe fn new(
[ ... ]
> +        try_pin_init!(&this in Self {
> +            handler <- handler,
> +            request: {
> +                // SAFETY:
> +                // - The callbacks are valid for use with request_irq.
> +                // - If this succeeds, the slot is guaranteed to be valid until the destructor of
> +                //   Self runs, which will deregister the callbacks before the memory location
> +                //   becomes invalid.
> +                // - When request_irq is called, everything that handle_irq_callback will touch
> +                //   has already been initialized, so it's safe for the callback to be called
> +                //   immediately.
> +                to_result(unsafe {
> +                    bindings::request_irq(
> +                        request.irq,
> +                        Some(handle_irq_callback::<T>),
> +                        flags.into_inner(),
> +                        name.as_char_ptr(),
> +                        this.as_ptr().cast::<c_void>(),
> +                    )
> +                })?;
> +                request
> +            },

[Severity: Low]
This isn't a bug, but shouldn't there be an // INVARIANT: comment inside
this initialization block to explain why the invariants for Registration
are satisfied?

[Severity: High]
This is a pre-existing issue, but does this pattern trigger undefined 
behavior if the interrupt fires immediately?

During try_pin_init!, bindings::request_irq() is called while the request 
field is still being initialized. If the IRQ handler is invoked before 
request_irq() returns:

handle_irq_callback()
    let ptr = ptr.cast_const().cast::<Registration<'_, T>>();
    let registration = unsafe { &*ptr };

Could this create a Rust reference to the Registration struct while the
request field is not fully initialized?

Could the request_irq call be moved into a _: { ... } block to ensure
the struct is fully initialized before the interrupt can fire?

[ ... ]
> @@ -241,19 +213,24 @@ pub fn handler(&self) -> &T {
>      }
>  
>      /// Wait for pending IRQ handlers on other CPUs.
> -    ///
> -    /// This will attempt to access the inner [`Devres`] container.
> -    pub fn try_synchronize(&self) -> Result {
> -        let inner = self.inner.try_access().ok_or(ENODEV)?;
> -        inner.synchronize();
> -        Ok(())
> +    pub fn synchronize(&self) {
> +        // SAFETY: `self.request.irq` is a valid registered IRQ number (type invariant).
> +        unsafe { bindings::synchronize_irq(self.request.irq) };
>      }

[Severity: Low]
This is a pre-existing issue and not a bug, but should this forwarding 
function (and the small accessor IrqRequest::irq() in the same file) be 
annotated with #[inline] according to the Rust subsystem guidelines?

[ ... ]
> @@ -375,13 +336,21 @@ fn handle_threaded(&self, device: &Device<Bound>) -> IrqReturn {
>  /// // This is executing in process context and assumes that `request` was
>  /// // previously acquired from a device.
>  /// fn register_threaded_irq(
> -///     handler: impl PinInit<Data, Error>,
>  ///     request: IrqRequest<'_>,
> -/// ) -> Result<Arc<ThreadedRegistration<Data>>> {
[ ... ]
> +/// ) -> Result<Pin<KBox<ThreadedRegistration<'_, Data>>>> {
> +///     // SAFETY: The returned Registration is not leaked.
> +///     let registration = unsafe {
> +///         ThreadedRegistration::new(

[Severity: Medium]
Is it safe to expose this leak-sensitive object from a safe function?

Similar to register_irq(), the ThreadedRegistration::new() constructor
is unsafe to guarantee proper drop semantics. Does returning a Pin<KBox>
from a safe function allow a caller to trivially leak the registration?

[ ... ]
> @@ -411,44 +380,45 @@ pub struct ThreadedRegistration<T: ThreadedHandler> {
[ ... ]
> +    pub unsafe fn new(
[ ... ]
> +        try_pin_init!(&this in Self {
> +            handler <- handler,
> +            request: {
> +                // SAFETY:
> +                // - The callbacks are valid for use with request_threaded_irq.
[ ... ]
> +                to_result(unsafe {
> +                    bindings::request_threaded_irq(
> +                        request.irq,
> +                        Some(handle_threaded_irq_callback::<T>),
> +                        Some(thread_fn_callback::<T>),
> +                        flags.into_inner(),
> +                        name.as_char_ptr(),
> +                        this.as_ptr().cast::<c_void>(),
> +                    )
> +                })?;
> +                request
> +            },

[Severity: Low]
This isn't a bug, but shouldn't there be an // INVARIANT: comment here 
explaining why the invariants are satisfied?

[Severity: High]
This is a pre-existing issue, but does this pattern also trigger undefined 
behavior if the interrupt fires immediately, similar to Registration::new()?

Could this request_threaded_irq call be moved into a _: { ... } block
to avoid creating a reference to an uninitialized request field?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260703210936.1128698-1-dakr@kernel.org?part=1

      reply	other threads:[~2026-07-03 21:19 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-03 21:09 [PATCH] rust: irq: make Registration compatible with lifetime-bound drivers Danilo Krummrich
2026-07-03 21:19 ` sashiko-bot [this message]

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=20260703211901.A777F1F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=dakr@kernel.org \
    --cc=linux-pci@vger.kernel.org \
    --cc=ojeda@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox