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