From 9b41e79f48c3e9bbb607bc8a0f156017c771b6a4 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Tue, 10 Dec 2024 14:19:38 +0000 Subject: [PATCH] ANDROID: ashmem_rust: add memfd ioctls When libcutils is configured to create memfds instead of ashmem fds, old applications may still attempt to call ashmem ioctls on the fd. Thus, add an ioctl handler to shmem to support the ioctls that are necessary. We don't need to support the ioctls on hugeltb because libcutils does not pass MFD_HUGETLB when creating the memfd. Bug: 370906207 Change-Id: Ib56d55db0f590ee90b83a3e2cde67413fa8e10d6 Signed-off-by: Alice Ryhl --- drivers/staging/android/ashmem_rust.rs | 139 +++++++++++++++++++++++++ mm/shmem.c | 11 ++ rust/bindings/bindings_helper.h | 1 + rust/helpers/fs.c | 5 + 4 files changed, 156 insertions(+) diff --git a/drivers/staging/android/ashmem_rust.rs b/drivers/staging/android/ashmem_rust.rs index ddf87dcf08b5..3be718cef354 100644 --- a/drivers/staging/android/ashmem_rust.rs +++ b/drivers/staging/android/ashmem_rust.rs @@ -498,3 +498,142 @@ impl AshmemInner { CStr::from_bytes_with_nul(&name[..len_with_nul]).unwrap() } } + +#[no_mangle] +unsafe extern "C" fn ashmem_memfd_ioctl(file: *mut bindings::file, cmd: u32, arg: usize) -> isize { + #[cfg(CONFIG_COMPAT)] + let cmd = match cmd { + bindings::COMPAT_ASHMEM_SET_SIZE => bindings::ASHMEM_SET_SIZE, + bindings::COMPAT_ASHMEM_SET_PROT_MASK => bindings::ASHMEM_SET_PROT_MASK, + cmd => cmd, + }; + + // SAFETY: + // * The file is valid for the duration of this call. + // * There is no active fdget_pos region on the file on this thread. + let file = unsafe { File::from_raw_file(file) }; + + match ashmem_memfd_ioctl_inner(file, cmd, arg) { + Ok(ret) => ret, + Err(err) => err.to_errno() as isize, + } +} + +fn ashmem_memfd_ioctl_inner(file: &File, cmd: u32, arg: usize) -> Result { + use kernel::bindings::{F_ADD_SEALS, F_GET_SEALS, F_SEAL_FUTURE_WRITE, F_SEAL_WRITE}; + const WRITE_SEALS_MASK: u64 = (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE) as u64; + + /// # Safety + /// The file must be a memfd file. + unsafe fn get_seals(file: &File) -> Result { + // SAFETY: This is a memfd file. + let seals: i64 = unsafe { bindings::memfd_fcntl(file.as_ptr(), F_GET_SEALS, 0) }; + if seals < 0 { + return Err(Error::from_errno(seals as i32)); + } + Ok(seals as u64) + } + + let size = _IOC_SIZE(cmd); + match cmd { + bindings::ASHMEM_GET_NAME => { + let file_ptr = file.as_ptr(); + // SAFETY: It's safe to access a file's dentry. + let dentry = unsafe { (*file_ptr).f_path.dentry }; + // SAFETY: memfd stores the supplied name at this location. A default value is stored + // when no name is supplied, so this is always a valid string. + let full_name = unsafe { + core::slice::from_raw_parts( + (*dentry).d_name.name, + (*dentry).d_name.__bindgen_anon_1.__bindgen_anon_1.len as usize, + ) + }; + + let name = full_name.strip_prefix(b"memfd:").unwrap_or(full_name); + let max = usize::min(name.len(), ASHMEM_NAME_LEN); + + let mut local_name = [0u8; ASHMEM_NAME_LEN]; + local_name[..max].copy_from_slice(&name[..max]); + local_name[ASHMEM_NAME_LEN - 1] = 0; + + let mut writer = UserSlice::new(arg, size).writer(); + writer.write_slice(&local_name)?; + Ok(0) + } + bindings::ASHMEM_GET_SIZE => { + let file_ptr = file.as_ptr(); + // SAFETY: It's safe to access a file's inode. + let inode = unsafe { (*file_ptr).f_inode }; + // SAFETY: It's safe to read the size of an inode. + let size = unsafe { bindings::i_size_read(inode) }; + Ok(size as isize) + } + bindings::ASHMEM_SET_PROT_MASK => { + // SAFETY: This is a memfd file. + let seals = unsafe { get_seals(file) }?; + let mut prot = arg; + + // The memfd compat layer does not support unsetting these. + prot |= PROT_READ | PROT_EXEC; + + let is_writable = seals & WRITE_SEALS_MASK == 0; + let should_be_writable = prot & PROT_WRITE != 0; + + if !is_writable && should_be_writable { + // Can't add PROT bits. + return Err(EINVAL); + } + + if is_writable && !should_be_writable { + // SAFETY: This is a memfd file. + let ret = unsafe { + bindings::memfd_fcntl(file.as_ptr(), F_ADD_SEALS, F_SEAL_FUTURE_WRITE) + }; + if ret < 0 { + return Err(Error::from_errno(ret as i32)); + } + } + Ok(0) + } + bindings::ASHMEM_GET_PROT_MASK => { + // SAFETY: This is a memfd file. + let seals = unsafe { get_seals(file) }?; + + let mut prot = PROT_READ | PROT_EXEC; + if seals & WRITE_SEALS_MASK == 0 { + prot |= PROT_WRITE; + } + Ok(prot as isize) + } + bindings::ASHMEM_GET_FILE_ID => { + // SAFETY: Accessing the ino is always okay. + let ino = unsafe { (*(*file.as_ptr()).f_inode).i_ino as usize }; + + let mut writer = UserSlice::new(arg, size).writer(); + writer.write(&ino)?; + Ok(0) + } + // Just ignore unpin requests. + ASHMEM_PIN => Ok(bindings::ASHMEM_NOT_PURGED as isize), + ASHMEM_UNPIN => Ok(0), + ASHMEM_GET_PIN_STATUS => Ok(bindings::ASHMEM_IS_PINNED as isize), + bindings::ASHMEM_PURGE_ALL_CACHES => { + if !has_cap_sys_admin() { + return Err(EPERM); + } + Ok(0) + } + // We do not need to implement SET_NAME or SET_SIZE. The ioctls in this function are only + // called when you: + // + // 1. Think you have an ashmem fd. + // 2. But actually have a memfd fd. + // + // This can only happen if you created the fd through the libcutils library, and that + // library sets the name and size in the fd constructor where it knows whether ashmem or + // memfd is used, so we should never end up here. + bindings::ASHMEM_SET_NAME => Err(EINVAL), + bindings::ASHMEM_SET_SIZE => Err(EINVAL), + _ => Err(EINVAL), + } +} diff --git a/mm/shmem.c b/mm/shmem.c index cf022bced240..1f127a218f9d 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -4830,6 +4830,11 @@ static const struct address_space_operations shmem_aops = { .error_remove_folio = shmem_error_remove_folio, }; +#ifdef CONFIG_ASHMEM_RUST +extern long ashmem_memfd_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); +#endif + static const struct file_operations shmem_file_operations = { .mmap = shmem_mmap, .open = shmem_file_open, @@ -4843,6 +4848,12 @@ static const struct file_operations shmem_file_operations = { .splice_write = iter_file_splice_write, .fallocate = shmem_fallocate, #endif +#ifdef CONFIG_ASHMEM_RUST + .unlocked_ioctl = ashmem_memfd_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ashmem_memfd_ioctl, +#endif +#endif }; static const struct inode_operations shmem_inode_operations = { diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 180bafe4de93..76a5a469ffcb 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/fs.c b/rust/helpers/fs.c index a75c96763372..bd80706bcb67 100644 --- a/rust/helpers/fs.c +++ b/rust/helpers/fs.c @@ -10,3 +10,8 @@ struct file *rust_helper_get_file(struct file *f) { return get_file(f); } + +loff_t rust_helper_i_size_read(const struct inode *inode) +{ + return i_size_read(inode); +}