starnix_kernel_runner/
component_runner.rs

1// Copyright 2022 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::{MountAction, run_component_features};
6use anyhow::{Context, Error, anyhow, bail};
7use fidl::AsyncChannel;
8use fidl::endpoints::{ControlHandle, RequestStream, ServerEnd};
9use fidl_fuchsia_component_runner::{
10    ComponentControllerMarker, ComponentControllerRequest, ComponentControllerRequestStream,
11    ComponentStartInfo,
12};
13use fuchsia_runtime::{HandleInfo, HandleType};
14use futures::channel::oneshot;
15use futures::{FutureExt, StreamExt};
16use rand::distr::Alphanumeric;
17use rand::{Rng, rng};
18use serde::Deserialize;
19use serde::de::Error as _;
20use starnix_core::execution::{create_init_child_process, execute_task_with_prerun_result};
21use starnix_core::fs::fuchsia::{RemoteFs, SyslogFile, create_file_from_handle};
22use starnix_core::task::{CurrentTask, ExitStatus, Task};
23use starnix_core::vfs::fs_args::MountParams;
24use starnix_core::vfs::{
25    FdNumber, FdTable, FileSystemOptions, FsString, LookupContext, NamespaceNode, WhatToMount,
26};
27use starnix_core::{security, signals};
28use starnix_logging::{log_debug, log_error, log_info, log_warn};
29use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Mutex, Unlocked};
30use starnix_task_command::TaskCommand;
31use starnix_types::ownership::WeakRef;
32use starnix_uapi::auth::{Capabilities, Credentials};
33use starnix_uapi::device_type::DeviceType;
34use starnix_uapi::errno;
35use starnix_uapi::errors::{EEXIST, ENOTDIR, Errno};
36use starnix_uapi::file_mode::mode;
37use starnix_uapi::mount_flags::MountFlags;
38use starnix_uapi::open_flags::OpenFlags;
39use starnix_uapi::signals::{SIGINT, SIGKILL};
40use starnix_uapi::unmount_flags::UnmountFlags;
41use std::ffi::CString;
42use std::ops::DerefMut;
43use std::os::unix::ffi::OsStrExt;
44use std::path::Path;
45use std::sync::Arc;
46use {
47    fidl_fuchsia_component as fcomponent, fidl_fuchsia_io as fio, fidl_fuchsia_process as fprocess,
48    fuchsia_async as fasync,
49};
50
51/// Component controller epitaph value used as the base value to pass non-zero error
52/// codes to the calling component.
53///
54/// TODO(https://fxbug.dev/42081234): Cleanup this once we have a proper mechanism to
55/// get Linux exit code from component runner.
56const COMPONENT_EXIT_CODE_BASE: i32 = 1024;
57
58#[derive(Debug, Deserialize)]
59#[serde(deny_unknown_fields)]
60struct ComponentProgram {
61    binary: CString,
62
63    #[serde(default)]
64    args: Vec<String>,
65
66    #[serde(default)]
67    environ: Vec<String>,
68
69    #[serde(default)]
70    cwd: Option<String>,
71
72    #[serde(default)]
73    uid: Option<runner::serde::StoreAsString<u32>>,
74
75    #[serde(default)]
76    component_mounts: Vec<String>,
77
78    #[serde(default)]
79    features: Vec<String>,
80
81    #[serde(default, deserialize_with = "parse_capabilities")]
82    capabilities: Option<Capabilities>,
83
84    #[serde(default)]
85    seclabel: Option<CString>,
86
87    #[serde(default, rename(deserialize = "test_target_kernel"))]
88    _test_target_kernel: Option<String>,
89}
90
91impl ComponentProgram {
92    fn resolve_templates(&mut self, component_path: &str, pkg_path: &str) {
93        let resolve_template = |values: &mut Vec<String>| {
94            for val in values {
95                *val = val
96                    .replace("{pkg_path}", &pkg_path)
97                    .replace("{component_path}", &component_path);
98            }
99        };
100
101        resolve_template(&mut self.args);
102        resolve_template(&mut self.environ);
103    }
104}
105
106fn parse_capabilities<'de, D>(deserializer: D) -> Result<Option<Capabilities>, D::Error>
107where
108    D: serde::Deserializer<'de>,
109{
110    let mut capabilities = Capabilities::empty();
111    for cap in Vec::<String>::deserialize(deserializer)? {
112        capabilities |= cap.parse().map_err(D::Error::custom)?;
113    }
114    Ok(Some(capabilities))
115}
116
117/// Starts a component inside the given container.
118///
119/// The component's `binary` can either:
120///   - an absolute path, in which case the path is treated as a path into the root filesystem that
121///     is mounted by the container's configuration
122///   - relative path, in which case the binary is read from the component's package (which is
123///     mounted at /container/component/{random}/pkg.)
124///
125/// The directories in the component's namespace are mounted at /container/component/{random}.
126pub async fn start_component(
127    mut start_info: ComponentStartInfo,
128    controller: ServerEnd<ComponentControllerMarker>,
129    system_task: &CurrentTask,
130) -> Result<(), Error> {
131    let url = start_info.resolved_url.clone().unwrap_or_else(|| "<unknown>".to_string());
132
133    let (task_complete_sender, task_complete) = oneshot::channel::<TaskResult>();
134
135    let weak_task = system_task.override_creds(
136        security::creds_start_internal_operation(system_task),
137        || {
138            // TODO(https://fxbug.dev/42076551): We leak the directory created by this function.
139            let component_path = generate_component_path(
140                system_task.kernel().kthreads.unlocked_for_async().deref_mut(),
141                system_task,
142            )?;
143            let pkg_path = format!("{component_path}/pkg");
144
145            let mount_record = Arc::new(Mutex::new(MountRecord::default()));
146
147            let ns = start_info.ns.take().ok_or_else(|| anyhow!("Missing namespace"))?;
148
149            let program = start_info.program.as_ref().context("reading program block")?;
150            let mut program: ComponentProgram =
151                runner::serde::deserialize_program(program).context("parsing program block")?;
152            program.resolve_templates(&component_path, &pkg_path);
153            log_debug!("start_component: {}\n{:#?}", url, program);
154
155            let ns_mount_options = system_task.kernel().features.default_ns_mount_options.as_ref();
156            let mut maybe_pkg = None;
157            let mut maybe_svc = None;
158            for entry in ns {
159                if let (Some(dir_path), Some(dir_handle)) = (entry.path, entry.directory) {
160                    let dir_path_str = dir_path.as_str();
161                    let mount_options = ns_mount_options
162                        .and_then(|mount_options| mount_options.get(dir_path_str).cloned());
163
164                    match dir_path_str {
165                        "/svc" => {
166                            maybe_svc = Some(fio::DirectoryProxy::new(AsyncChannel::from_channel(
167                                dir_handle.into_channel(),
168                            )));
169                        }
170                        "/custom_artifacts" => {
171                            // Mount custom_artifacts directory at root of container
172                            // We may want to transition to have this directory unique per component
173                            let dir_proxy =
174                                fio::DirectorySynchronousProxy::new(dir_handle.into_channel());
175                            mount_record
176                                .lock()
177                                .mount_remote(
178                                    system_task.kernel().kthreads.unlocked_for_async().deref_mut(),
179                                    system_task,
180                                    &dir_proxy,
181                                    &dir_path,
182                                    mount_options.as_ref(),
183                                )
184                                .with_context(|| {
185                                    format!("failed to mount_remote on path {}", &dir_path)
186                                })?;
187                        }
188                        _ => {
189                            let dir_proxy =
190                                fio::DirectorySynchronousProxy::new(dir_handle.into_channel());
191                            mount_record
192                                .lock()
193                                .mount_remote(
194                                    system_task.kernel().kthreads.unlocked_for_async().deref_mut(),
195                                    system_task,
196                                    &dir_proxy,
197                                    &format!("{component_path}/{dir_path}"),
198                                    mount_options.as_ref(),
199                                )
200                                .with_context(|| {
201                                    format!(
202                                        "failed to mount_remote on path {component_path}/{dir_path}"
203                                    )
204                                })?;
205                            if dir_path == "/pkg" {
206                                maybe_pkg = Some(dir_proxy);
207                            }
208                        }
209                    }
210                }
211            }
212
213            let pkg = maybe_pkg.ok_or_else(|| anyhow!("Missing /pkg entry in namespace"))?;
214
215            let uid = program
216                .uid
217                .map(|uid| uid.0)
218                .unwrap_or_else(|| system_task.kernel().features.default_uid);
219
220            let mut credentials = Credentials::with_ids(uid, uid);
221            if let Some(capabilities) = program.capabilities {
222                credentials.cap_permitted = capabilities;
223                credentials.cap_effective = capabilities;
224                credentials.cap_inheritable = capabilities;
225                credentials.cap_ambient = capabilities;
226            }
227
228            run_component_features(system_task.kernel(), &program.features, maybe_svc)
229                .unwrap_or_else(|e| {
230                    log_error!("failed to set component features for {} - {:?}", url, e);
231                });
232
233            let current_task = create_init_child_process(
234                system_task.kernel().kthreads.unlocked_for_async().deref_mut(),
235                system_task.kernel(),
236                TaskCommand::new(program.binary.as_bytes()),
237                program.seclabel.as_ref(),
238            )?;
239
240            execute_task_with_prerun_result(
241                system_task.kernel().kthreads.unlocked_for_async().deref_mut(),
242                current_task,
243                {
244                    let mount_record = mount_record.clone();
245                    move |locked, current_task| {
246                        let cwd_path = FsString::from(program.cwd.unwrap_or(pkg_path));
247                        let cwd = current_task.lookup_path(
248                            locked,
249                            &mut LookupContext::default(),
250                            current_task.fs().root(),
251                            cwd_path.as_ref(),
252                        )?;
253                        current_task.fs().chdir(locked, current_task, cwd)?;
254
255                        current_task.set_creds(credentials);
256
257                        for mount in &program.component_mounts {
258                            let action = MountAction::from_spec(locked, current_task, &pkg, mount)
259                                .map_err(|e| {
260                                    log_error!("Error while mounting the filesystems: {e:?}");
261                                    errno!(EINVAL)
262                                })?;
263                            let mount_point =
264                                current_task.lookup_path_from_root(locked, action.path.as_ref())?;
265                            mount_record.lock().mount(
266                                mount_point,
267                                WhatToMount::Fs(action.fs),
268                                action.flags,
269                            )?;
270                        }
271
272                        parse_numbered_handles(
273                            locked,
274                            current_task,
275                            start_info.numbered_handles,
276                            &current_task.files,
277                        )
278                        .map_err(|e| {
279                            log_error!("Error while parsing the numbered handles: {e:?}");
280                            errno!(EINVAL)
281                        })?;
282
283                        let mut argv = vec![program.binary.clone()];
284                        for arg in program.args {
285                            argv.push(CString::new(arg).map_err(|_| errno!(EINVAL))?);
286                        }
287
288                        let mut environ = vec![];
289                        for env in program.environ {
290                            environ.push(CString::new(env).map_err(|_| errno!(EINVAL))?);
291                        }
292
293                        let executable = current_task.open_file(
294                            locked,
295                            program.binary.as_bytes().into(),
296                            OpenFlags::RDONLY,
297                        )?;
298                        current_task.exec(locked, executable, program.binary, argv, environ)?;
299
300                        Ok(WeakRef::from(&current_task.task))
301                    }
302                },
303                move |result| {
304                    // Unmount all the directories for this component.
305                    std::mem::drop(mount_record);
306
307                    // If the component controller server has gone away, there is nobody for us to
308                    // report the result to.
309                    let _ = task_complete_sender.send(result);
310                },
311                None,
312            )
313            .map_err(anyhow::Error::from)
314        },
315    )?;
316
317    let controller = controller.into_stream();
318    fasync::Task::local(serve_component_controller(controller, weak_task, task_complete)).detach();
319
320    Ok(())
321}
322
323type TaskResult = Result<ExitStatus, Error>;
324
325/// Translates [ComponentControllerRequest] messages to signals on the `task`.
326///
327/// When a `Stop` request is received, it will send a `SIGINT` to the task.
328/// When a `Kill` request is received, it will send a `SIGKILL` to the task and close the component
329/// controller channel regardless if/how the task responded to the signal. Due to Linux's design,
330/// this may not reliably cleanup everything that was started as a result of running the component.
331///
332/// If the task has completed, it will also close the controller channel.
333async fn serve_component_controller(
334    controller: ComponentControllerRequestStream,
335    task: WeakRef<Task>,
336    task_complete: oneshot::Receiver<TaskResult>,
337) {
338    let controller_handle = controller.control_handle();
339
340    enum Event<T, U> {
341        Controller(T),
342        Completion(U),
343    }
344
345    let mut stream = futures::stream::select(
346        controller.map(Event::Controller),
347        task_complete.into_stream().map(Event::Completion),
348    );
349
350    while let Some(event) = stream.next().await {
351        match event {
352            Event::Controller(request) => match request {
353                Ok(ComponentControllerRequest::Stop { .. }) => {
354                    if let Some(task) = task.upgrade() {
355                        signals::send_standard_signal(
356                            task.kernel().kthreads.unlocked_for_async().deref_mut(),
357                            task.as_ref(),
358                            signals::SignalInfo::kernel(SIGINT),
359                        );
360                        log_info!("Sent SIGINT to program {}", task.command());
361                    }
362                }
363                Ok(ComponentControllerRequest::Kill { .. }) => {
364                    if let Some(task) = task.upgrade() {
365                        signals::send_standard_signal(
366                            task.kernel().kthreads.unlocked_for_async().deref_mut(),
367                            &task,
368                            signals::SignalInfo::kernel(SIGKILL),
369                        );
370                        log_info!("Sent SIGKILL to program {}", task.command());
371                        controller_handle.shutdown_with_epitaph(zx::Status::from_raw(
372                            fcomponent::Error::InstanceDied.into_primitive() as i32,
373                        ));
374                    }
375                    return;
376                }
377                Ok(ComponentControllerRequest::_UnknownMethod { ordinal, .. }) => {
378                    log_warn!("Unknown ComponentController request: {ordinal}");
379                }
380                Err(_) => {
381                    return;
382                }
383            },
384            Event::Completion(result) => match result {
385                Ok(Ok(ExitStatus::Exit(0))) => {
386                    controller_handle.shutdown_with_epitaph(zx::Status::OK)
387                }
388                Ok(Ok(ExitStatus::Exit(n))) => controller_handle.shutdown_with_epitaph(
389                    zx::Status::from_raw(COMPONENT_EXIT_CODE_BASE + n as i32),
390                ),
391                _ => controller_handle.shutdown_with_epitaph(zx::Status::from_raw(
392                    fcomponent::Error::InstanceDied.into_primitive() as i32,
393                )),
394            },
395        }
396    }
397}
398
399/// Returns /container/component/{random} that doesn't already exist
400fn generate_component_path<L>(
401    locked: &mut Locked<L>,
402    system_task: &CurrentTask,
403) -> Result<String, Error>
404where
405    L: LockEqualOrBefore<FileOpsCore>,
406{
407    // Checking container directory already exists.
408    // If this lookup fails, the container might not have the "container" feature enabled.
409    let mount_point = system_task.lookup_path_from_root(locked, "/container/component/".into())?;
410
411    // Find /container/component/{random} that doesn't already exist
412    let component_path = loop {
413        let random_string: String =
414            rng().sample_iter(&Alphanumeric).take(10).map(char::from).collect();
415
416        // This returns EEXIST if /container/component/{random} already exists.
417        // If so, try again with another {random} string.
418        match mount_point.create_node(
419            locked,
420            system_task,
421            random_string.as_str().into(),
422            mode!(IFDIR, 0o755),
423            DeviceType::NONE,
424        ) {
425            Ok(_) => break format!("/container/component/{random_string}"),
426            Err(errno) if errno == EEXIST => {}
427            Err(e) => bail!(e),
428        };
429    };
430
431    Ok(component_path)
432}
433
434/// Adds the given startup handles to a CurrentTask.
435///
436/// The `numbered_handles` of type `HandleType::FileDescriptor` are used to
437/// create files, and the handles are required to be of type `zx::Socket`.
438///
439/// If there is a `numbered_handles` of type `HandleType::User0`, that is
440/// interpreted as the server end of the ShellController protocol.
441pub fn parse_numbered_handles(
442    locked: &mut Locked<Unlocked>,
443    current_task: &CurrentTask,
444    numbered_handles: Option<Vec<fprocess::HandleInfo>>,
445    files: &FdTable,
446) -> Result<(), Error> {
447    if let Some(numbered_handles) = numbered_handles {
448        for numbered_handle in numbered_handles {
449            let info = HandleInfo::try_from(numbered_handle.id)?;
450            if info.handle_type() == HandleType::FileDescriptor {
451                let file = create_file_from_handle(locked, current_task, numbered_handle.handle)?;
452                files.insert(locked, current_task, FdNumber::from_raw(info.arg().into()), file)?;
453            }
454        }
455    }
456
457    let stdio = SyslogFile::new_file(locked, current_task);
458    // If no numbered handle is provided for each stdio handle, default to syslog.
459    for i in [0, 1, 2] {
460        if files.get(FdNumber::from_raw(i)).is_err() {
461            files.insert(locked, current_task, FdNumber::from_raw(i), stdio.clone())?;
462        }
463    }
464
465    Ok(())
466}
467
468/// A record of the mounts created when starting a component.
469///
470/// When the record is dropped, the mounts are unmounted.
471#[derive(Default)]
472struct MountRecord {
473    /// The namespace nodes at which we have crated mounts for this component.
474    mounts: Vec<NamespaceNode>,
475}
476
477impl MountRecord {
478    fn mount(
479        &mut self,
480        mount_point: NamespaceNode,
481        what: WhatToMount,
482        flags: MountFlags,
483    ) -> Result<(), Errno> {
484        mount_point.mount(what, flags)?;
485        self.mounts.push(mount_point);
486        Ok(())
487    }
488
489    fn mount_remote<L>(
490        &mut self,
491        locked: &mut Locked<L>,
492        system_task: &CurrentTask,
493        directory: &fio::DirectorySynchronousProxy,
494        path: &str,
495        mount_options: Option<&String>,
496    ) -> Result<(), Error>
497    where
498        L: LockEqualOrBefore<FileOpsCore>,
499    {
500        // The incoming dir_path might not be top level, e.g. it could be /foo/bar.
501        // Iterate through each component directory starting from the parent and
502        // create it if it doesn't exist.
503        let mut current_node =
504            system_task.lookup_path_from_root(locked, ".".into()).context("looking up '.'")?;
505        let mut context = LookupContext::default();
506
507        // Extract each component using Path::new(path).components(). For example,
508        // Path::new("/foo/bar").components() will return [RootDir, Normal("foo"), Normal("bar")].
509        // We're not interested in the RootDir, so we drop the prefix "/" if it exists.
510        let path = if let Some(path) = path.strip_prefix('/') { path } else { path };
511
512        for sub_dir in Path::new(path).components() {
513            let sub_dir_bytes = sub_dir.as_os_str().as_bytes();
514            current_node = match current_node.create_node(
515                locked,
516                system_task,
517                sub_dir_bytes.into(),
518                mode!(IFDIR, 0o755),
519                DeviceType::NONE,
520            ) {
521                Ok(node) => node,
522                Err(errno) if errno == EEXIST || errno == ENOTDIR => current_node
523                    .lookup_child(locked, system_task, &mut context, sub_dir_bytes.into())
524                    .with_context(|| format!("looking up {sub_dir:?}"))?,
525                Err(e) => bail!(e),
526            };
527        }
528
529        let flags = directory
530            .get_flags(zx::MonotonicInstant::INFINITE)
531            .context("transport error")?
532            .map_err(zx::Status::from_raw)
533            .context("get_flags")?;
534        let rights = flags.intersection(fio::MASK_KNOWN_PERMISSIONS);
535
536        let (client_end, server_end) = zx::Channel::create();
537        directory.clone(ServerEnd::new(server_end)).context("cloning directory")?;
538
539        // If a filesystem security label argument was provided then apply it to all files via
540        // mountpoint-labeling, with a "context=..." mount option.
541        let params = if let Some(mount_options) = mount_options {
542            MountParams::parse(mount_options.as_str().into())
543                .expect("failed to parse default_ns_mount_options")
544        } else {
545            MountParams::default()
546        };
547
548        let fs = RemoteFs::new_fs(
549            locked,
550            system_task.kernel(),
551            client_end,
552            FileSystemOptions { source: path.into(), params, ..Default::default() },
553            rights,
554        )
555        .context("making remote fs")?;
556
557        security::file_system_resolve_security(locked, system_task, &fs)
558            .context("resolving security")?;
559
560        // Fuchsia doesn't specify mount flags in the incoming namespace, so we need to make
561        // up some flags.
562        let flags = MountFlags::NOSUID | MountFlags::NODEV | MountFlags::RELATIME;
563        current_node.mount(WhatToMount::Fs(fs), flags).context("mounting fs")?;
564        self.mounts.push(current_node);
565
566        Ok(())
567    }
568
569    fn unmount(&mut self) -> Result<(), Errno> {
570        while let Some(node) = self.mounts.pop() {
571            node.unmount(UnmountFlags::DETACH)?;
572        }
573        Ok(())
574    }
575}
576
577impl Drop for MountRecord {
578    fn drop(&mut self) {
579        match self.unmount() {
580            Ok(()) => {}
581            Err(e) => log_error!("failed to unmount during component exit: {:?}", e),
582        }
583    }
584}