From: "Benno Lossin" <lossin@kernel.org>
To: "Boqun Feng" <boqun.feng@gmail.com>
Cc: "Viresh Kumar" <viresh.kumar@linaro.org>,
"Rafael J. Wysocki" <rafael@kernel.org>,
"Yury Norov" <yury.norov@gmail.com>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Alex Gaynor" <alex.gaynor@gmail.com>,
"Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Vincent Guittot" <vincent.guittot@linaro.org>,
linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org,
rust-for-linux@vger.kernel.org
Subject: Re: [PATCH] rust: cpumask: Validate CPU number in set() and clear()
Date: Fri, 06 Jun 2025 21:34:04 +0200 [thread overview]
Message-ID: <DAFP9ZRENV0S.ON0XKIXIAEKY@kernel.org> (raw)
In-Reply-To: <aELugDefiviXZjx6@Mac.home>
On Fri Jun 6, 2025 at 3:34 PM CEST, Boqun Feng wrote:
> On Fri, Jun 06, 2025 at 10:11:13AM +0200, Benno Lossin wrote:
>> On Fri Jun 6, 2025 at 6:37 AM CEST, Boqun Feng wrote:
>> > On Fri, Jun 06, 2025 at 09:47:28AM +0530, Viresh Kumar wrote:
>> >> The C `cpumask_{set|clear}_cpu()` APIs emit a warning when given an
>> >> invalid CPU number - but only if `CONFIG_DEBUG_PER_CPU_MAPS=y` is set.
>> >>
>> >> Meanwhile, `cpumask_weight()` only considers CPUs up to `nr_cpu_ids`,
>> >> which can cause inconsistencies: a CPU number greater than `nr_cpu_ids`
>> >> may be set in the mask, yet the weight calculation won't reflect it.
>> >>
>> >> This leads to doctest failures when `nr_cpu_ids < 4`, as the test tries
>> >> to set CPUs 2 and 3:
>> >>
>> >> rust_doctest_kernel_cpumask_rs_0.location: rust/kernel/cpumask.rs:180
>> >> rust_doctest_kernel_cpumask_rs_0: ASSERTION FAILED at rust/kernel/cpumask.rs:190
>> >>
>> >> Fix this by validating the CPU number in the Rust `set()` and `clear()`
>> >> methods to prevent out-of-bounds modifications.
>> >>
>> >
>> > Thanks for the quick fix!
>> >
>> > While this can fix the current problem, but it's not a good solution for
>> > the long run. Because outside a test, we should never use an arbitrary
>> > i32 as a cpu number (we usually get it from smp_processor_id(), or
>> > something else). So the `< nr_cpu_ids` testing is not necessary in
>> > normal use cases.
>> >
>> > We should instead provide a wrapper for cpu id:
>> >
>> > /// # Invariants
>> > ///
>> > /// The number is always in [0..nr_cpu_ids) range.
>> > pub struct CpuId(i32);
>> >
>> > and
>> >
>> > impl CpuId {
>> > /// # Safety
>> > /// Callers must ensure `i` is a valid cpu id (i.e. 0 <= i <
>> > /// nr_cpu_ids).
>> > pub unsafe fn from_i32_unchecked(i: i32) -> Self {
>> > // INVARIANT: The function safety guarantees `i` is a valid
>> > // cpu id.
>> > CpuId(id);
>> > }
>> >
>> > pub fn from_i32(i: i32) -> Option<Self> {
>> > if i < 0 || i >= nr_cpu_ids {
>> > None
>> > } else {
>> > // SAFETY: `i` has just been checked as a valid cpu id.
>> > Some(unsafe { Self::from_i32_unchecked(i) })
>> > }
>> > }
>> >
>> > pub fn current() -> Self {
>> > // SAFETY: smp_processor_id() always return valid cpu id.
>> > unsafe { Self::from_i32_unchecked(smp_processor_id()) }
>> > }
>> > }
>> >
>> > All `Cpumask` functions then take `CpuId` instead of `i32` as the
>> > parameter. Needless to say if we were to have a cpumask_next() wrapper,
>> > the return value will be `CpuId` (or `Option<CpuId>`), i.e. if a bit was
>> > set in a cpumask, then it must represent a correct cpu id.
>> >
>> > Make sense?
>>
>> Just to make sure, the `nr_cpu_ids` stays constant, right?
>>
>
> Sort of. I believe the value won't be changed once the kernel boots, in
> most cases (modulo NR_CPUS=1 or CONFIG_FORCE_NR_CPUS=y), it's a
> read-mostly variable not a constant, and the value gets set by either a
> kernel command line or how many CPUs the kernel actually detect at boot
> time. See:
>
> https://github.com/Rust-for-Linux/linux/blob/rust-next/kernel/smp.c#L995:w
It's allowed to increase, but if it ever decreases, the invariant of
`CpuId` will be wrong (ie it's not *invariant* :).
>> >> @@ -101,10 +108,16 @@ pub fn set(&mut self, cpu: u32) {
>> >> /// This mismatches kernel naming convention and corresponds to the C
>> >> /// function `__cpumask_clear_cpu()`.
>> >> #[inline]
>> >> - pub fn clear(&mut self, cpu: i32) {
>> >> + pub fn clear(&mut self, cpu: i32) -> Result {
>> >> + // SAFETY: It is safe to read `nr_cpu_ids`.
>> >> + if unsafe { cpu as u32 >= bindings::nr_cpu_ids } {
>> >
>> > You probably want to check whether `bindings::nr_cpu_ids` can be
>> > accessible if NR_CPUS == 1 or CONFIG_FORCE_NR_CPUS=y, because then
>> > nr_cpu_ids is a macro definition.
>>
>> Just define a helper function?
>>
>
> Maybe, but it is then "a variable read" vs "a FFI function call" if we
> want to check every time in clear()/set(), of course if we only check it
> in CpuId::from_i32() mentioned above, the performance impact shouldn't
> be observable, because we won't call that method often.
Sure, you could also have a rust function that is inlined that does the
two different checks depending on the config.
> Either, I was just pointing out the current fix may cause build errors.
Yeah that should be fixed.
---
Cheers,
Benno
next prev parent reply other threads:[~2025-06-06 19:34 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-06-06 4:17 [PATCH] rust: cpumask: Validate CPU number in set() and clear() Viresh Kumar
2025-06-06 4:37 ` Boqun Feng
2025-06-06 8:11 ` Benno Lossin
2025-06-06 13:34 ` Boqun Feng
2025-06-06 19:34 ` Benno Lossin [this message]
2025-06-06 19:40 ` Boqun Feng
2025-06-09 11:17 ` Viresh Kumar
2025-06-06 10:11 ` Miguel Ojeda
2025-06-09 2:36 ` Viresh Kumar
2025-06-06 23:23 ` kernel test robot
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=DAFP9ZRENV0S.ON0XKIXIAEKY@kernel.org \
--to=lossin@kernel.org \
--cc=a.hindborg@kernel.org \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=dakr@kernel.org \
--cc=gary@garyguo.net \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pm@vger.kernel.org \
--cc=ojeda@kernel.org \
--cc=rafael@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=tmgross@umich.edu \
--cc=vincent.guittot@linaro.org \
--cc=viresh.kumar@linaro.org \
--cc=yury.norov@gmail.com \
/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;
as well as URLs for NNTP newsgroup(s).