Skip to main content

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