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