• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0
2 
3 // Copyright (C) 2024 Google LLC.
4 
5 //! Safe rust abstraction around a shmem file for use by ashmem.
6 
7 use kernel::{
8     bindings,
9     error::{from_err_ptr, to_result, Result},
10     ffi::{c_int, c_ulong},
11     fs::file::{File, LocalFile},
12     miscdevice::{loff_t, IovIter},
13     mm::virt::{vm_flags_t, VmaNew},
14     prelude::*,
15     str::CStr,
16     types::ARef,
17 };
18 
19 use core::{
20     cell::UnsafeCell,
21     ptr::{addr_of_mut, NonNull},
22 };
23 
24 /// # Safety
25 ///
26 /// Caller must ensure that access to the file position is properly synchronized.
file_get_fpos(file: &LocalFile) -> loff_t27 pub(crate) unsafe fn file_get_fpos(file: &LocalFile) -> loff_t {
28     // SAFETY: Caller ensures that this is okay.
29     unsafe { (*file.as_ptr()).f_pos }
30 }
31 
32 /// # Safety
33 ///
34 /// Caller must ensure that access to the file position is properly synchronized.
file_set_fpos(file: &LocalFile, pos: loff_t)35 pub(crate) unsafe fn file_set_fpos(file: &LocalFile, pos: loff_t) {
36     // SAFETY: Caller ensures that this is okay.
37     unsafe { (*file.as_ptr()).f_pos = pos };
38 }
39 
vma_set_anonymous(vma: &VmaNew)40 pub(crate) fn vma_set_anonymous(vma: &VmaNew) {
41     // SAFETY: The `VmaNew` type is only used when the vma is being set up, so this operation is
42     // safe.
43     unsafe { (*vma.as_ptr()).vm_ops = core::ptr::null_mut() };
44 }
45 
46 /// Wrapper around a file that is known to be a shmem file.
47 #[derive(Clone)]
48 pub(crate) struct ShmemFile {
49     inner: ARef<File>,
50 }
51 
52 impl ShmemFile {
53     /// Create a shmem file for use by ashmem.
54     ///
55     /// This sets up the file with the exact configuration that ashmem needs.
new(name: &CStr, size: usize, flags: vm_flags_t) -> Result<Self>56     pub(crate) fn new(name: &CStr, size: usize, flags: vm_flags_t) -> Result<Self> {
57         // SAFETY: The name is a nul-terminated string.
58         let vmfile = from_err_ptr(unsafe {
59             bindings::shmem_file_setup(name.as_char_ptr(), size as _, flags)
60         })?;
61 
62         // SAFETY: The call to `shmem_file_setup` was successful, so `vmfile` is a valid pointer to
63         // a file and we can transfer ownership of the refcount it created to an `ARef<File>`.
64         let vmfile = unsafe { ARef::<File>::from_raw(NonNull::new_unchecked(vmfile.cast())) };
65 
66         // The C driver sets the FMODE_LSEEK bit in `f_mode` here. However, that is not necessary
67         // anymore. It was added to the C driver in commit 97fbfef6bd59 ("staging: android: ashmem:
68         // lseek failed due to no FMODE_LSEEK.") since they started using the VFS implementation of
69         // lseek rather than a custom hook, and the VFS version actually checks the permissions.
70         //
71         // However, commit e7478158e137 ("fs: clear or set FMODE_LSEEK based on llseek function")
72         // has since made it so that if lseek is implemented, then FMODE_LSEEK will be set on
73         // pseudo-files by default. Since llseek is implemented on shmem files, we no longer need
74         // to set FMODE_LSEEK.
75 
76         set_inode_lockdep_class(&vmfile);
77 
78         // SAFETY: We just created the file and have not yet published it, so nobody else is
79         // looking at this field yet.
80         unsafe { (*vmfile.as_ptr()).f_op = get_shmem_fops((*vmfile.as_ptr()).f_op) };
81 
82         Ok(Self { inner: vmfile })
83     }
84 
file(&self) -> &File85     pub(crate) fn file(&self) -> &File {
86         &self.inner
87     }
88 
vfs_llseek(&self, offset: loff_t, whence: c_int) -> Result<loff_t>89     pub(crate) fn vfs_llseek(&self, offset: loff_t, whence: c_int) -> Result<loff_t> {
90         // SAFETY: Just an FFI call. The file is valid.
91         let ret = unsafe { bindings::vfs_llseek(self.inner.as_ptr(), offset, whence) };
92 
93         if ret < 0 {
94             Err(Error::from_errno(ret as i32))
95         } else {
96             Ok(ret)
97         }
98     }
99 
vfs_iter_read(&self, iov: &mut IovIter, pos: &mut loff_t) -> Result<loff_t>100     pub(crate) fn vfs_iter_read(&self, iov: &mut IovIter, pos: &mut loff_t) -> Result<loff_t> {
101         // SAFETY: Just an FFI call. The file and iov is valid.
102         let ret = unsafe { bindings::vfs_iter_read(self.inner.as_ptr(), iov.as_raw(), pos, 0) };
103 
104         if ret < 0 {
105             Err(Error::from_errno(ret as i32))
106         } else {
107             Ok(ret as loff_t)
108         }
109     }
110 
punch_hole(&self, start: usize, len: usize)111     pub(crate) fn punch_hole(&self, start: usize, len: usize) {
112         use kernel::bindings::{FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE};
113 
114         let f = self.inner.as_ptr();
115         // SAFETY: f_op of a file is immutable, so okay to read.
116         let fallocate = unsafe { (*(*f).f_op).fallocate };
117 
118         if let Some(fallocate) = fallocate {
119             unsafe {
120                 fallocate(
121                     f,
122                     (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE) as _,
123                     start as _,
124                     len as _,
125                 )
126             };
127         }
128     }
129 
inode_ino(&self) -> usize130     pub(crate) fn inode_ino(&self) -> usize {
131         // SAFETY: Accessing the ino is always okay.
132         unsafe { (*(*self.inner.as_ptr()).f_inode).i_ino as usize }
133     }
134 }
135 
136 /// Fix the lockdep class of the shmem inode.
137 ///
138 /// A separate lockdep class for the backing shmem inodes to resolve the lockdep warning about the
139 /// race between kswapd taking fs_reclaim before inode_lock and write syscall taking inode_lock and
140 /// then fs_reclaim. Note that such race is impossible because ashmem does not support write
141 /// syscalls operating on the backing shmem.
set_inode_lockdep_class(vmfile: &File)142 fn set_inode_lockdep_class(vmfile: &File) {
143     // SAFETY: This sets the lockdep class correctly.
144     unsafe {
145         let inode = (*vmfile.as_ptr()).f_inode;
146         let lock = addr_of_mut!((*inode).i_rwsem);
147         bindings::lockdep_set_class_rwsem(
148             lock,
149             kernel::static_lock_class!().as_ptr(),
150             kernel::c_str!("backing_shmem_inode_class").as_char_ptr(),
151         )
152     }
153 }
154 
zero_setup(vma: &VmaNew) -> Result<()>155 pub(crate) fn zero_setup(vma: &VmaNew) -> Result<()> {
156     // SAFETY: The `VmaNew` type is only used when the vma is being set up, so we can set up the
157     // vma.
158     to_result(unsafe { bindings::shmem_zero_setup(vma.as_ptr()) })
159 }
160 
set_file(vma: &VmaNew, file: &File)161 pub(crate) fn set_file(vma: &VmaNew, file: &File) {
162     let file = ARef::from(file);
163     // SAFETY: We're setting up the vma, so we can read the file pointer.
164     let old_file = unsafe { (*vma.as_ptr()).vm_file };
165 
166     // INVARIANT: This transfers ownership of the refcount we just created to the vma.
167     //
168     // SAFETY: We're setting up the vma, so we can write to the file pointer.
169     unsafe { (*vma.as_ptr()).vm_file = ARef::into_raw(file).as_ptr().cast() };
170 
171     if let Some(old_file) = NonNull::new(old_file) {
172         // SAFETY: We took ownership of the file refcount from the vma, so we can drop it.
173         drop(unsafe { ARef::<File>::from_raw(old_file.cast()) });
174     }
175 }
176 
177 // Used to synchronize the initialization of `VMFILE_FOPS`.
178 //
179 // INVARIANT: Once `SHMEM_FOPS_ONCE` becomes true, `VMFILE_FOPS` is permanently immutable.
180 kernel::sync::global_lock! {
181     // SAFETY: We call `init` as the very first thing in the initialization of this module, so
182     // there are no calls to `lock` before `init` is called.
183     pub(super) unsafe(uninit) static SHMEM_FOPS_ONCE: Mutex<bool> = false;
184 }
185 
186 /// # Safety
187 ///
188 /// Must only be used with the fops of a shmem file.
get_shmem_fops( shmem_fops: *const bindings::file_operations, ) -> &'static bindings::file_operations189 unsafe fn get_shmem_fops(
190     shmem_fops: *const bindings::file_operations,
191 ) -> &'static bindings::file_operations {
192     struct FopsHelper {
193         inner: UnsafeCell<bindings::file_operations>,
194     }
195     unsafe impl Sync for FopsHelper {}
196 
197     static VMFILE_FOPS: FopsHelper = FopsHelper {
198         // SAFETY: All zeros is valid for `struct file_operations`.
199         inner: UnsafeCell::new(unsafe { core::mem::zeroed() }),
200     };
201 
202     let fops_ptr = VMFILE_FOPS.inner.get();
203 
204     let mut once_guard = SHMEM_FOPS_ONCE.lock();
205     if !*once_guard {
206         // SAFETY: This points at the file operations of an existing file, so the contents must be
207         // immutable.
208         let mut new_fops = unsafe { *shmem_fops };
209         new_fops.mmap = Some(ashmem_vmfile_mmap);
210         new_fops.get_unmapped_area = Some(ashmem_vmfile_get_unmapped_area);
211         // SAFETY: We hold the `SHMEM_FOPS_ONCE` guard, so there are no other writers. The value of
212         // `SHMEM_FOPS_ONCE` is false, so there are no readers either.
213         unsafe { *fops_ptr = new_fops };
214         *once_guard = true;
215     }
216     drop(once_guard);
217 
218     // SAFETY: The value of `SHMEM_FOPS_ONCE` is true, so `VMFILE_FOPS` is never going to change
219     // again.
220     unsafe { &*fops_ptr }
221 }
222 
ashmem_vmfile_mmap( _file: *mut bindings::file, _vma: *mut bindings::vm_area_struct, ) -> c_int223 extern "C" fn ashmem_vmfile_mmap(
224     _file: *mut bindings::file,
225     _vma: *mut bindings::vm_area_struct,
226 ) -> c_int {
227     EPERM.to_errno()
228 }
229 
ashmem_vmfile_get_unmapped_area( file: *mut bindings::file, addr: c_ulong, len: c_ulong, pgoff: c_ulong, flags: c_ulong, ) -> c_ulong230 unsafe extern "C" fn ashmem_vmfile_get_unmapped_area(
231     file: *mut bindings::file,
232     addr: c_ulong,
233     len: c_ulong,
234     pgoff: c_ulong,
235     flags: c_ulong,
236 ) -> c_ulong {
237     // SAFETY: The `mm` of current does not change, so it is safe to access.
238     let mm = unsafe { (*bindings::get_current()).mm };
239     // SAFETY: This calls the right get_unmapped_area for a shmem.
240     unsafe { bindings::mm_get_unmapped_area(mm, file, addr, len, pgoff, flags) }
241 }
242