sandbox/
dir_connector.rs

1// Copyright 2024 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::{CapabilityBound, DirReceiver};
6use cm_types::RelativePath;
7use fidl::endpoints::ServerEnd;
8use fidl_fuchsia_io as fio;
9use futures::channel::mpsc;
10use std::fmt::Debug;
11use std::sync::{Arc, LazyLock};
12
13/// These are the flags which may always be set when opening something through a DirConnector. See
14/// the comment on [`DirConnectable::maximum_flags`] for more information.
15static ALWAYS_ALLOWED_FLAGS: LazyLock<fio::Flags> = LazyLock::new(|| {
16    fio::Flags::PROTOCOL_SERVICE
17        | fio::Flags::PROTOCOL_NODE
18        | fio::Flags::PROTOCOL_DIRECTORY
19        | fio::Flags::PROTOCOL_FILE
20        | fio::Flags::PROTOCOL_SYMLINK
21        | fio::Flags::FLAG_SEND_REPRESENTATION
22        | fio::Flags::FLAG_MAYBE_CREATE
23        | fio::Flags::FLAG_MUST_CREATE
24        | fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY
25        | fio::Flags::FILE_APPEND
26        | fio::Flags::FILE_TRUNCATE
27});
28
29/// Types that implement [`DirConnectable`] let the holder send directory channels
30/// to them. Any `DirConnectable` should be wrapped in a [`DirConnector`].
31pub trait DirConnectable: Send + Sync + Debug {
32    /// Returns the maximum set of flags that may be passed to this DirConnectable. For example, to
33    /// disallow calling `send` with write permissions, this function could return
34    /// `fidl_fuchsia_io::PERM_READABLE`.
35    ///
36    /// The following flags are always permitted, regardless of the returned value:
37    ///
38    /// - `fidl_fuchsia_io::Flags::PROTOCOL_SERVICE`
39    /// - `fidl_fuchsia_io::Flags::PROTOCOL_NODE`
40    /// - `fidl_fuchsia_io::Flags::PROTOCOL_DIRECTORY`
41    /// - `fidl_fuchsia_io::Flags::PROTOCOL_FILE`
42    /// - `fidl_fuchsia_io::Flags::PROTOCOL_SYMLINK`
43    /// - `fidl_fuchsia_io::Flags::FLAG_SEND_REPRESENTATION`
44    /// - `fidl_fuchsia_io::Flags::FLAG_MAYBE_CREATE`
45    /// - `fidl_fuchsia_io::Flags::FLAG_MUST_CREATE`
46    /// - `fidl_fuchsia_io::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY`
47    /// - `fidl_fuchsia_io::Flags::FILE_APPEND`
48    /// - `fidl_fuchsia_io::Flags::FILE_TRUNCATE`
49    ///
50    /// If the returned value does not contain the set of flags
51    /// `fidl_fuchsia_io::INHERITED_WRITE_PERMISSIONS`, then
52    /// `fidl_fuchsia_io::Flags::PERM_INHERIT_WRITE` will be stripped from any flags passed to
53    /// `send` if it is set.
54    ///
55    /// If the returned value does not contain the flag
56    /// `fidl_fuchsia_io::Flags::PERM_INHERIT_EXECUTE`, then `fidl_fuchsia_io::Flags::PERM_EXECUTE`
57    /// will be stripped from any flags passed to `send` if it is set.
58    fn maximum_flags(&self) -> fio::Flags;
59
60    fn send(
61        &self,
62        dir: ServerEnd<fio::DirectoryMarker>,
63        subdir: RelativePath,
64        flags: Option<fio::Flags>,
65    ) -> Result<(), ()>;
66}
67
68impl DirConnectable for mpsc::UnboundedSender<ServerEnd<fio::DirectoryMarker>> {
69    fn maximum_flags(&self) -> fio::Flags {
70        fio::Flags::empty()
71    }
72
73    fn send(
74        &self,
75        dir: ServerEnd<fio::DirectoryMarker>,
76        subdir: RelativePath,
77        flags: Option<fio::Flags>,
78    ) -> Result<(), ()> {
79        assert_eq!(subdir, RelativePath::dot());
80        assert_eq!(flags, None);
81        self.unbounded_send(dir).map_err(|_| ())
82    }
83}
84
85/// A capability to obtain a channel to a [fuchsia.io/Directory]. As the name suggests, this is
86/// similar to [Connector], except the channel type is always [fuchsia.io/Directory], and vfs
87/// nodes that wrap this capability should have the `DIRECTORY` entry_type.
88#[derive(Debug, Clone)]
89pub struct DirConnector {
90    inner: Arc<dyn DirConnectable>,
91}
92
93impl CapabilityBound for DirConnector {
94    fn debug_typename() -> &'static str {
95        "DirConnector"
96    }
97}
98
99impl DirConnector {
100    pub fn new() -> (DirReceiver, Self) {
101        let (sender, receiver) = mpsc::unbounded();
102        let receiver = DirReceiver::new(receiver);
103        let this = Self::new_sendable(sender);
104        (receiver, this)
105    }
106
107    pub fn from_proxy(proxy: fio::DirectoryProxy, subdir: RelativePath, flags: fio::Flags) -> Self {
108        Self::new_sendable(DirectoryProxyForwarder { proxy, subdir, flags })
109    }
110
111    pub fn new_sendable(connector: impl DirConnectable + 'static) -> Self {
112        Self { inner: Arc::new(connector) }
113    }
114
115    pub fn send(
116        &self,
117        dir: ServerEnd<fio::DirectoryMarker>,
118        subdir: RelativePath,
119        mut flags: Option<fio::Flags>,
120    ) -> Result<(), ()> {
121        if let Some(flags) = flags.as_mut() {
122            let mut maximum_flags_and_always_allowed =
123                self.inner.maximum_flags() | *ALWAYS_ALLOWED_FLAGS;
124            if flags.contains(fio::Flags::PERM_INHERIT_WRITE) {
125                if !maximum_flags_and_always_allowed.contains(
126                    fio::Flags::from_bits(fio::INHERITED_WRITE_PERMISSIONS.bits()).unwrap(),
127                ) {
128                    flags.remove(fio::Flags::PERM_INHERIT_WRITE);
129                } else {
130                    maximum_flags_and_always_allowed.insert(fio::Flags::PERM_INHERIT_WRITE);
131                }
132            }
133            if flags.contains(fio::Flags::PERM_INHERIT_EXECUTE) {
134                if !maximum_flags_and_always_allowed.contains(fio::Flags::PERM_EXECUTE) {
135                    flags.remove(fio::Flags::PERM_INHERIT_EXECUTE);
136                } else {
137                    maximum_flags_and_always_allowed.insert(fio::Flags::PERM_INHERIT_EXECUTE);
138                }
139            }
140            if !maximum_flags_and_always_allowed.contains(*flags) {
141                // The caller has requested greater permissions than is allowed.
142                return Err(());
143            }
144        }
145        self.inner.send(dir, subdir, flags)
146    }
147
148    pub fn with_subdir(self, subdir: RelativePath) -> Self {
149        Self::new_sendable(DirConnectorSubdir { parent_dir_connector: self, subdir })
150    }
151}
152
153impl DirConnectable for DirConnector {
154    fn maximum_flags(&self) -> fio::Flags {
155        self.inner.maximum_flags()
156    }
157
158    fn send(
159        &self,
160        channel: ServerEnd<fio::DirectoryMarker>,
161        subdir: RelativePath,
162        flags: Option<fio::Flags>,
163    ) -> Result<(), ()> {
164        self.inner.send(channel, subdir, flags)
165    }
166}
167
168#[derive(Debug)]
169struct DirConnectorSubdir {
170    parent_dir_connector: DirConnector,
171    subdir: RelativePath,
172}
173
174impl DirConnectable for DirConnectorSubdir {
175    fn maximum_flags(&self) -> fio::Flags {
176        self.parent_dir_connector.maximum_flags()
177    }
178
179    fn send(
180        &self,
181        channel: ServerEnd<fio::DirectoryMarker>,
182        subdir: RelativePath,
183        flags: Option<fio::Flags>,
184    ) -> Result<(), ()> {
185        let mut combined_subdir = self.subdir.clone();
186        let success = combined_subdir.extend(subdir);
187        if !success {
188            // subdir is too long
189            return Err(());
190        }
191        self.parent_dir_connector.send(channel, combined_subdir, flags)
192    }
193}
194
195#[derive(Debug)]
196struct DirectoryProxyForwarder {
197    proxy: fio::DirectoryProxy,
198    subdir: RelativePath,
199    flags: fio::Flags,
200}
201
202impl DirConnectable for DirectoryProxyForwarder {
203    fn maximum_flags(&self) -> fio::Flags {
204        self.flags
205    }
206
207    fn send(
208        &self,
209        server_end: ServerEnd<fio::DirectoryMarker>,
210        subdir: RelativePath,
211        flags: Option<fio::Flags>,
212    ) -> Result<(), ()> {
213        let flags = flags.unwrap_or(self.flags | fio::Flags::PROTOCOL_DIRECTORY);
214        let mut combined_subdir = self.subdir.clone();
215        let success = combined_subdir.extend(subdir);
216        if !success {
217            // The requested path is too long.
218            return Err(());
219        }
220        self.proxy
221            .open(
222                &format!("{}", combined_subdir),
223                flags,
224                &fio::Options::default(),
225                server_end.into_channel(),
226            )
227            .map_err(|_| ())
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234    use fidl::endpoints;
235    use fidl::handle::{HandleBased, Rights};
236    use fidl_fuchsia_component_sandbox as fsandbox;
237    use futures::StreamExt;
238
239    // NOTE: sending-and-receiving tests are written in `receiver.rs`.
240
241    /// Tests that a DirConnector can be cloned by cloning its FIDL token.
242    /// and capabilities sent to the original and clone arrive at the same Receiver.
243    #[fuchsia::test]
244    async fn fidl_clone() {
245        let (receiver, sender) = DirConnector::new();
246
247        // Send a channel through the DirConnector.
248        let (_ch1, ch2) = endpoints::create_endpoints::<fio::DirectoryMarker>();
249        sender.send(ch2, RelativePath::dot(), None).unwrap();
250
251        // Convert the Sender to a FIDL token.
252        let connector: fsandbox::DirConnector = sender.into();
253
254        // Clone the Sender by cloning the token.
255        let token_clone = fsandbox::DirConnector {
256            token: connector.token.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
257        };
258        let connector_clone =
259            match crate::Capability::try_from(fsandbox::Capability::DirConnector(token_clone))
260                .unwrap()
261            {
262                crate::Capability::DirConnector(connector) => connector,
263                capability @ _ => panic!("wrong type {capability:?}"),
264            };
265
266        // Send a channel through the cloned Sender.
267        let (_ch1, ch2) = endpoints::create_endpoints::<fio::DirectoryMarker>();
268        connector_clone.send(ch2, RelativePath::dot(), None).unwrap();
269
270        // The Receiver should receive two channels, one from each connector.
271        for _ in 0..2 {
272            let _ch = receiver.receive().await.unwrap();
273        }
274    }
275
276    #[fuchsia::test]
277    async fn flags_check() {
278        #[derive(Debug)]
279        struct DirConnectableStruct {
280            maximum_flags: fio::Flags,
281            sender: mpsc::UnboundedSender<Option<fio::Flags>>,
282        }
283        impl DirConnectable for DirConnectableStruct {
284            fn maximum_flags(&self) -> fio::Flags {
285                self.maximum_flags
286            }
287            fn send(
288                &self,
289                _dir: ServerEnd<fio::DirectoryMarker>,
290                _subdir: RelativePath,
291                flags: Option<fio::Flags>,
292            ) -> Result<(), ()> {
293                self.sender.unbounded_send(flags).unwrap();
294                Ok(())
295            }
296        }
297
298        let (sender, mut receiver) = mpsc::unbounded();
299        let dc1 = DirConnector::new_sendable(DirConnectableStruct {
300            maximum_flags: fio::PERM_READABLE,
301            sender: sender.clone(),
302        });
303
304        for (input_flags, expected_output_flags) in [
305            (None, None),
306            (Some(fio::PERM_READABLE), Some(fio::PERM_READABLE)),
307            (
308                Some(fio::PERM_READABLE | fio::Flags::FLAG_MUST_CREATE),
309                Some(fio::PERM_READABLE | fio::Flags::FLAG_MUST_CREATE),
310            ),
311            (
312                Some(fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE),
313                Some(fio::PERM_READABLE | fio::Flags::PROTOCOL_FILE),
314            ),
315            (Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE), Some(fio::PERM_READABLE)),
316            (Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_EXECUTE), Some(fio::PERM_READABLE)),
317        ] {
318            let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
319            assert_eq!(
320                Ok(()),
321                dc1.send(server, RelativePath::dot(), input_flags),
322                "failed to send input {input_flags:?}"
323            );
324            assert_eq!(expected_output_flags, receiver.next().await.unwrap());
325        }
326
327        let dc2 = DirConnector::new_sendable(DirConnectableStruct {
328            maximum_flags: fio::PERM_READABLE | fio::PERM_WRITABLE,
329            sender: sender.clone(),
330        });
331        for (input_flags, expected_output_flags) in [
332            (Some(fio::PERM_WRITABLE), Some(fio::PERM_WRITABLE)),
333            (
334                Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE),
335                Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE),
336            ),
337            (Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_EXECUTE), Some(fio::PERM_READABLE)),
338        ] {
339            let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
340            assert_eq!(
341                Ok(()),
342                dc2.send(server, RelativePath::dot(), input_flags),
343                "failed to send input {input_flags:?}"
344            );
345            assert_eq!(expected_output_flags, receiver.next().await.unwrap());
346        }
347
348        let dc3 = DirConnector::new_sendable(DirConnectableStruct {
349            maximum_flags: fio::PERM_READABLE | fio::PERM_EXECUTABLE,
350            sender: sender.clone(),
351        });
352        for (input_flags, expected_output_flags) in [
353            (Some(fio::PERM_EXECUTABLE), Some(fio::PERM_EXECUTABLE)),
354            (Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_WRITE), Some(fio::PERM_READABLE)),
355            (
356                Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_EXECUTE),
357                Some(fio::PERM_READABLE | fio::Flags::PERM_INHERIT_EXECUTE),
358            ),
359        ] {
360            let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
361            assert_eq!(
362                Ok(()),
363                dc3.send(server, RelativePath::dot(), input_flags),
364                "failed to send input {input_flags:?}"
365            );
366            assert_eq!(expected_output_flags, receiver.next().await.unwrap());
367        }
368
369        for (maximum_flags, input_flags) in [
370            (fio::PERM_READABLE, fio::PERM_READABLE | fio::PERM_EXECUTABLE),
371            (fio::PERM_READABLE | fio::PERM_WRITABLE, fio::PERM_EXECUTABLE),
372            (fio::PERM_READABLE | fio::PERM_EXECUTABLE, fio::PERM_WRITABLE),
373        ] {
374            let dc = DirConnector::new_sendable(DirConnectableStruct {
375                maximum_flags,
376                sender: sender.clone(),
377            });
378            let (_client, server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
379            assert_eq!(
380                Err(()),
381                dc.send(server, RelativePath::dot(), Some(input_flags)),
382                "unexpectedly succeeded at sending input {input_flags:?}"
383            );
384        }
385    }
386}