rust: uaccess: add UserSliceReader::strcpy_into_buf

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>
Reviewed-by: Benno Lossin <lossin@kernel.org>
Link: https://lore.kernel.org/r/20250616-strncpy-from-user-v5-2-2d3fb0e1f5af@google.com
[ Use `from_mut` to clean `clippy::ref_as_ptr` lint. Reworded
  title. - Miguel ]
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
This commit is contained in:
Alice Ryhl 2025-06-16 12:41:48 +00:00 committed by Miguel Ojeda
commit 17bbbefbf6

View file

@ -291,6 +291,65 @@ impl UserSliceReader {
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 *(core::ptr::from_mut(buf) 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 @@ impl UserSliceWriter {
/// 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;