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 <aliceryhl@google.com>
This commit is contained in:
Alice Ryhl
2024-12-10 14:19:38 +00:00
committed by Isaac Manjarres
parent 372903c09b
commit 9b41e79f48
4 changed files with 156 additions and 0 deletions
+139
View File
@@ -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<isize> {
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<u64> {
// 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),
}
}
+11
View File
@@ -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 = {
+1
View File
@@ -23,6 +23,7 @@
#include <linux/mdio.h>
#include <linux/miscdevice.h>
#include <linux/mman.h>
#include <linux/memfd.h>
#include <linux/phy.h>
#include <linux/pid_namespace.h>
#include <linux/poll.h>
+5
View File
@@ -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);
}