Skip to main content

vfs/
protocols.rs

1// Copyright 2023 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::common::CreationMode;
6use crate::directory::DirectoryOptions;
7use crate::file::FileOptions;
8use crate::node::NodeOptions;
9use crate::service::ServiceOptions;
10use crate::symlink::SymlinkOptions;
11use flex_fuchsia_io as fio;
12use zx_status::Status;
13
14/// Extends fio::Flags and fio::OpenFlags
15pub trait ProtocolsExt: ToFileOptions + ToNodeOptions + Send + Sync + 'static {
16    /// True if the directory protocol is allowed.
17    fn is_dir_allowed(&self) -> bool;
18
19    /// True if the file protocol is allowed.
20    fn is_file_allowed(&self) -> bool;
21
22    /// True if the symlink protocol is allowed.
23    fn is_symlink_allowed(&self) -> bool;
24
25    /// The creation mode for the connection.
26    fn creation_mode(&self) -> CreationMode;
27
28    /// The rights for the connection.  If None, it means the connection is not for a node based
29    /// protocol.  If the connection is supposed to use the same rights as the parent connection,
30    /// the rights should have been populated.
31    fn rights(&self) -> Option<fio::Operations>;
32
33    /// Convert to directory options.  Returns an error if the request does not permit a directory.
34    fn to_directory_options(&self) -> Result<DirectoryOptions, Status>;
35
36    /// Convert to symlink options.  Returns an error if the request does not permit a symlink.
37    fn to_symlink_options(&self) -> Result<SymlinkOptions, Status>;
38
39    /// Convert to service options.  Returns an error if the request is not valid for a service.
40    fn to_service_options(&self) -> Result<ServiceOptions, Status>;
41
42    /// True if the file should be in append mode.
43    fn is_append(&self) -> bool;
44
45    /// True if the file should be truncated.
46    fn is_truncate(&self) -> bool;
47
48    /// If creating an object, Whether to create a directory.
49    fn create_directory(&self) -> bool;
50
51    /// True if the protocol should be a limited node connection.
52    fn is_node(&self) -> bool;
53
54    /// True if creating as an unnamed temporary object.
55    fn create_unnamed_temporary_in_directory_path(&self) -> bool;
56}
57
58impl ProtocolsExt for fio::OpenFlags {
59    fn is_dir_allowed(&self) -> bool {
60        !self.contains(fio::OpenFlags::NOT_DIRECTORY)
61    }
62
63    fn is_file_allowed(&self) -> bool {
64        !self.contains(fio::OpenFlags::DIRECTORY)
65    }
66
67    fn is_symlink_allowed(&self) -> bool {
68        !self.contains(fio::OpenFlags::DIRECTORY)
69    }
70
71    fn creation_mode(&self) -> CreationMode {
72        if self.contains(fio::OpenFlags::CREATE) {
73            if self.contains(fio::OpenFlags::CREATE_IF_ABSENT) {
74                CreationMode::Always
75            } else {
76                CreationMode::AllowExisting
77            }
78        } else {
79            CreationMode::Never
80        }
81    }
82
83    fn rights(&self) -> Option<fio::Operations> {
84        if self.contains(fio::OpenFlags::CLONE_SAME_RIGHTS) {
85            None
86        } else {
87            let mut rights = fio::Operations::GET_ATTRIBUTES
88                | fio::Operations::CONNECT
89                | fio::Operations::TRAVERSE;
90            if self.contains(fio::OpenFlags::RIGHT_READABLE) {
91                rights |= fio::R_STAR_DIR;
92            }
93            if self.contains(fio::OpenFlags::RIGHT_WRITABLE) {
94                rights |= fio::W_STAR_DIR;
95            }
96            if self.contains(fio::OpenFlags::RIGHT_EXECUTABLE) {
97                rights |= fio::X_STAR_DIR;
98            }
99            Some(rights)
100        }
101    }
102
103    /// Checks flags provided for a new directory connection.  Returns directory options (cleaning
104    /// up some ambiguities) or an error, in case new new connection flags are not permitting the
105    /// connection to be opened.
106    ///
107    /// Changing this function can be dangerous!  Flags operations may have security implications.
108    fn to_directory_options(&self) -> Result<DirectoryOptions, Status> {
109        assert!(!self.intersects(fio::OpenFlags::NODE_REFERENCE));
110
111        let mut flags = *self;
112
113        if flags.intersects(fio::OpenFlags::DIRECTORY) {
114            flags &= !fio::OpenFlags::DIRECTORY;
115        }
116
117        if flags.intersects(fio::OpenFlags::NOT_DIRECTORY) {
118            return Err(Status::NOT_FILE);
119        }
120
121        // Parent connection must check the POSIX flags in `check_child_connection_flags`, so if any
122        // are still present, we expand their respective rights and remove any remaining flags.
123        if flags.intersects(fio::OpenFlags::POSIX_EXECUTABLE) {
124            flags |= fio::OpenFlags::RIGHT_EXECUTABLE;
125        }
126        if flags.intersects(fio::OpenFlags::POSIX_WRITABLE) {
127            flags |= fio::OpenFlags::RIGHT_WRITABLE;
128        }
129        flags &= !(fio::OpenFlags::POSIX_WRITABLE | fio::OpenFlags::POSIX_EXECUTABLE);
130
131        let allowed_flags = fio::OpenFlags::DESCRIBE
132            | fio::OpenFlags::CREATE
133            | fio::OpenFlags::CREATE_IF_ABSENT
134            | fio::OpenFlags::DIRECTORY
135            | fio::OpenFlags::RIGHT_READABLE
136            | fio::OpenFlags::RIGHT_WRITABLE
137            | fio::OpenFlags::RIGHT_EXECUTABLE;
138
139        let prohibited_flags = fio::OpenFlags::APPEND | fio::OpenFlags::TRUNCATE;
140
141        if flags.intersects(prohibited_flags) {
142            return Err(Status::INVALID_ARGS);
143        }
144
145        if flags.intersects(!allowed_flags) {
146            return Err(Status::NOT_SUPPORTED);
147        }
148
149        // Map io1 OpenFlags::RIGHT_* flags to the corresponding set of io2 rights. Using Open1
150        // requires GET_ATTRIBUTES, as this was previously an privileged operation.
151        let mut rights = fio::Rights::GET_ATTRIBUTES | fio::Rights::TRAVERSE;
152        if flags.contains(fio::OpenFlags::RIGHT_READABLE) {
153            rights |= fio::R_STAR_DIR;
154        }
155        if flags.contains(fio::OpenFlags::RIGHT_WRITABLE) {
156            rights |= fio::W_STAR_DIR;
157        }
158        if flags.contains(fio::OpenFlags::RIGHT_EXECUTABLE) {
159            rights |= fio::X_STAR_DIR;
160        }
161
162        Ok(DirectoryOptions { rights })
163    }
164
165    fn to_symlink_options(&self) -> Result<SymlinkOptions, Status> {
166        if self.intersects(fio::OpenFlags::DIRECTORY) {
167            return Err(Status::NOT_DIR);
168        }
169
170        // We allow write and executable access because the client might not know this is a symbolic
171        // link and they want to open the target of the link with write or executable rights.
172        let optional = fio::OpenFlags::NOT_DIRECTORY
173            | fio::OpenFlags::DESCRIBE
174            | fio::OpenFlags::RIGHT_WRITABLE
175            | fio::OpenFlags::RIGHT_EXECUTABLE;
176
177        if *self & !optional != fio::OpenFlags::RIGHT_READABLE {
178            return Err(Status::INVALID_ARGS);
179        }
180
181        let mut rights = fio::Operations::GET_ATTRIBUTES;
182        if self.contains(fio::OpenFlags::RIGHT_READABLE) {
183            rights |= fio::Operations::READ_BYTES;
184        }
185        if self.contains(fio::OpenFlags::RIGHT_WRITABLE) {
186            rights |= fio::Operations::WRITE_BYTES | fio::Operations::UPDATE_ATTRIBUTES;
187        }
188        if self.contains(fio::OpenFlags::RIGHT_EXECUTABLE) {
189            rights |= fio::Operations::EXECUTE;
190        }
191
192        Ok(SymlinkOptions { rights })
193    }
194
195    fn to_service_options(&self) -> Result<ServiceOptions, Status> {
196        if self.intersects(fio::OpenFlags::DIRECTORY) {
197            return Err(Status::NOT_DIR);
198        }
199
200        if self.intersects(!fio::OpenFlags::DESCRIBE.union(fio::OpenFlags::NOT_DIRECTORY)) {
201            return Err(Status::INVALID_ARGS);
202        }
203
204        Ok(ServiceOptions)
205    }
206
207    fn is_append(&self) -> bool {
208        self.contains(fio::OpenFlags::APPEND)
209    }
210
211    fn is_truncate(&self) -> bool {
212        self.contains(fio::OpenFlags::TRUNCATE)
213    }
214
215    fn create_directory(&self) -> bool {
216        self.contains(fio::OpenFlags::DIRECTORY)
217    }
218
219    fn is_node(&self) -> bool {
220        self.contains(fio::OpenFlags::NODE_REFERENCE)
221    }
222
223    fn create_unnamed_temporary_in_directory_path(&self) -> bool {
224        false
225    }
226}
227
228impl ProtocolsExt for fio::Flags {
229    fn is_dir_allowed(&self) -> bool {
230        self.contains(fio::Flags::PROTOCOL_DIRECTORY)
231            || self.intersection(fio::MASK_KNOWN_PROTOCOLS).is_empty()
232    }
233
234    fn is_file_allowed(&self) -> bool {
235        self.contains(fio::Flags::PROTOCOL_FILE)
236            || self.intersection(fio::MASK_KNOWN_PROTOCOLS).is_empty()
237    }
238
239    fn is_symlink_allowed(&self) -> bool {
240        self.contains(fio::Flags::PROTOCOL_SYMLINK)
241            || self.intersection(fio::MASK_KNOWN_PROTOCOLS).is_empty()
242    }
243
244    fn creation_mode(&self) -> CreationMode {
245        #[cfg(fuchsia_api_level_at_least = "HEAD")]
246        {
247            if self.contains(fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY) {
248                if self.contains(fio::Flags::FLAG_MUST_CREATE) {
249                    return CreationMode::UnlinkableUnnamedTemporary;
250                }
251                return CreationMode::UnnamedTemporary;
252            }
253        }
254        if self.contains(fio::Flags::FLAG_MUST_CREATE) {
255            CreationMode::Always
256        } else if self.contains(fio::Flags::FLAG_MAYBE_CREATE) {
257            CreationMode::AllowExisting
258        } else {
259            CreationMode::Never
260        }
261    }
262
263    fn rights(&self) -> Option<fio::Operations> {
264        Some(flags_to_rights(self))
265    }
266
267    fn to_directory_options(&self) -> Result<DirectoryOptions, Status> {
268        // Verify protocols.
269        if !self.is_dir_allowed() {
270            return Err(if self.is_file_allowed() { Status::NOT_FILE } else { Status::WRONG_TYPE });
271        }
272
273        // Expand the POSIX flags to their respective rights. This is done with the assumption that
274        // the POSIX flags would have been validated prior to calling this. E.g. in the vfs
275        // connection later.
276        let mut updated_flags = *self;
277        if updated_flags.contains(fio::Flags::PERM_INHERIT_WRITE) {
278            updated_flags |=
279                fio::Flags::from_bits_truncate(fio::INHERITED_WRITE_PERMISSIONS.bits());
280        }
281        if updated_flags.contains(fio::Flags::PERM_INHERIT_EXECUTE) {
282            updated_flags |= fio::Flags::PERM_EXECUTE;
283        }
284
285        // Verify that there are no file-related flags.
286        if updated_flags.intersects(fio::Flags::FILE_APPEND | fio::Flags::FILE_TRUNCATE) {
287            return Err(Status::INVALID_ARGS);
288        }
289
290        Ok(DirectoryOptions { rights: flags_to_rights(&updated_flags) })
291    }
292
293    fn to_symlink_options(&self) -> Result<SymlinkOptions, Status> {
294        if !self.is_symlink_allowed() {
295            return Err(Status::WRONG_TYPE);
296        }
297
298        // If is_symlink_allowed() returned true, there must be rights.
299        let rights = self.rights().unwrap();
300        if !rights.contains(fio::Operations::GET_ATTRIBUTES) {
301            return Err(Status::INVALID_ARGS);
302        }
303        Ok(SymlinkOptions { rights })
304    }
305
306    fn to_service_options(&self) -> Result<ServiceOptions, Status> {
307        // This should not be called if fio::Flags::PROTOCOL_NODE was set (`to_node_options` would
308        // be called instead).
309        assert!(!self.contains(fio::Flags::PROTOCOL_NODE));
310        if !self
311            .intersection(fio::MASK_KNOWN_PROTOCOLS)
312            .difference(fio::Flags::PROTOCOL_SERVICE)
313            .is_empty()
314        {
315            return if self.is_dir_allowed() {
316                Err(Status::NOT_DIR)
317            } else if self.is_file_allowed() {
318                Err(Status::NOT_FILE)
319            } else {
320                Err(Status::WRONG_TYPE)
321            };
322        }
323
324        if !self.difference(fio::Flags::PROTOCOL_SERVICE).is_empty() {
325            return Err(Status::INVALID_ARGS);
326        }
327
328        Ok(ServiceOptions)
329    }
330
331    fn is_append(&self) -> bool {
332        self.contains(fio::Flags::FILE_APPEND)
333    }
334
335    fn is_truncate(&self) -> bool {
336        self.contains(fio::Flags::FILE_TRUNCATE)
337    }
338
339    fn create_directory(&self) -> bool {
340        self.contains(fio::Flags::PROTOCOL_DIRECTORY)
341    }
342
343    fn is_node(&self) -> bool {
344        self.contains(fio::Flags::PROTOCOL_NODE)
345    }
346
347    fn create_unnamed_temporary_in_directory_path(&self) -> bool {
348        self.creation_mode() == CreationMode::UnnamedTemporary
349            || self.creation_mode() == CreationMode::UnlinkableUnnamedTemporary
350    }
351}
352
353pub trait ToFileOptions: Send + 'static {
354    fn to_file_options(&self) -> Result<FileOptions, Status>;
355}
356
357impl ToFileOptions for fio::OpenFlags {
358    fn to_file_options(&self) -> Result<FileOptions, Status> {
359        assert!(!self.intersects(fio::OpenFlags::NODE_REFERENCE));
360
361        if self.contains(fio::OpenFlags::DIRECTORY) {
362            return Err(Status::NOT_DIR);
363        }
364
365        // Verify allowed operations/flags this node supports.
366        let flags_without_rights = self.difference(
367            fio::OpenFlags::RIGHT_READABLE
368                | fio::OpenFlags::RIGHT_WRITABLE
369                | fio::OpenFlags::RIGHT_EXECUTABLE,
370        );
371        const ALLOWED_FLAGS: fio::OpenFlags = fio::OpenFlags::DESCRIBE
372            .union(fio::OpenFlags::CREATE)
373            .union(fio::OpenFlags::CREATE_IF_ABSENT)
374            .union(fio::OpenFlags::APPEND)
375            .union(fio::OpenFlags::TRUNCATE)
376            .union(fio::OpenFlags::POSIX_WRITABLE)
377            .union(fio::OpenFlags::POSIX_EXECUTABLE)
378            .union(fio::OpenFlags::NOT_DIRECTORY);
379        if flags_without_rights.intersects(!ALLOWED_FLAGS) {
380            return Err(Status::NOT_SUPPORTED);
381        }
382
383        // Disallow invalid flag combinations.
384        let mut prohibited_flags = fio::OpenFlags::empty();
385        if !self.intersects(fio::OpenFlags::RIGHT_WRITABLE) {
386            prohibited_flags |= fio::OpenFlags::TRUNCATE
387        }
388        if self.intersects(prohibited_flags) {
389            return Err(Status::INVALID_ARGS);
390        }
391
392        Ok(FileOptions {
393            rights: {
394                let mut rights = fio::Operations::GET_ATTRIBUTES;
395                if self.contains(fio::OpenFlags::RIGHT_READABLE) {
396                    rights |= fio::Operations::READ_BYTES;
397                }
398                if self.contains(fio::OpenFlags::RIGHT_WRITABLE) {
399                    rights |= fio::Operations::WRITE_BYTES | fio::Operations::UPDATE_ATTRIBUTES;
400                }
401                if self.contains(fio::OpenFlags::RIGHT_EXECUTABLE) {
402                    rights |= fio::Operations::EXECUTE;
403                }
404                rights
405            },
406            is_append: self.contains(fio::OpenFlags::APPEND),
407            #[cfg(fuchsia_api_level_at_least = "HEAD")]
408            is_linkable: true,
409        })
410    }
411}
412
413impl ToFileOptions for fio::Flags {
414    fn to_file_options(&self) -> Result<FileOptions, Status> {
415        // Verify protocols.
416        if !self.is_file_allowed() {
417            if self.is_dir_allowed() && !self.is_symlink_allowed() {
418                return Err(Status::NOT_DIR);
419            } else {
420                return Err(Status::WRONG_TYPE);
421            }
422        }
423
424        // Verify prohibited flags and disallow invalid flag combinations.
425        if self.contains(fio::Flags::FILE_TRUNCATE) && !self.contains(fio::Flags::PERM_WRITE_BYTES)
426        {
427            return Err(Status::INVALID_ARGS);
428        }
429
430        // Used to remove any non-file flags.
431        const ALLOWED_RIGHTS: fio::Operations = fio::Operations::empty()
432            .union(fio::Operations::GET_ATTRIBUTES)
433            .union(fio::Operations::READ_BYTES)
434            .union(fio::Operations::WRITE_BYTES)
435            .union(fio::Operations::UPDATE_ATTRIBUTES)
436            .union(fio::Operations::EXECUTE);
437
438        Ok(FileOptions {
439            rights: flags_to_rights(self).intersection(ALLOWED_RIGHTS),
440            is_append: self.contains(fio::Flags::FILE_APPEND),
441            #[cfg(fuchsia_api_level_at_least = "HEAD")]
442            is_linkable: !self.contains(
443                fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY | fio::FLAG_TEMPORARY_AS_NOT_LINKABLE,
444            ),
445        })
446    }
447}
448
449impl ToFileOptions for FileOptions {
450    fn to_file_options(&self) -> Result<FileOptions, Status> {
451        Ok(*self)
452    }
453}
454
455pub trait ToNodeOptions: Send + 'static {
456    fn to_node_options(&self, dirent_type: fio::DirentType) -> Result<NodeOptions, Status>;
457}
458
459impl ToNodeOptions for fio::OpenFlags {
460    fn to_node_options(&self, dirent_type: fio::DirentType) -> Result<NodeOptions, Status> {
461        // Strictly, we shouldn't allow rights to be specified with NODE_REFERENCE, but there's a
462        // CTS pkgdir test that asserts these flags work and fixing that is painful so we preserve
463        // old behaviour (which permitted these flags).
464        let allowed_rights =
465            fio::OPEN_RIGHTS | fio::OpenFlags::POSIX_WRITABLE | fio::OpenFlags::POSIX_EXECUTABLE;
466        if self.intersects(!(fio::OPEN_FLAGS_ALLOWED_WITH_NODE_REFERENCE | allowed_rights)) {
467            Err(Status::INVALID_ARGS)
468        } else if self.contains(fio::OpenFlags::DIRECTORY)
469            && dirent_type != fio::DirentType::Directory
470        {
471            Err(Status::NOT_DIR)
472        } else {
473            Ok(NodeOptions { rights: fio::Operations::GET_ATTRIBUTES })
474        }
475    }
476}
477
478impl ToNodeOptions for fio::Flags {
479    fn to_node_options(&self, dirent_type: fio::DirentType) -> Result<NodeOptions, Status> {
480        // Strictly, we shouldn't allow rights to be specified with PROTOCOL_NODE, but there's a
481        // CTS pkgdir test that asserts these flags work and fixing that is painful so we preserve
482        // old behaviour (which permitted these flags).
483        const ALLOWED_FLAGS: fio::Flags = fio::Flags::FLAG_SEND_REPRESENTATION
484            .union(fio::MASK_KNOWN_PERMISSIONS)
485            .union(fio::MASK_KNOWN_PROTOCOLS);
486
487        if self.intersects(!ALLOWED_FLAGS) {
488            return Err(Status::INVALID_ARGS);
489        }
490
491        // If other `PROTOCOL_*` were were specified along with `PROTOCOL_NODE`, verify that the
492        // target node supports it.
493        if self.intersects(fio::MASK_KNOWN_PROTOCOLS.difference(fio::Flags::PROTOCOL_NODE)) {
494            if dirent_type == fio::DirentType::Directory {
495                if !self.intersects(fio::Flags::PROTOCOL_DIRECTORY) {
496                    if self.intersects(fio::Flags::PROTOCOL_FILE) {
497                        return Err(Status::NOT_FILE);
498                    } else {
499                        return Err(Status::WRONG_TYPE);
500                    }
501                }
502            } else if dirent_type == fio::DirentType::File {
503                if !self.intersects(fio::Flags::PROTOCOL_FILE) {
504                    if self.intersects(fio::Flags::PROTOCOL_DIRECTORY) {
505                        return Err(Status::NOT_DIR);
506                    } else {
507                        return Err(Status::WRONG_TYPE);
508                    }
509                }
510            } else if dirent_type == fio::DirentType::Symlink {
511                if !self.intersects(fio::Flags::PROTOCOL_SYMLINK) {
512                    return Err(Status::WRONG_TYPE);
513                }
514            }
515        }
516
517        Ok(NodeOptions {
518            rights: flags_to_rights(self).intersection(fio::Operations::GET_ATTRIBUTES),
519        })
520    }
521}
522
523impl ToNodeOptions for NodeOptions {
524    fn to_node_options(&self, _dirent_type: fio::DirentType) -> Result<NodeOptions, Status> {
525        Ok(*self)
526    }
527}
528
529fn flags_to_rights(flags: &fio::Flags) -> fio::Rights {
530    fio::Rights::from_bits_truncate(flags.bits())
531}