Skip to main content

starnix_core/vfs/
memory_regular.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::fs::tmpfs::TmpFs;
6use crate::mm::memory::MemoryObject;
7use crate::mm::{PAGE_SIZE, ProtectionFlags, VMEX_RESOURCE};
8use crate::security;
9use crate::signals::{SignalInfo, send_standard_signal};
10use crate::task::CurrentTask;
11use crate::vfs::buffers::{InputBuffer, OutputBuffer};
12use crate::vfs::{
13    AppendLockWriteGuard, DirEntry, FallocMode, FileHandle, FileObject, FileOps, FileSystemHandle,
14    FsNode, FsNodeInfo, FsNodeLinkBehavior, FsNodeOps, FsString, MAX_LFS_FILESIZE,
15    MemoryXattrStorage, Mount, MountInfo, NamespaceNode, WhatToMount, XattrStorage as _,
16    fileops_impl_noop_sync, fs_node_impl_not_dir, fs_node_impl_xattr_delegate,
17};
18use linux_uapi::{ASHMEM_GET_SIZE, ASHMEM_SET_SIZE};
19use starnix_logging::{impossible_error, track_stub};
20use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
21use starnix_types::math::round_up_to_system_page_size;
22use starnix_uapi::errors::{EFBIG, Errno};
23use starnix_uapi::file_mode::{AccessCheck, mode};
24use starnix_uapi::open_flags::OpenFlags;
25use starnix_uapi::resource_limits::Resource;
26use starnix_uapi::seal_flags::SealFlags;
27use starnix_uapi::signals::SIGXFSZ;
28use starnix_uapi::{errno, error};
29use std::sync::Arc;
30
31pub struct MemoryRegularNode {
32    /// The memory that backs this file.
33    memory: Arc<MemoryObject>,
34    xattrs: MemoryXattrStorage,
35}
36
37impl MemoryRegularNode {
38    /// Create a new writable file node based on a blank VMO.
39    pub fn new() -> Result<Self, Errno> {
40        let vmo =
41            zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, 0).map_err(|_| errno!(ENOMEM))?;
42        Ok(Self {
43            memory: Arc::new(MemoryObject::from(vmo).with_zx_name(b"starnix:vfs")),
44            xattrs: MemoryXattrStorage::default(),
45        })
46    }
47
48    /// Create a new file node based on an existing VMO.
49    /// Attempts to open the file for writing will fail unless [`memory`] has both
50    /// the `WRITE` and `RESIZE` rights.
51    pub fn from_memory(memory: Arc<MemoryObject>) -> Self {
52        Self { memory, xattrs: MemoryXattrStorage::default() }
53    }
54}
55
56impl FsNodeOps for MemoryRegularNode {
57    fs_node_impl_not_dir!();
58    fs_node_impl_xattr_delegate!(self, self.xattrs);
59
60    fn initial_info(&self, info: &mut FsNodeInfo) {
61        info.size = self.memory.get_content_size() as usize;
62    }
63
64    fn create_file_ops(
65        &self,
66        node: &FsNode,
67        _current_task: &CurrentTask,
68        flags: OpenFlags,
69    ) -> Result<Box<dyn FileOps>, Errno> {
70        if flags.contains(OpenFlags::TRUNC) {
71            // Truncating to zero length must pass the shrink seal check.
72            node.write_guard_state.lock().check_no_seal(SealFlags::SHRINK)?;
73        }
74
75        // Produce a VMO handle with rights reduced to those requested in |flags|.
76        let mut desired_rights = zx::Rights::VMO_DEFAULT | zx::Rights::RESIZE;
77        if !flags.can_read() {
78            desired_rights.remove(zx::Rights::READ);
79        }
80        if !flags.can_write() {
81            desired_rights.remove(zx::Rights::WRITE | zx::Rights::RESIZE);
82        }
83        let scoped_memory =
84            Arc::new(self.memory.duplicate_handle(desired_rights).map_err(|_e| errno!(EIO))?);
85        let file_object = MemoryRegularFile::new(scoped_memory);
86
87        Ok(Box::new(file_object))
88    }
89
90    fn truncate(
91        &self,
92        _guard: &AppendLockWriteGuard<'_>,
93        node: &FsNode,
94        _current_task: &CurrentTask,
95        length: u64,
96    ) -> Result<(), Errno> {
97        let length = length as usize;
98
99        node.update_info(|info| {
100            if info.size == length {
101                // The file size remains unaffected.
102                return Ok(());
103            }
104
105            // We must hold the lock till the end of the operation to guarantee that
106            // there is no change to the seals.
107            let state = node.write_guard_state.lock();
108
109            if info.size > length {
110                // A decrease in file size must pass the shrink seal check.
111                state.check_no_seal(SealFlags::SHRINK)?;
112            } else {
113                // An increase in file size must pass the grow seal check.
114                state.check_no_seal(SealFlags::GROW)?;
115            }
116
117            let memory_size = update_memory_file_size(&self.memory, info, length)?;
118            info.size = length;
119
120            // Zero unused parts of the VMO.
121            if memory_size > length {
122                self.memory
123                    .op_range(zx::VmoOp::ZERO, length as u64, (memory_size - length) as u64)
124                    .map_err(impossible_error)?;
125            }
126
127            Ok(())
128        })
129    }
130
131    fn allocate(
132        &self,
133        _guard: &AppendLockWriteGuard<'_>,
134        node: &FsNode,
135        _current_task: &CurrentTask,
136        mode: FallocMode,
137        offset: u64,
138        length: u64,
139    ) -> Result<(), Errno> {
140        match mode {
141            FallocMode::PunchHole => {
142                // Lock `info()` before acquiring the `write_guard_state` lock to ensure consistent
143                // lock ordering.
144                let info = node.info();
145
146                // Check write seal. Hold the lock to ensure seals don't change.
147                let state = node.write_guard_state.lock();
148                state.check_no_seal(SealFlags::WRITE | SealFlags::FUTURE_WRITE)?;
149
150                let mut end = offset.checked_add(length).ok_or_else(|| errno!(EINVAL))? as usize;
151
152                let memory_size = info.blksize * info.blocks;
153                if offset as usize >= memory_size {
154                    return Ok(());
155                }
156
157                // If punching hole at the end of the file then zero all the
158                // way to the end of the VMO to avoid keeping any pages for the tail.
159                if end >= info.size {
160                    end = memory_size;
161                }
162
163                self.memory
164                    .op_range(zx::VmoOp::ZERO, offset, end as u64 - offset)
165                    .map_err(impossible_error)?;
166
167                Ok(())
168            }
169
170            FallocMode::Allocate { keep_size } => {
171                node.update_info(|info| {
172                    let new_size = (offset + length) as usize;
173                    if new_size > info.size {
174                        // Check GROW seal (even with `keep_size=true`). Hold the lock to ensure
175                        // seals don't change.
176                        let state = node.write_guard_state.lock();
177                        state.check_no_seal(SealFlags::GROW)?;
178
179                        update_memory_file_size(&self.memory, info, new_size)?;
180
181                        if !keep_size {
182                            info.size = new_size;
183                        }
184                    }
185                    Ok(())
186                })
187            }
188
189            _ => error!(EOPNOTSUPP),
190        }
191    }
192}
193
194pub struct MemoryRegularFile {
195    pub memory: Arc<MemoryObject>,
196}
197
198impl MemoryRegularFile {
199    /// Create a file object based on a VMO.
200    pub fn new(memory: Arc<MemoryObject>) -> Self {
201        MemoryRegularFile { memory }
202    }
203}
204
205impl MemoryRegularFile {
206    pub fn read(
207        memory: &Arc<MemoryObject>,
208        file: &FileObject,
209        offset: usize,
210        data: &mut dyn OutputBuffer,
211    ) -> Result<usize, Errno> {
212        let actual = {
213            let info = file.node().info();
214            let file_length = info.size;
215            let want_read = data.available();
216            if offset < file_length {
217                let to_read =
218                    if file_length < offset + want_read { file_length - offset } else { want_read };
219                let buf =
220                    memory.read_to_vec(offset as u64, to_read as u64).map_err(|_| errno!(EIO))?;
221                drop(info);
222                data.write_all(&buf[..])?;
223                to_read
224            } else {
225                0
226            }
227        };
228        Ok(actual)
229    }
230
231    pub fn write(
232        memory: &Arc<MemoryObject>,
233        file: &FileObject,
234        current_task: &CurrentTask,
235        offset: usize,
236        data: &mut dyn InputBuffer,
237    ) -> Result<usize, Errno> {
238        let mut want_write = data.available();
239        let buf = data.peek_all()?;
240
241        let result = file.node().update_info(|info| {
242            let mut write_end = offset + want_write;
243            let mut update_content_size = false;
244
245            // We must hold the lock till the end of the operation to guarantee that
246            // there is no change to the seals.
247            let state = file.name.entry.node.write_guard_state.lock();
248
249            // Non-zero writes must pass the write seal check.
250            if want_write != 0 {
251                state.check_no_seal(SealFlags::WRITE | SealFlags::FUTURE_WRITE)?;
252            }
253
254            // Writing past the file size
255            if write_end > info.size {
256                // The grow seal check failed.
257                if let Err(e) = state.check_no_seal(SealFlags::GROW) {
258                    if offset >= info.size {
259                        // Write starts outside the file.
260                        // Forbid because nothing can be written without growing.
261                        return Err(e);
262                    } else if info.size == info.storage_size() {
263                        // Write starts inside file and EOF page does not need to grow.
264                        // End write at EOF.
265                        write_end = info.size;
266                        want_write = write_end - offset;
267                    } else {
268                        // Write starts inside file and EOF page needs to grow.
269                        let eof_page_start = info.storage_size() - (*PAGE_SIZE as usize);
270
271                        if offset >= eof_page_start {
272                            // Write starts in EOF page.
273                            // Forbid because EOF page cannot grow.
274                            return Err(e);
275                        }
276
277                        // End write at page before EOF.
278                        write_end = eof_page_start;
279                        want_write = write_end - offset;
280                    }
281                }
282            }
283
284            // Check against the FSIZE limit
285            let fsize_limit = current_task.thread_group().get_rlimit(Resource::FSIZE) as usize;
286            if write_end > fsize_limit {
287                if offset >= fsize_limit {
288                    // Write starts beyond the FSIZE limit.
289                    return error!(EFBIG);
290                }
291
292                // End write at FSIZE limit.
293                write_end = fsize_limit;
294                want_write = write_end - offset;
295            }
296
297            if write_end > info.size {
298                if write_end > info.storage_size() {
299                    update_memory_file_size(memory, info, write_end)?;
300                }
301                update_content_size = true;
302            }
303            memory.write(&buf[..want_write], offset as u64).map_err(|_| errno!(EIO))?;
304
305            if update_content_size {
306                info.size = write_end;
307            }
308            data.advance(want_write)?;
309            Ok(want_write)
310        });
311        if let Err(ref e) = result
312            && *e == EFBIG
313        {
314            // EFBIG must trigger a signal. Sending the signal must be done outside of the
315            // update_info method to ensure no deadlock.
316            send_standard_signal(current_task, SignalInfo::kernel(SIGXFSZ));
317        }
318        result
319    }
320
321    pub fn get_memory(
322        memory: &Arc<MemoryObject>,
323        file: &FileObject,
324        _current_task: &CurrentTask,
325        prot: ProtectionFlags,
326    ) -> Result<Arc<MemoryObject>, Errno> {
327        // In MemoryFileNode::create_file_ops, we downscoped the rights
328        // on the VMO to match the rights on the file object. If the caller
329        // wants more rights than exist on the file object, return an error
330        // instead of returning a MemoryObject that does not conform to
331        // the FileOps::get_memory contract.
332        if prot.contains(ProtectionFlags::READ) && !file.can_read() {
333            return error!(EACCES);
334        }
335        if prot.contains(ProtectionFlags::WRITE) && !file.can_write() {
336            return error!(EACCES);
337        }
338        let mut memory = Arc::clone(memory);
339        if prot.contains(ProtectionFlags::EXEC) {
340            memory = Arc::new(
341                memory
342                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
343                    .map_err(impossible_error)?
344                    .replace_as_executable(&VMEX_RESOURCE)
345                    .map_err(impossible_error)?,
346            );
347        }
348        Ok(memory)
349    }
350}
351
352#[macro_export]
353macro_rules! fileops_impl_memory {
354    ($self:ident, $memory:expr) => {
355        $crate::fileops_impl_seekable!();
356
357        fn read(
358            &$self,
359            file: &$crate::vfs::FileObject,
360            _current_task: &$crate::task::CurrentTask,
361            offset: usize,
362            data: &mut dyn $crate::vfs::buffers::OutputBuffer,
363        ) -> Result<usize, starnix_uapi::errors::Errno> {
364            $crate::vfs::MemoryRegularFile::read($memory, file, offset, data)
365        }
366
367        fn write(
368            &$self,
369            file: &$crate::vfs::FileObject,
370            current_task: &$crate::task::CurrentTask,
371            offset: usize,
372            data: &mut dyn $crate::vfs::buffers::InputBuffer,
373        ) -> Result<usize, starnix_uapi::errors::Errno> {
374            $crate::vfs::MemoryRegularFile::write($memory, file, current_task, offset, data)
375        }
376
377        fn get_memory(
378            &$self,
379            file: &$crate::vfs::FileObject,
380            current_task: &$crate::task::CurrentTask,
381            _length: Option<usize>,
382            prot: $crate::mm::ProtectionFlags,
383        ) -> Result<Arc<$crate::mm::memory::MemoryObject>, starnix_uapi::errors::Errno> {
384            $crate::vfs::MemoryRegularFile::get_memory($memory, file, current_task, prot)
385        }
386    }
387}
388pub use fileops_impl_memory;
389
390impl FileOps for MemoryRegularFile {
391    fileops_impl_memory!(self, &self.memory);
392    fileops_impl_noop_sync!();
393
394    fn readahead(
395        &self,
396        _file: &FileObject,
397        _current_task: &CurrentTask,
398        _offset: usize,
399        _length: usize,
400    ) -> Result<(), Errno> {
401        track_stub!(TODO("https://fxbug.dev/42082608"), "paged VMO readahead");
402        Ok(())
403    }
404
405    fn ioctl(
406        &self,
407        _file: &FileObject,
408        _current_task: &CurrentTask,
409        request: u32,
410        arg: SyscallArg,
411    ) -> Result<SyscallResult, Errno> {
412        match request {
413            ASHMEM_GET_SIZE => {
414                track_stub!(TODO("https://fxbug.dev/389102161"), "ashmem get_size on memfd");
415                Ok(self.memory.get_size().into())
416            }
417            ASHMEM_SET_SIZE => {
418                track_stub!(TODO("https://fxbug.dev/389102161"), "ashmem set_size on memfd");
419                self.memory.set_size(arg.into()).map_err(|_| errno!(EINVAL))?;
420                Ok(SUCCESS)
421            }
422            _ => error!(ENOTTY),
423        }
424    }
425}
426
427pub fn new_memfd(
428    current_task: &CurrentTask,
429    mut name: FsString,
430    seals: SealFlags,
431    flags: OpenFlags,
432) -> Result<FileHandle, Errno> {
433    struct MemFdTmpfs {
434        tmpfs: FileSystemHandle,
435        mount: Arc<Mount>,
436    }
437
438    let fs = current_task.kernel().expando.get_or_init(|| {
439        let tmpfs = TmpFs::new_fs(current_task.kernel());
440        security::file_system_resolve_security(&current_task, &tmpfs).expect("resolve fs security");
441        let mounts_guard = current_task.kernel().mounts_lock();
442        let mount = Mount::new(&mounts_guard, WhatToMount::Fs(tmpfs.clone()), Default::default())
443            .expect("create new tempfs mount for memfd");
444        MemFdTmpfs { tmpfs, mount }
445    });
446
447    // Create the node as a kernel-internal operation, to skip the filesystem access-checks.
448    // TODO: https://fxbug.dev/455785957 - Validate whether any access-checks should be performed
449    // during "memfd" creation.
450    let fs_node = current_task.override_creds(
451        security::creds_start_internal_operation(current_task),
452        || {
453            fs.tmpfs.root().node.create_tmpfile(
454                current_task,
455                &MountInfo::detached(),
456                mode!(IFREG, 0o600),
457                current_task.current_fscred(),
458                FsNodeLinkBehavior::Disallowed,
459            )
460        },
461    )?;
462
463    // LSM allows security modules to choose to treat mem-FDs as a kind of anonymous inode.
464    security::fs_node_init_anon(current_task, &fs_node, "[memfd]")?;
465
466    fs_node.write_guard_state.lock().enable_sealing(seals);
467
468    // memfd instances appear in /proc[pid]/fd as though they are O_TMPFILE files with names of
469    // the form "memfd:[name]".
470    let mut local_name = FsString::from("memfd:");
471    local_name.append(&mut name);
472    let dir_entry = DirEntry::new_deleted(fs_node, Some(fs.tmpfs.root().clone()), local_name);
473    security::fs_node_init_with_dentry(current_task, &dir_entry)?;
474
475    let name = NamespaceNode::new(fs.mount.clone(), dir_entry);
476    name.open(current_task, flags, AccessCheck::skip())
477}
478
479/// Sets memory size to `min_size` rounded to whole pages. Returns the new size of the VMO in bytes.
480fn update_memory_file_size(
481    memory: &MemoryObject,
482    node_info: &mut FsNodeInfo,
483    requested_size: usize,
484) -> Result<usize, Errno> {
485    assert!(requested_size <= MAX_LFS_FILESIZE);
486    let size = round_up_to_system_page_size(requested_size)?;
487    memory.set_size(size as u64).map_err(|status| match status {
488        zx::Status::NO_MEMORY => errno!(ENOMEM),
489        zx::Status::OUT_OF_RANGE => errno!(ENOMEM),
490        _ => impossible_error(status),
491    })?;
492    node_info.blocks = size / node_info.blksize;
493    Ok(size)
494}