Skip to contentSkip to conversation

rust: module_param: Fix potentially incorrect access of `SetOnce<T>`

PATCH Superseded Priya Bala Govindasamy 4 participants 7 messages Sep 17 – Oct 14

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover rust: module_param: Fix potentially incorrect access of `SetOnce<T>`

Dear Linux kernel maintainers,

We are developing a tool called FerroLens to detect potential unsound behavior in Rust code in the Linux kernel. The tool internally uses an LLM to detect bugs.
We then perform manual analysis to verify these reports. FerroLens reported the following bug in rust/kernel/module_param.rs:

The set_param function casts kernel_param.arg to *const SetOnce<T> But the Rust module macro in rust/macros/module.rs initializes arg with #param_name.as_void_ptr(), and #param_name is a ModuleParamAccess<T>, not a SetOnce<T>.

ModuleParamAccess<T> has default Rust layout but set_param accesses its first field SetOnce<T> assuming it to be at offset 0. This is not guaranteed by Rust and if SetOnce<T> is not at offset 0 it could cause type confusion leading to data corruption.

In particular, if the kernel is built with a randomized layout (e.g with KRUSTFLAGS='-Zrandomize-layout=yes -Zlayout-seed=1'), the first field of ModuleParamAccess<T> may not be at offset 0. This leads to incorrect behavior when loading the rust_minimal module from samples.

$ sudo insmod samples/rust/rust_minimal.ko test_parameter=1 test_bool_parameter=false
insmod: ERROR: could not insert module samples/rust/rust_minimal.ko: File exists
[  854.638251] rust_minimal: `1' invalid for parameter `test_parameter'

Priya Bala Govindasamy (1):
rust: module_param: Fix potentially incorrect layout for
ModuleParamAccess<T>

 rust/kernel/module_param.rs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
Signature · 1 line
2.34.1
#1 lore raw

PATCH 1/1 rust: module_param: Fix potentially incorrect access of `SetOnce<T>`

The set_param function casts kernel_param.arg to *const SetOnce<T> But the Rust module macro in rust/macros/module.rs initializes arg with #param_name.as_void_ptr(), and #param_name is a ModuleParamAccess<T>, not a SetOnce<T>.

ModuleParamAccess<T> has default Rust layout but set_param accesses its first field SetOnce<T> assuming it to be at offset 0. This is not guaranteed by Rust and could cause type confusion leading to data corruption.

Fix this by casting kernel_param.arg to ModuleParamAccess<T> and then accessing the value: SetOnce<T> field.

Fixes 0b08fc292842 ("rust: introduce module_param module")
Assisted-by LLM
---
 rust/kernel/module_param.rs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
rust/kernel/module_param.rs +3 -1
index f9a14765a926..7ae87ffe1d6b 100644
@@ -75,7 +75,9 @@ pub trait ModuleParam: Sized + Copy {
         let new_value = T::try_from_param_arg(arg)?;
 
         // SAFETY: By function safety requirements, this access is safe.
-        let container = unsafe { &*((*param).__bindgen_anon_1.arg.cast::<SetOnce<T>>()) };
+        let param_access = unsafe { &*((*param)
+            .__bindgen_anon_1.arg.cast::<ModuleParamAccess<T>>()) };
+        let container = &param_access.value;
 
         container
             .populate(new_value)
Signature · 1 line
2.34.1
#2 lore raw
Quoted 2 lines · Priya Bala Govindasamy
Fixes 0b08fc292842 ("rust: introduce module_param module")

Before it is forgotten:

Without taking a proper look reproducing etc., it is indeed true that Rust's default representation may reorder fields (even without the flags).

Thanks!

Cheers,
Miguel

maintainer Reviewed-by on v1 1/1
#3 lore raw
In reply to Priya Bala Govindasamy
Quoted 26 lines · Priya Bala Govindasamy

The set_param function casts kernel_param.arg to *const SetOnce<T> But the Rust module macro in rust/macros/module.rs initializes arg with #param_name.as_void_ptr(), and #param_name is a ModuleParamAccess<T>, not a SetOnce<T>.

ModuleParamAccess<T> has default Rust layout but set_param accesses its first field SetOnce<T> assuming it to be at offset 0. This is not guaranteed by Rust and could cause type confusion leading to data corruption.

Fix this by casting kernel_param.arg to ModuleParamAccess<T> and then accessing the value: SetOnce<T> field.

Fixes 0b08fc292842 ("rust: introduce module_param module")
Assisted-by LLM
---
 rust/kernel/module_param.rs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
rust/kernel/module_param.rs +0 -0
index f9a14765a926..7ae87ffe1d6b 100644

rust/kernel/module_param.rs pub trait ModuleParam: Sized + Copy { · line 83

Quoted 10 lines
rust/kernel/module_param.rs +3 -1
@@ -75,7 +75,9 @@ pub trait ModuleParam: Sized + Copy {
         let new_value = T::try_from_param_arg(arg)?;
 
         // SAFETY: By function safety requirements, this access is safe.
-        let container = unsafe { &*((*param).__bindgen_anon_1.arg.cast::<SetOnce<T>>()) };
+        let param_access = unsafe { &*((*param)
+            .__bindgen_anon_1.arg.cast::<ModuleParamAccess<T>>()) };
+        let container = &param_access.value;
 
         container
             .populate(new_value)

This looks ok to me, except for the formatting. I plan to take it on the modules tree, after giving others a bit more time to potentially comment.

Signature · 2 lines
Thanks,
Petr
#4 lore raw
In reply to Priya Bala Govindasamy
Quoted 37 lines · Priya Bala Govindasamy

The set_param function casts kernel_param.arg to *const SetOnce<T> But the Rust module macro in rust/macros/module.rs initializes arg with #param_name.as_void_ptr(), and #param_name is a ModuleParamAccess<T>, not a SetOnce<T>.

ModuleParamAccess<T> has default Rust layout but set_param accesses its first field SetOnce<T> assuming it to be at offset 0. This is not guaranteed by Rust and could cause type confusion leading to data corruption.

Fix this by casting kernel_param.arg to ModuleParamAccess<T> and then accessing the value: SetOnce<T> field.

Fixes 0b08fc292842 ("rust: introduce module_param module")
Assisted-by LLM
---
 rust/kernel/module_param.rs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
rust/kernel/module_param.rs +3 -2
index f9a14765a926..7ae87ffe1d6b 100644
@@ -75,7 +75,9 @@ pub trait ModuleParam: Sized + Copy {
         let new_value = T::try_from_param_arg(arg)?;
 
         // SAFETY: By function safety requirements, this access is safe.
-        let container = unsafe { &*((*param).__bindgen_anon_1.arg.cast::<SetOnce<T>>()) };
+        let param_access = unsafe { &*((*param)
+            .__bindgen_anon_1.arg.cast::<ModuleParamAccess<T>>()) };
+        let container = &param_access.value;
 
         container
             .populate(new_value)
--
Quoted 1 line

2.34.1

This is indeed a bug. Nice catch.

I think the primary reason for this error is that the safety comment is lacking. It is also covering two distinct unsafe operations. I think we should rephrase as so:

rust/kernel/module_param.rs +6 -3
index 7ae87ffe1d6b..1a2fcbf4a5b3 100644
@@ -74,9 +74,12 @@ pub trait ModuleParam: Sized + Copy {
     crate::error::from_result(|| {
         let new_value = T::try_from_param_arg(arg)?;
 
-        // SAFETY: By function safety requirements, this access is safe.
-        let param_access = unsafe { &*((*param)
-            .__bindgen_anon_1.arg.cast::<ModuleParamAccess<T>>()) };
+        // SAFETY: By function safety requirements, `param` is valid for read.
+        let arg = unsafe { (*param).__bindgen_anon_1.arg };
+        let param_access_ptr = arg.cast::<ModuleParamAccess<T>>();
+        // SAFETY: The `arg` field is initialized with a pointer to a `static ModuleAccessParam` by
+        // the rust module macro. Thus, the pointer is valid for use as a reference.
+        let param_access = unsafe { &*(param_access_ptr) };
         let container = &param_access.value;
 
         container

---

Please also remember to run make rustfmt to format the code properly.

Best regards,
Andreas Hindborg

#5 lore raw

PATCH v2 1/1 rust: module_param: Fix potentially incorrect access of `SetOnce<T>`

In reply to Priya Bala Govindasamy

The set_param function casts kernel_param.arg to *const SetOnce<T> But the Rust module macro in rust/macros/module.rs initializes arg with #param_name.as_void_ptr(), and #param_name is a ModuleParamAccess<T>, not a SetOnce<T>.

ModuleParamAccess<T> has default Rust layout but set_param accesses its first field SetOnce<T> assuming it to be at offset 0. This is not guaranteed by Rust and could cause type confusion leading to data corruption.

Fix this by casting kernel_param.arg to ModuleParamAccess<T> and then accessing the value: SetOnce<T> field.

Fixes 0b08fc292842 ("rust: introduce module_param module")
Assisted-by LLM

---
Changes in v2:
- Split unsafe block into two separate unsafe operations
- Add safety comments explaining the unsafe operations

 rust/kernel/module_param.rs | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)
rust/kernel/module_param.rs +7 -2
index f9a14765a926..1a2fcbf4a5b3 100644
@@ -74,8 +74,13 @@ pub trait ModuleParam: Sized + Copy {
     crate::error::from_result(|| {
         let new_value = T::try_from_param_arg(arg)?;
 
-        // SAFETY: By function safety requirements, this access is safe.
-        let container = unsafe { &*((*param).__bindgen_anon_1.arg.cast::<SetOnce<T>>()) };
+        // SAFETY: By function safety requirements, `param` is valid for read.
+        let arg = unsafe { (*param).__bindgen_anon_1.arg };
+        let param_access_ptr = arg.cast::<ModuleParamAccess<T>>();
+        // SAFETY: The `arg` field is initialized with a pointer to a `static ModuleAccessParam` by
+        // the rust module macro. Thus, the pointer is valid for use as a reference.
+        let param_access = unsafe { &*(param_access_ptr) };
+        let container = &param_access.value;
 
         container
             .populate(new_value)
Signature · 1 line
2.34.1
maintainer
#6 lore raw
Quoted 13 lines · Priya Bala Govindasamy

The set_param function casts kernel_param.arg to *const SetOnce<T> But the Rust module macro in rust/macros/module.rs initializes arg with #param_name.as_void_ptr(), and #param_name is a ModuleParamAccess<T>, not a SetOnce<T>.

ModuleParamAccess<T> has default Rust layout but set_param accesses its first field SetOnce<T> assuming it to be at offset 0. This is not guaranteed by Rust and could cause type confusion leading to data corruption.

Fix this by casting kernel_param.arg to ModuleParamAccess<T> and then accessing the value: SetOnce<T> field.

Quoted 6 lines
Fixes 0b08fc292842 ("rust: introduce module_param module")
Assisted-by LLM

I've queued the patch (v2) on modules-next for v7.4-rc1.

Signature · 2 lines
Thanks,
Petr