• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0
2 
3 // Copyright (C) 2024 Google LLC.
4 
5 //! Anonymous Shared Memory Subsystem for Android.
6 //!
7 //! The ashmem subsystem is a new shared memory allocator, similar to POSIX SHM but with different
8 //! behavior and sporting a simpler file-based API.
9 //!
10 //! It is, in theory, a good memory allocator for low-memory devices, because it can discard shared
11 //! memory units when under memory pressure.
12 
13 use core::{
14     pin::Pin,
15     ptr::null_mut,
16     sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering},
17 };
18 use kernel::{
19     bindings::{self, ASHMEM_GET_PIN_STATUS, ASHMEM_PIN, ASHMEM_UNPIN},
20     c_str,
21     error::Result,
22     ffi::c_int,
23     fs::{File, LocalFile},
24     ioctl::_IOC_SIZE,
25     miscdevice::{loff_t, IovIter, Kiocb, MiscDevice, MiscDeviceOptions, MiscDeviceRegistration},
26     mm::virt::{flags as vma_flags, VmaNew},
27     page::{page_align, PAGE_MASK, PAGE_SIZE},
28     page_size_compat::__page_align,
29     prelude::*,
30     seq_file::{seq_print, SeqFile},
31     sync::{new_mutex, Mutex, UniqueArc},
32     task::Task,
33     types::ForeignOwnable,
34     uaccess::{UserSlice, UserSliceReader, UserSliceWriter},
35 };
36 
37 const ASHMEM_NAME_LEN: usize = bindings::ASHMEM_NAME_LEN as usize;
38 const ASHMEM_FULL_NAME_LEN: usize = bindings::ASHMEM_FULL_NAME_LEN as usize;
39 const ASHMEM_NAME_PREFIX_LEN: usize = bindings::ASHMEM_NAME_PREFIX_LEN as usize;
40 const ASHMEM_NAME_PREFIX: [u8; ASHMEM_NAME_PREFIX_LEN] = *b"dev/ashmem/";
41 
42 const ASHMEM_MAX_SIZE: usize = usize::MAX >> 1;
43 
44 const PROT_READ: usize = bindings::PROT_READ as usize;
45 const PROT_EXEC: usize = bindings::PROT_EXEC as usize;
46 const PROT_WRITE: usize = bindings::PROT_WRITE as usize;
47 const PROT_MASK: usize = PROT_EXEC | PROT_READ | PROT_WRITE;
48 
49 mod ashmem_shrinker;
50 
51 mod ashmem_range;
52 use ashmem_range::{Area, AshmemGuard, NewRange, ASHMEM_MUTEX, LRU_COUNT};
53 
54 mod shmem;
55 use shmem::ShmemFile;
56 
57 mod ashmem_toggle;
58 use ashmem_toggle::{AshmemToggleExec, AshmemToggleMisc, AshmemToggleRead, AshmemToggleShrinker};
59 
60 /// Does PROT_READ imply PROT_EXEC for this task?
read_implies_exec(task: &Task) -> bool61 fn read_implies_exec(task: &Task) -> bool {
62     // SAFETY: Always safe to read.
63     let personality = unsafe { (*task.as_ptr()).personality };
64     (personality & bindings::READ_IMPLIES_EXEC) != 0
65 }
66 
67 /// Calls `capable(CAP_SYS_ADMIN)`.
has_cap_sys_admin() -> bool68 fn has_cap_sys_admin() -> bool {
69     use kernel::bindings::CAP_SYS_ADMIN;
70     unsafe { bindings::capable(CAP_SYS_ADMIN as c_int) }
71 }
72 
73 static NUM_PIN_IOCTLS_WAITING: AtomicUsize = AtomicUsize::new(0);
74 static IGNORE_UNSET_PROT_READ: AtomicBool = AtomicBool::new(false);
75 static IGNORE_UNSET_PROT_EXEC: AtomicBool = AtomicBool::new(false);
76 static ASHMEM_FOPS_PTR: AtomicPtr<bindings::file_operations> = AtomicPtr::new(null_mut());
77 
shrinker_should_stop() -> bool78 fn shrinker_should_stop() -> bool {
79     NUM_PIN_IOCTLS_WAITING.load(Ordering::Relaxed) > 0
80 }
81 
82 module! {
83     type: AshmemModule,
84     name: "ashmem_rust",
85     author: "Alice Ryhl",
86     description: "Anonymous Shared Memory Subsystem",
87     license: "GPL",
88 }
89 
90 struct AshmemModule {
91     _misc: Pin<KBox<MiscDeviceRegistration<Ashmem>>>,
92     _toggle_unpin: Pin<KBox<MiscDeviceRegistration<AshmemToggleMisc<AshmemToggleShrinker>>>>,
93     _toggle_read: Pin<KBox<MiscDeviceRegistration<AshmemToggleMisc<AshmemToggleRead>>>>,
94     _toggle_exec: Pin<KBox<MiscDeviceRegistration<AshmemToggleMisc<AshmemToggleExec>>>>,
95 }
96 
97 impl kernel::Module for AshmemModule {
init(_module: &'static kernel::ThisModule) -> Result<Self>98     fn init(_module: &'static kernel::ThisModule) -> Result<Self> {
99         // SAFETY: Called once since this is the module initializer.
100         unsafe { shmem::SHMEM_FOPS_ONCE.init() };
101         // SAFETY: Called once since this is the module initializer.
102         unsafe { ASHMEM_MUTEX.init() };
103         // SAFETY: Called once since this is the module initializer.
104         unsafe { ashmem_range::ASHMEM_SHRINKER.init() };
105 
106         pr_info!("Using Rust implementation.");
107 
108         ashmem_range::set_shrinker_enabled(true)?;
109 
110         let ashmem_miscdevice_registration = KBox::pin_init(
111             MiscDeviceRegistration::register(MiscDeviceOptions {
112                 name: c_str!("ashmem"),
113             }),
114             GFP_KERNEL,
115         )?;
116         let ashmem_miscdevice_ptr = ashmem_miscdevice_registration.as_raw();
117         // SAFETY: ashmem_miscdevice_registration is pinned and is never destroyed, so reading
118         // and storing the fops pointer this way should be fine.
119         let fops_ptr = unsafe { (*ashmem_miscdevice_ptr).fops };
120         ASHMEM_FOPS_PTR.store(fops_ptr.cast_mut(), Ordering::Relaxed);
121 
122         Ok(Self {
123             _misc: ashmem_miscdevice_registration,
124             _toggle_unpin: AshmemToggleMisc::<AshmemToggleShrinker>::new()?,
125             _toggle_read: AshmemToggleMisc::<AshmemToggleRead>::new()?,
126             _toggle_exec: AshmemToggleMisc::<AshmemToggleExec>::new()?,
127         })
128     }
129 }
130 
131 /// Represents an open ashmem file.
132 #[pin_data]
133 struct Ashmem {
134     #[pin]
135     inner: Mutex<AshmemInner>,
136 }
137 
138 struct AshmemInner {
139     size: usize,
140     prot_mask: usize,
141     /// If set, then this holds the ashmem name without the dev/ashmem/ prefix. No zero terminator.
142     name: Option<KVec<u8>>,
143     file: Option<ShmemFile>,
144     area: Area,
145 }
146 
147 #[vtable]
148 impl MiscDevice for Ashmem {
149     type Ptr = Pin<KBox<Self>>;
150 
open(_: &File, _: &MiscDeviceRegistration<Ashmem>) -> Result<Pin<KBox<Self>>>151     fn open(_: &File, _: &MiscDeviceRegistration<Ashmem>) -> Result<Pin<KBox<Self>>> {
152         KBox::try_pin_init(
153             try_pin_init! {
154                 Ashmem {
155                     inner <- new_mutex!(AshmemInner {
156                         size: 0,
157                         prot_mask: PROT_MASK,
158                         name: None,
159                         file: None,
160                         area: Area::new(),
161                     }),
162                 }
163             },
164             GFP_KERNEL,
165         )
166     }
167 
mmap(me: Pin<&Ashmem>, _file: &File, vma: &VmaNew) -> Result<()>168     fn mmap(me: Pin<&Ashmem>, _file: &File, vma: &VmaNew) -> Result<()> {
169         let asma = &mut *me.inner.lock();
170 
171         // User needs to SET_SIZE before mapping.
172         if asma.size == 0 || asma.size >= ASHMEM_MAX_SIZE {
173             return Err(EINVAL);
174         }
175 
176         // Requested mapping size larger than object size.
177         if vma.end() - vma.start() > __page_align(asma.size) {
178             return Err(EINVAL);
179         }
180 
181         if asma.prot_mask & PROT_WRITE == 0 {
182             vma.try_clear_maywrite().map_err(|_| EPERM)?;
183         }
184         if asma.prot_mask & PROT_EXEC == 0 {
185             vma.try_clear_mayexec().map_err(|_| EPERM)?;
186         }
187         if asma.prot_mask & PROT_READ == 0 {
188             vma.try_clear_mayread().map_err(|_| EPERM)?;
189         }
190 
191         let file = match asma.file.as_ref() {
192             Some(file) => file,
193             None => {
194                 let mut name_buffer = [0u8; ASHMEM_FULL_NAME_LEN];
195                 let name = asma.full_name(&mut name_buffer);
196                 asma.file
197                     .insert(ShmemFile::new(name, asma.size, vma.flags())?)
198             }
199         };
200 
201         if vma.flags() & vma_flags::SHARED != 0 {
202             // We're really using this just to set vm_ops to `shmem_anon_vm_ops`. Anything else it
203             // does is undone by the call to `set_file` below.
204             shmem::zero_setup(vma)?;
205         } else {
206             shmem::vma_set_anonymous(vma);
207         }
208 
209         shmem::set_file(vma, file.file());
210         Ok(())
211     }
212 
llseek(me: Pin<&Ashmem>, file: &LocalFile, offset: loff_t, whence: c_int) -> Result<loff_t>213     fn llseek(me: Pin<&Ashmem>, file: &LocalFile, offset: loff_t, whence: c_int) -> Result<loff_t> {
214         let asma_file = {
215             let asma = me.inner.lock();
216             if asma.size == 0 {
217                 return Err(EINVAL);
218             }
219             match asma.file.as_ref() {
220                 Some(asma_file) => asma_file.clone(),
221                 None => return Err(EBADF),
222             }
223         };
224 
225         let ret = asma_file.vfs_llseek(offset, whence)?;
226 
227         // SAFETY: We protect the shmem file with the same mechanism as the ashmem file. We are in
228         // llseek, so our caller ensures that accessing f_pos is okay.
229         unsafe { shmem::file_set_fpos(file, shmem::file_get_fpos(asma_file.file())) };
230 
231         Ok(ret)
232     }
233 
read_iter(mut kiocb: Kiocb<'_, Self::Ptr>, iov: &mut IovIter) -> Result<usize>234     fn read_iter(mut kiocb: Kiocb<'_, Self::Ptr>, iov: &mut IovIter) -> Result<usize> {
235         let me = kiocb.private_data();
236         let asma_file = {
237             let asma = me.inner.lock();
238             if asma.size == 0 {
239                 // If size is not set, or set to 0, always return EOF.
240                 return Ok(0);
241             }
242             match asma.file.as_ref() {
243                 Some(asma_file) => asma_file.clone(),
244                 None => return Err(EBADF),
245             }
246         };
247 
248         let ret = asma_file.vfs_iter_read(iov, kiocb.ki_pos_mut())?;
249 
250         // SAFETY: We protect the shmem file with the same mechanism as the ashmem file. We are in
251         // read_iter, so our caller ensures that accessing f_pos is okay.
252         unsafe { shmem::file_set_fpos(asma_file.file(), kiocb.ki_pos()) };
253 
254         Ok(ret as usize)
255     }
256 
ioctl(me: Pin<&Ashmem>, _file: &File, cmd: u32, arg: usize) -> Result<isize>257     fn ioctl(me: Pin<&Ashmem>, _file: &File, cmd: u32, arg: usize) -> Result<isize> {
258         let size = _IOC_SIZE(cmd);
259         match cmd {
260             bindings::ASHMEM_SET_NAME => me.set_name(UserSlice::new(arg, size).reader()),
261             bindings::ASHMEM_GET_NAME => me.get_name(UserSlice::new(arg, size).writer()),
262             bindings::ASHMEM_SET_SIZE => me.set_size(arg),
263             bindings::ASHMEM_GET_SIZE => me.get_size(),
264             bindings::ASHMEM_SET_PROT_MASK => me.set_prot_mask(arg),
265             bindings::ASHMEM_GET_PROT_MASK => me.get_prot_mask(),
266             bindings::ASHMEM_GET_FILE_ID => me.get_file_id(UserSlice::new(arg, size).writer()),
267             ASHMEM_PIN | ASHMEM_UNPIN | ASHMEM_GET_PIN_STATUS => {
268                 me.pin_unpin(cmd, UserSlice::new(arg, size).reader())
269             }
270             bindings::ASHMEM_PURGE_ALL_CACHES => me.purge_all_caches(),
271             _ => Err(ENOTTY),
272         }
273     }
274 
275     #[cfg(CONFIG_COMPAT)]
compat_ioctl(me: Pin<&Ashmem>, file: &File, compat_cmd: u32, arg: usize) -> Result<isize>276     fn compat_ioctl(me: Pin<&Ashmem>, file: &File, compat_cmd: u32, arg: usize) -> Result<isize> {
277         let cmd = match compat_cmd {
278             bindings::COMPAT_ASHMEM_SET_SIZE => bindings::ASHMEM_SET_SIZE,
279             bindings::COMPAT_ASHMEM_SET_PROT_MASK => bindings::ASHMEM_SET_PROT_MASK,
280             _ => compat_cmd,
281         };
282         Self::ioctl(me, file, cmd, arg)
283     }
284 
show_fdinfo(me: Pin<&Ashmem>, m: &SeqFile, _file: &File)285     fn show_fdinfo(me: Pin<&Ashmem>, m: &SeqFile, _file: &File) {
286         let asma = me.inner.lock();
287 
288         if let Some(file) = asma.file.as_ref() {
289             seq_print!(m, "inode:\t{}\n", file.inode_ino());
290         }
291         if let Some(name) = asma.name.as_ref() {
292             let name = core::str::from_utf8(name).unwrap_or("<invalid utf-8>");
293             seq_print!(m, "name:\t{}\n", name);
294         }
295         seq_print!(m, "size\t{}\n", asma.size);
296     }
297 }
298 
299 impl Ashmem {
set_name(&self, reader: UserSliceReader) -> Result<isize>300     fn set_name(&self, reader: UserSliceReader) -> Result<isize> {
301         let mut buf = [0u8; ASHMEM_NAME_LEN];
302         let name = reader.strcpy_into_buf(&mut buf)?.as_bytes();
303 
304         let mut v = KVec::with_capacity(name.len(), GFP_KERNEL)?;
305         v.extend_from_slice(name, GFP_KERNEL)?;
306 
307         let mut asma = self.inner.lock();
308         if asma.file.is_some() {
309             return Err(EINVAL);
310         }
311         asma.name = Some(v);
312         Ok(0)
313     }
314 
get_name(&self, mut writer: UserSliceWriter) -> Result<isize>315     fn get_name(&self, mut writer: UserSliceWriter) -> Result<isize> {
316         let mut local_name = [0u8; ASHMEM_NAME_LEN];
317         let asma = self.inner.lock();
318         let name = asma.name.as_deref().unwrap_or(b"dev/ashmem");
319         let len = name.len();
320         let len_with_nul = len + 1;
321         if local_name.len() < len_with_nul {
322             // This shouldn't happen in practice since `set_name` will refuse to store a string
323             // that is too long.
324             return Err(EINVAL);
325         }
326         local_name[..len].copy_from_slice(name);
327         local_name[len] = 0;
328         drop(asma);
329 
330         writer.write_slice(&local_name[..len_with_nul])?;
331         Ok(0)
332     }
333 
set_size(&self, size: usize) -> Result<isize>334     fn set_size(&self, size: usize) -> Result<isize> {
335         let mut asma = self.inner.lock();
336         if asma.file.is_some() {
337             return Err(EINVAL);
338         }
339         asma.size = size;
340         Ok(0)
341     }
342 
get_size(&self) -> Result<isize>343     fn get_size(&self) -> Result<isize> {
344         Ok(self.inner.lock().size as isize)
345     }
346 
set_prot_mask(&self, mut prot: usize) -> Result<isize>347     fn set_prot_mask(&self, mut prot: usize) -> Result<isize> {
348         let mut asma = self.inner.lock();
349 
350         if (prot & PROT_READ != 0) && read_implies_exec(current!()) {
351             prot |= PROT_EXEC;
352         }
353 
354         if IGNORE_UNSET_PROT_READ.load(Ordering::Relaxed) {
355             // Add back PROT_READ if asma.prot_mask has it.
356             prot |= asma.prot_mask & PROT_READ;
357         }
358 
359         if IGNORE_UNSET_PROT_EXEC.load(Ordering::Relaxed) {
360             // Add back PROT_EXEC if asma.prot_mask has it.
361             prot |= asma.prot_mask & PROT_EXEC;
362         }
363 
364         // The user can only remove, not add, protection bits.
365         if (asma.prot_mask & prot) != prot {
366             return Err(EINVAL);
367         }
368 
369         asma.prot_mask = prot;
370         Ok(0)
371     }
372 
get_prot_mask(&self) -> Result<isize>373     fn get_prot_mask(&self) -> Result<isize> {
374         Ok(self.inner.lock().prot_mask as isize)
375     }
376 
get_file_id(&self, mut writer: UserSliceWriter) -> Result<isize>377     fn get_file_id(&self, mut writer: UserSliceWriter) -> Result<isize> {
378         let ino = {
379             let asma = self.inner.lock();
380             let Some(file) = asma.file.as_ref() else {
381                 return Err(EINVAL);
382             };
383             file.inode_ino()
384         };
385         writer.write(&ino)?;
386         Ok(0)
387     }
388 
pin_unpin(&self, cmd: u32, mut reader: UserSliceReader) -> Result<isize>389     fn pin_unpin(&self, cmd: u32, mut reader: UserSliceReader) -> Result<isize> {
390         let (offset, cmd_len) = {
391             #[allow(dead_code)] // spurious warning because it is never explicitly constructed
392             #[repr(transparent)]
393             struct AshmemPin(bindings::ashmem_pin);
394             // SAFETY: All bit-patterns are valid for `ashmem_pin`.
395             unsafe impl kernel::types::FromBytes for AshmemPin {}
396             let AshmemPin(pin) = reader.read()?;
397             (pin.offset as usize, pin.len as usize)
398         };
399 
400         // If `pin`/`unpin` needs a new range, they will take it from this `Option`. Otherwise,
401         // they will leave it here, and it gets dropped after the mutexes are released.
402         let new_range = if cmd == ASHMEM_GET_PIN_STATUS {
403             None
404         } else {
405             Some(UniqueArc::new_uninit(GFP_KERNEL)?)
406         };
407 
408         NUM_PIN_IOCTLS_WAITING.fetch_add(1, Ordering::Relaxed);
409         let mut guard = AshmemGuard(ASHMEM_MUTEX.lock());
410         NUM_PIN_IOCTLS_WAITING.fetch_sub(1, Ordering::Relaxed);
411 
412         // C ashmem waits for in-flight shrinkers here using a separate mechanism, but we don't
413         // release the lock when calling `punch_hole` in the shrinker, so we don't need to do that.
414 
415         let asma = &mut *self.inner.lock();
416         let mut new_range = match asma.file.as_ref() {
417             Some(file) => new_range.map(|alloc| NewRange { file, alloc }),
418             None => return Err(EINVAL),
419         };
420 
421         let max_size = page_align(asma.size);
422         let remaining = max_size.checked_sub(offset).ok_or(EINVAL)?;
423 
424         // Per custom, you can pass zero for len to mean "everything onward".
425         let len = if cmd_len == 0 { remaining } else { cmd_len };
426 
427         if (offset | len) & !PAGE_MASK != 0 {
428             return Err(EINVAL);
429         }
430         let len_plus_offset = offset.checked_add(len).ok_or(EINVAL)?;
431         if max_size < len_plus_offset {
432             return Err(EINVAL);
433         }
434 
435         let pgstart = offset / PAGE_SIZE;
436         let pgend = pgstart + (len / PAGE_SIZE) - 1;
437 
438         match cmd {
439             ASHMEM_PIN => {
440                 if asma.area.pin(pgstart, pgend, &mut new_range, &mut guard) {
441                     Ok(bindings::ASHMEM_WAS_PURGED as isize)
442                 } else {
443                     Ok(bindings::ASHMEM_NOT_PURGED as isize)
444                 }
445             }
446             ASHMEM_UNPIN => {
447                 asma.area.unpin(pgstart, pgend, &mut new_range, &mut guard);
448                 Ok(0)
449             }
450             ASHMEM_GET_PIN_STATUS => {
451                 if asma
452                     .area
453                     .range_has_unpinned_page(pgstart, pgend, &mut guard)
454                 {
455                     Ok(bindings::ASHMEM_IS_UNPINNED as isize)
456                 } else {
457                     Ok(bindings::ASHMEM_IS_PINNED as isize)
458                 }
459             }
460             _ => unreachable!(),
461         }
462     }
463 
purge_all_caches(&self) -> Result<isize>464     fn purge_all_caches(&self) -> Result<isize> {
465         if !has_cap_sys_admin() {
466             return Err(EPERM);
467         }
468         let mut guard = AshmemGuard(ASHMEM_MUTEX.lock());
469         let total_num_pages = LRU_COUNT.load(Ordering::Relaxed);
470         let _num_freed = guard.free_lru(usize::MAX);
471         // ASHMEM_PURGE_ALL_CACHES returns the total number of pages even if we stopped early.
472         Ok(isize::try_from(total_num_pages).unwrap_or(isize::MAX))
473     }
474 }
475 
476 impl AshmemInner {
477     /// Get the full name.
478     ///
479     /// If the name is `Some(name)`, then this returns `dev/ashmem/name\0`.
480     ///
481     /// If the name is `None`, then this returns `dev/ashmem\0`.
full_name<'name>(&self, name: &'name mut [u8; ASHMEM_FULL_NAME_LEN]) -> &'name CStr482     fn full_name<'name>(&self, name: &'name mut [u8; ASHMEM_FULL_NAME_LEN]) -> &'name CStr {
483         name[..ASHMEM_NAME_PREFIX_LEN].copy_from_slice(&ASHMEM_NAME_PREFIX);
484         if let Some(set_name) = self.name.as_deref() {
485             name[ASHMEM_NAME_PREFIX_LEN..][..set_name.len()].copy_from_slice(set_name);
486         } else {
487             // Remove last slash if no name set.
488             name[ASHMEM_NAME_PREFIX_LEN - 1] = 0;
489         }
490         name[ASHMEM_FULL_NAME_LEN - 1] = 0;
491 
492         // This unwrap only fails if there's no nul-byte, but we just added one at the end above.
493         let len_with_nul = name
494             .iter()
495             .position(|&c| c == 0)
496             .map(|len| len + 1)
497             .unwrap();
498 
499         // This unwrap fails if the last byte is not a nul-byte, or if there are any nul-bytes
500         // before the last byte. Neither of those are possible here since `len_with_nul` is the
501         // index of the first nul-byte in `name`.
502         CStr::from_bytes_with_nul(&name[..len_with_nul]).unwrap()
503     }
504 }
505 
506 #[no_mangle]
ashmem_memfd_ioctl(file: *mut bindings::file, cmd: u32, arg: usize) -> isize507 unsafe extern "C" fn ashmem_memfd_ioctl(file: *mut bindings::file, cmd: u32, arg: usize) -> isize {
508     #[cfg(CONFIG_COMPAT)]
509     let cmd = match cmd {
510         bindings::COMPAT_ASHMEM_SET_SIZE => bindings::ASHMEM_SET_SIZE,
511         bindings::COMPAT_ASHMEM_SET_PROT_MASK => bindings::ASHMEM_SET_PROT_MASK,
512         cmd => cmd,
513     };
514 
515     // SAFETY:
516     // * The file is valid for the duration of this call.
517     // * There is no active fdget_pos region on the file on this thread.
518     let file = unsafe { File::from_raw_file(file) };
519 
520     match ashmem_memfd_ioctl_inner(file, cmd, arg) {
521         Ok(ret) => ret,
522         Err(err) => err.to_errno() as isize,
523     }
524 }
525 
ashmem_memfd_ioctl_inner(file: &File, cmd: u32, arg: usize) -> Result<isize>526 fn ashmem_memfd_ioctl_inner(file: &File, cmd: u32, arg: usize) -> Result<isize> {
527     use kernel::bindings::{F_ADD_SEALS, F_GET_SEALS, F_SEAL_FUTURE_WRITE, F_SEAL_WRITE};
528     const WRITE_SEALS_MASK: usize = (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE) as usize;
529 
530     /// # Safety
531     /// The file must be a memfd file.
532     unsafe fn get_seals(file: &File) -> Result<usize> {
533         // SAFETY: This is a memfd file.
534         let seals: isize = unsafe { bindings::memfd_fcntl(file.as_ptr(), F_GET_SEALS, 0) };
535         if seals < 0 {
536             return Err(Error::from_errno(seals as i32));
537         }
538         Ok(seals as usize)
539     }
540 
541     let size = _IOC_SIZE(cmd);
542     match cmd {
543         bindings::ASHMEM_GET_NAME => {
544             let file_ptr = file.as_ptr();
545             // SAFETY: It's safe to access a file's dentry.
546             let dentry = unsafe { (*file_ptr).f_path.dentry };
547             // SAFETY: memfd stores the supplied name at this location. A default value is stored
548             // when no name is supplied, so this is always a valid string.
549             let full_name = unsafe {
550                 core::slice::from_raw_parts(
551                     (*dentry).d_name.name,
552                     (*dentry).d_name.__bindgen_anon_1.__bindgen_anon_1.len as usize,
553                 )
554             };
555 
556             let name = full_name.strip_prefix(b"memfd:").unwrap_or(full_name);
557             let max = usize::min(name.len(), ASHMEM_NAME_LEN);
558 
559             let mut local_name = [0u8; ASHMEM_NAME_LEN];
560             local_name[..max].copy_from_slice(&name[..max]);
561             local_name[ASHMEM_NAME_LEN - 1] = 0;
562 
563             let mut writer = UserSlice::new(arg, size).writer();
564             writer.write_slice(&local_name)?;
565             Ok(0)
566         }
567         bindings::ASHMEM_GET_SIZE => {
568             let file_ptr = file.as_ptr();
569             // SAFETY: It's safe to access a file's inode.
570             let inode = unsafe { (*file_ptr).f_inode };
571             // SAFETY: It's safe to read the size of an inode.
572             let size = unsafe { bindings::i_size_read(inode) };
573             Ok(size as isize)
574         }
575         bindings::ASHMEM_SET_PROT_MASK => {
576             // SAFETY: This is a memfd file.
577             let seals = unsafe { get_seals(file) }?;
578             let mut prot = arg;
579 
580             // The memfd compat layer does not support unsetting these.
581             prot |= PROT_READ | PROT_EXEC;
582 
583             let is_writable = seals & WRITE_SEALS_MASK == 0;
584             let should_be_writable = prot & PROT_WRITE != 0;
585 
586             if !is_writable && should_be_writable {
587                 // Can't add PROT bits.
588                 return Err(EINVAL);
589             }
590 
591             if is_writable && !should_be_writable {
592                 // SAFETY: This is a memfd file.
593                 let ret = unsafe {
594                     bindings::memfd_fcntl(file.as_ptr(), F_ADD_SEALS, F_SEAL_FUTURE_WRITE)
595                 };
596                 if ret < 0 {
597                     return Err(Error::from_errno(ret as i32));
598                 }
599             }
600             Ok(0)
601         }
602         bindings::ASHMEM_GET_PROT_MASK => {
603             // SAFETY: This is a memfd file.
604             let seals = unsafe { get_seals(file) }?;
605 
606             let mut prot = PROT_READ | PROT_EXEC;
607             if seals & WRITE_SEALS_MASK == 0 {
608                 prot |= PROT_WRITE;
609             }
610             Ok(prot as isize)
611         }
612         bindings::ASHMEM_GET_FILE_ID => {
613             // SAFETY: Accessing the ino is always okay.
614             let ino = unsafe { (*(*file.as_ptr()).f_inode).i_ino as usize };
615 
616             let mut writer = UserSlice::new(arg, size).writer();
617             writer.write(&ino)?;
618             Ok(0)
619         }
620         // Just ignore unpin requests.
621         ASHMEM_PIN => Ok(bindings::ASHMEM_NOT_PURGED as isize),
622         ASHMEM_UNPIN => Ok(0),
623         ASHMEM_GET_PIN_STATUS => Ok(bindings::ASHMEM_IS_PINNED as isize),
624         bindings::ASHMEM_PURGE_ALL_CACHES => {
625             if !has_cap_sys_admin() {
626                 return Err(EPERM);
627             }
628             Ok(0)
629         }
630         // We do not need to implement SET_NAME or SET_SIZE. The ioctls in this function are only
631         // called when you:
632         //
633         // 1. Think you have an ashmem fd.
634         // 2. But actually have a memfd fd.
635         //
636         // This can only happen if you created the fd through the libcutils library, and that
637         // library sets the name and size in the fd constructor where it knows whether ashmem or
638         // memfd is used, so we should never end up here.
639         bindings::ASHMEM_SET_NAME => Err(EINVAL),
640         bindings::ASHMEM_SET_SIZE => Err(EINVAL),
641         _ => Err(EINVAL),
642     }
643 }
644 
645 /// # Safety
646 ///
647 /// The caller must ensure that `file` is valid for the duration of this function.
648 #[no_mangle]
is_ashmem_file(file: *mut bindings::file) -> bool649 unsafe extern "C" fn is_ashmem_file(file: *mut bindings::file) -> bool {
650     let ashmem_fops_ptr = ASHMEM_FOPS_PTR.load(Ordering::Relaxed);
651     if file.is_null() || ashmem_fops_ptr.is_null() {
652         return false;
653     }
654 
655     // SAFETY: Accessing the f_op field of a non-NULL file structure is always okay.
656     let fops_ptr = unsafe { (*file).f_op };
657     fops_ptr == ashmem_fops_ptr
658 }
659 
660 /// # Safety
661 ///
662 /// The caller must ensure that `file` references a valid file for the duration of 'a.
get_ashmem_area<'a>(file: *mut bindings::file) -> Result<&'a Ashmem, Error>663 unsafe fn get_ashmem_area<'a>(file: *mut bindings::file) -> Result<&'a Ashmem, Error> {
664     // SAFETY: Caller ensures that file is valid, so this should be safe.
665     if unsafe { is_ashmem_file(file) } {
666         return Err(EINVAL);
667     }
668 
669     // SAFETY: Given that this is an ashmem file, it should be safe to access the private_data
670     // field containing the Ashmem struct.
671     let private = unsafe { (*file).private_data };
672     // SAFETY: Since this is an ashmem file, we know the type of the struct and can reference it
673     // safely.
674     let ashmem = unsafe { <<Ashmem as MiscDevice>::Ptr as ForeignOwnable>::borrow(private) };
675     Ok(ashmem.get_ref())
676 }
677 
678 /// # Safety
679 ///
680 /// The caller must ensure the following prior to invoking this function:
681 /// 1. `name` is valid for writing and at least of size ASHMEM_FULL_NAME_LEN.
682 /// 2. `file` is valid for the duration of this function.
683 #[no_mangle]
ashmem_area_name( file: *mut bindings::file, name: *mut kernel::ffi::c_char, ) -> c_int684 unsafe extern "C" fn ashmem_area_name(
685     file: *mut bindings::file,
686     name: *mut kernel::ffi::c_char,
687 ) -> c_int {
688     if name.is_null() {
689         return EINVAL.to_errno() as c_int;
690     }
691 
692     // SAFETY: file is valid for the duration of this function.
693     match unsafe { get_ashmem_area(file) } {
694         Ok(ashmem) => {
695             let name_buffer = name.cast::<[u8; ASHMEM_FULL_NAME_LEN]>();
696             // SAFETY: Caller guarantees that the pointer is valid for writing.
697             ashmem.inner.lock().full_name(unsafe { &mut *name_buffer });
698             0
699         }
700         Err(err) => err.to_errno() as c_int,
701     }
702 }
703 
704 /// # Safety
705 ///
706 /// The caller must ensure that `file` is valid for the duration of this function.
707 #[no_mangle]
ashmem_area_size(file: *mut bindings::file) -> isize708 unsafe extern "C" fn ashmem_area_size(file: *mut bindings::file) -> isize {
709     // SAFETY: file is valid for the duration of this function.
710     let ashmem = match unsafe { get_ashmem_area(file) } {
711         Ok(area) => area,
712         Err(_err) => return 0,
713     };
714 
715     match ashmem.get_size() {
716         Ok(size) => size,
717         Err(_err) => 0,
718     }
719 }
720 
721 /// # Safety
722 ///
723 /// The caller must ensure that `file` is valid for the duration of this function.
724 ///
725 /// If this function returns a non-NULL pointer to a file structure, the refcount for that
726 /// file will be incremented by 1. It is the caller's responsibility to decrement the refcount
727 /// when the file is no longer needed.
728 #[no_mangle]
ashmem_area_vmfile(file: *mut bindings::file) -> *mut bindings::file729 unsafe extern "C" fn ashmem_area_vmfile(file: *mut bindings::file) -> *mut bindings::file {
730     // SAFETY: file is valid for the duration of this function.
731     let ashmem = match unsafe { get_ashmem_area(file) } {
732         Ok(area) => area,
733         Err(_err) => return null_mut(),
734     };
735 
736     let asma = &mut *ashmem.inner.lock();
737     match asma.file.as_ref() {
738         Some(shmem_file) => {
739             let shmem_file_ptr = shmem_file.file().as_ptr();
740             // SAFETY: file is valid for the duration of the function, which means shmem file is
741             // also valid at this point.
742             unsafe { bindings::get_file(shmem_file_ptr) };
743             shmem_file_ptr
744         }
745         None => null_mut(),
746     }
747 }
748