[PATCH 1/2] rust: str: parse_int: only accept a sign before the radix prefix
From: Younes Akhouayri via B4 Relay
Date: Tue Oct 27 2026 - 08:17:17 EST
From: Younes Akhouayri <git@xxxxxxxxx>
`ParseInt::from_str()` strips an optional `-` and a radix prefix, then
hands the remaining digits to `from_str_radix()` from `core`. That
function accepts a leading `+`, and also `-` for signed types, so a
sign placed after the prefix, or a second sign, is parsed too:
i32::from_str("0x-5") == Ok(-5)
i32::from_str("0x+5") == Ok(5)
i32::from_str("-+5") == Ok(-5)
i32::from_str("-0x+5") == Ok(-5)
`kstrtoint()` rejects all of these with -EINVAL. On the other hand,
`kstrtoint()` accepts "+0x5" as 5, but `from_str()` rejects it, because
it does not strip a leading `+` before looking for the prefix. Since
the documentation says that any string parsed by `kstrtol()` is also
parsed by `from_str()`, this is a bug.
Integer module parameters are parsed with `from_str()`, so this is
visible from the kernel command line. Booting with
`rust_minimal.test_parameter=0x-5` makes the `rust_minimal` sample
print `test_parameter: -5`.
Fix it by accepting a single `+` or `-` before the radix prefix only,
and by making `strip_radix()` reject digits that start with a sign.
Add doctests for these cases.
Fixes: 51d9ee90ea90 ("rust: str: add radix prefixed integer parsing functions")
Signed-off-by: Younes Akhouayri <git@xxxxxxxxx>
---
rust/kernel/str/parse_int.rs | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/rust/kernel/str/parse_int.rs b/rust/kernel/str/parse_int.rs
index 48eb4c202984..07e1fbd7be8f 100644
--- a/rust/kernel/str/parse_int.rs
+++ b/rust/kernel/str/parse_int.rs
@@ -28,8 +28,11 @@ pub trait FromStrRadix: Sized {
/// Extract the radix from an integer literal optionally prefixed with
/// one of `0x`, `0X`, `0o`, `0O`, `0b`, `0B`, `0`.
-fn strip_radix(src: &BStr) -> (u32, &BStr) {
- match src.deref() {
+///
+/// Returns [`EINVAL`] if the digits start with a sign, which
+/// `from_str_radix()` would otherwise accept.
+fn strip_radix(src: &BStr) -> Result<(u32, &BStr)> {
+ let (radix, digits) = match src.deref() {
[b'0', b'x' | b'X', rest @ ..] => (16, rest.as_ref()),
[b'0', b'o' | b'O', rest @ ..] => (8, rest.as_ref()),
[b'0', b'b' | b'B', rest @ ..] => (2, rest.as_ref()),
@@ -38,6 +41,11 @@ fn strip_radix(src: &BStr) -> (u32, &BStr) {
// not be able to parse `0`.
[b'0', ..] => (8, src),
_ => (10, src),
+ };
+
+ match digits.deref() {
+ [b'+' | b'-', ..] => Err(EINVAL),
+ _ => Ok((radix, digits)),
}
}
@@ -75,13 +83,18 @@ fn strip_radix(src: &BStr) -> (u32, &BStr) {
/// assert!(i8::from_str(b_str!("-129")).is_err());
/// assert_eq!(Ok(255u8), u8::from_str(b_str!("255")));
/// assert!(u8::from_str(b_str!("256")).is_err());
+///
+/// assert_eq!(Ok(0x5i32), i32::from_str(b_str!("+0x5")));
+/// assert!(i32::from_str(b_str!("0x-5")).is_err());
+/// assert!(i32::from_str(b_str!("0x+5")).is_err());
+/// assert!(i32::from_str(b_str!("-+5")).is_err());
/// ```
pub trait ParseInt: private::FromStrRadix + TryFrom<u64> {
/// Parse a string according to the description in [`Self`].
fn from_str(src: &BStr) -> Result<Self> {
match src.deref() {
[b'-', rest @ ..] => {
- let (radix, digits) = strip_radix(rest.as_ref());
+ let (radix, digits) = strip_radix(rest.as_ref())?;
// 2's complement values range from -2^(b-1) to 2^(b-1)-1.
// So if we want to parse negative numbers as positive and
// later multiply by -1, we have to parse into a larger
@@ -94,8 +107,8 @@ fn from_str(src: &BStr) -> Result<Self> {
.map_err(|_| EINVAL)?;
Self::from_u64_negated(val)
}
- _ => {
- let (radix, digits) = strip_radix(src);
+ [b'+', rest @ ..] | rest => {
+ let (radix, digits) = strip_radix(rest.as_ref())?;
Self::from_str_radix(digits, radix).map_err(|_| EINVAL)
}
}
--
2.56.0