Thank you for your contribution! Sashiko AI review found 1 potential issue(s) 
to consider:
- [Medium] rv: Use-After-Free in rv_test_rvtest_arm_after_destroy due to 
dropped RCU lock
--

commit 62c45f491a4a036b9788ee3cd149fc4c81f32003
Author: Wen Yang <[email protected]>

rv: Add a KUnit test for the storage free path

The test resolves a storage, desstroys it with da_destroy_storage(), and
then arms the HA timer through the stale pointer. The free hook must
disarm that timer too, so it never fires and no reaction is reported.

> diff --git a/kernel/trace/rv/rv_monitors_test.c 
> b/kernel/trace/rv/rv_monitors_test.c
> index 3ad11195e664a..b30e49419e34b 100644
> --- a/kernel/trace/rv/rv_monitors_test.c
> +++ b/kernel/trace/rv/rv_monitors_test.c
> @@ -146,6 +227,83 @@ static void rv_test_dummy(struct kunit *test)
>       active_ctx = NULL;
>  }
>  
> +/*
> + * rv_test_rvtest_underflow - the HA clock math must not underflow as u64
> + *
> + * An invariant checked while the uptime is below the budget holds, and a
> + * timer armed after the budget elapsed expires now.
> + */
> +static void rv_test_rvtest_underflow(struct kunit *test)
> +{
> +     struct ha_monitor *ha_mon;
> +     struct da_monitor *da_mon;
> +     const int id = 4242;
> +
> +     prepare_test(test, &rvtest_mon);
> +     da_handle_start_run_event(id, NULL, start_rvtest);
> +
> +     scoped_guard(rcu)
> +             da_mon = da_get_monitor(id, NULL);
> +     KUNIT_ASSERT_NOT_NULL(test, da_mon);
> +     ha_mon = to_ha_monitor(da_mon);
> +
> +     /* Uptime below the budget: time_ns - expire_ns would underflow. */
> +     ha_reset_clk_ns(ha_mon, clk_rvtest, 0);

[Severity: Medium]
Are we dereferencing an RCU-protected pointer outside of its RCU read-side
critical section here?

The scoped_guard(rcu) is used without braces, meaning the RCU read-side
critical section ends immediately after da_get_monitor(). The da_mon variable
is then used by to_ha_monitor() and passed to ha_reset_clk_ns().

Even if there is no concurrent deletion in this specific test environment,
isn't this an RCU read-side violation that dereferences an RCU-protected
pointer after the lock is released?

> +     KUNIT_EXPECT_TRUE(test, ha_check_invariant_ns(ha_mon, clk_rvtest, 0,
> +                                                   RVTEST_BUDGET_NS));
> +
> +     /*
> +      * Budget already elapsed: expire - passed would underflow and queue
> +      * the timer at KTIME_SEC_MAX.
> +      */
> +     da_monitor_reset(&ha_mon->da_mon);
> +     ha_reset_clk_ns(ha_mon, clk_rvtest, 0);
> +     ha_start_timer_ns(ha_mon, clk_rvtest, RVTEST_BUDGET_NS,
> +                       RVTEST_BUDGET_NS + NSEC_PER_SEC);
> +     KUNIT_EXPECT_TRUE(test,
> +                       ktime_before(hrtimer_get_expires(&ha_mon->hrtimer),
> +                                    ktime_get() + NSEC_PER_SEC));
> +
> +     ha_cancel_timer_sync(ha_mon);
> +}
> +
> +/*
> + * rv_test_rvtest_arm_after_destroy - a storage outlived by its own timer
> + *
> + * da_destroy_storage() cancels the timer, then unlinks:
> + *
> + *   da_monitor_reset_hook()         cancels
> + *   hash_del_rcu()
> + *   call_rcu()
> + *
> + * A reader that resolved the storage before the unlink can arm past that
> + * cancel.  Handlers resolve and use under one guard(rcu)(), so the two steps
> + * are issued separately here.
> + */
> +static void rv_test_rvtest_arm_after_destroy(struct kunit *test)
> +{
> +     struct rv_kunit_ctx *ctx = test->priv;
> +     const int id = 4242;
> +     struct da_monitor *stale;
> +
> +     prepare_test(test, &rvtest_mon);
> +
> +     da_handle_start_run_event(id, NULL, start_rvtest);
> +
> +     scoped_guard(rcu)                       /* resolve before the unlink */
> +             stale = da_get_monitor(id, NULL);
> +     KUNIT_ASSERT_NOT_NULL(test, stale);
> +
> +     da_destroy_storage(id);                 /* cancel, unlink, call_rcu */
> +     ha_start_timer_ns(to_ha_monitor(stale), clk_rvtest,
> +                       RVTEST_BUDGET_NS, ha_get_ns());

[Severity: Medium]
Does this sequence result in an actual use-after-free rather than just
simulating one?

Similar to the above, stale is resolved within a scoped_guard(rcu) that
ends immediately. da_destroy_storage() then unlinks the object and defers
its destruction via call_rcu(). 

Since the test thread no longer holds the RCU read lock, it can be preempted,
allowing the RCU grace period to complete and free the stale object. When
the test thread resumes, doesn't calling ha_start_timer_ns() write to the
freed memory?

To safely simulate the race mentioned in the comment, shouldn't the test hold
the RCU lock continuously across both the lookup and the timer arming steps?

> +
> +     rcu_barrier();
> +
> +     msleep(600);
> +     RV_KUNIT_EXPECT_NO_REACTION(test, ctx);
> +}

-- 
Sashiko AI review ยท 
https://sashiko.dev/#/patchset/[email protected]?part=5

Reply via email to