library_loader/
lib.rs

1// Copyright 2019 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 anyhow::{format_err, Error};
6use fidl::prelude::*;
7use fidl_fuchsia_ldsvc::{LoaderRequest, LoaderRequestStream};
8use futures::{TryFutureExt, TryStreamExt};
9use log::*;
10use {fidl_fuchsia_io as fio, fuchsia_async as fasync};
11
12/// Helper function to load `object_name` from `search_dirs`.
13/// This function looks in the given directories, and returns the
14/// first VMO matching |object_name| that is found.
15pub async fn load_object(
16    search_dirs: &Vec<fio::DirectoryProxy>,
17    object_name: &str,
18) -> Result<zx::Vmo, Vec<Error>> {
19    let mut errors = vec![];
20    for dir_proxy in search_dirs {
21        match load_vmo(dir_proxy, &object_name).await {
22            Ok(b) => {
23                return Ok(b);
24            }
25            Err(e) => errors.push(e),
26        }
27    }
28    Err(errors.into())
29}
30
31/// start will expose the `fuchsia.ldsvc.Loader` service over the given channel, providing VMO
32/// buffers of requested library object names from `lib_proxy`.
33///
34/// `lib_proxy` must have been opened with at minimum OPEN_RIGHT_READABLE and OPEN_RIGHT_EXECUTABLE
35/// rights.
36pub fn start(lib_proxy: fio::DirectoryProxy, chan: zx::Channel) {
37    start_with_multiple_dirs(vec![lib_proxy], chan);
38}
39
40/// start_with_multiple_dirs will expose the `fuchsia.ldsvc.Loader` service over the given channel,
41/// providing VMO buffers of requested library object names from any of the library directories in
42/// `lib_dirs`.
43///
44/// Each library directory must have been opened with at minimum OPEN_RIGHT_READABLE and
45/// OPEN_RIGHT_EXECUTABLE rights.
46pub fn start_with_multiple_dirs(lib_dirs: Vec<fio::DirectoryProxy>, chan: zx::Channel) {
47    fasync::Task::spawn(
48        async move {
49            let mut search_dirs = lib_dirs.clone();
50            // Wait for requests
51            let mut stream = LoaderRequestStream::from_channel(fasync::Channel::from_channel(chan));
52            while let Some(req) = stream.try_next().await? {
53                match req {
54                    LoaderRequest::Done { control_handle } => {
55                        control_handle.shutdown();
56                    }
57                    LoaderRequest::LoadObject { object_name, responder } => {
58                        match load_object(&search_dirs, &object_name).await {
59                            Ok(vmo) => responder.send(zx::sys::ZX_OK, Some(vmo))?,
60                            Err(e) => {
61                                warn!("failed to load object: {:?}", e);
62                                responder.send(zx::sys::ZX_ERR_NOT_FOUND, None)?;
63                            }
64                        }
65                    }
66                    LoaderRequest::Config { config, responder } => {
67                        match parse_config_string(&lib_dirs, &config) {
68                            Ok(new_search_path) => {
69                                search_dirs = new_search_path;
70                                responder.send(zx::sys::ZX_OK)?;
71                            }
72                            Err(e) => {
73                                warn!("failed to parse config: {}", e);
74                                responder.send(zx::sys::ZX_ERR_INVALID_ARGS)?;
75                            }
76                        }
77                    }
78                    LoaderRequest::Clone { loader, responder } => {
79                        start_with_multiple_dirs(lib_dirs.clone(), loader.into_channel());
80                        responder.send(zx::sys::ZX_OK)?;
81                    }
82                }
83            }
84            Ok(())
85        }
86        .unwrap_or_else(|e: Error| warn!("couldn't run library loader service: {}", e)),
87    )
88    .detach();
89}
90
91/// load_vmo will attempt to open the provided name in `dir` and return an executable VMO
92/// with the contents.
93///
94/// `dir` must have been opened with at minimum OPEN_RIGHT_READABLE and OPEN_RIGHT_EXECUTABLE
95/// rights.
96pub async fn load_vmo<'a>(
97    dir: &impl fuchsia_component::directory::AsRefDirectory,
98    object_name: &'a str,
99) -> Result<zx::Vmo, Error> {
100    let file_proxy =
101        fuchsia_component::directory::open_file_async(dir, object_name, fio::RX_STAR_DIR)?;
102    // TODO(https://fxbug.dev/42129773): This does not ask or wait for a Describe event, which means a failure to
103    // open the file will appear as a PEER_CLOSED error on this call.
104    let vmo = file_proxy
105        .get_backing_memory(
106            // Clone the VMO because it could still be written by the debugger.
107            fio::VmoFlags::READ | fio::VmoFlags::EXECUTE | fio::VmoFlags::PRIVATE_CLONE,
108        )
109        .await
110        .map_err(|e| format_err!("reading object at {:?} failed: {}", object_name, e))?
111        .map_err(|status| {
112            let status = zx::Status::from_raw(status);
113            format_err!("reading object at {:?} failed: {}", object_name, status)
114        })?;
115    Ok(vmo)
116}
117
118/// parses a config string from the `fuchsia.ldsvc.Loader` service. See
119/// `//docs/concepts/booting/program_loading.md` for a description of the format. Returns the set
120/// of directories which should be searched for objects.
121pub fn parse_config_string(
122    lib_dirs: &Vec<fio::DirectoryProxy>,
123    config: &str,
124) -> Result<Vec<fio::DirectoryProxy>, Error> {
125    if config.contains("/") {
126        return Err(format_err!("'/' character found in loader service config string"));
127    }
128    let (config, search_root) = match config.strip_suffix('!') {
129        Some(config) => (config, false),
130        None => (config, true),
131    };
132    let mut search_dirs = vec![];
133    for dir_proxy in lib_dirs {
134        let sub_dir_proxy = fuchsia_fs::directory::open_directory_async(
135            dir_proxy,
136            config,
137            fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE,
138        )?;
139        search_dirs.push(sub_dir_proxy);
140    }
141    if search_root {
142        search_dirs.append(&mut lib_dirs.clone());
143    }
144    Ok(search_dirs)
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use fidl_fuchsia_ldsvc::LoaderMarker;
151
152    async fn list_directory<'a>(root_proxy: &'a fio::DirectoryProxy) -> Vec<String> {
153        let entries = fuchsia_fs::directory::readdir(root_proxy).await.expect("readdir failed");
154        entries.iter().map(|entry| entry.name.clone()).collect::<Vec<String>>()
155    }
156
157    #[fasync::run_singlethreaded(test)]
158    async fn load_objects_test() -> Result<(), Error> {
159        // Open this test's real /pkg/lib directory to use for this test, and then check to see
160        // whether an asan subdirectory is present, and use it instead if so.
161        // TODO(https://fxbug.dev/42061196): Replace conditional logic with a pseudo-directory using Rust VFS.
162        let rights = fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE;
163        let mut pkg_lib = fuchsia_fs::directory::open_in_namespace("/pkg/lib", rights)?;
164        let entries = list_directory(&pkg_lib).await;
165        if let Some(name) = [
166            "asan",
167            "asan-ubsan",
168            "coverage",
169            "coverage-cts",
170            "coverage-rust",
171            "hwasan",
172            "hwasan-ubsan",
173            "profile",
174        ]
175        .iter()
176        .find(|&&name| entries.iter().any(|f| f == name))
177        {
178            pkg_lib = fuchsia_fs::directory::open_directory_async(&pkg_lib, name, rights)?;
179        }
180        let (loader_proxy, loader_service) = fidl::endpoints::create_proxy::<LoaderMarker>();
181        start(pkg_lib.into(), loader_service.into_channel());
182
183        for (obj_name, should_succeed) in vec![
184            // Should be able to access lib/ld.so.1
185            ("ld.so.1", true),
186            // Should be able to access lib/libfdio.so
187            ("libfdio.so", true),
188            // Should not be able to access lib/lib/ld.so.1
189            ("lib/ld.so.1", false),
190            // Should not be able to access lib/../lib/ld.so.1
191            ("../lib/ld.so.1", false),
192            // Should not be able to access test/component_manager_tests
193            ("../test/component_manager_tests", false),
194            // Should not be able to access lib/bin/hello_world
195            ("bin/hello_world", false),
196            // Should not be able to access bin/hello_world
197            ("../bin/hello_world", false),
198            // Should not be able to access meta/hello_world.cm
199            ("../meta/hello_world.cm", false),
200        ] {
201            let (res, o_vmo) = loader_proxy.load_object(obj_name).await?;
202            if should_succeed {
203                assert_eq!(zx::sys::ZX_OK, res, "loading {} did not succeed", obj_name);
204                assert!(o_vmo.is_some());
205            } else {
206                assert_ne!(zx::sys::ZX_OK, res, "loading {} did not fail", obj_name);
207                assert!(o_vmo.is_none());
208            }
209        }
210        Ok(())
211    }
212
213    #[fasync::run_singlethreaded(test)]
214    async fn config_test() -> Result<(), Error> {
215        // This /pkg/lib/config_test/ directory is added by the build rules for this test package,
216        // since we need a directory that supports OPEN_RIGHT_EXECUTABLE. It contains a file 'foo'
217        // which contains 'hippos' and a file 'bar/baz' (that is, baz in a subdirectory bar) which
218        // contains 'rule'.
219        // TODO(https://fxbug.dev/42061196): Replace conditional logic with a pseudo-directory using Rust VFS.
220        let pkg_lib = fuchsia_fs::directory::open_in_namespace(
221            "/pkg/lib/config_test/",
222            fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE,
223        )?;
224        let (loader_proxy, loader_service) = fidl::endpoints::create_proxy::<LoaderMarker>();
225        start(pkg_lib.into(), loader_service.into_channel());
226
227        // Attempt to access things with different configurations
228        for (obj_name, config, expected_result) in vec![
229            // Should be able to load foo
230            ("foo", None, Some("hippos")),
231            // Should not be able to load bar (it's a directory)
232            ("bar", None, None),
233            // Should not be able to load baz (it's in a sub directory)
234            ("baz", None, None),
235            // Should be able to load baz with config "bar!" (only look in sub directory bar)
236            ("baz", Some("bar!"), Some("rule")),
237            // Should not be able to load foo with config "bar!" (only look in sub directory bar)
238            ("foo", Some("bar!"), None),
239            // Should be able to load foo with config "bar" (also look in sub directory bar)
240            ("foo", Some("bar"), Some("hippos")),
241            // Should be able to load baz with config "bar" (also look in sub directory bar)
242            ("baz", Some("bar"), Some("rule")),
243        ] {
244            if let Some(config) = config {
245                assert_eq!(zx::sys::ZX_OK, loader_proxy.config(config).await?);
246            }
247
248            let (res, o_vmo) = loader_proxy.load_object(obj_name).await?;
249            if let Some(expected_result) = expected_result {
250                assert_eq!(zx::sys::ZX_OK, res);
251                let mut buf = vec![0; expected_result.len()];
252                o_vmo.ok_or(format_err!("missing vmo"))?.read(&mut buf, 0)?;
253                assert_eq!(expected_result.as_bytes(), buf.as_slice());
254            } else {
255                assert_ne!(zx::sys::ZX_OK, res);
256                assert!(o_vmo.is_none());
257            }
258        }
259        Ok(())
260    }
261
262    #[fasync::run_singlethreaded(test)]
263    async fn load_objects_multiple_dir_test() -> Result<(), Error> {
264        // This /pkg/lib/config_test/ directory is added by the build rules for this test package,
265        // since we need a directory that supports OPEN_RIGHT_EXECUTABLE. It contains a file 'foo'
266        // which contains 'hippos' and a file 'bar/baz' (that is, baz in a subdirectory bar) which
267        // contains 'rule'.
268        // TODO(https://fxbug.dev/42061196): Replace conditional logic with a pseudo-directory using Rust VFS.
269        let pkg_lib_1 = fuchsia_fs::directory::open_in_namespace(
270            "/pkg/lib/config_test/",
271            fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE,
272        )?;
273        let pkg_lib_2 = fuchsia_fs::directory::open_in_namespace(
274            "/pkg/lib/config_test/bar",
275            fuchsia_fs::PERM_READABLE | fuchsia_fs::PERM_EXECUTABLE,
276        )?;
277
278        let (loader_proxy, loader_service) = fidl::endpoints::create_proxy::<LoaderMarker>();
279        start_with_multiple_dirs(
280            vec![pkg_lib_1.into(), pkg_lib_2.into()],
281            loader_service.into_channel(),
282        );
283
284        for (obj_name, should_succeed) in vec![
285            // Should be able to access foo from dir #1
286            ("foo", true),
287            // Should be able to access baz from dir #2
288            ("baz", true),
289            // Should not be able to access bar (it's a directory)
290            ("bar", false),
291        ] {
292            let (res, o_vmo) = loader_proxy.load_object(obj_name).await?;
293            if should_succeed {
294                assert_eq!(zx::sys::ZX_OK, res, "loading {} did not succeed", obj_name);
295                assert!(o_vmo.is_some());
296            } else {
297                assert_ne!(zx::sys::ZX_OK, res, "loading {} did not fail", obj_name);
298                assert!(o_vmo.is_none());
299            }
300        }
301        Ok(())
302    }
303}