elf_runner/
process_launcher.rs

1// Copyright 2023 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::vdso_vmo::get_stable_vdso_vmo;
6use anyhow::Context;
7use cm_types::NamespacePath;
8use fidl_connector::Connect;
9use fidl_fuchsia_process as fproc;
10use fuchsia_runtime::{HandleInfo, HandleInfoError};
11use futures::prelude::*;
12use process_builder::{
13    BuiltProcess, NamespaceEntry, ProcessBuilder, ProcessBuilderError, StartupHandle,
14};
15use std::ffi::CString;
16use std::fmt::Debug;
17use std::sync::{Arc, LazyLock};
18use thiserror::Error;
19use zx::{self as zx, AsHandleRef, sys};
20
21/// Internal error type for ProcessLauncher which conveniently wraps errors that might
22/// result during process launching and allows for mapping them to an equivalent zx::Status, which
23/// is what actually gets returned through the protocol.
24#[derive(Error, Debug)]
25enum LauncherError {
26    #[error("Invalid arg: {}", _0)]
27    InvalidArg(&'static str),
28    #[error("Failed to build new process: {}", _0)]
29    BuilderError(ProcessBuilderError),
30    #[error("Invalid handle info: {}", _0)]
31    HandleInfoError(HandleInfoError),
32}
33
34impl LauncherError {
35    pub fn as_zx_status(&self) -> zx::Status {
36        match self {
37            LauncherError::InvalidArg(_) => zx::Status::INVALID_ARGS,
38            LauncherError::BuilderError(e) => e.as_zx_status(),
39            LauncherError::HandleInfoError(_) => zx::Status::INVALID_ARGS,
40        }
41    }
42}
43
44impl From<ProcessBuilderError> for LauncherError {
45    fn from(err: ProcessBuilderError) -> Self {
46        LauncherError::BuilderError(err)
47    }
48}
49
50impl From<HandleInfoError> for LauncherError {
51    fn from(err: HandleInfoError) -> Self {
52        LauncherError::HandleInfoError(err)
53    }
54}
55
56#[derive(Default, Debug)]
57struct ProcessLauncherState {
58    args: Vec<Vec<u8>>,
59    environ: Vec<Vec<u8>>,
60    name_info: Vec<fproc::NameInfo>,
61    handles: Vec<fproc::HandleInfo>,
62    options: zx::ProcessOptions,
63}
64
65/// Similar to fproc::LaunchInfo, but with the job wrapped in an Arc (to enable use after the struct
66/// is moved).
67#[derive(Debug)]
68struct LaunchInfo {
69    executable: zx::Vmo,
70    job: Arc<zx::Job>,
71    name: String,
72}
73
74impl From<fproc::LaunchInfo> for LaunchInfo {
75    fn from(info: fproc::LaunchInfo) -> Self {
76        LaunchInfo { executable: info.executable, job: Arc::new(info.job), name: info.name }
77    }
78}
79
80/// An implementation of the `fuchsia.process.Launcher` protocol using the `process_builder` crate.
81pub struct ProcessLauncher;
82
83impl ProcessLauncher {
84    /// Serves an instance of the `fuchsia.process.Launcher` protocol given an appropriate
85    /// RequestStream. Returns when the channel backing the RequestStream is closed or an
86    /// unrecoverable error, like a failure to read from the stream, occurs.
87    pub async fn serve(mut stream: fproc::LauncherRequestStream) -> Result<(), fidl::Error> {
88        // `fuchsia.process.Launcher is stateful. The Add methods accumulate state that is
89        // consumed/reset by either Launch or CreateWithoutStarting.
90        let mut state = ProcessLauncherState::default();
91
92        while let Some(req) = stream.try_next().await? {
93            match req {
94                fproc::LauncherRequest::Launch { info, responder } => {
95                    let info = LaunchInfo::from(info);
96                    let job = info.job.clone();
97                    let name = info.name.clone();
98
99                    match Self::launch_process(info, state).await {
100                        Ok(process) => {
101                            responder.send(zx::Status::OK.into_raw(), Some(process))?;
102                        }
103                        Err(err) => {
104                            log_launcher_error(&err, "launch", job, name);
105                            responder.send(err.as_zx_status().into_raw(), None)?;
106                        }
107                    }
108
109                    // Reset state to defaults.
110                    state = ProcessLauncherState::default();
111                }
112                fproc::LauncherRequest::CreateWithoutStarting { info, responder } => {
113                    let info = LaunchInfo::from(info);
114                    let job = info.job.clone();
115                    let name = info.name.clone();
116
117                    match Self::create_process(info, state).await {
118                        Ok(built) => {
119                            let process_data = fproc::ProcessStartData {
120                                process: built.process,
121                                root_vmar: built.root_vmar,
122                                thread: built.thread,
123                                entry: built.entry as u64,
124                                stack: built.stack as u64,
125                                bootstrap: built.bootstrap,
126                                vdso_base: built.vdso_base as u64,
127                                base: built.elf_base as u64,
128                            };
129                            responder.send(zx::Status::OK.into_raw(), Some(process_data))?;
130                        }
131                        Err(err) => {
132                            log_launcher_error(&err, "create", job, name);
133                            responder.send(err.as_zx_status().into_raw(), None)?;
134                        }
135                    }
136
137                    // Reset state to defaults.
138                    state = ProcessLauncherState::default();
139                }
140                fproc::LauncherRequest::AddArgs { mut args, control_handle: _ } => {
141                    state.args.append(&mut args);
142                }
143                fproc::LauncherRequest::AddEnvirons { mut environ, control_handle: _ } => {
144                    state.environ.append(&mut environ);
145                }
146                fproc::LauncherRequest::AddNames { mut names, control_handle: _ } => {
147                    state.name_info.append(&mut names);
148                }
149                fproc::LauncherRequest::AddHandles { mut handles, control_handle: _ } => {
150                    state.handles.append(&mut handles);
151                }
152                fproc::LauncherRequest::SetOptions { options, .. } => {
153                    // These options are passed directly to `zx_process_create`, which
154                    // will determine whether or not the options are valid.
155                    state.options = zx::ProcessOptions::from_bits_retain(options);
156                }
157            }
158        }
159        Ok(())
160    }
161
162    async fn launch_process(
163        info: LaunchInfo,
164        state: ProcessLauncherState,
165    ) -> Result<zx::Process, LauncherError> {
166        Ok(Self::create_process(info, state).await?.start()?)
167    }
168
169    async fn create_process(
170        info: LaunchInfo,
171        state: ProcessLauncherState,
172    ) -> Result<BuiltProcess, LauncherError> {
173        Ok(Self::create_process_builder(info, state)?.build().await?)
174    }
175
176    fn create_process_builder(
177        info: LaunchInfo,
178        state: ProcessLauncherState,
179    ) -> Result<ProcessBuilder, LauncherError> {
180        let proc_name = CString::new(info.name)
181            .map_err(|_| LauncherError::InvalidArg("Process name contained null byte"))?;
182        let stable_vdso_vmo = get_stable_vdso_vmo().map_err(|_| {
183            LauncherError::BuilderError(ProcessBuilderError::BadHandle("Failed to get stable vDSO"))
184        })?;
185        let mut b = ProcessBuilder::new(
186            &proc_name,
187            &info.job,
188            state.options,
189            info.executable,
190            stable_vdso_vmo,
191        )?;
192
193        let arg_cstr = state
194            .args
195            .into_iter()
196            .map(|a| CString::new(a))
197            .collect::<Result<_, _>>()
198            .map_err(|_| LauncherError::InvalidArg("Argument contained null byte"))?;
199        b.add_arguments(arg_cstr);
200
201        let env_cstr = state
202            .environ
203            .into_iter()
204            .map(|e| CString::new(e))
205            .collect::<Result<_, _>>()
206            .map_err(|_| LauncherError::InvalidArg("Environment string contained null byte"))?;
207        b.add_environment_variables(env_cstr);
208
209        let entries = state
210            .name_info
211            .into_iter()
212            .map(|n| Self::new_namespace_entry(n))
213            .collect::<Result<_, _>>()?;
214        b.add_namespace_entries(entries)?;
215
216        // Note that clients of ``fuchsia.process.Launcher` provide the `fuchsia.ldsvc.Loader`
217        // through AddHandles, with a handle type of [HandleType::LdsvcLoader].
218        // [ProcessBuilder::add_handles] automatically handles that for convenience.
219        let handles = state
220            .handles
221            .into_iter()
222            .map(|h| Self::new_startup_handle(h))
223            .collect::<Result<_, _>>()?;
224        b.add_handles(handles)?;
225
226        Ok(b)
227    }
228
229    // Can't impl TryFrom for these because both types are from external crates. :(
230    // Could wrap in a newtype, but then have to unwrap, so this is simplest.
231    fn new_namespace_entry(info: fproc::NameInfo) -> Result<NamespaceEntry, LauncherError> {
232        let cstr = CString::new(info.path)
233            .map_err(|_| LauncherError::InvalidArg("Namespace path contained null byte"))?;
234        Ok(NamespaceEntry { path: cstr, directory: info.directory })
235    }
236
237    fn new_startup_handle(info: fproc::HandleInfo) -> Result<StartupHandle, LauncherError> {
238        Ok(StartupHandle { handle: info.handle, info: HandleInfo::try_from(info.id)? })
239    }
240}
241
242#[derive(Debug, PartialEq)]
243enum LogStyle {
244    JobKilled,
245    Warn,
246    Error,
247}
248
249#[derive(Debug, PartialEq)]
250struct LogInfo {
251    style: LogStyle,
252    job_info: String,
253    message: &'static str,
254}
255
256fn log_launcher_error(err: &LauncherError, op: &str, job: Arc<zx::Job>, name: String) {
257    let job_koid = job
258        .get_koid()
259        .map(|j| j.raw_koid().to_string())
260        .unwrap_or_else(|_| "<unknown>".to_string());
261    let LogInfo { style, job_info, message } = describe_error(err, job.as_handle_ref().cast());
262
263    let level = match style {
264        LogStyle::JobKilled => log::Level::Info,
265        LogStyle::Warn => log::Level::Warn,
266        LogStyle::Error => log::Level::Error,
267    };
268    log::log!(level,
269        op:%, process_name:% = name, job_koid:%, job_info:%, error:% = err;
270        "{message}",
271    );
272}
273
274/// Describes the process launching error.
275///
276/// Special case BAD_STATE errors to check the job's exited status and log more info.
277/// BAD_STATE errors are expected if the job has exited for reasons outside our control, like
278/// another process killing the job or the parent job exiting, while a new process is being
279/// created in it.
280fn describe_error<'a>(err: &LauncherError, job: zx::Unowned<'a, zx::Job>) -> LogInfo {
281    let log_level: LogStyle;
282    let job_message: String;
283    match err {
284        LauncherError::BuilderError(err)
285            if err.as_zx_status() == zx::Status::BAD_STATE
286                || matches!(err, ProcessBuilderError::LoadDynamicLinkerTimeout()) =>
287        {
288            match job.info() {
289                Ok(job_info) => {
290                    match job_info.exited {
291                        true => {
292                            log_level = match job_info.return_code {
293                                sys::ZX_TASK_RETCODE_SYSCALL_KILL => LogStyle::JobKilled,
294                                _ => LogStyle::Warn,
295                            };
296                            let return_code_str = match job_info.return_code {
297                                sys::ZX_TASK_RETCODE_SYSCALL_KILL => "killed",
298                                sys::ZX_TASK_RETCODE_OOM_KILL => "killed on oom",
299                                sys::ZX_TASK_RETCODE_POLICY_KILL => {
300                                    "killed due to policy violation"
301                                }
302                                sys::ZX_TASK_RETCODE_VDSO_KILL => {
303                                    "killed due to breaking vdso API contract"
304                                }
305                                sys::ZX_TASK_RETCODE_EXCEPTION_KILL => "killed due to exception",
306                                _ => "exited for unknown reason",
307                            };
308                            job_message = format!(
309                                "job was {} (retcode {})",
310                                return_code_str, job_info.return_code
311                            );
312                        }
313                        false => {
314                            // If the job has not exited, then the BAD_STATE error was unexpected
315                            // and indicates a bug somewhere.
316                            log_level = LogStyle::Error;
317                            job_message = "job is running".to_string();
318                        }
319                    }
320                }
321                Err(status) => {
322                    log_level = LogStyle::Error;
323                    job_message = format!(" (error {} getting job state)", status);
324                }
325            }
326        }
327        _ => {
328            // Errors other than process builder error do not concern the job's status.
329            log_level = LogStyle::Error;
330            job_message = "".to_string();
331        }
332    }
333
334    let message = match log_level {
335        LogStyle::JobKilled => {
336            "Process operation failed because the parent job was killed. This is expected."
337        }
338        LogStyle::Warn | LogStyle::Error => "Process operation failed",
339    };
340
341    LogInfo { style: log_level, job_info: job_message, message }
342}
343
344pub type Connector = Box<dyn Connect<Proxy = fproc::LauncherProxy> + Send + Sync>;
345
346/// A protocol connector for `fuchsia.process.Launcher` that serves the protocol with the
347/// `ProcessLauncher` implementation.
348#[cfg(test)]
349pub struct BuiltInConnector {}
350
351#[cfg(test)]
352impl Connect for BuiltInConnector {
353    type Proxy = fproc::LauncherProxy;
354
355    fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
356        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<fproc::LauncherMarker>();
357        fuchsia_async::Task::spawn(async move {
358            let result = ProcessLauncher::serve(stream).await;
359            if let Err(error) = result {
360                log::warn!(error:%; "ProcessLauncher.serve failed");
361            }
362        })
363        .detach();
364        Ok(proxy)
365    }
366}
367
368/// A protocol connector for `fuchsia.process.Launcher` that connects to the protocol from a
369/// namespace object.
370pub struct NamespaceConnector {
371    pub namespace: Arc<namespace::Namespace>,
372}
373
374#[derive(Error, Debug)]
375enum NamespaceConnectorError {
376    #[error("Missing /svc in namespace: {0:?}")]
377    MissingSvcInNamespace(Vec<NamespacePath>),
378}
379
380impl Connect for NamespaceConnector {
381    type Proxy = fproc::LauncherProxy;
382
383    fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
384        static PATH: LazyLock<NamespacePath> = LazyLock::new(|| "/svc".parse().unwrap());
385        let svc = self.namespace.get(&PATH).ok_or_else(|| {
386            NamespaceConnectorError::MissingSvcInNamespace(self.namespace.paths())
387        })?;
388        fuchsia_component::client::connect_to_protocol_at_dir_root::<fproc::LauncherMarker>(svc)
389            .context("failed to connect to external launcher service")
390    }
391}
392
393#[cfg(test)]
394mod tests {
395    use fuchsia_runtime::job_default;
396    use zx::Task;
397
398    use super::*;
399
400    #[test]
401    fn describe_expected_error_in_killed_job() {
402        // Create a child job then kill it.
403        let job = job_default().create_child_job().unwrap();
404        job.kill().unwrap();
405        let errors = [
406            LauncherError::BuilderError(ProcessBuilderError::CreateProcess(zx::Status::BAD_STATE)),
407            LauncherError::BuilderError(ProcessBuilderError::LoadDynamicLinkerTimeout()),
408        ];
409        for err in &errors {
410            let description = describe_error(err, job.as_handle_ref().cast());
411            assert_eq!(
412                description,
413                LogInfo {
414                    style: LogStyle::JobKilled,
415                    job_info: "job was killed (retcode -1024)".to_string(),
416                    message: "Process operation failed because the parent job was killed. This is expected."
417                }
418            );
419        }
420    }
421
422    #[test]
423    fn describe_unexpected_error_in_killed_job() {
424        // Create a child job then kill it.
425        let job = job_default().create_child_job().unwrap();
426        job.kill().unwrap();
427        let expected_err = LauncherError::BuilderError(ProcessBuilderError::CreateProcess(
428            zx::Status::ACCESS_DENIED,
429        ));
430        let description = describe_error(&expected_err, job.as_handle_ref().cast());
431        assert_eq!(
432            description,
433            LogInfo {
434                style: LogStyle::Error,
435                job_info: "".to_string(),
436                message: "Process operation failed"
437            }
438        );
439    }
440
441    #[test]
442    fn describe_error_in_running_job() {
443        // Use our own job which must be running.
444        let job = job_default();
445        let err =
446            LauncherError::BuilderError(ProcessBuilderError::CreateProcess(zx::Status::BAD_STATE));
447        let description = describe_error(&err, job.clone());
448        assert_eq!(
449            description,
450            LogInfo {
451                style: LogStyle::Error,
452                job_info: "job is running".to_string(),
453                message: "Process operation failed"
454            }
455        );
456    }
457}