Skip to main content

routing/bedrock/
with_rights.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::error::RoutingError;
6use crate::rights::Rights;
7use async_trait::async_trait;
8use capability_source::CapabilitySource;
9use fidl_fuchsia_component_runtime::RouteRequest;
10use fidl_fuchsia_io as fio;
11use moniker::ExtendedMoniker;
12use router_error::RouterError;
13use runtime_capabilities::{CapabilityBound, Routable, Router, WeakInstanceToken};
14use std::sync::Arc;
15
16struct RightsRouter<T: CapabilityBound> {
17    router: Arc<Router<T>>,
18    rights: Rights,
19    moniker: ExtendedMoniker,
20}
21
22impl<T: CapabilityBound> RightsRouter<T> {
23    fn check_and_compute_rights(
24        &self,
25        mut request: RouteRequest,
26    ) -> Result<RouteRequest, RouterError> {
27        if request == RouteRequest::default() {
28            return Err(RouterError::InvalidArgs);
29        }
30        let RightsRouter { router: _, rights, moniker } = self;
31        let inherit = request.inherit_rights.ok_or(RouterError::InvalidArgs)?;
32        let request_rights: Rights = match request.directory_rights {
33            Some(request_rights) => request_rights.into(),
34            None => {
35                if inherit {
36                    request.directory_rights = Some(fio::Flags::from(*rights));
37                    *rights
38                } else {
39                    Err(RouterError::InvalidArgs)?
40                }
41            }
42        };
43        // The rights of the previous step (if any) of the route must be
44        // compatible with this step of the route.
45        if let Some(intermediate_rights) = request.directory_intermediate_rights {
46            Rights::from(intermediate_rights)
47                .validate_next(&rights, moniker.clone().into())
48                .map_err(|e| router_error::RouterError::from(RoutingError::from(e)))?;
49        };
50        request.directory_intermediate_rights = Some(fio::Flags::from(*rights));
51        // The rights of the request must be compatible with the
52        // rights of this step of the route.
53        match request_rights.validate_next(&rights, moniker.clone().into()) {
54            Ok(()) => Ok(request),
55            Err(e) => Err(RoutingError::from(e).into()),
56        }
57    }
58}
59
60#[async_trait]
61impl<T: CapabilityBound> Routable<T> for RightsRouter<T> {
62    async fn route(
63        &self,
64        request: RouteRequest,
65        target: Arc<WeakInstanceToken>,
66    ) -> Result<Option<Arc<T>>, RouterError> {
67        let request = self.check_and_compute_rights(request)?;
68        self.router.route(request, target).await
69    }
70
71    async fn route_debug(
72        &self,
73        request: RouteRequest,
74        target: Arc<WeakInstanceToken>,
75    ) -> Result<CapabilitySource, RouterError> {
76        let request = self.check_and_compute_rights(request)?;
77        self.router.route_debug(request, target).await
78    }
79}
80
81pub trait WithRights {
82    /// Returns a router that ensures the capability request does not request
83    /// greater rights than provided at this stage of the route.
84    fn with_rights(self, moniker: impl Into<ExtendedMoniker>, rights: Rights) -> Self;
85}
86
87impl<T: CapabilityBound> WithRights for Arc<Router<T>> {
88    fn with_rights(self, moniker: impl Into<ExtendedMoniker>, rights: Rights) -> Self {
89        Router::<T>::new(RightsRouter { rights, router: self, moniker: moniker.into() })
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use assert_matches::assert_matches;
97    use fidl_fuchsia_io as fio;
98    use router_error::RouterError;
99    use runtime_capabilities::{Data, WeakInstanceToken};
100    use std::sync::Arc;
101
102    #[derive(Debug)]
103    struct FakeComponentToken {}
104
105    impl FakeComponentToken {
106        fn new() -> Arc<WeakInstanceToken> {
107            Arc::new(WeakInstanceToken { inner: Box::new(FakeComponentToken {}) })
108        }
109    }
110
111    impl runtime_capabilities::WeakInstanceTokenAny for FakeComponentToken {
112        fn as_any(&self) -> &dyn std::any::Any {
113            self
114        }
115    }
116
117    #[fuchsia::test]
118    async fn rights_good() {
119        let source = Arc::new(Data::String("hello".into()));
120        let base = Router::<Data>::new_ok(source);
121        let proxy = base.with_rights(ExtendedMoniker::ComponentManager, fio::RW_STAR_DIR.into());
122        let request = RouteRequest {
123            directory_rights: Some(fio::PERM_READABLE),
124            inherit_rights: Some(false),
125            ..Default::default()
126        };
127        let capability = proxy.route(request, FakeComponentToken::new()).await.unwrap();
128        let capability = match capability {
129            Some(d) => d,
130            c => panic!("Bad enum {:#?}", c),
131        };
132        assert_eq!(&*capability, &Data::String("hello".into()));
133    }
134
135    #[fuchsia::test]
136    async fn rights_bad() {
137        let source = Arc::new(Data::String("hello".into()));
138        let base = Router::<Data>::new_ok(source);
139        let proxy = base.with_rights(ExtendedMoniker::ComponentManager, fio::R_STAR_DIR.into());
140        let request = RouteRequest {
141            directory_rights: Some(fio::PERM_READABLE | fio::PERM_WRITABLE),
142            inherit_rights: Some(false),
143            ..Default::default()
144        };
145        let error = proxy.route(request, FakeComponentToken::new()).await.unwrap_err();
146        assert_matches!(
147            error,
148            RouterError::NotFound(err)
149            if matches!(
150                err.as_any().downcast_ref::<RoutingError>(),
151                Some(RoutingError::RightsRoutingError(
152                    crate::error::RightsRoutingError::Invalid { moniker: ExtendedMoniker::ComponentManager, requested, provided }
153                )) if *requested == <fio::Operations as Into<Rights>>::into(fio::RW_STAR_DIR) && *provided == <fio::Operations as Into<Rights>>::into(fio::R_STAR_DIR)
154            )
155        );
156    }
157
158    #[fuchsia::test]
159    async fn invalid_intermediate_rights() {
160        let source = Data::String("hello".into());
161        let base = Router::<Data>::new_ok(source)
162            .with_rights(ExtendedMoniker::ComponentManager, fio::R_STAR_DIR.into());
163        let intermediate =
164            base.with_rights(ExtendedMoniker::ComponentManager, fio::RW_STAR_DIR.into());
165        let request = RouteRequest {
166            directory_rights: Some(fio::PERM_READABLE),
167            inherit_rights: Some(false),
168            ..Default::default()
169        };
170        let error = intermediate.route(request, FakeComponentToken::new()).await.unwrap_err();
171        assert_matches!(
172            error,
173            RouterError::NotFound(err)
174            if matches!(
175                err.as_any().downcast_ref::<RoutingError>(),
176                Some(RoutingError::RightsRoutingError(
177                    crate::error::RightsRoutingError::Invalid { moniker: ExtendedMoniker::ComponentManager, requested, provided }
178                )) if *requested == <fio::Operations as Into<Rights>>::into(fio::RW_STAR_DIR) && *provided == <fio::Operations as Into<Rights>>::into(fio::R_STAR_DIR)
179            )
180        );
181    }
182}