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    connection_flags: fio::OpenFlags,
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        && !connection_flags.intersects(fio::OpenFlags::RIGHT_READABLE)
75    {
76        return Err(Status::ACCESS_DENIED);
77    }
78    if vmo_flags.contains(fio::VmoFlags::WRITE)
79        && !connection_flags.intersects(fio::OpenFlags::RIGHT_WRITABLE)
80    {
81        return Err(Status::ACCESS_DENIED);
82    }
83    if vmo_flags.contains(fio::VmoFlags::EXECUTE)
84        && !connection_flags.intersects(fio::OpenFlags::RIGHT_EXECUTABLE)
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        && !connection_flags.intersects(fio::OpenFlags::RIGHT_READABLE)
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 io_flags_to_rights(flags: fio::OpenFlags) -> (bool, bool, bool) {
112        return (
113            flags.intersects(fio::OpenFlags::RIGHT_READABLE),
114            flags.intersects(fio::OpenFlags::RIGHT_WRITABLE),
115            flags.intersects(fio::OpenFlags::RIGHT_EXECUTABLE),
116        );
117    }
118
119    fn ncvf(
120        flags: fio::OpenFlags,
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_posix() {
132        // OPEN_FLAG_POSIX_* is ignored for files.
133        const ALL_POSIX_FLAGS: fio::OpenFlags = fio::OpenFlags::empty()
134            .union(fio::OpenFlags::POSIX_WRITABLE)
135            .union(fio::OpenFlags::POSIX_EXECUTABLE);
136        for open_flags in build_flag_combinations(
137            fio::OpenFlags::empty(),
138            fio::OpenFlags::RIGHT_READABLE
139                | fio::OpenFlags::RIGHT_WRITABLE
140                | fio::OpenFlags::RIGHT_EXECUTABLE
141                | ALL_POSIX_FLAGS,
142        ) {
143            let (readable, writable, executable) = io_flags_to_rights(open_flags);
144            // Skip disallowed W+X combinations, and skip combinations without any POSIX flags.
145            if (writable && executable) || !open_flags.intersects(ALL_POSIX_FLAGS) {
146                continue;
147            }
148            assert_matches!(ncvf(open_flags, readable, writable, executable), Ok(_));
149        }
150    }
151
152    #[test]
153    fn new_connection_validate_flags_create() {
154        for open_flags in build_flag_combinations(
155            fio::OpenFlags::CREATE,
156            fio::OpenFlags::RIGHT_READABLE
157                | fio::OpenFlags::RIGHT_WRITABLE
158                | fio::OpenFlags::CREATE_IF_ABSENT,
159        ) {
160            let (readable, writable, executable) = io_flags_to_rights(open_flags);
161            assert_matches!(ncvf(open_flags, readable, writable, executable), Ok(_));
162        }
163    }
164
165    #[test]
166    fn new_connection_validate_flags_truncate() {
167        assert_matches!(
168            ncvf(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::TRUNCATE, true, true, false,),
169            Err(Status::INVALID_ARGS)
170        );
171        assert_matches!(
172            ncvf(fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::TRUNCATE, true, true, false),
173            Ok(_)
174        );
175        assert_matches!(
176            ncvf(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::TRUNCATE, true, false, false),
177            Err(Status::INVALID_ARGS)
178        );
179    }
180
181    #[test]
182    fn new_connection_validate_flags_append() {
183        assert_matches!(
184            ncvf(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::APPEND, true, false, false),
185            Ok(_)
186        );
187        assert_matches!(
188            ncvf(fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::APPEND, true, true, false),
189            Ok(_)
190        );
191        assert_matches!(
192            ncvf(fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::APPEND, true, true, false),
193            Ok(_)
194        );
195    }
196
197    #[test]
198    fn new_connection_validate_flags_open_rights() {
199        for open_flags in build_flag_combinations(
200            fio::OpenFlags::empty(),
201            fio::OpenFlags::RIGHT_READABLE
202                | fio::OpenFlags::RIGHT_WRITABLE
203                | fio::OpenFlags::RIGHT_EXECUTABLE,
204        ) {
205            let (readable, writable, executable) = io_flags_to_rights(open_flags);
206
207            // Ensure all combinations are valid except when both writable and executable are set,
208            // as this combination is disallowed.
209            if !(writable && executable) {
210                assert_matches!(ncvf(open_flags, readable, writable, executable), Ok(_));
211            }
212
213            // Ensure we report ACCESS_DENIED if open_flags exceeds the supported connection rights.
214            if readable && !(writable && executable) {
215                assert_eq!(
216                    ncvf(open_flags, false, writable, executable),
217                    Err(Status::ACCESS_DENIED)
218                );
219            }
220            if writable {
221                assert_eq!(
222                    ncvf(open_flags, readable, false, executable),
223                    Err(Status::ACCESS_DENIED)
224                );
225            }
226            if executable {
227                assert_eq!(ncvf(open_flags, readable, writable, false), Err(Status::ACCESS_DENIED));
228            }
229        }
230    }
231
232    #[cfg(target_os = "fuchsia")]
233    mod vmo_tests {
234        use super::super::{get_backing_memory_validate_flags, vmo_flags_to_rights};
235        use super::*;
236
237        fn rights_to_vmo_flags(readable: bool, writable: bool, executable: bool) -> fio::VmoFlags {
238            return if readable { fio::VmoFlags::READ } else { fio::VmoFlags::empty() }
239                | if writable { fio::VmoFlags::WRITE } else { fio::VmoFlags::empty() }
240                | if executable { fio::VmoFlags::EXECUTE } else { fio::VmoFlags::empty() };
241        }
242
243        /// Validates that the passed VMO flags are correctly mapped to their respective Rights.
244        #[test]
245        fn test_vmo_flags_to_rights() {
246            for vmo_flags in build_flag_combinations(
247                fio::VmoFlags::empty(),
248                fio::VmoFlags::READ | fio::VmoFlags::WRITE | fio::VmoFlags::EXECUTE,
249            ) {
250                let rights: fidl::Rights = vmo_flags_to_rights(vmo_flags);
251                assert_eq!(
252                    vmo_flags.contains(fio::VmoFlags::READ),
253                    rights.contains(fidl::Rights::READ)
254                );
255                assert_eq!(
256                    vmo_flags.contains(fio::VmoFlags::WRITE),
257                    rights.contains(fidl::Rights::WRITE)
258                );
259                assert_eq!(
260                    vmo_flags.contains(fio::VmoFlags::EXECUTE),
261                    rights.contains(fidl::Rights::EXECUTE)
262                );
263            }
264        }
265
266        #[test]
267        fn get_backing_memory_validate_flags_invalid() {
268            // Cannot specify both PRIVATE and EXACT at the same time, since they conflict.
269            assert_eq!(
270                get_backing_memory_validate_flags(
271                    fio::VmoFlags::PRIVATE_CLONE | fio::VmoFlags::SHARED_BUFFER,
272                    fio::OpenFlags::empty()
273                ),
274                Err(Status::INVALID_ARGS)
275            );
276        }
277
278        /// Ensure that the check passes if we request the same or less rights
279        /// than the connection has.
280        #[test]
281        fn get_backing_memory_validate_flags_less_rights() {
282            for open_flags in build_flag_combinations(
283                fio::OpenFlags::empty(),
284                fio::OpenFlags::RIGHT_READABLE
285                    | fio::OpenFlags::RIGHT_WRITABLE
286                    | fio::OpenFlags::RIGHT_EXECUTABLE,
287            ) {
288                let (readable, writable, executable) = io_flags_to_rights(open_flags);
289                let vmo_flags = rights_to_vmo_flags(readable, writable, executable);
290
291                // The io1.fidl protocol specifies that VmoFlags::EXECUTE requires the connection to be
292                // both readable and executable.
293                if executable && !readable {
294                    assert_eq!(
295                        get_backing_memory_validate_flags(vmo_flags, open_flags),
296                        Err(Status::ACCESS_DENIED)
297                    );
298                    continue;
299                }
300
301                // Ensure that we can open the VMO with the same rights as the connection.
302                get_backing_memory_validate_flags(vmo_flags, open_flags)
303                    .expect("Failed to validate flags");
304
305                // Ensure that we can also open the VMO with *less* rights than the connection has.
306                if readable {
307                    let vmo_flags = rights_to_vmo_flags(false, writable, false);
308                    get_backing_memory_validate_flags(vmo_flags, open_flags)
309                        .expect("Failed to validate flags");
310                }
311                if writable {
312                    let vmo_flags = rights_to_vmo_flags(readable, false, executable);
313                    get_backing_memory_validate_flags(vmo_flags, open_flags)
314                        .expect("Failed to validate flags");
315                }
316                if executable {
317                    let vmo_flags = rights_to_vmo_flags(true, writable, false);
318                    get_backing_memory_validate_flags(vmo_flags, open_flags)
319                        .expect("Failed to validate flags");
320                }
321            }
322        }
323
324        /// Ensure that vmo_flags cannot exceed rights of connection_flags.
325        #[test]
326        fn get_backing_memory_validate_flags_more_rights() {
327            for open_flags in build_flag_combinations(
328                fio::OpenFlags::empty(),
329                fio::OpenFlags::RIGHT_READABLE
330                    | fio::OpenFlags::RIGHT_WRITABLE
331                    | fio::OpenFlags::RIGHT_EXECUTABLE,
332            ) {
333                // Ensure we cannot return a VMO with more rights than the connection itself has.
334                let (readable, writable, executable) = io_flags_to_rights(open_flags);
335                if !readable {
336                    let vmo_flags = rights_to_vmo_flags(true, writable, executable);
337                    assert_eq!(
338                        get_backing_memory_validate_flags(vmo_flags, open_flags),
339                        Err(Status::ACCESS_DENIED)
340                    );
341                }
342                if !writable {
343                    let vmo_flags = rights_to_vmo_flags(readable, true, false);
344                    assert_eq!(
345                        get_backing_memory_validate_flags(vmo_flags, open_flags),
346                        Err(Status::ACCESS_DENIED)
347                    );
348                }
349                if !executable {
350                    let vmo_flags = rights_to_vmo_flags(readable, false, true);
351                    assert_eq!(
352                        get_backing_memory_validate_flags(vmo_flags, open_flags),
353                        Err(Status::ACCESS_DENIED)
354                    );
355                }
356            }
357        }
358    }
359}