vfs/file/
common.rs

1// Copyright 2019 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
5//! Common utilities for implementing file nodes and connections.
6
7use crate::file::FileOptions;
8use fidl_fuchsia_io as fio;
9use zx_status::Status;
10
11/// Validate that the requested flags for a new connection are valid. This includes permission
12/// handling, only allowing certain operations.
13///
14/// Changing this function can be dangerous! There are security implications.
15pub fn new_connection_validate_options(
16    options: &FileOptions,
17    readable: bool,
18    writable: bool,
19    executable: bool,
20) -> Result<(), Status> {
21    // Nodes supporting both W+X rights are not supported.
22    debug_assert!(!(writable && executable));
23
24    // Validate permissions.
25    if !readable && options.rights.contains(fio::Operations::READ_BYTES) {
26        return Err(Status::ACCESS_DENIED);
27    }
28    if !writable && options.rights.contains(fio::Operations::WRITE_BYTES) {
29        return Err(Status::ACCESS_DENIED);
30    }
31    if !executable && options.rights.contains(fio::Operations::EXECUTE) {
32        return Err(Status::ACCESS_DENIED);
33    }
34
35    Ok(())
36}
37
38/// Converts the set of validated VMO flags to their respective zx::Rights.
39#[cfg(target_os = "fuchsia")]
40pub fn vmo_flags_to_rights(vmo_flags: fio::VmoFlags) -> fidl::Rights {
41    // Map VMO flags to their respective rights.
42    let mut rights = fidl::Rights::NONE;
43    if vmo_flags.contains(fio::VmoFlags::READ) {
44        rights |= fidl::Rights::READ;
45    }
46    if vmo_flags.contains(fio::VmoFlags::WRITE) {
47        rights |= fidl::Rights::WRITE;
48    }
49    if vmo_flags.contains(fio::VmoFlags::EXECUTE) {
50        rights |= fidl::Rights::EXECUTE;
51    }
52
53    rights
54}
55
56/// Validate flags passed to `get_backing_memory` against the underlying connection flags.
57/// Returns Ok() if the flags were validated, and an Error(Status) otherwise.
58///
59/// Changing this function can be dangerous! Flags operations may have security implications.
60#[cfg(target_os = "fuchsia")]
61pub fn get_backing_memory_validate_flags(
62    vmo_flags: fio::VmoFlags,
63    options: FileOptions,
64) -> Result<(), Status> {
65    // Disallow inconsistent flag combination.
66    if vmo_flags.contains(fio::VmoFlags::PRIVATE_CLONE)
67        && vmo_flags.contains(fio::VmoFlags::SHARED_BUFFER)
68    {
69        return Err(Status::INVALID_ARGS);
70    }
71
72    // Ensure the requested rights in vmo_flags do not exceed those of the underlying connection.
73    if vmo_flags.contains(fio::VmoFlags::READ)
74        && !options.rights.intersects(fio::Operations::READ_BYTES)
75    {
76        return Err(Status::ACCESS_DENIED);
77    }
78    if vmo_flags.contains(fio::VmoFlags::WRITE)
79        && !options.rights.intersects(fio::Operations::WRITE_BYTES)
80    {
81        return Err(Status::ACCESS_DENIED);
82    }
83    if vmo_flags.contains(fio::VmoFlags::EXECUTE)
84        && !options.rights.intersects(fio::Operations::EXECUTE)
85    {
86        return Err(Status::ACCESS_DENIED);
87    }
88
89    Ok(())
90}
91
92#[cfg(test)]
93mod tests {
94    use super::new_connection_validate_options;
95    use crate::file::FileOptions;
96    use crate::protocols::ToFileOptions;
97    use crate::test_utils::build_flag_combinations;
98
99    use assert_matches::assert_matches;
100    use fidl_fuchsia_io as fio;
101    use zx_status::Status;
102
103    fn options_to_rights(options: FileOptions) -> (bool, bool, bool) {
104        return (
105            options.rights.intersects(fio::Operations::READ_BYTES),
106            options.rights.intersects(fio::Operations::WRITE_BYTES),
107            options.rights.intersects(fio::Operations::EXECUTE),
108        );
109    }
110
111    fn ncvf(
112        flags: impl ToFileOptions,
113        readable: bool,
114        writable: bool,
115        executable: bool,
116    ) -> Result<FileOptions, Status> {
117        let options = flags.to_file_options()?;
118        new_connection_validate_options(&options, readable, writable, executable)?;
119        Ok(options)
120    }
121
122    #[test]
123    fn new_connection_validate_flags_create() {
124        for open_flags in build_flag_combinations(
125            fio::Flags::FLAG_MAYBE_CREATE,
126            fio::Flags::PERM_READ_BYTES
127                | fio::Flags::PERM_WRITE_BYTES
128                | fio::Flags::FLAG_MUST_CREATE,
129        ) {
130            let options = open_flags.to_file_options().unwrap();
131            let (readable, writable, executable) = options_to_rights(options);
132            assert_matches!(ncvf(options, readable, writable, executable), Ok(_));
133        }
134    }
135
136    #[test]
137    fn new_connection_validate_flags_truncate() {
138        assert_matches!(
139            ncvf(fio::Flags::PERM_READ_BYTES | fio::Flags::FILE_TRUNCATE, true, true, false),
140            Err(Status::INVALID_ARGS)
141        );
142        assert_matches!(
143            ncvf(fio::Flags::PERM_WRITE_BYTES | fio::Flags::FILE_TRUNCATE, true, true, false),
144            Ok(_)
145        );
146        assert_matches!(
147            ncvf(fio::Flags::PERM_READ_BYTES | fio::Flags::FILE_TRUNCATE, true, false, false),
148            Err(Status::INVALID_ARGS)
149        );
150    }
151
152    #[test]
153    fn new_connection_validate_flags_append() {
154        assert_matches!(
155            ncvf(fio::Flags::PERM_READ_BYTES | fio::Flags::FILE_APPEND, true, false, false),
156            Ok(_)
157        );
158        assert_matches!(
159            ncvf(fio::Flags::PERM_READ_BYTES | fio::Flags::FILE_APPEND, true, true, false),
160            Ok(_)
161        );
162        assert_matches!(
163            ncvf(fio::Flags::PERM_READ_BYTES | fio::Flags::FILE_APPEND, true, true, false),
164            Ok(_)
165        );
166    }
167
168    #[test]
169    fn new_connection_validate_flags_open_rights() {
170        for open_flags in build_flag_combinations(
171            fio::Flags::empty(),
172            fio::Flags::PERM_READ_BYTES | fio::Flags::PERM_READ_BYTES | fio::Flags::PERM_EXECUTE,
173        ) {
174            let options = open_flags.to_file_options().unwrap();
175            let (readable, writable, executable) = options_to_rights(options);
176
177            // Ensure all combinations are valid except when both writable and executable are set,
178            // as this combination is disallowed.
179            if !(writable && executable) {
180                assert_matches!(ncvf(options, readable, writable, executable), Ok(_));
181            }
182
183            // Ensure we report ACCESS_DENIED if open_flags exceeds the supported connection rights.
184            if readable && !(writable && executable) {
185                assert_eq!(ncvf(options, false, writable, executable), Err(Status::ACCESS_DENIED));
186            }
187            if writable {
188                assert_eq!(ncvf(options, readable, false, executable), Err(Status::ACCESS_DENIED));
189            }
190            if executable {
191                assert_eq!(ncvf(options, readable, writable, false), Err(Status::ACCESS_DENIED));
192            }
193        }
194    }
195
196    #[cfg(target_os = "fuchsia")]
197    mod vmo_tests {
198        use super::super::{get_backing_memory_validate_flags, vmo_flags_to_rights};
199        use super::*;
200
201        fn rights_to_vmo_flags(readable: bool, writable: bool, executable: bool) -> fio::VmoFlags {
202            return if readable { fio::VmoFlags::READ } else { fio::VmoFlags::empty() }
203                | if writable { fio::VmoFlags::WRITE } else { fio::VmoFlags::empty() }
204                | if executable { fio::VmoFlags::EXECUTE } else { fio::VmoFlags::empty() };
205        }
206
207        /// Validates that the passed VMO flags are correctly mapped to their respective Rights.
208        #[test]
209        fn test_vmo_flags_to_rights() {
210            for vmo_flags in build_flag_combinations(
211                fio::VmoFlags::empty(),
212                fio::VmoFlags::READ | fio::VmoFlags::WRITE | fio::VmoFlags::EXECUTE,
213            ) {
214                let rights: fidl::Rights = vmo_flags_to_rights(vmo_flags);
215                assert_eq!(
216                    vmo_flags.contains(fio::VmoFlags::READ),
217                    rights.contains(fidl::Rights::READ)
218                );
219                assert_eq!(
220                    vmo_flags.contains(fio::VmoFlags::WRITE),
221                    rights.contains(fidl::Rights::WRITE)
222                );
223                assert_eq!(
224                    vmo_flags.contains(fio::VmoFlags::EXECUTE),
225                    rights.contains(fidl::Rights::EXECUTE)
226                );
227            }
228        }
229
230        #[test]
231        fn get_backing_memory_validate_flags_invalid() {
232            // Cannot specify both PRIVATE and EXACT at the same time, since they conflict.
233            assert_eq!(
234                get_backing_memory_validate_flags(
235                    fio::VmoFlags::PRIVATE_CLONE | fio::VmoFlags::SHARED_BUFFER,
236                    fio::Flags::empty().to_file_options().unwrap()
237                ),
238                Err(Status::INVALID_ARGS)
239            );
240        }
241
242        /// Ensure that the check passes if we request the same or less rights
243        /// than the connection has.
244        #[test]
245        fn get_backing_memory_validate_flags_less_rights() {
246            for open_flags in build_flag_combinations(
247                fio::Flags::empty(),
248                fio::Flags::PERM_READ_BYTES
249                    | fio::Flags::PERM_WRITE_BYTES
250                    | fio::Flags::PERM_EXECUTE,
251            ) {
252                let options = open_flags.to_file_options().unwrap();
253                let (readable, writable, executable) = options_to_rights(options);
254                let vmo_flags = rights_to_vmo_flags(readable, writable, executable);
255
256                // Ensure that we can open the VMO with the same rights as the connection.
257                get_backing_memory_validate_flags(vmo_flags, options)
258                    .expect("Failed to validate flags");
259
260                // Ensure that we can also open the VMO with *less* rights than the connection has.
261                if readable {
262                    let vmo_flags = rights_to_vmo_flags(false, writable, executable);
263                    get_backing_memory_validate_flags(vmo_flags, options)
264                        .expect("Failed to validate flags");
265                }
266                if writable {
267                    let vmo_flags = rights_to_vmo_flags(readable, false, executable);
268                    get_backing_memory_validate_flags(vmo_flags, options)
269                        .expect("Failed to validate flags");
270                }
271                if executable {
272                    let vmo_flags = rights_to_vmo_flags(readable, writable, false);
273                    get_backing_memory_validate_flags(vmo_flags, options)
274                        .expect("Failed to validate flags");
275                }
276            }
277        }
278
279        /// Ensure that vmo_flags cannot exceed rights of connection_flags.
280        #[test]
281        fn get_backing_memory_validate_flags_more_rights() {
282            for open_flags in build_flag_combinations(
283                fio::Flags::empty(),
284                fio::Flags::PERM_READ_BYTES
285                    | fio::Flags::PERM_WRITE_BYTES
286                    | fio::Flags::PERM_EXECUTE,
287            ) {
288                let options = open_flags.to_file_options().unwrap();
289                // Ensure we cannot return a VMO with more rights than the connection itself has.
290                let (readable, writable, executable) = options_to_rights(options);
291                if !readable {
292                    let vmo_flags = rights_to_vmo_flags(true, writable, executable);
293                    assert_eq!(
294                        get_backing_memory_validate_flags(vmo_flags, options),
295                        Err(Status::ACCESS_DENIED)
296                    );
297                }
298                if !writable {
299                    let vmo_flags = rights_to_vmo_flags(readable, true, false);
300                    assert_eq!(
301                        get_backing_memory_validate_flags(vmo_flags, options),
302                        Err(Status::ACCESS_DENIED)
303                    );
304                }
305                if !executable {
306                    let vmo_flags = rights_to_vmo_flags(readable, false, true);
307                    assert_eq!(
308                        get_backing_memory_validate_flags(vmo_flags, options),
309                        Err(Status::ACCESS_DENIED)
310                    );
311                }
312            }
313        }
314    }
315}