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