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