This patch adds a more convenient method for reading C strings from
userspace. Logic is added to NUL-terminate the buffer when necessary so
that a &CStr can be returned.
Note that we treat attempts to read past `self.length` as a fault, so
this returns EFAULT if that limit is exceeded before `buf.len()` is
reached.
Reviewed-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Alice Ryhl <aliceryhl@google.com>
---
rust/kernel/uaccess.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 59 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/uaccess.rs b/rust/kernel/uaccess.rs
index 635a03e0989f3fe99be80987aa47763782de1d3f..106aa05ea1b88868fe48f64ca9c86b20ad7db68e 100644
--- a/rust/kernel/uaccess.rs
+++ b/rust/kernel/uaccess.rs
@@ -291,6 +291,65 @@ pub fn read_all<A: Allocator>(mut self, buf: &mut Vec<u8, A>, flags: Flags) -> R
unsafe { buf.inc_len(len) };
Ok(())
}
+
+ /// Read a NUL-terminated string from userspace and return it.
+ ///
+ /// The string is read into `buf` and a NUL-terminator is added if the end of `buf` is reached.
+ /// Since there must be space to add a NUL-terminator, the buffer must not be empty. The
+ /// returned `&CStr` points into `buf`.
+ ///
+ /// Fails with [`EFAULT`] if the read happens on a bad address (some data may have been
+ /// copied).
+ #[doc(alias = "strncpy_from_user")]
+ pub fn strcpy_into_buf<'buf>(self, buf: &'buf mut [u8]) -> Result<&'buf CStr> {
+ if buf.is_empty() {
+ return Err(EINVAL);
+ }
+
+ // SAFETY: The types are compatible and `strncpy_from_user` doesn't write uninitialized
+ // bytes to `buf`.
+ let mut dst = unsafe { &mut *(buf as *mut [u8] as *mut [MaybeUninit<u8>]) };
+
+ // We never read more than `self.length` bytes.
+ if dst.len() > self.length {
+ dst = &mut dst[..self.length];
+ }
+
+ let mut len = raw_strncpy_from_user(dst, self.ptr)?;
+ if len < dst.len() {
+ // Add one to include the NUL-terminator.
+ len += 1;
+ } else if len < buf.len() {
+ // This implies that `len == dst.len() < buf.len()`.
+ //
+ // This means that we could not fill the entire buffer, but we had to stop reading
+ // because we hit the `self.length` limit of this `UserSliceReader`. Since we did not
+ // fill the buffer, we treat this case as if we tried to read past the `self.length`
+ // limit and received a page fault, which is consistent with other `UserSliceReader`
+ // methods that also return page faults when you exceed `self.length`.
+ return Err(EFAULT);
+ } else {
+ // This implies that `len == buf.len()`.
+ //
+ // This means that we filled the buffer exactly. In this case, we add a NUL-terminator
+ // and return it. Unlike the `len < dst.len()` branch, don't modify `len` because it
+ // already represents the length including the NUL-terminator.
+ //
+ // SAFETY: Due to the check at the beginning, the buffer is not empty.
+ unsafe { *buf.last_mut().unwrap_unchecked() = 0 };
+ }
+
+ // This method consumes `self`, so it can only be called once, thus we do not need to
+ // update `self.length`. This sidesteps concerns such as whether `self.length` should be
+ // incremented by `len` or `len-1` in the `len == buf.len()` case.
+
+ // SAFETY: There are two cases:
+ // * If we hit the `len < dst.len()` case, then `raw_strncpy_from_user` guarantees that
+ // this slice contains exactly one NUL byte at the end of the string.
+ // * Otherwise, `raw_strncpy_from_user` guarantees that the string contained no NUL bytes,
+ // and we have since added a NUL byte at the end.
+ Ok(unsafe { CStr::from_bytes_with_nul_unchecked(&buf[..len]) })
+ }
}
/// A writer for [`UserSlice`].
@@ -380,7 +439,6 @@ pub fn write<T: AsBytes>(&mut self, value: &T) -> Result {
/// When this function returns `Ok(len)`, it is guaranteed that the first `len` bytes of `dst` are
/// initialized and non-zero. Furthermore, if `len < dst.len()`, then `dst[len]` is a NUL byte.
#[inline]
-#[expect(dead_code)]
fn raw_strncpy_from_user(dst: &mut [MaybeUninit<u8>], src: UserPtr) -> Result<usize> {
// CAST: Slice lengths are guaranteed to be `<= isize::MAX`.
let len = dst.len() as isize;
--
2.50.0.rc2.692.g299adb8693-goog
On Mon Jun 16, 2025 at 2:41 PM CEST, Alice Ryhl wrote: > This patch adds a more convenient method for reading C strings from > userspace. Logic is added to NUL-terminate the buffer when necessary so > that a &CStr can be returned. > > Note that we treat attempts to read past `self.length` as a fault, so > this returns EFAULT if that limit is exceeded before `buf.len()` is > reached. > > Reviewed-by: Danilo Krummrich <dakr@kernel.org> > Signed-off-by: Alice Ryhl <aliceryhl@google.com> I have one concern left below, when we fix or resolve that: Reviewed-by: Benno Lossin <lossin@kernel.org> > --- > rust/kernel/uaccess.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 59 insertions(+), 1 deletion(-) > > diff --git a/rust/kernel/uaccess.rs b/rust/kernel/uaccess.rs > index 635a03e0989f3fe99be80987aa47763782de1d3f..106aa05ea1b88868fe48f64ca9c86b20ad7db68e 100644 > --- a/rust/kernel/uaccess.rs > +++ b/rust/kernel/uaccess.rs > @@ -291,6 +291,65 @@ pub fn read_all<A: Allocator>(mut self, buf: &mut Vec<u8, A>, flags: Flags) -> R > unsafe { buf.inc_len(len) }; > Ok(()) > } > + > + /// Read a NUL-terminated string from userspace and return it. > + /// > + /// The string is read into `buf` and a NUL-terminator is added if the end of `buf` is reached. > + /// Since there must be space to add a NUL-terminator, the buffer must not be empty. The > + /// returned `&CStr` points into `buf`. > + /// > + /// Fails with [`EFAULT`] if the read happens on a bad address (some data may have been > + /// copied). > + #[doc(alias = "strncpy_from_user")] > + pub fn strcpy_into_buf<'buf>(self, buf: &'buf mut [u8]) -> Result<&'buf CStr> { > + if buf.is_empty() { > + return Err(EINVAL); > + } > + > + // SAFETY: The types are compatible and `strncpy_from_user` doesn't write uninitialized > + // bytes to `buf`. > + let mut dst = unsafe { &mut *(buf as *mut [u8] as *mut [MaybeUninit<u8>]) }; > + > + // We never read more than `self.length` bytes. > + if dst.len() > self.length { > + dst = &mut dst[..self.length]; > + } > + > + let mut len = raw_strncpy_from_user(dst, self.ptr)?; > + if len < dst.len() { > + // Add one to include the NUL-terminator. > + len += 1; > + } else if len < buf.len() { > + // This implies that `len == dst.len() < buf.len()`. > + // > + // This means that we could not fill the entire buffer, but we had to stop reading > + // because we hit the `self.length` limit of this `UserSliceReader`. Since we did not > + // fill the buffer, we treat this case as if we tried to read past the `self.length` > + // limit and received a page fault, which is consistent with other `UserSliceReader` > + // methods that also return page faults when you exceed `self.length`. > + return Err(EFAULT); > + } else { > + // This implies that `len == buf.len()`. > + // > + // This means that we filled the buffer exactly. In this case, we add a NUL-terminator > + // and return it. Unlike the `len < dst.len()` branch, don't modify `len` because it > + // already represents the length including the NUL-terminator. > + // > + // SAFETY: Due to the check at the beginning, the buffer is not empty. > + unsafe { *buf.last_mut().unwrap_unchecked() = 0 }; What about the case `self.length == 0`? Will `raw_strncpy_from_user` return early with a page fault, or will it return with `len == 0`? Because if it is the latter, then this will result in UB. --- Cheers, Benno > + } > + > + // This method consumes `self`, so it can only be called once, thus we do not need to > + // update `self.length`. This sidesteps concerns such as whether `self.length` should be > + // incremented by `len` or `len-1` in the `len == buf.len()` case. > + > + // SAFETY: There are two cases: > + // * If we hit the `len < dst.len()` case, then `raw_strncpy_from_user` guarantees that > + // this slice contains exactly one NUL byte at the end of the string. > + // * Otherwise, `raw_strncpy_from_user` guarantees that the string contained no NUL bytes, > + // and we have since added a NUL byte at the end. > + Ok(unsafe { CStr::from_bytes_with_nul_unchecked(&buf[..len]) }) > + } > } > > /// A writer for [`UserSlice`]. > @@ -380,7 +439,6 @@ pub fn write<T: AsBytes>(&mut self, value: &T) -> Result { > /// When this function returns `Ok(len)`, it is guaranteed that the first `len` bytes of `dst` are > /// initialized and non-zero. Furthermore, if `len < dst.len()`, then `dst[len]` is a NUL byte. > #[inline] > -#[expect(dead_code)] > fn raw_strncpy_from_user(dst: &mut [MaybeUninit<u8>], src: UserPtr) -> Result<usize> { > // CAST: Slice lengths are guaranteed to be `<= isize::MAX`. > let len = dst.len() as isize;
On Tue, Jun 17, 2025 at 9:38 AM Benno Lossin <lossin@kernel.org> wrote: > > On Mon Jun 16, 2025 at 2:41 PM CEST, Alice Ryhl wrote: > > This patch adds a more convenient method for reading C strings from > > userspace. Logic is added to NUL-terminate the buffer when necessary so > > that a &CStr can be returned. > > > > Note that we treat attempts to read past `self.length` as a fault, so > > this returns EFAULT if that limit is exceeded before `buf.len()` is > > reached. > > > > Reviewed-by: Danilo Krummrich <dakr@kernel.org> > > Signed-off-by: Alice Ryhl <aliceryhl@google.com> > > I have one concern left below, when we fix or resolve that: > > Reviewed-by: Benno Lossin <lossin@kernel.org> > > > --- > > rust/kernel/uaccess.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++++- > > 1 file changed, 59 insertions(+), 1 deletion(-) > > > > diff --git a/rust/kernel/uaccess.rs b/rust/kernel/uaccess.rs > > index 635a03e0989f3fe99be80987aa47763782de1d3f..106aa05ea1b88868fe48f64ca9c86b20ad7db68e 100644 > > --- a/rust/kernel/uaccess.rs > > +++ b/rust/kernel/uaccess.rs > > @@ -291,6 +291,65 @@ pub fn read_all<A: Allocator>(mut self, buf: &mut Vec<u8, A>, flags: Flags) -> R > > unsafe { buf.inc_len(len) }; > > Ok(()) > > } > > + > > + /// Read a NUL-terminated string from userspace and return it. > > + /// > > + /// The string is read into `buf` and a NUL-terminator is added if the end of `buf` is reached. > > + /// Since there must be space to add a NUL-terminator, the buffer must not be empty. The > > + /// returned `&CStr` points into `buf`. > > + /// > > + /// Fails with [`EFAULT`] if the read happens on a bad address (some data may have been > > + /// copied). > > + #[doc(alias = "strncpy_from_user")] > > + pub fn strcpy_into_buf<'buf>(self, buf: &'buf mut [u8]) -> Result<&'buf CStr> { > > + if buf.is_empty() { > > + return Err(EINVAL); > > + } > > + > > + // SAFETY: The types are compatible and `strncpy_from_user` doesn't write uninitialized > > + // bytes to `buf`. > > + let mut dst = unsafe { &mut *(buf as *mut [u8] as *mut [MaybeUninit<u8>]) }; > > + > > + // We never read more than `self.length` bytes. > > + if dst.len() > self.length { > > + dst = &mut dst[..self.length]; > > + } > > + > > + let mut len = raw_strncpy_from_user(dst, self.ptr)?; > > + if len < dst.len() { > > + // Add one to include the NUL-terminator. > > + len += 1; > > + } else if len < buf.len() { > > + // This implies that `len == dst.len() < buf.len()`. > > + // > > + // This means that we could not fill the entire buffer, but we had to stop reading > > + // because we hit the `self.length` limit of this `UserSliceReader`. Since we did not > > + // fill the buffer, we treat this case as if we tried to read past the `self.length` > > + // limit and received a page fault, which is consistent with other `UserSliceReader` > > + // methods that also return page faults when you exceed `self.length`. > > + return Err(EFAULT); > > + } else { > > + // This implies that `len == buf.len()`. > > + // > > + // This means that we filled the buffer exactly. In this case, we add a NUL-terminator > > + // and return it. Unlike the `len < dst.len()` branch, don't modify `len` because it > > + // already represents the length including the NUL-terminator. > > + // > > + // SAFETY: Due to the check at the beginning, the buffer is not empty. > > + unsafe { *buf.last_mut().unwrap_unchecked() = 0 }; > > What about the case `self.length == 0`? Will `raw_strncpy_from_user` > return early with a page fault, or will it return with `len == 0`? > Because if it is the latter, then this will result in UB. If `self.length == 0`, then you will either: 1. If buf.is_empty() then you return EINVAL at the top. 2. Otherwise, you return EFAULT from the `len < buf.len()` case. Alice
On Tue Jun 17, 2025 at 10:55 AM CEST, Alice Ryhl wrote: > On Tue, Jun 17, 2025 at 9:38 AM Benno Lossin <lossin@kernel.org> wrote: >> What about the case `self.length == 0`? Will `raw_strncpy_from_user` >> return early with a page fault, or will it return with `len == 0`? >> Because if it is the latter, then this will result in UB. > > If `self.length == 0`, then you will either: > 1. If buf.is_empty() then you return EINVAL at the top. > 2. Otherwise, you return EFAULT from the `len < buf.len()` case. Ah that makes sense. Thanks for explaining. --- Cheers, Benno
© 2016 - 2025 Red Hat, Inc.