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    Anon, AppendLockWriteGuard, DirEntry, FallocMode, FileHandle, FileObject, FileOps,
14    FileSystemHandle, FsNode, FsNodeFlags, FsNodeInfo, FsNodeLinkBehavior, FsNodeOps, FsString,
15    MAX_LFS_FILESIZE, MemoryXattrStorage, Mount, MountInfo, NamespaceNode, OpenAccessCheck,
16    WhatToMount, XattrStorage as _, anon_fs, fileops_impl_noop_sync, fs_node_impl_not_dir,
17    fs_node_impl_xattr_delegate,
18};
19use linux_uapi::{ASHMEM_GET_SIZE, ASHMEM_SET_SIZE};
20use once_cell::sync::OnceCell;
21use starnix_logging::{impossible_error, track_stub};
22use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
23use starnix_types::math::round_up_to_system_page_size;
24use starnix_uapi::errors::{EFBIG, Errno};
25use starnix_uapi::file_mode::mode;
26use starnix_uapi::open_flags::OpenFlags;
27use starnix_uapi::resource_limits::Resource;
28use starnix_uapi::seal_flags::SealFlags;
29use starnix_uapi::signals::SIGXFSZ;
30use starnix_uapi::{errno, error};
31use std::sync::Arc;
32
33pub struct MemoryRegularNode {
34    /// The memory that backs this file.
35    memory: Arc<MemoryObject>,
36    /// Cached read-only VMO handle (without WRITE or RESIZE rights).
37    read_only_memory: OnceCell<Arc<MemoryObject>>,
38    /// Cached read/exec VMO handle (without WRITE or RESIZE rights, with EXECUTE right).
39    read_exec_memory: OnceCell<Arc<MemoryObject>>,
40    /// Cached read/write/exec VMO handle (with WRITE, RESIZE, and EXECUTE rights).
41    write_exec_memory: OnceCell<Arc<MemoryObject>>,
42    xattrs: MemoryXattrStorage,
43}
44
45impl MemoryRegularNode {
46    /// Create a new writable file node based on a blank VMO.
47    pub fn new() -> Result<Self, Errno> {
48        let vmo =
49            zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, 0).map_err(|_| errno!(ENOMEM))?;
50        Ok(Self {
51            memory: Arc::new(MemoryObject::from(vmo).with_zx_name(b"starnix:vfs")),
52            read_only_memory: OnceCell::new(),
53            read_exec_memory: OnceCell::new(),
54            write_exec_memory: OnceCell::new(),
55            xattrs: MemoryXattrStorage::default(),
56        })
57    }
58
59    /// Create a new file node based on an existing VMO.
60    /// Attempts to open the file for writing will fail unless [`memory`] has both
61    /// the `WRITE` and `RESIZE` rights.
62    pub fn from_memory(memory: Arc<MemoryObject>) -> Self {
63        Self {
64            memory,
65            read_only_memory: OnceCell::new(),
66            read_exec_memory: OnceCell::new(),
67            write_exec_memory: OnceCell::new(),
68            xattrs: MemoryXattrStorage::default(),
69        }
70    }
71
72    /// Returns the cached read-only handle (without WRITE or RESIZE rights) to the backing
73    /// memory, shared by all read-only opens of this node.
74    fn read_only_memory(&self) -> Result<&Arc<MemoryObject>, Errno> {
75        self.read_only_memory.get_or_try_init(|| {
76            self.memory
77                .duplicate_handle(zx::Rights::VMO_DEFAULT - zx::Rights::WRITE)
78                .map(Arc::new)
79                .map_err(|_e| errno!(EIO))
80        })
81    }
82
83    /// Returns the cached executable handle to the backing memory. The handle carries exactly
84    /// the rights that `create_file_ops` grants to a readable open: `writable` selects between
85    /// the rights of an `O_RDWR` open (with WRITE and RESIZE) and an `O_RDONLY` open (without).
86    fn exec_memory(&self, writable: bool) -> Result<Arc<MemoryObject>, Errno> {
87        if writable {
88            self.write_exec_memory.get_or_try_init(|| {
89                self.memory
90                    .duplicate_handle(zx::Rights::VMO_DEFAULT | zx::Rights::RESIZE)
91                    .map_err(|_e| errno!(EIO))?
92                    .replace_as_executable(&VMEX_RESOURCE)
93                    .map(Arc::new)
94                    .map_err(impossible_error)
95            })
96        } else {
97            self.read_exec_memory.get_or_try_init(|| {
98                self.read_only_memory()?
99                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
100                    .and_then(|m| m.replace_as_executable(&VMEX_RESOURCE))
101                    .map(Arc::new)
102                    .map_err(impossible_error)
103            })
104        }
105        .cloned()
106    }
107}
108
109impl FsNodeOps for MemoryRegularNode {
110    fs_node_impl_not_dir!();
111    fs_node_impl_xattr_delegate!(self, self.xattrs);
112
113    fn initial_info(&self, info: &mut FsNodeInfo) {
114        info.size = self.memory.get_content_size() as usize;
115    }
116
117    fn create_file_ops(
118        &self,
119        node: &FsNode,
120        _current_task: &CurrentTask,
121        flags: OpenFlags,
122    ) -> Result<Box<dyn FileOps>, Errno> {
123        if flags.contains(OpenFlags::TRUNC) {
124            // Truncating to zero length must pass the shrink seal check.
125            node.write_guard_state.lock().check_no_seal(SealFlags::SHRINK)?;
126        }
127
128        // Produce a VMO handle with rights reduced to those requested in |flags|.
129        let scoped_memory = if flags.can_read() && !flags.can_write() {
130            self.read_only_memory()?.clone()
131        } else {
132            let mut desired_rights = zx::Rights::VMO_DEFAULT | zx::Rights::RESIZE;
133            if !flags.can_read() {
134                desired_rights.remove(zx::Rights::READ);
135            }
136            if !flags.can_write() {
137                desired_rights.remove(zx::Rights::WRITE | zx::Rights::RESIZE);
138            }
139            Arc::new(self.memory.duplicate_handle(desired_rights).map_err(|_e| errno!(EIO))?)
140        };
141        let file_object = MemoryRegularFile::new(scoped_memory);
142
143        Ok(Box::new(file_object))
144    }
145
146    fn truncate(
147        &self,
148        _guard: &AppendLockWriteGuard<'_>,
149        node: &FsNode,
150        _current_task: &CurrentTask,
151        length: u64,
152    ) -> Result<(), Errno> {
153        let length = length as usize;
154
155        node.update_info(|info| {
156            if info.size == length {
157                // The file size remains unaffected.
158                return Ok(());
159            }
160
161            // We must hold the lock till the end of the operation to guarantee that
162            // there is no change to the seals.
163            let state = node.write_guard_state.lock();
164
165            if info.size > length {
166                // A decrease in file size must pass the shrink seal check.
167                state.check_no_seal(SealFlags::SHRINK)?;
168            } else {
169                // An increase in file size must pass the grow seal check.
170                state.check_no_seal(SealFlags::GROW)?;
171            }
172
173            let memory_size = update_memory_file_size(&self.memory, info, length)?;
174            info.size = length;
175
176            // Zero unused parts of the VMO.
177            if memory_size > length {
178                self.memory
179                    .op_range(zx::VmoOp::ZERO, length as u64, (memory_size - length) as u64)
180                    .map_err(impossible_error)?;
181            }
182
183            Ok(())
184        })
185    }
186
187    fn allocate(
188        &self,
189        _guard: &AppendLockWriteGuard<'_>,
190        node: &FsNode,
191        _current_task: &CurrentTask,
192        mode: FallocMode,
193        offset: u64,
194        length: u64,
195    ) -> Result<(), Errno> {
196        match mode {
197            FallocMode::PunchHole => {
198                // Lock `info()` before acquiring the `write_guard_state` lock to ensure consistent
199                // lock ordering.
200                let info = node.info();
201
202                // Check write seal. Hold the lock to ensure seals don't change.
203                let state = node.write_guard_state.lock();
204                state.check_no_seal(SealFlags::WRITE | SealFlags::FUTURE_WRITE)?;
205
206                let mut end = offset.checked_add(length).ok_or_else(|| errno!(EINVAL))? as usize;
207
208                let memory_size = info.blksize * info.blocks;
209                if offset as usize >= memory_size {
210                    return Ok(());
211                }
212
213                // If punching hole at the end of the file then zero all the
214                // way to the end of the VMO to avoid keeping any pages for the tail.
215                if end >= info.size {
216                    end = memory_size;
217                }
218
219                self.memory
220                    .op_range(zx::VmoOp::ZERO, offset, end as u64 - offset)
221                    .map_err(impossible_error)?;
222
223                Ok(())
224            }
225
226            FallocMode::Allocate { keep_size } => {
227                node.update_info(|info| {
228                    let new_size = (offset + length) as usize;
229                    if new_size > info.size {
230                        // Check GROW seal (even with `keep_size=true`). Hold the lock to ensure
231                        // seals don't change.
232                        let state = node.write_guard_state.lock();
233                        state.check_no_seal(SealFlags::GROW)?;
234
235                        update_memory_file_size(&self.memory, info, new_size)?;
236
237                        if !keep_size {
238                            info.size = new_size;
239                        }
240                    }
241                    Ok(())
242                })
243            }
244
245            _ => error!(EOPNOTSUPP),
246        }
247    }
248}
249
250pub struct MemoryRegularFile {
251    pub memory: Arc<MemoryObject>,
252}
253
254impl MemoryRegularFile {
255    /// Creates a file object based on a VMO.
256    pub fn new(memory: Arc<MemoryObject>) -> Self {
257        MemoryRegularFile { memory }
258    }
259
260    /// Creates a new anonymous file backed by a private regular [`FsNode`] with the specified
261    /// `size`, to which no security labeling nor access-checks will be applied.
262    pub fn new_private_file_with_ops(
263        current_task: &CurrentTask,
264        ops: Box<dyn FileOps>,
265        size: usize,
266        flags: OpenFlags,
267        name: &'static str,
268    ) -> FileHandle {
269        let mut info = FsNodeInfo::new(mode!(IFREG, 0o600), current_task.current_fscred());
270        info.size = size;
271        let fs = anon_fs(current_task.kernel());
272        let node = fs.create_node_with_flags(None, Anon {}, info, FsNodeFlags::IS_PRIVATE);
273        security::fs_node_init_anon(current_task, &node, name)
274            .expect("Private anon_inode creation cannot fail");
275        let name = NamespaceNode::new_anonymous(DirEntry::new(node, None, name.into()));
276        FileObject::new(current_task, ops, name, flags)
277            .expect("FileObject::new on private anon_inode cannot fail")
278    }
279
280    /// Creates a new anonymous file backed by `memory` and a private regular [`FsNode`], to which
281    /// no security labeling nor access-checks will be applied.
282    pub fn new_private_file(
283        current_task: &CurrentTask,
284        memory: Arc<MemoryObject>,
285        flags: OpenFlags,
286        name: &'static str,
287    ) -> FileHandle {
288        let size = memory.get_size() as usize;
289        Self::new_private_file_with_ops(
290            current_task,
291            Box::new(Self::new(memory)),
292            size,
293            flags,
294            name,
295        )
296    }
297
298    pub fn read(
299        memory: &Arc<MemoryObject>,
300        file: &FileObject,
301        offset: usize,
302        data: &mut dyn OutputBuffer,
303    ) -> Result<usize, Errno> {
304        let actual = {
305            let info = file.node().info();
306            let file_length = info.size;
307            let want_read = data.available();
308            if offset < file_length {
309                let to_read =
310                    if file_length < offset + want_read { file_length - offset } else { want_read };
311                let buf =
312                    memory.read_to_vec(offset as u64, to_read as u64).map_err(|_| errno!(EIO))?;
313                drop(info);
314                data.write_all(&buf[..])?;
315                to_read
316            } else {
317                0
318            }
319        };
320        Ok(actual)
321    }
322
323    pub fn write(
324        memory: &Arc<MemoryObject>,
325        file: &FileObject,
326        current_task: &CurrentTask,
327        offset: usize,
328        data: &mut dyn InputBuffer,
329    ) -> Result<usize, Errno> {
330        let mut want_write = data.available();
331        let buf = data.peek_all()?;
332
333        let result = file.node().update_info(|info| {
334            let mut write_end = offset + want_write;
335            let mut update_content_size = false;
336
337            // We must hold the lock till the end of the operation to guarantee that
338            // there is no change to the seals.
339            let state = file.name.entry.node.write_guard_state.lock();
340
341            // Non-zero writes must pass the write seal check.
342            if want_write != 0 {
343                state.check_no_seal(SealFlags::WRITE | SealFlags::FUTURE_WRITE)?;
344            }
345
346            // Writing past the file size
347            if write_end > info.size {
348                // The grow seal check failed.
349                if let Err(e) = state.check_no_seal(SealFlags::GROW) {
350                    if offset >= info.size {
351                        // Write starts outside the file.
352                        // Forbid because nothing can be written without growing.
353                        return Err(e);
354                    } else if info.size == info.storage_size() {
355                        // Write starts inside file and EOF page does not need to grow.
356                        // End write at EOF.
357                        write_end = info.size;
358                        want_write = write_end - offset;
359                    } else {
360                        // Write starts inside file and EOF page needs to grow.
361                        let eof_page_start = info.storage_size() - (*PAGE_SIZE as usize);
362
363                        if offset >= eof_page_start {
364                            // Write starts in EOF page.
365                            // Forbid because EOF page cannot grow.
366                            return Err(e);
367                        }
368
369                        // End write at page before EOF.
370                        write_end = eof_page_start;
371                        want_write = write_end - offset;
372                    }
373                }
374            }
375
376            // Check against the FSIZE limit
377            let fsize_limit = current_task.thread_group().get_rlimit(Resource::FSIZE) as usize;
378            if write_end > fsize_limit {
379                if offset >= fsize_limit {
380                    // Write starts beyond the FSIZE limit.
381                    return error!(EFBIG);
382                }
383
384                // End write at FSIZE limit.
385                write_end = fsize_limit;
386                want_write = write_end - offset;
387            }
388
389            if write_end > info.size {
390                if write_end > info.storage_size() {
391                    update_memory_file_size(memory, info, write_end)?;
392                }
393                update_content_size = true;
394            }
395            memory.write(&buf[..want_write], offset as u64).map_err(|_| errno!(EIO))?;
396
397            if update_content_size {
398                info.size = write_end;
399            }
400            data.advance(want_write)?;
401            Ok(want_write)
402        });
403        if let Err(ref e) = result
404            && *e == EFBIG
405        {
406            // EFBIG must trigger a signal. Sending the signal must be done outside of the
407            // update_info method to ensure no deadlock.
408            send_standard_signal(current_task, SignalInfo::kernel(SIGXFSZ));
409        }
410        result
411    }
412
413    pub fn get_memory(
414        memory: &Arc<MemoryObject>,
415        file: &FileObject,
416        _current_task: &CurrentTask,
417        prot: ProtectionFlags,
418    ) -> Result<Arc<MemoryObject>, Errno> {
419        // In MemoryFileNode::create_file_ops, we downscoped the rights
420        // on the VMO to match the rights on the file object. If the caller
421        // wants more rights than exist on the file object, return an error
422        // instead of returning a MemoryObject that does not conform to
423        // the FileOps::get_memory contract.
424        if prot.contains(ProtectionFlags::READ) && !file.can_read() {
425            return error!(EACCES);
426        }
427        if prot.contains(ProtectionFlags::WRITE) && !file.can_write() {
428            return error!(EACCES);
429        }
430        let mut memory = Arc::clone(memory);
431        if prot.contains(ProtectionFlags::EXEC) {
432            // Readable opens of a `MemoryRegularNode` receive handles with a fixed set of rights
433            // determined by `create_file_ops`, so the executable handle can be shared across all
434            // such opens via the node. Write-only opens get a bespoke handle without READ, so
435            // they derive their own executable handle below rather than polluting the cache.
436            if file.can_read()
437                && let Some(node) = file.node().downcast_ops::<MemoryRegularNode>()
438            {
439                return node.exec_memory(file.can_write());
440            }
441            memory = Arc::new(
442                memory
443                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
444                    .map_err(impossible_error)?
445                    .replace_as_executable(&VMEX_RESOURCE)
446                    .map_err(impossible_error)?,
447            );
448        }
449        Ok(memory)
450    }
451}
452
453#[macro_export]
454macro_rules! fileops_impl_memory {
455    ($self:ident, $memory:expr) => {
456        $crate::fileops_impl_seekable!();
457
458        fn read(
459            &$self,
460            file: &$crate::vfs::FileObject,
461            _current_task: &$crate::task::CurrentTask,
462            offset: usize,
463            data: &mut dyn $crate::vfs::buffers::OutputBuffer,
464        ) -> Result<usize, starnix_uapi::errors::Errno> {
465            $crate::vfs::MemoryRegularFile::read($memory, file, offset, data)
466        }
467
468        fn write(
469            &$self,
470            file: &$crate::vfs::FileObject,
471            current_task: &$crate::task::CurrentTask,
472            offset: usize,
473            data: &mut dyn $crate::vfs::buffers::InputBuffer,
474        ) -> Result<usize, starnix_uapi::errors::Errno> {
475            $crate::vfs::MemoryRegularFile::write($memory, file, current_task, offset, data)
476        }
477
478        fn get_memory(
479            &$self,
480            file: &$crate::vfs::FileObject,
481            current_task: &$crate::task::CurrentTask,
482            _length: Option<usize>,
483            prot: $crate::mm::ProtectionFlags,
484        ) -> Result<Arc<$crate::mm::memory::MemoryObject>, starnix_uapi::errors::Errno> {
485            $crate::vfs::MemoryRegularFile::get_memory($memory, file, current_task, prot)
486        }
487    }
488}
489pub use fileops_impl_memory;
490
491impl FileOps for MemoryRegularFile {
492    fileops_impl_memory!(self, &self.memory);
493    fileops_impl_noop_sync!();
494
495    fn readahead(
496        &self,
497        _file: &FileObject,
498        _current_task: &CurrentTask,
499        _offset: usize,
500        _length: usize,
501    ) -> Result<(), Errno> {
502        track_stub!(TODO("https://fxbug.dev/42082608"), "paged VMO readahead");
503        Ok(())
504    }
505
506    fn ioctl(
507        &self,
508        _file: &FileObject,
509        _current_task: &CurrentTask,
510        request: u32,
511        arg: SyscallArg,
512    ) -> Result<SyscallResult, Errno> {
513        match request {
514            ASHMEM_GET_SIZE => {
515                track_stub!(TODO("https://fxbug.dev/389102161"), "ashmem get_size on memfd");
516                Ok(self.memory.get_size().into())
517            }
518            ASHMEM_SET_SIZE => {
519                track_stub!(TODO("https://fxbug.dev/389102161"), "ashmem set_size on memfd");
520                self.memory.set_size(arg.into()).map_err(|_| errno!(EINVAL))?;
521                Ok(SUCCESS)
522            }
523            _ => error!(ENOTTY),
524        }
525    }
526}
527
528pub fn new_memfd(
529    current_task: &CurrentTask,
530    mut name: FsString,
531    seals: SealFlags,
532    flags: OpenFlags,
533) -> Result<FileHandle, Errno> {
534    struct MemFdTmpfs {
535        tmpfs: FileSystemHandle,
536        mount: Arc<Mount>,
537    }
538
539    let fs = current_task.kernel().expando.get_or_init(|| {
540        let tmpfs = TmpFs::new_fs(current_task.kernel());
541        security::file_system_resolve_security(&current_task, &tmpfs).expect("resolve fs security");
542        let mounts_guard = current_task.kernel().mounts_lock();
543        let mount = Mount::new(&mounts_guard, WhatToMount::Fs(tmpfs.clone()), Default::default())
544            .expect("create new tempfs mount for memfd");
545        MemFdTmpfs { tmpfs, mount }
546    });
547
548    // Create the node as a kernel-internal operation, to skip the filesystem access-checks.
549    // TODO: https://fxbug.dev/455785957 - Validate whether any access-checks should be performed
550    // during "memfd" creation.
551    let fs_node = current_task.override_creds(
552        security::creds_start_internal_operation(current_task),
553        || {
554            fs.tmpfs.root().node.create_tmpfile(
555                current_task,
556                &MountInfo::detached(),
557                mode!(IFREG, 0o600),
558                current_task.current_fscred(),
559                FsNodeLinkBehavior::Disallowed,
560            )
561        },
562    )?;
563
564    // LSM allows security modules to choose to treat mem-FDs as a kind of anonymous inode.
565    security::fs_node_init_anon(current_task, &fs_node, "[memfd]")?;
566
567    fs_node.write_guard_state.lock().enable_sealing(seals);
568
569    // memfd instances appear in /proc[pid]/fd as though they are O_TMPFILE files with names of
570    // the form "memfd:[name]".
571    let mut local_name = FsString::from("memfd:");
572    local_name.append(&mut name);
573    let dir_entry = DirEntry::new_deleted(fs_node, Some(fs.tmpfs.root().clone()), local_name);
574    security::fs_node_init_with_dentry(current_task, &dir_entry)?;
575
576    let name = NamespaceNode::new(fs.mount.clone(), dir_entry);
577    name.open(current_task, OpenAccessCheck::skip(flags))
578}
579
580/// Sets memory size to `min_size` rounded to whole pages. Returns the new size of the VMO in bytes.
581fn update_memory_file_size(
582    memory: &MemoryObject,
583    node_info: &mut FsNodeInfo,
584    requested_size: usize,
585) -> Result<usize, Errno> {
586    assert!(requested_size <= MAX_LFS_FILESIZE);
587    let size = round_up_to_system_page_size(requested_size)?;
588    memory.set_size(size as u64).map_err(|status| match status {
589        zx::Status::NO_MEMORY => errno!(ENOMEM),
590        zx::Status::OUT_OF_RANGE => errno!(ENOMEM),
591        _ => impossible_error(status),
592    })?;
593    node_info.blocks = size / node_info.blksize;
594    Ok(size)
595}
596
597#[cfg(test)]
598mod test {
599    use super::*;
600    use crate::testing::spawn_kernel_and_run;
601    use crate::vfs::{LookupContext, Namespace};
602    use starnix_uapi::device_id::DeviceId;
603
604    #[::fuchsia::test]
605    async fn test_get_memory_cached_across_opens_and_exec() {
606        spawn_kernel_and_run(async |current_task| {
607            let fs = TmpFs::new_fs(current_task.kernel());
608            let ns = Namespace::new(fs);
609            let root = ns.root();
610
611            root.create_node(current_task, "file".into(), mode!(IFREG, 0o755), DeviceId::NONE)
612                .expect("create_node failed");
613
614            let mut context = LookupContext::default();
615            let file_node =
616                root.lookup_child(current_task, &mut context, "file".into()).expect("lookup");
617
618            let file1 = file_node.open(current_task, OpenFlags::RDONLY).expect("open 1 failed");
619            let ro_mem1 = file1
620                .get_memory(current_task, None, ProtectionFlags::READ)
621                .expect("get_memory READ 1 failed");
622            let rx_mem1 = file1
623                .get_memory(current_task, None, ProtectionFlags::READ | ProtectionFlags::EXEC)
624                .expect("get_memory READ|EXEC 1 failed");
625            assert!(rx_mem1.get_rights().contains(zx::Rights::EXECUTE));
626            assert!(!rx_mem1.get_rights().contains(zx::Rights::WRITE));
627
628            let file2 = file_node.open(current_task, OpenFlags::RDONLY).expect("open 2 failed");
629            let ro_mem2 = file2
630                .get_memory(current_task, None, ProtectionFlags::READ)
631                .expect("get_memory READ 2 failed");
632            let rx_mem2 = file2
633                .get_memory(current_task, None, ProtectionFlags::READ | ProtectionFlags::EXEC)
634                .expect("get_memory READ|EXEC 2 failed");
635            assert!(Arc::ptr_eq(&ro_mem1, &ro_mem2));
636            assert!(Arc::ptr_eq(&rx_mem1, &rx_mem2));
637
638            // Read/write opens share a separate executable handle that retains WRITE.
639            let file_rw1 = file_node.open(current_task, OpenFlags::RDWR).expect("open rw1 failed");
640            let rwx_mem1 = file_rw1
641                .get_memory(
642                    current_task,
643                    None,
644                    ProtectionFlags::READ | ProtectionFlags::WRITE | ProtectionFlags::EXEC,
645                )
646                .expect("get_memory READ|WRITE|EXEC 1 failed");
647            assert!(rwx_mem1.get_rights().contains(
648                zx::Rights::READ | zx::Rights::WRITE | zx::Rights::RESIZE | zx::Rights::EXECUTE
649            ));
650            assert!(!Arc::ptr_eq(&rwx_mem1, &rx_mem1));
651
652            let file_rw2 = file_node.open(current_task, OpenFlags::RDWR).expect("open rw2 failed");
653            let rwx_mem2 = file_rw2
654                .get_memory(current_task, None, ProtectionFlags::READ | ProtectionFlags::EXEC)
655                .expect("get_memory READ|EXEC rw2 failed");
656            assert!(Arc::ptr_eq(&rwx_mem1, &rwx_mem2));
657        })
658        .await;
659    }
660
661    #[::fuchsia::test]
662    async fn test_get_memory_write_only_exec_does_not_pollute_cache() {
663        spawn_kernel_and_run(async |current_task| {
664            let fs = TmpFs::new_fs(current_task.kernel());
665            let ns = Namespace::new(fs);
666            let root = ns.root();
667
668            root.create_node(current_task, "file".into(), mode!(IFREG, 0o755), DeviceId::NONE)
669                .expect("create_node failed");
670
671            let mut context = LookupContext::default();
672            let file_node =
673                root.lookup_child(current_task, &mut context, "file".into()).expect("lookup");
674
675            // A write-only open gets an executable handle without READ...
676            let file_wo = file_node.open(current_task, OpenFlags::WRONLY).expect("open wo failed");
677            let wx_mem = file_wo
678                .get_memory(current_task, None, ProtectionFlags::WRITE | ProtectionFlags::EXEC)
679                .expect("get_memory WRITE|EXEC failed");
680            assert!(wx_mem.get_rights().contains(zx::Rights::WRITE | zx::Rights::EXECUTE));
681            assert!(!wx_mem.get_rights().contains(zx::Rights::READ));
682
683            // ...and must not be handed to a subsequent read/write open.
684            let file_rw = file_node.open(current_task, OpenFlags::RDWR).expect("open rw failed");
685            let rwx_mem = file_rw
686                .get_memory(
687                    current_task,
688                    None,
689                    ProtectionFlags::READ | ProtectionFlags::WRITE | ProtectionFlags::EXEC,
690                )
691                .expect("get_memory READ|WRITE|EXEC failed");
692            assert!(!Arc::ptr_eq(&wx_mem, &rwx_mem));
693            assert!(rwx_mem.get_rights().contains(
694                zx::Rights::READ | zx::Rights::WRITE | zx::Rights::RESIZE | zx::Rights::EXECUTE
695            ));
696
697            // Nor to a read-only open.
698            let file_ro = file_node.open(current_task, OpenFlags::RDONLY).expect("open ro failed");
699            let rx_mem = file_ro
700                .get_memory(current_task, None, ProtectionFlags::READ | ProtectionFlags::EXEC)
701                .expect("get_memory READ|EXEC failed");
702            assert!(!Arc::ptr_eq(&wx_mem, &rx_mem));
703            assert!(!Arc::ptr_eq(&rwx_mem, &rx_mem));
704            assert!(rx_mem.get_rights().contains(zx::Rights::READ | zx::Rights::EXECUTE));
705            assert!(!rx_mem.get_rights().contains(zx::Rights::WRITE));
706        })
707        .await;
708    }
709}