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    // As documented in the fuchsia.io interface, if VmoFlags::EXECUTE is requested, ensure that the
90    // connection also has OPEN_RIGHT_READABLE.
91    if vmo_flags.contains(fio::VmoFlags::EXECUTE)
92        && !options.rights.intersects(fio::Operations::READ_BYTES)
93    {
94        return Err(Status::ACCESS_DENIED);
95    }
96
97    Ok(())
98}
99
100#[cfg(test)]
101mod tests {
102    use super::new_connection_validate_options;
103    use crate::file::FileOptions;
104    use crate::protocols::ToFileOptions;
105    use crate::test_utils::build_flag_combinations;
106
107    use assert_matches::assert_matches;
108    use fidl_fuchsia_io as fio;
109    use zx_status::Status;
110
111    fn options_to_rights(options: FileOptions) -> (bool, bool, bool) {
112        return (
113            options.rights.intersects(fio::Operations::READ_BYTES),
114            options.rights.intersects(fio::Operations::WRITE_BYTES),
115            options.rights.intersects(fio::Operations::EXECUTE),
116        );
117    }
118
119    fn ncvf(
120        flags: impl ToFileOptions,
121        readable: bool,
122        writable: bool,
123        executable: bool,
124    ) -> Result<FileOptions, Status> {
125        let options = flags.to_file_options()?;
126        new_connection_validate_options(&options, readable, writable, executable)?;
127        Ok(options)
128    }
129
130    #[test]
131    fn new_connection_validate_flags_create() {
132        for open_flags in build_flag_combinations(
133            fio::Flags::FLAG_MAYBE_CREATE,
134            fio::Flags::PERM_READ_BYTES
135                | fio::Flags::PERM_WRITE_BYTES
136                | fio::Flags::FLAG_MUST_CREATE,
137        ) {
138            let options = open_flags.to_file_options().unwrap();
139            let (readable, writable, executable) = options_to_rights(options);
140            assert_matches!(ncvf(options, readable, writable, executable), Ok(_));
141        }
142    }
143
144    #[test]
145    fn new_connection_validate_flags_truncate() {
146        assert_matches!(
147            ncvf(fio::Flags::PERM_READ_BYTES | fio::Flags::FILE_TRUNCATE, true, true, false),
148            Err(Status::INVALID_ARGS)
149        );
150        assert_matches!(
151            ncvf(fio::Flags::PERM_WRITE_BYTES | fio::Flags::FILE_TRUNCATE, true, true, false),
152            Ok(_)
153        );
154        assert_matches!(
155            ncvf(fio::Flags::PERM_READ_BYTES | fio::Flags::FILE_TRUNCATE, true, false, false),
156            Err(Status::INVALID_ARGS)
157        );
158    }
159
160    #[test]
161    fn new_connection_validate_flags_append() {
162        assert_matches!(
163            ncvf(fio::Flags::PERM_READ_BYTES | fio::Flags::FILE_APPEND, true, false, false),
164            Ok(_)
165        );
166        assert_matches!(
167            ncvf(fio::Flags::PERM_READ_BYTES | fio::Flags::FILE_APPEND, true, true, false),
168            Ok(_)
169        );
170        assert_matches!(
171            ncvf(fio::Flags::PERM_READ_BYTES | fio::Flags::FILE_APPEND, true, true, false),
172            Ok(_)
173        );
174    }
175
176    #[test]
177    fn new_connection_validate_flags_open_rights() {
178        for open_flags in build_flag_combinations(
179            fio::Flags::empty(),
180            fio::Flags::PERM_READ_BYTES | fio::Flags::PERM_READ_BYTES | fio::Flags::PERM_EXECUTE,
181        ) {
182            let options = open_flags.to_file_options().unwrap();
183            let (readable, writable, executable) = options_to_rights(options);
184
185            // Ensure all combinations are valid except when both writable and executable are set,
186            // as this combination is disallowed.
187            if !(writable && executable) {
188                assert_matches!(ncvf(options, readable, writable, executable), Ok(_));
189            }
190
191            // Ensure we report ACCESS_DENIED if open_flags exceeds the supported connection rights.
192            if readable && !(writable && executable) {
193                assert_eq!(ncvf(options, false, writable, executable), Err(Status::ACCESS_DENIED));
194            }
195            if writable {
196                assert_eq!(ncvf(options, readable, false, executable), Err(Status::ACCESS_DENIED));
197            }
198            if executable {
199                assert_eq!(ncvf(options, readable, writable, false), Err(Status::ACCESS_DENIED));
200            }
201        }
202    }
203
204    #[cfg(target_os = "fuchsia")]
205    mod vmo_tests {
206        use super::super::{get_backing_memory_validate_flags, vmo_flags_to_rights};
207        use super::*;
208
209        fn rights_to_vmo_flags(readable: bool, writable: bool, executable: bool) -> fio::VmoFlags {
210            return if readable { fio::VmoFlags::READ } else { fio::VmoFlags::empty() }
211                | if writable { fio::VmoFlags::WRITE } else { fio::VmoFlags::empty() }
212                | if executable { fio::VmoFlags::EXECUTE } else { fio::VmoFlags::empty() };
213        }
214
215        /// Validates that the passed VMO flags are correctly mapped to their respective Rights.
216        #[test]
217        fn test_vmo_flags_to_rights() {
218            for vmo_flags in build_flag_combinations(
219                fio::VmoFlags::empty(),
220                fio::VmoFlags::READ | fio::VmoFlags::WRITE | fio::VmoFlags::EXECUTE,
221            ) {
222                let rights: fidl::Rights = vmo_flags_to_rights(vmo_flags);
223                assert_eq!(
224                    vmo_flags.contains(fio::VmoFlags::READ),
225                    rights.contains(fidl::Rights::READ)
226                );
227                assert_eq!(
228                    vmo_flags.contains(fio::VmoFlags::WRITE),
229                    rights.contains(fidl::Rights::WRITE)
230                );
231                assert_eq!(
232                    vmo_flags.contains(fio::VmoFlags::EXECUTE),
233                    rights.contains(fidl::Rights::EXECUTE)
234                );
235            }
236        }
237
238        #[test]
239        fn get_backing_memory_validate_flags_invalid() {
240            // Cannot specify both PRIVATE and EXACT at the same time, since they conflict.
241            assert_eq!(
242                get_backing_memory_validate_flags(
243                    fio::VmoFlags::PRIVATE_CLONE | fio::VmoFlags::SHARED_BUFFER,
244                    fio::Flags::empty().to_file_options().unwrap()
245                ),
246                Err(Status::INVALID_ARGS)
247            );
248        }
249
250        /// Ensure that the check passes if we request the same or less rights
251        /// than the connection has.
252        #[test]
253        fn get_backing_memory_validate_flags_less_rights() {
254            for open_flags in build_flag_combinations(
255                fio::Flags::empty(),
256                fio::Flags::PERM_READ_BYTES
257                    | fio::Flags::PERM_WRITE_BYTES
258                    | fio::Flags::PERM_EXECUTE,
259            ) {
260                let options = open_flags.to_file_options().unwrap();
261                let (readable, writable, executable) = options_to_rights(options);
262                let vmo_flags = rights_to_vmo_flags(readable, writable, executable);
263
264                // The io1.fidl protocol specifies that VmoFlags::EXECUTE requires the connection to be
265                // both readable and executable.
266                if executable && !readable {
267                    assert_eq!(
268                        get_backing_memory_validate_flags(vmo_flags, options),
269                        Err(Status::ACCESS_DENIED)
270                    );
271                    continue;
272                }
273
274                // Ensure that we can open the VMO with the same rights as the connection.
275                get_backing_memory_validate_flags(vmo_flags, options)
276                    .expect("Failed to validate flags");
277
278                // Ensure that we can also open the VMO with *less* rights than the connection has.
279                if readable {
280                    let vmo_flags = rights_to_vmo_flags(false, writable, false);
281                    get_backing_memory_validate_flags(vmo_flags, options)
282                        .expect("Failed to validate flags");
283                }
284                if writable {
285                    let vmo_flags = rights_to_vmo_flags(readable, false, executable);
286                    get_backing_memory_validate_flags(vmo_flags, options)
287                        .expect("Failed to validate flags");
288                }
289                if executable {
290                    let vmo_flags = rights_to_vmo_flags(true, writable, false);
291                    get_backing_memory_validate_flags(vmo_flags, options)
292                        .expect("Failed to validate flags");
293                }
294            }
295        }
296
297        /// Ensure that vmo_flags cannot exceed rights of connection_flags.
298        #[test]
299        fn get_backing_memory_validate_flags_more_rights() {
300            for open_flags in build_flag_combinations(
301                fio::Flags::empty(),
302                fio::Flags::PERM_READ_BYTES
303                    | fio::Flags::PERM_WRITE_BYTES
304                    | fio::Flags::PERM_EXECUTE,
305            ) {
306                let options = open_flags.to_file_options().unwrap();
307                // Ensure we cannot return a VMO with more rights than the connection itself has.
308                let (readable, writable, executable) = options_to_rights(options);
309                if !readable {
310                    let vmo_flags = rights_to_vmo_flags(true, writable, executable);
311                    assert_eq!(
312                        get_backing_memory_validate_flags(vmo_flags, options),
313                        Err(Status::ACCESS_DENIED)
314                    );
315                }
316                if !writable {
317                    let vmo_flags = rights_to_vmo_flags(readable, true, false);
318                    assert_eq!(
319                        get_backing_memory_validate_flags(vmo_flags, options),
320                        Err(Status::ACCESS_DENIED)
321                    );
322                }
323                if !executable {
324                    let vmo_flags = rights_to_vmo_flags(readable, false, true);
325                    assert_eq!(
326                        get_backing_memory_validate_flags(vmo_flags, options),
327                        Err(Status::ACCESS_DENIED)
328                    );
329                }
330            }
331        }
332    }
333}