elf_runner/
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
5mod component;
6mod component_set;
7mod config;
8mod crash_handler;
9pub mod crash_info;
10mod error;
11mod logger;
12mod memory;
13pub mod process_launcher;
14mod runtime_dir;
15mod stdout;
16pub mod vdso_vmo;
17
18use self::component::{ElfComponent, ElfComponentInfo};
19use self::config::ElfProgramConfig;
20use self::error::{JobError, StartComponentError, StartInfoError};
21use self::runtime_dir::RuntimeDirBuilder;
22use self::stdout::bind_streams_to_syslog;
23use crate::component_set::ComponentSet;
24use crate::crash_info::CrashRecords;
25use crate::memory::reporter::MemoryReporter;
26use crate::vdso_vmo::get_next_vdso_vmo;
27use ::routing::policy::ScopedPolicyChecker;
28use chrono::DateTime;
29use fidl::endpoints::ServerEnd;
30use fidl_fuchsia_component_runner::{
31    ComponentDiagnostics, ComponentTasks, Task as DiagnosticsTask,
32};
33use fidl_fuchsia_process_lifecycle::LifecycleMarker;
34use fuchsia_async::{self as fasync, TimeoutExt};
35use fuchsia_runtime::{HandleInfo, HandleType, UtcClock, duplicate_utc_clock_handle, job_default};
36use futures::TryStreamExt;
37use futures::channel::oneshot;
38use log::warn;
39use moniker::Moniker;
40use runner::StartInfo;
41use runner::component::StopInfo;
42use std::path::Path;
43use std::sync::Arc;
44use vfs::execution_scope::ExecutionScope;
45use zx::{self as zx, AsHandleRef, HandleBased};
46use {
47    fidl_fuchsia_component as fcomp, fidl_fuchsia_component_runner as fcrunner,
48    fidl_fuchsia_io as fio, fidl_fuchsia_memory_attribution as fattribution,
49    fidl_fuchsia_process as fproc,
50};
51
52// Maximum time that the runner will wait for break_on_start eventpair to signal.
53// This is set to prevent debuggers from blocking us for too long, either intentionally
54// or unintentionally.
55const MAX_WAIT_BREAK_ON_START: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(300);
56
57// Minimum timer slack amount and default mode. The amount should be large enough to allow for some
58// coalescing of timers, but small enough to ensure applications don't miss deadlines.
59//
60// TODO(https://fxbug.dev/42120293): For now, set the value to 50us to avoid delaying performance-critical
61// timers in Scenic and other system services.
62const TIMER_SLACK_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_micros(50);
63
64// Rights used when duplicating the UTC clock handle.
65//
66// Formed out of:
67// * `zx::Rights::BASIC`, but
68// * with `zx::Rights::WRITE` stripped (UTC clock is normally read-only), and
69// * with `zx::Rights::INSPECT` added (so that `ZX_INFO_CLOCK_MAPPED_SIZE` can be queried).
70//
71// Rather than subtracting `WRITE` from `BASIC`, we build the rights explicitly to avoid
72// including unintended rights by accident.
73//
74// The bitwise `|` operator for `bitflags` is implemented through the `std::ops::BitOr` trait,
75// which cannot be used in a const context. The workaround is to bitwise OR the raw bits.
76const DUPLICATE_CLOCK_RIGHTS: zx::Rights = zx::Rights::from_bits_truncate(
77    zx::Rights::READ.bits() // BASIC
78        | zx::Rights::WAIT.bits() // BASIC
79        | zx::Rights::DUPLICATE.bits() // BASIC
80        | zx::Rights::TRANSFER.bits() // BASIC
81        | zx::Rights::INSPECT.bits()
82        // Allows calls to zx_clock_read_mappable and zx_clock_get_details_mappable.
83        // Since "regular" read and details only require READ, and mappable read
84        // and details read the clock the same way, it seems safe to include MAP
85        // in this set of rights.
86        | zx::Rights::MAP.bits(),
87);
88
89// Builds and serves the runtime directory
90/// Runs components with ELF binaries.
91pub struct ElfRunner {
92    /// Each ELF component run by this runner will live inside a job that is a
93    /// child of this job.
94    job: zx::Job,
95
96    launcher_connector: process_launcher::Connector,
97
98    /// If `utc_clock` is populated then that Clock's handle will
99    /// be passed into the newly created process. Otherwise, the UTC
100    /// clock will be duplicated from current process' process table.
101    /// The latter is typically the case in unit tests and nested
102    /// component managers.
103    utc_clock: Option<Arc<UtcClock>>,
104
105    crash_records: CrashRecords,
106
107    /// Tracks the ELF components that are currently running under this runner.
108    components: Arc<ComponentSet>,
109
110    /// Tracks reporting memory changes to an observer.
111    memory_reporter: MemoryReporter,
112
113    /// Tasks that support the runner are launched in this scope
114    scope: ExecutionScope,
115}
116
117/// The job for a component.
118pub enum Job {
119    Single(zx::Job),
120    Multiple { parent: zx::Job, child: zx::Job },
121}
122
123impl Job {
124    fn top(&self) -> &zx::Job {
125        match self {
126            Job::Single(job) => job,
127            Job::Multiple { parent, child: _ } => parent,
128        }
129    }
130
131    fn proc(&self) -> &zx::Job {
132        match self {
133            Job::Single(job) => job,
134            Job::Multiple { parent: _, child } => child,
135        }
136    }
137}
138
139impl ElfRunner {
140    pub fn new(
141        job: zx::Job,
142        launcher_connector: process_launcher::Connector,
143        utc_clock: Option<Arc<UtcClock>>,
144        crash_records: CrashRecords,
145    ) -> ElfRunner {
146        let scope = ExecutionScope::new();
147        let components = ComponentSet::new(scope.clone());
148        let memory_reporter = MemoryReporter::new(components.clone());
149        ElfRunner {
150            job,
151            launcher_connector,
152            utc_clock,
153            crash_records,
154            components,
155            memory_reporter,
156            scope,
157        }
158    }
159
160    /// Returns a UTC clock handle.
161    ///
162    /// Duplicates `self.utc_clock` if populated, or the UTC clock assigned to the current process.
163    async fn duplicate_utc_clock(&self) -> Result<UtcClock, zx::Status> {
164        if let Some(utc_clock) = &self.utc_clock {
165            utc_clock.duplicate_handle(DUPLICATE_CLOCK_RIGHTS)
166        } else {
167            duplicate_utc_clock_handle(DUPLICATE_CLOCK_RIGHTS)
168        }
169    }
170
171    /// Creates a job for a component.
172    fn create_job(&self, program_config: &ElfProgramConfig) -> Result<Job, JobError> {
173        let job = self.job.create_child_job().map_err(JobError::CreateChild)?;
174
175        // Set timer slack.
176        //
177        // Why Late and not Center or Early? Timers firing a little later than requested is not
178        // uncommon in non-realtime systems. Programs are generally tolerant of some
179        // delays. However, timers firing before their deadline can be unexpected and lead to bugs.
180        job.set_policy(zx::JobPolicy::TimerSlack(
181            TIMER_SLACK_DURATION,
182            zx::JobDefaultTimerMode::Late,
183        ))
184        .map_err(JobError::SetPolicy)?;
185
186        // Prevent direct creation of processes.
187        //
188        // The kernel-level mechanisms for creating processes are very low-level. We require that
189        // all processes be created via fuchsia.process.Launcher in order for the platform to
190        // maintain change-control over how processes are created.
191        if !program_config.create_raw_processes {
192            job.set_policy(zx::JobPolicy::Basic(
193                zx::JobPolicyOption::Absolute,
194                vec![(zx::JobCondition::NewProcess, zx::JobAction::Deny)],
195            ))
196            .map_err(JobError::SetPolicy)?;
197        }
198
199        // Default deny the job policy which allows ambiently marking VMOs executable, i.e. calling
200        // vmo_replace_as_executable without an appropriate resource handle.
201        if !program_config.ambient_mark_vmo_exec {
202            job.set_policy(zx::JobPolicy::Basic(
203                zx::JobPolicyOption::Absolute,
204                vec![(zx::JobCondition::AmbientMarkVmoExec, zx::JobAction::Deny)],
205            ))
206            .map_err(JobError::SetPolicy)?;
207        }
208
209        if program_config.deny_bad_handles {
210            job.set_policy(zx::JobPolicy::Basic(
211                zx::JobPolicyOption::Absolute,
212                vec![(zx::JobCondition::BadHandle, zx::JobAction::DenyException)],
213            ))
214            .map_err(JobError::SetPolicy)?;
215        }
216
217        Ok(if program_config.job_with_available_exception_channel {
218            // Create a new job to hold the process because the component wants
219            // the process to be a direct child of a job that has its exception
220            // channel available for taking. Note that we (the ELF runner) uses
221            // a job's exception channel for crash recording so we create a new
222            // job underneath the original job to hold the process.
223            let child = job.create_child_job().map_err(JobError::CreateChild)?;
224            Job::Multiple { parent: job, child }
225        } else {
226            Job::Single(job)
227        })
228    }
229
230    fn create_handle_infos(
231        outgoing_dir: Option<zx::Channel>,
232        lifecycle_server: Option<zx::Channel>,
233        utc_clock: UtcClock,
234        next_vdso: Option<zx::Vmo>,
235        config_vmo: Option<zx::Vmo>,
236    ) -> Vec<fproc::HandleInfo> {
237        let mut handle_infos = vec![];
238
239        if let Some(outgoing_dir) = outgoing_dir {
240            handle_infos.push(fproc::HandleInfo {
241                handle: outgoing_dir.into_handle(),
242                id: HandleInfo::new(HandleType::DirectoryRequest, 0).as_raw(),
243            });
244        }
245
246        if let Some(lifecycle_chan) = lifecycle_server {
247            handle_infos.push(fproc::HandleInfo {
248                handle: lifecycle_chan.into_handle(),
249                id: HandleInfo::new(HandleType::Lifecycle, 0).as_raw(),
250            })
251        };
252
253        handle_infos.push(fproc::HandleInfo {
254            handle: utc_clock.into_handle(),
255            id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
256        });
257
258        if let Some(next_vdso) = next_vdso {
259            handle_infos.push(fproc::HandleInfo {
260                handle: next_vdso.into_handle(),
261                id: HandleInfo::new(HandleType::VdsoVmo, 0).as_raw(),
262            });
263        }
264
265        if let Some(config_vmo) = config_vmo {
266            handle_infos.push(fproc::HandleInfo {
267                handle: config_vmo.into_handle(),
268                id: HandleInfo::new(HandleType::ComponentConfigVmo, 0).as_raw(),
269            });
270        }
271
272        handle_infos
273    }
274
275    pub async fn start_component(
276        &self,
277        start_info: fcrunner::ComponentStartInfo,
278        checker: &ScopedPolicyChecker,
279    ) -> Result<ElfComponent, StartComponentError> {
280        let start_info: StartInfo =
281            start_info.try_into().map_err(StartInfoError::StartInfoError)?;
282
283        let resolved_url = start_info.resolved_url.clone();
284
285        // This also checks relevant security policy for config that it wraps using the provided
286        // PolicyChecker.
287        let program_config = ElfProgramConfig::parse_and_check(&start_info.program, &checker)
288            .map_err(|err| {
289                StartComponentError::StartInfoError(StartInfoError::ProgramError(err))
290            })?;
291
292        let main_process_critical = program_config.main_process_critical;
293        let res: Result<ElfComponent, StartComponentError> =
294            self.start_component_helper(start_info, checker.scope.clone(), program_config).await;
295        match res {
296            Err(e) if main_process_critical => {
297                panic!(
298                    "failed to launch component with a critical process ({:?}): {:?}",
299                    &resolved_url, e
300                )
301            }
302            x => x,
303        }
304    }
305
306    async fn start_component_helper(
307        &self,
308        mut start_info: StartInfo,
309        moniker: Moniker,
310        program_config: ElfProgramConfig,
311    ) -> Result<ElfComponent, StartComponentError> {
312        let resolved_url = &start_info.resolved_url;
313
314        // Fail early if there are clock issues.
315        let boot_clock = zx::Clock::<zx::MonotonicTimeline, zx::BootTimeline>::create(
316            zx::ClockOpts::CONTINUOUS,
317            /*backstop=*/ None,
318        )
319        .map_err(StartComponentError::BootClockCreateFailed)?;
320
321        // Connect to `fuchsia.process.Launcher`.
322        let launcher = self
323            .launcher_connector
324            .connect()
325            .map_err(|err| StartComponentError::ProcessLauncherConnectError(err.into()))?;
326
327        // Create a job for this component that will contain its process.
328        let job = self.create_job(&program_config)?;
329
330        crash_handler::run_exceptions_server(
331            &self.scope,
332            job.top(),
333            moniker.clone(),
334            resolved_url.clone(),
335            self.crash_records.clone(),
336        )
337        .map_err(StartComponentError::ExceptionRegistrationFailed)?;
338
339        // Convert the directories into proxies, so we can find "/pkg" and open "lib" and bin_path
340        let ns = namespace::Namespace::try_from(start_info.namespace)
341            .map_err(StartComponentError::NamespaceError)?;
342
343        let config_vmo =
344            start_info.encoded_config.take().map(runner::get_config_vmo).transpose()?;
345
346        let next_vdso = program_config.use_next_vdso.then(get_next_vdso_vmo).transpose()?;
347
348        let (lifecycle_client, lifecycle_server) = if program_config.notify_lifecycle_stop {
349            // Creating a channel is not expected to fail.
350            let (client, server) = fidl::endpoints::create_proxy::<LifecycleMarker>();
351            (Some(client), Some(server.into_channel()))
352        } else {
353            (None, None)
354        };
355
356        // Take the UTC clock handle out of `start_info.numbered_handles`, if available.
357        let utc_handle = start_info
358            .numbered_handles
359            .iter()
360            .position(|handles| handles.id == HandleInfo::new(HandleType::ClockUtc, 0).as_raw())
361            .map(|position| start_info.numbered_handles.swap_remove(position).handle);
362
363        let utc_clock = if let Some(handle) = utc_handle {
364            zx::Clock::from(handle)
365        } else {
366            self.duplicate_utc_clock()
367                .await
368                .map_err(StartComponentError::UtcClockDuplicateFailed)?
369        };
370
371        // Duplicate the clock handle again, used later to wait for the clock to start, while
372        // the original handle is passed to the process.
373        let utc_clock_dup = utc_clock
374            .duplicate_handle(zx::Rights::SAME_RIGHTS)
375            .map_err(StartComponentError::UtcClockDuplicateFailed)?;
376
377        // Create and serve the runtime dir.
378        let runtime_dir_server_end = start_info
379            .runtime_dir
380            .ok_or(StartComponentError::StartInfoError(StartInfoError::MissingRuntimeDir))?;
381        let job_koid =
382            job.proc().get_koid().map_err(StartComponentError::JobGetKoidFailed)?.raw_koid();
383
384        let runtime_dir = RuntimeDirBuilder::new(runtime_dir_server_end)
385            .args(program_config.args.clone())
386            .job_id(job_koid)
387            .serve();
388
389        // If the component supports memory attribution, clone its outgoing directory connection
390        // so that we may later connect to it.
391        let outgoing_directory = if program_config.memory_attribution {
392            let Some(outgoing_dir) = start_info.outgoing_dir else {
393                return Err(StartComponentError::StartInfoError(
394                    StartInfoError::MissingOutgoingDir,
395                ));
396            };
397            let (outgoing_dir_client, outgoing_dir_server) = fidl::endpoints::create_endpoints();
398            start_info.outgoing_dir = Some(outgoing_dir_server);
399            fdio::open_at(
400                outgoing_dir_client.channel(),
401                ".",
402                fio::Flags::PROTOCOL_DIRECTORY,
403                outgoing_dir.into_channel(),
404            )
405            .unwrap();
406            Some(outgoing_dir_client)
407        } else {
408            None
409        };
410
411        // Create procarg handles.
412        let mut handle_infos = ElfRunner::create_handle_infos(
413            start_info.outgoing_dir.map(|dir| dir.into_channel()),
414            lifecycle_server,
415            utc_clock,
416            next_vdso,
417            config_vmo,
418        );
419
420        // Add stdout and stderr handles that forward to syslog.
421        let (local_scope, stdout_and_stderr_handles) =
422            bind_streams_to_syslog(&ns, program_config.stdout_sink, program_config.stderr_sink);
423        handle_infos.extend(stdout_and_stderr_handles);
424
425        // Add any external numbered handles.
426        handle_infos.extend(start_info.numbered_handles);
427
428        // If the program escrowed a dictionary, give it back via `numbered_handles`.
429        if let Some(escrowed_dictionary) = start_info.escrowed_dictionary {
430            handle_infos.push(fproc::HandleInfo {
431                handle: escrowed_dictionary.token.into_handle().into(),
432                id: HandleInfo::new(HandleType::EscrowedDictionary, 0).as_raw(),
433            });
434        }
435
436        // Configure the process launcher.
437        let proc_job_dup = job
438            .proc()
439            .duplicate_handle(zx::Rights::SAME_RIGHTS)
440            .map_err(StartComponentError::JobDuplicateFailed)?;
441
442        let name = Path::new(resolved_url)
443            .file_name()
444            .and_then(|filename| filename.to_str())
445            .ok_or_else(|| {
446                StartComponentError::StartInfoError(StartInfoError::BadResolvedUrl(
447                    resolved_url.clone(),
448                ))
449            })?;
450
451        let launch_info =
452            runner::component::configure_launcher(runner::component::LauncherConfigArgs {
453                bin_path: &program_config.binary,
454                name,
455                options: program_config.process_options(),
456                args: Some(program_config.args.clone()),
457                ns,
458                job: Some(proc_job_dup),
459                handle_infos: Some(handle_infos),
460                name_infos: None,
461                environs: program_config.environ.clone(),
462                launcher: &launcher,
463                loader_proxy_chan: None,
464                executable_vmo: None,
465            })
466            .await?;
467
468        // Wait on break_on_start with a timeout and don't fail.
469        if let Some(break_on_start) = start_info.break_on_start {
470            fasync::OnSignals::new(&break_on_start, zx::Signals::OBJECT_PEER_CLOSED)
471                .on_timeout(MAX_WAIT_BREAK_ON_START, || Err(zx::Status::TIMED_OUT))
472                .await
473                .err()
474                .map(|error| warn!(moniker:%, error:%; "Failed to wait break_on_start"));
475        }
476
477        // Launch the process.
478        let (status, process) = launcher
479            .launch(launch_info)
480            .await
481            .map_err(StartComponentError::ProcessLauncherFidlError)?;
482        zx::Status::ok(status).map_err(StartComponentError::CreateProcessFailed)?;
483        let process = process.unwrap(); // Process is present iff status is OK.
484        if program_config.main_process_critical {
485            job_default()
486                .set_critical(zx::JobCriticalOptions::RETCODE_NONZERO, &process)
487                .map_err(StartComponentError::ProcessMarkCriticalFailed)
488                .expect("failed to set process as critical");
489        }
490
491        let pid = process.get_koid().map_err(StartComponentError::ProcessGetKoidFailed)?.raw_koid();
492
493        // Add process ID to the runtime dir.
494        runtime_dir.add_process_id(pid);
495
496        fuchsia_trace::instant!(
497            c"component:start",
498            c"elf",
499            fuchsia_trace::Scope::Thread,
500            "moniker" => format!("{}", moniker).as_str(),
501            "url" => resolved_url.as_str(),
502            "pid" => pid
503        );
504
505        // Add process start time to the runtime dir.
506        let process_start_mono_ns =
507            process.info().map_err(StartComponentError::ProcessInfoFailed)?.start_time;
508        runtime_dir.add_process_start_time(process_start_mono_ns);
509
510        // Add UTC estimate of the process start time to the runtime dir.
511        let utc_clock_started = fasync::OnSignals::new(&utc_clock_dup, zx::Signals::CLOCK_STARTED)
512            .on_timeout(zx::MonotonicInstant::after(zx::MonotonicDuration::default()), || {
513                Err(zx::Status::TIMED_OUT)
514            })
515            .await
516            .is_ok();
517
518        // The clock transformations needed to map a timestamp on a monotonic timeline
519        // to a timestamp on the UTC timeline.
520        let mono_to_clock_transformation =
521            boot_clock.get_details().map(|details| details.reference_to_synthetic).ok();
522        let boot_to_utc_transformation = utc_clock_started
523            .then(|| utc_clock_dup.get_details().map(|details| details.reference_to_synthetic).ok())
524            .flatten();
525
526        if let Some(clock_transformation) = boot_to_utc_transformation {
527            // This composes two transformations, to get from a timestamp expressed in
528            // nanoseconds on the monotonic timeline, to our best estimate of the
529            // corresponding UTC date-time.
530            //
531            // The clock transformations are computed before they are applied. If
532            // a suspend intervenes exactly between the computation and application,
533            // the timelines will drift away during sleep, causing a wrong date-time
534            // to be exposed in `runtime_dir`.
535            //
536            // This should not be a huge issue in practice, as the chances of that
537            // happening are vanishingly small.
538            let process_start_instant_mono =
539                zx::MonotonicInstant::from_nanos(process_start_mono_ns);
540            let maybe_time_utc = mono_to_clock_transformation
541                .map(|t| t.apply(process_start_instant_mono))
542                .map(|time_boot| clock_transformation.apply(time_boot));
543
544            if let Some(utc_timestamp) = maybe_time_utc {
545                let utc_time_ns = utc_timestamp.into_nanos();
546                let seconds = (utc_time_ns / 1_000_000_000) as i64;
547                let nanos = (utc_time_ns % 1_000_000_000) as u32;
548                let dt = DateTime::from_timestamp(seconds, nanos).unwrap();
549
550                // If any of the above values are unavailable (unlikely), then this
551                // does not happen.
552                runtime_dir.add_process_start_time_utc_estimate(dt.to_string())
553            }
554        };
555
556        Ok(ElfComponent::new(
557            runtime_dir,
558            moniker,
559            job,
560            process,
561            lifecycle_client,
562            program_config.main_process_critical,
563            local_scope,
564            resolved_url.clone(),
565            outgoing_directory,
566            program_config,
567            start_info.component_instance.ok_or(StartComponentError::StartInfoError(
568                StartInfoError::MissingComponentInstanceToken,
569            ))?,
570        ))
571    }
572
573    pub fn get_scoped_runner(
574        self: Arc<Self>,
575        checker: ScopedPolicyChecker,
576    ) -> Arc<ScopedElfRunner> {
577        Arc::new(ScopedElfRunner { runner: self, checker })
578    }
579
580    pub fn serve_memory_reporter(&self, stream: fattribution::ProviderRequestStream) {
581        self.memory_reporter.serve(stream);
582    }
583}
584
585pub struct ScopedElfRunner {
586    runner: Arc<ElfRunner>,
587    checker: ScopedPolicyChecker,
588}
589
590impl ScopedElfRunner {
591    pub fn serve(&self, mut stream: fcrunner::ComponentRunnerRequestStream) {
592        let runner = self.runner.clone();
593        let checker = self.checker.clone();
594        self.scope().spawn(async move {
595            while let Ok(Some(request)) = stream.try_next().await {
596                match request {
597                    fcrunner::ComponentRunnerRequest::Start { start_info, controller, .. } => {
598                        start(&runner, checker.clone(), start_info, controller).await;
599                    }
600                    fcrunner::ComponentRunnerRequest::_UnknownMethod { ordinal, .. } => {
601                        warn!(ordinal:%; "Unknown ComponentRunner request");
602                    }
603                }
604            }
605        });
606    }
607
608    pub async fn start(
609        &self,
610        start_info: fcrunner::ComponentStartInfo,
611        server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
612    ) {
613        start(&self.runner, self.checker.clone(), start_info, server_end).await
614    }
615
616    pub(crate) fn scope(&self) -> &ExecutionScope {
617        &self.runner.scope
618    }
619}
620
621/// Starts a component by creating a new Job and Process for the component.
622async fn start(
623    runner: &ElfRunner,
624    checker: ScopedPolicyChecker,
625    start_info: fcrunner::ComponentStartInfo,
626    server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
627) {
628    let resolved_url = start_info.resolved_url.clone().unwrap_or_else(|| "<unknown>".to_string());
629
630    let elf_component = match runner.start_component(start_info, &checker).await {
631        Ok(elf_component) => elf_component,
632        Err(err) => {
633            runner::component::report_start_error(
634                err.as_zx_status(),
635                format!("{}", err),
636                &resolved_url,
637                server_end,
638            );
639            return;
640        }
641    };
642
643    let (termination_tx, termination_rx) = oneshot::channel::<StopInfo>();
644    // This function waits for something from the channel and
645    // returns it or Error::Internal if the channel is closed
646    let termination_fn = Box::pin(async move {
647        termination_rx
648            .await
649            .unwrap_or_else(|_| {
650                warn!("epitaph oneshot channel closed unexpectedly");
651                StopInfo::from_error(fcomp::Error::Internal, None)
652            })
653            .into()
654    });
655
656    let Some(proc_copy) = elf_component.copy_process() else {
657        runner::component::report_start_error(
658            zx::Status::from_raw(
659                i32::try_from(fcomp::Error::InstanceCannotStart.into_primitive()).unwrap(),
660            ),
661            "Component unexpectedly had no process".to_string(),
662            &resolved_url,
663            server_end,
664        );
665        return;
666    };
667
668    let component_diagnostics = elf_component
669        .info()
670        .copy_job_for_diagnostics()
671        .map(|job| ComponentDiagnostics {
672            tasks: Some(ComponentTasks {
673                component_task: Some(DiagnosticsTask::Job(job.into())),
674                ..Default::default()
675            }),
676            ..Default::default()
677        })
678        .map_err(|error| {
679            warn!(error:%; "Failed to copy job for diagnostics");
680            ()
681        })
682        .ok();
683
684    let (server_stream, control) = server_end.into_stream_and_control_handle();
685
686    // Spawn a future that watches for the process to exit
687    runner.scope.spawn({
688        let resolved_url = resolved_url.clone();
689        async move {
690            fasync::OnSignals::new(&proc_copy.as_handle_ref(), zx::Signals::PROCESS_TERMINATED)
691                .await
692                .map(|_: fidl::Signals| ()) // Discard.
693                .unwrap_or_else(|error| warn!(error:%; "error creating signal handler"));
694            // Process exit code '0' is considered a clean return.
695            // TODO(https://fxbug.dev/42134825) If we create an epitaph that indicates
696            // intentional, non-zero exit, use that for all non-0 exit
697            // codes.
698            let stop_info = match proc_copy.info() {
699                Ok(zx::ProcessInfo { return_code, .. }) => {
700                    match return_code {
701                        0 => StopInfo::from_ok(Some(return_code)),
702                        // Don't log SYSCALL_KILL codes because they are expected in the course
703                        // of normal operation. When elf_runner process a `Kill` method call for
704                        // a component it makes a zx_task_kill syscall which sets this return code.
705                        zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL => StopInfo::from_error(
706                            fcomp::Error::InstanceDied.into(),
707                            Some(return_code),
708                        ),
709                        _ => {
710                            warn!(url:% = resolved_url, return_code:%;
711                                "process terminated with abnormal return code");
712                            StopInfo::from_error(fcomp::Error::InstanceDied, Some(return_code))
713                        }
714                    }
715                }
716                Err(error) => {
717                    warn!(error:%; "Unable to query process info");
718                    StopInfo::from_error(fcomp::Error::Internal, None)
719                }
720            };
721            termination_tx.send(stop_info).unwrap_or_else(|_| warn!("error sending done signal"));
722        }
723    });
724
725    let mut elf_component = elf_component;
726    runner.components.clone().add(&mut elf_component);
727
728    // Create a future which owns and serves the controller
729    // channel. The `epitaph_fn` future completes when the
730    // component's main process exits. The controller then sets the
731    // epitaph on the controller channel, closes it, and stops
732    // serving the protocol.
733    runner.scope.spawn(async move {
734        if let Some(component_diagnostics) = component_diagnostics {
735            control.send_on_publish_diagnostics(component_diagnostics).unwrap_or_else(
736                |error| warn!(url:% = resolved_url, error:%; "sending diagnostics failed"),
737            );
738        }
739        runner::component::Controller::new(elf_component, server_stream, control)
740            .serve(termination_fn)
741            .await;
742    });
743}
744
745#[cfg(test)]
746mod tests {
747    use super::runtime_dir::RuntimeDirectory;
748    use super::*;
749    use anyhow::{Context, Error};
750    use assert_matches::assert_matches;
751    use cm_config::{AllowlistEntryBuilder, JobPolicyAllowlists, SecurityPolicy};
752    use fidl::endpoints::{DiscoverableProtocolMarker, Proxy, create_endpoints, create_proxy};
753    use fidl_connector::Connect;
754    use fidl_fuchsia_component_runner::Task as DiagnosticsTask;
755    use fidl_fuchsia_logger::{LogSinkMarker, LogSinkRequestStream};
756    use fidl_fuchsia_process_lifecycle::LifecycleProxy;
757    use fidl_test_util::spawn_stream_handler;
758    use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
759    use futures::channel::mpsc;
760    use futures::lock::Mutex;
761    use futures::{StreamExt, join};
762    use runner::component::Controllable;
763    use std::task::Poll;
764    use zx::{self as zx, Task};
765    use {
766        fidl_fuchsia_component as fcomp, fidl_fuchsia_component_runner as fcrunner,
767        fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio, fuchsia_async as fasync,
768    };
769
770    pub enum MockServiceRequest {
771        LogSink(LogSinkRequestStream),
772    }
773
774    pub type MockServiceFs<'a> = ServiceFs<ServiceObjLocal<'a, MockServiceRequest>>;
775
776    /// Create a new local fs and install a mock LogSink service into.
777    /// Returns the created directory and corresponding namespace entries.
778    pub fn create_fs_with_mock_logsink()
779    -> Result<(MockServiceFs<'static>, Vec<fcrunner::ComponentNamespaceEntry>), Error> {
780        let (dir_client, dir_server) = create_endpoints::<fio::DirectoryMarker>();
781
782        let mut dir = ServiceFs::new_local();
783        dir.add_fidl_service_at(LogSinkMarker::PROTOCOL_NAME, MockServiceRequest::LogSink);
784        dir.serve_connection(dir_server).context("Failed to add serving channel.")?;
785
786        let namespace = vec![fcrunner::ComponentNamespaceEntry {
787            path: Some("/svc".to_string()),
788            directory: Some(dir_client),
789            ..Default::default()
790        }];
791
792        Ok((dir, namespace))
793    }
794
795    // Provide a UTC clock to avoid reusing the system UTC clock in tests, which may
796    // limit the changes that are allowed to be made to this code. We create this clock
797    // here, and start it from current time.
798    pub fn new_utc_clock_for_tests() -> Arc<UtcClock> {
799        let reference_now = zx::BootInstant::get();
800        let system_utc_clock = duplicate_utc_clock_handle(zx::Rights::SAME_RIGHTS).unwrap();
801        let utc_now = system_utc_clock.read().unwrap();
802
803        let utc_clock_for_tests =
804            Arc::new(UtcClock::create(zx::ClockOpts::MAPPABLE, /*backstop=*/ None).unwrap());
805        // This will start the test-only UTC clock.
806        utc_clock_for_tests
807            .update(zx::ClockUpdate::builder().absolute_value(reference_now, utc_now.into()))
808            .unwrap();
809        utc_clock_for_tests
810    }
811
812    pub fn new_elf_runner_for_test() -> Arc<ElfRunner> {
813        Arc::new(ElfRunner::new(
814            job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
815            Box::new(process_launcher::BuiltInConnector {}),
816            Some(new_utc_clock_for_tests()),
817            CrashRecords::new(),
818        ))
819    }
820
821    fn namespace_entry(path: &str, flags: fio::Flags) -> fcrunner::ComponentNamespaceEntry {
822        // Get a handle to /pkg
823        let ns_path = path.to_string();
824        let ns_dir = fuchsia_fs::directory::open_in_namespace(path, flags).unwrap();
825        let client_end = ns_dir.into_client_end().unwrap();
826        fcrunner::ComponentNamespaceEntry {
827            path: Some(ns_path),
828            directory: Some(client_end),
829            ..Default::default()
830        }
831    }
832
833    fn pkg_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
834        namespace_entry("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
835    }
836
837    fn svc_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
838        namespace_entry("/svc", fio::PERM_READABLE)
839    }
840
841    fn hello_world_startinfo(
842        runtime_dir: ServerEnd<fio::DirectoryMarker>,
843    ) -> fcrunner::ComponentStartInfo {
844        let ns = vec![pkg_dir_namespace_entry()];
845
846        fcrunner::ComponentStartInfo {
847            resolved_url: Some(
848                "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/hello-world-rust.cm".to_string(),
849            ),
850            program: Some(fdata::Dictionary {
851                entries: Some(vec![
852                    fdata::DictionaryEntry {
853                        key: "args".to_string(),
854                        value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
855                            "foo".to_string(),
856                            "bar".to_string(),
857                        ]))),
858                    },
859                    fdata::DictionaryEntry {
860                        key: "binary".to_string(),
861                        value: Some(Box::new(fdata::DictionaryValue::Str(
862                            "bin/hello_world_rust".to_string(),
863                        ))),
864                    },
865                ]),
866                ..Default::default()
867            }),
868            ns: Some(ns),
869            outgoing_dir: None,
870            runtime_dir: Some(runtime_dir),
871            component_instance: Some(zx::Event::create()),
872            ..Default::default()
873        }
874    }
875
876    /// ComponentStartInfo that points to a non-existent binary.
877    fn invalid_binary_startinfo(
878        runtime_dir: ServerEnd<fio::DirectoryMarker>,
879    ) -> fcrunner::ComponentStartInfo {
880        let ns = vec![pkg_dir_namespace_entry()];
881
882        fcrunner::ComponentStartInfo {
883            resolved_url: Some(
884                "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/does-not-exist.cm".to_string(),
885            ),
886            program: Some(fdata::Dictionary {
887                entries: Some(vec![fdata::DictionaryEntry {
888                    key: "binary".to_string(),
889                    value: Some(Box::new(fdata::DictionaryValue::Str(
890                        "bin/does_not_exist".to_string(),
891                    ))),
892                }]),
893                ..Default::default()
894            }),
895            ns: Some(ns),
896            outgoing_dir: None,
897            runtime_dir: Some(runtime_dir),
898            component_instance: Some(zx::Event::create()),
899            ..Default::default()
900        }
901    }
902
903    /// Creates start info for a component which runs until told to exit. The
904    /// ComponentController protocol can be used to stop the component when the
905    /// test is done inspecting the launched component.
906    pub fn lifecycle_startinfo(
907        runtime_dir: ServerEnd<fio::DirectoryMarker>,
908    ) -> fcrunner::ComponentStartInfo {
909        let ns = vec![pkg_dir_namespace_entry()];
910
911        fcrunner::ComponentStartInfo {
912            resolved_url: Some(
913                "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm".to_string(),
914            ),
915            program: Some(fdata::Dictionary {
916                entries: Some(vec![
917                    fdata::DictionaryEntry {
918                        key: "args".to_string(),
919                        value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
920                            "foo".to_string(),
921                            "bar".to_string(),
922                        ]))),
923                    },
924                    fdata::DictionaryEntry {
925                        key: "binary".to_string(),
926                        value: Some(Box::new(fdata::DictionaryValue::Str(
927                            "bin/lifecycle_placeholder".to_string(),
928                        ))),
929                    },
930                    fdata::DictionaryEntry {
931                        key: "lifecycle.stop_event".to_string(),
932                        value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
933                    },
934                ]),
935                ..Default::default()
936            }),
937            ns: Some(ns),
938            outgoing_dir: None,
939            runtime_dir: Some(runtime_dir),
940            component_instance: Some(zx::Event::create()),
941            ..Default::default()
942        }
943    }
944
945    fn create_child_process(job: &zx::Job, name: &str) -> zx::Process {
946        let (process, _vmar) = job
947            .create_child_process(zx::ProcessOptions::empty(), name.as_bytes())
948            .expect("could not create process");
949        process
950    }
951
952    fn make_default_elf_component(
953        lifecycle_client: Option<LifecycleProxy>,
954        critical: bool,
955    ) -> (scoped_task::Scoped<zx::Job>, ElfComponent) {
956        let job = scoped_task::create_child_job().expect("failed to make child job");
957        let process = create_child_process(&job, "test_process");
958        let job_copy =
959            job.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("job handle duplication failed");
960        let component = ElfComponent::new(
961            RuntimeDirectory::empty(),
962            Moniker::default(),
963            Job::Single(job_copy),
964            process,
965            lifecycle_client,
966            critical,
967            ExecutionScope::new(),
968            "".to_string(),
969            None,
970            Default::default(),
971            zx::Event::create(),
972        );
973        (job, component)
974    }
975
976    // TODO(https://fxbug.dev/42073224): A variation of this is used in a couple of places. We should consider
977    // refactoring this into a test util file.
978    async fn read_file<'a>(root_proxy: &'a fio::DirectoryProxy, path: &'a str) -> String {
979        let file_proxy =
980            fuchsia_fs::directory::open_file_async(&root_proxy, path, fuchsia_fs::PERM_READABLE)
981                .expect("Failed to open file.");
982        let res = fuchsia_fs::file::read_to_string(&file_proxy).await;
983        res.expect("Unable to read file.")
984    }
985
986    #[fuchsia::test]
987    async fn test_runtime_dir_entries() -> Result<(), Error> {
988        let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
989        let start_info = lifecycle_startinfo(runtime_dir_server);
990
991        let runner = new_elf_runner_for_test();
992        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
993            Arc::new(SecurityPolicy::default()),
994            Moniker::root(),
995        ));
996        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
997
998        runner.start(start_info, server_controller).await;
999
1000        // Verify that args are added to the runtime directory.
1001        assert_eq!("foo", read_file(&runtime_dir, "args/0").await);
1002        assert_eq!("bar", read_file(&runtime_dir, "args/1").await);
1003
1004        // Process Id, Process Start Time, Job Id will vary with every run of this test. Here we
1005        // verify that they exist in the runtime directory, they can be parsed as integers,
1006        // they're greater than zero and they are not the same value. Those are about the only
1007        // invariants we can verify across test runs.
1008        let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1009        let process_start_time =
1010            read_file(&runtime_dir, "elf/process_start_time").await.parse::<i64>()?;
1011        let process_start_time_utc_estimate =
1012            read_file(&runtime_dir, "elf/process_start_time_utc_estimate").await;
1013        let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>()?;
1014        assert!(process_id > 0);
1015        assert!(process_start_time > 0);
1016        assert!(process_start_time_utc_estimate.contains("UTC"));
1017        assert!(job_id > 0);
1018        assert_ne!(process_id, job_id);
1019
1020        controller.stop().expect("Stop request failed");
1021        // Wait for the process to exit so the test doesn't pagefault due to an invalid stdout
1022        // handle.
1023        controller.on_closed().await.expect("failed waiting for channel to close");
1024        Ok(())
1025    }
1026
1027    #[fuchsia::test]
1028    async fn test_kill_component() -> Result<(), Error> {
1029        let (job, mut component) = make_default_elf_component(None, false);
1030
1031        let job_info = job.info()?;
1032        assert!(!job_info.exited);
1033
1034        component.kill().await;
1035
1036        let h = job.as_handle_ref();
1037        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1038            .await
1039            .expect("failed waiting for termination signal");
1040
1041        let job_info = job.info()?;
1042        assert!(job_info.exited);
1043        Ok(())
1044    }
1045
1046    #[fuchsia::test]
1047    fn test_stop_critical_component() -> Result<(), Error> {
1048        let mut exec = fasync::TestExecutor::new();
1049        // Presence of the Lifecycle channel isn't used by ElfComponent to sense
1050        // component exit, but it does modify the stop behavior and this is
1051        // what we want to test.
1052        let (lifecycle_client, _lifecycle_server) = create_proxy::<LifecycleMarker>();
1053        let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1054        let process = component.copy_process().unwrap();
1055        let job_info = job.info()?;
1056        assert!(!job_info.exited);
1057
1058        // Ask the runner to stop the component, it returns a future which
1059        // completes when the component closes its side of the lifecycle
1060        // channel
1061        let mut completes_when_stopped = component.stop();
1062
1063        // The returned future shouldn't complete because we're holding the
1064        // lifecycle channel open.
1065        match exec.run_until_stalled(&mut completes_when_stopped) {
1066            Poll::Ready(_) => {
1067                panic!("runner should still be waiting for lifecycle channel to stop");
1068            }
1069            _ => {}
1070        }
1071        assert_eq!(process.kill(), Ok(()));
1072
1073        exec.run_singlethreaded(&mut completes_when_stopped);
1074
1075        // Check that the runner killed the job hosting the exited component.
1076        let h = job.as_handle_ref();
1077        let termination_fut = async move {
1078            fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1079                .await
1080                .expect("failed waiting for termination signal");
1081        };
1082        exec.run_singlethreaded(termination_fut);
1083
1084        let job_info = job.info()?;
1085        assert!(job_info.exited);
1086        Ok(())
1087    }
1088
1089    #[fuchsia::test]
1090    fn test_stop_noncritical_component() -> Result<(), Error> {
1091        let mut exec = fasync::TestExecutor::new();
1092        // Presence of the Lifecycle channel isn't used by ElfComponent to sense
1093        // component exit, but it does modify the stop behavior and this is
1094        // what we want to test.
1095        let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1096        let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1097
1098        let job_info = job.info()?;
1099        assert!(!job_info.exited);
1100
1101        // Ask the runner to stop the component, it returns a future which
1102        // completes when the component closes its side of the lifecycle
1103        // channel
1104        let mut completes_when_stopped = component.stop();
1105
1106        // The returned future shouldn't complete because we're holding the
1107        // lifecycle channel open.
1108        match exec.run_until_stalled(&mut completes_when_stopped) {
1109            Poll::Ready(_) => {
1110                panic!("runner should still be waiting for lifecycle channel to stop");
1111            }
1112            _ => {}
1113        }
1114        drop(lifecycle_server);
1115
1116        match exec.run_until_stalled(&mut completes_when_stopped) {
1117            Poll::Ready(_) => {}
1118            _ => {
1119                panic!("runner future should have completed, lifecycle channel is closed.");
1120            }
1121        }
1122        // Check that the runner killed the job hosting the exited component.
1123        let h = job.as_handle_ref();
1124        let termination_fut = async move {
1125            fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1126                .await
1127                .expect("failed waiting for termination signal");
1128        };
1129        exec.run_singlethreaded(termination_fut);
1130
1131        let job_info = job.info()?;
1132        assert!(job_info.exited);
1133        Ok(())
1134    }
1135
1136    /// Stopping a component which doesn't have a lifecycle channel should be
1137    /// equivalent to killing a component directly.
1138    #[fuchsia::test]
1139    async fn test_stop_component_without_lifecycle() -> Result<(), Error> {
1140        let (job, mut component) = make_default_elf_component(None, false);
1141
1142        let job_info = job.info()?;
1143        assert!(!job_info.exited);
1144
1145        component.stop().await;
1146
1147        let h = job.as_handle_ref();
1148        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1149            .await
1150            .expect("failed waiting for termination signal");
1151
1152        let job_info = job.info()?;
1153        assert!(job_info.exited);
1154        Ok(())
1155    }
1156
1157    #[fuchsia::test]
1158    async fn test_stop_critical_component_with_closed_lifecycle() -> Result<(), Error> {
1159        let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1160        let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1161        let process = component.copy_process().unwrap();
1162        let job_info = job.info()?;
1163        assert!(!job_info.exited);
1164
1165        // Close the lifecycle channel
1166        drop(lifecycle_server);
1167        // Kill the process because this is what ElfComponent monitors to
1168        // determine if the component exited.
1169        process.kill()?;
1170        component.stop().await;
1171
1172        let h = job.as_handle_ref();
1173        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1174            .await
1175            .expect("failed waiting for termination signal");
1176
1177        let job_info = job.info()?;
1178        assert!(job_info.exited);
1179        Ok(())
1180    }
1181
1182    #[fuchsia::test]
1183    async fn test_stop_noncritical_component_with_closed_lifecycle() -> Result<(), Error> {
1184        let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1185        let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1186
1187        let job_info = job.info()?;
1188        assert!(!job_info.exited);
1189
1190        // Close the lifecycle channel
1191        drop(lifecycle_server);
1192        // Kill the process because this is what ElfComponent monitors to
1193        // determine if the component exited.
1194        component.stop().await;
1195
1196        let h = job.as_handle_ref();
1197        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1198            .await
1199            .expect("failed waiting for termination signal");
1200
1201        let job_info = job.info()?;
1202        assert!(job_info.exited);
1203        Ok(())
1204    }
1205
1206    /// Dropping the component should kill the job hosting it.
1207    #[fuchsia::test]
1208    async fn test_drop() -> Result<(), Error> {
1209        let (job, component) = make_default_elf_component(None, false);
1210
1211        let job_info = job.info()?;
1212        assert!(!job_info.exited);
1213
1214        drop(component);
1215
1216        let h = job.as_handle_ref();
1217        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1218            .await
1219            .expect("failed waiting for termination signal");
1220
1221        let job_info = job.info()?;
1222        assert!(job_info.exited);
1223        Ok(())
1224    }
1225
1226    fn with_mark_vmo_exec(
1227        mut start_info: fcrunner::ComponentStartInfo,
1228    ) -> fcrunner::ComponentStartInfo {
1229        start_info.program.as_mut().map(|dict| {
1230            dict.entries.as_mut().map(|entry| {
1231                entry.push(fdata::DictionaryEntry {
1232                    key: "job_policy_ambient_mark_vmo_exec".to_string(),
1233                    value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1234                });
1235                entry
1236            })
1237        });
1238        start_info
1239    }
1240
1241    fn with_main_process_critical(
1242        mut start_info: fcrunner::ComponentStartInfo,
1243    ) -> fcrunner::ComponentStartInfo {
1244        start_info.program.as_mut().map(|dict| {
1245            dict.entries.as_mut().map(|entry| {
1246                entry.push(fdata::DictionaryEntry {
1247                    key: "main_process_critical".to_string(),
1248                    value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1249                });
1250                entry
1251            })
1252        });
1253        start_info
1254    }
1255
1256    #[fuchsia::test]
1257    async fn vmex_security_policy_denied() -> Result<(), Error> {
1258        let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1259        let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1260
1261        // Config does not allowlist any monikers to have access to the job policy.
1262        let runner = new_elf_runner_for_test();
1263        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1264            Arc::new(SecurityPolicy::default()),
1265            Moniker::root(),
1266        ));
1267        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1268
1269        // Attempting to start the component should fail, which we detect by looking for an
1270        // ACCESS_DENIED epitaph on the ComponentController's event stream.
1271        runner.start(start_info, server_controller).await;
1272        assert_matches!(
1273            controller.take_event_stream().try_next().await,
1274            Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1275        );
1276
1277        Ok(())
1278    }
1279
1280    #[fuchsia::test]
1281    async fn vmex_security_policy_allowed() -> Result<(), Error> {
1282        let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1283        let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1284
1285        let policy = SecurityPolicy {
1286            job_policy: JobPolicyAllowlists {
1287                ambient_mark_vmo_exec: vec![AllowlistEntryBuilder::new().exact("foo").build()],
1288                ..Default::default()
1289            },
1290            ..Default::default()
1291        };
1292        let runner = new_elf_runner_for_test();
1293        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1294            Arc::new(policy),
1295            Moniker::try_from(["foo"]).unwrap(),
1296        ));
1297        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1298        runner.start(start_info, server_controller).await;
1299
1300        // Runtime dir won't exist if the component failed to start.
1301        let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1302        assert!(process_id > 0);
1303        // Component controller should get shutdown normally; no ACCESS_DENIED epitaph.
1304        controller.kill().expect("kill failed");
1305
1306        // We expect the event stream to have closed, which is reported as an
1307        // error and the value of the error should match the epitaph for a
1308        // process that was killed.
1309        let mut event_stream = controller.take_event_stream();
1310        expect_diagnostics_event(&mut event_stream).await;
1311
1312        let s = zx::Status::from_raw(
1313            i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
1314        );
1315        expect_on_stop(&mut event_stream, s, Some(zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL)).await;
1316        expect_channel_closed(&mut event_stream).await;
1317        Ok(())
1318    }
1319
1320    #[fuchsia::test]
1321    async fn critical_security_policy_denied() -> Result<(), Error> {
1322        let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1323        let start_info = with_main_process_critical(hello_world_startinfo(runtime_dir_server));
1324
1325        // Default policy does not allowlist any monikers to be marked as critical
1326        let runner = new_elf_runner_for_test();
1327        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1328            Arc::new(SecurityPolicy::default()),
1329            Moniker::root(),
1330        ));
1331        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1332
1333        // Attempting to start the component should fail, which we detect by looking for an
1334        // ACCESS_DENIED epitaph on the ComponentController's event stream.
1335        runner.start(start_info, server_controller).await;
1336        assert_matches!(
1337            controller.take_event_stream().try_next().await,
1338            Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1339        );
1340
1341        Ok(())
1342    }
1343
1344    #[fuchsia::test]
1345    #[should_panic]
1346    async fn fail_to_launch_critical_component() {
1347        let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1348
1349        // ElfRunner should fail to start the component because this start_info points
1350        // to a binary that does not exist in the test package.
1351        let start_info = with_main_process_critical(invalid_binary_startinfo(runtime_dir_server));
1352
1353        // Policy does not allowlist any monikers to be marked as critical without being
1354        // allowlisted, so make sure we permit this one.
1355        let policy = SecurityPolicy {
1356            job_policy: JobPolicyAllowlists {
1357                main_process_critical: vec![AllowlistEntryBuilder::new().build()],
1358                ..Default::default()
1359            },
1360            ..Default::default()
1361        };
1362        let runner = new_elf_runner_for_test();
1363        let runner =
1364            runner.get_scoped_runner(ScopedPolicyChecker::new(Arc::new(policy), Moniker::root()));
1365        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1366
1367        runner.start(start_info, server_controller).await;
1368
1369        controller
1370            .take_event_stream()
1371            .try_next()
1372            .await
1373            .map(|_: Option<fcrunner::ComponentControllerEvent>| ()) // Discard.
1374            .unwrap_or_else(|error| warn!(error:%; "error reading from event stream"));
1375    }
1376
1377    fn hello_world_startinfo_forward_stdout_to_log(
1378        runtime_dir: ServerEnd<fio::DirectoryMarker>,
1379        mut ns: Vec<fcrunner::ComponentNamespaceEntry>,
1380    ) -> fcrunner::ComponentStartInfo {
1381        ns.push(pkg_dir_namespace_entry());
1382
1383        fcrunner::ComponentStartInfo {
1384            resolved_url: Some(
1385                "fuchsia-pkg://fuchsia.com/hello-world-rust#meta/hello-world-rust.cm".to_string(),
1386            ),
1387            program: Some(fdata::Dictionary {
1388                entries: Some(vec![
1389                    fdata::DictionaryEntry {
1390                        key: "binary".to_string(),
1391                        value: Some(Box::new(fdata::DictionaryValue::Str(
1392                            "bin/hello_world_rust".to_string(),
1393                        ))),
1394                    },
1395                    fdata::DictionaryEntry {
1396                        key: "forward_stdout_to".to_string(),
1397                        value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1398                    },
1399                    fdata::DictionaryEntry {
1400                        key: "forward_stderr_to".to_string(),
1401                        value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1402                    },
1403                ]),
1404                ..Default::default()
1405            }),
1406            ns: Some(ns),
1407            outgoing_dir: None,
1408            runtime_dir: Some(runtime_dir),
1409            component_instance: Some(zx::Event::create()),
1410            ..Default::default()
1411        }
1412    }
1413
1414    // TODO(https://fxbug.dev/42148789): Following function shares a lot of code with
1415    // //src/sys/component_manager/src/model/namespace.rs tests. Shared
1416    // functionality should be refactored into a common test util lib.
1417    #[fuchsia::test]
1418    async fn enable_stdout_and_stderr_logging() -> Result<(), Error> {
1419        let (mut dir, ns) = create_fs_with_mock_logsink()?;
1420
1421        let run_component_fut = async move {
1422            let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1423            let start_info = hello_world_startinfo_forward_stdout_to_log(runtime_dir_server, ns);
1424
1425            let runner = new_elf_runner_for_test();
1426            let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1427                Arc::new(SecurityPolicy::default()),
1428                Moniker::root(),
1429            ));
1430            let (client_controller, server_controller) =
1431                create_proxy::<fcrunner::ComponentControllerMarker>();
1432
1433            runner.start(start_info, server_controller).await;
1434            let mut event_stream = client_controller.take_event_stream();
1435            expect_diagnostics_event(&mut event_stream).await;
1436            expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1437            expect_channel_closed(&mut event_stream).await;
1438        };
1439
1440        // Just check for connection count, other integration tests cover decoding the actual logs.
1441        let service_fs_listener_fut = async {
1442            let mut requests = Vec::new();
1443            while let Some(MockServiceRequest::LogSink(r)) = dir.next().await {
1444                // The client is expecting us to send OnInit, but we're not testing that, so just
1445                // park the requests.
1446                requests.push(r);
1447            }
1448            requests.len()
1449        };
1450
1451        let connection_count = join!(run_component_fut, service_fs_listener_fut).1;
1452
1453        assert_eq!(connection_count, 1);
1454        Ok(())
1455    }
1456
1457    #[fuchsia::test]
1458    async fn on_publish_diagnostics_contains_job_handle() -> Result<(), Error> {
1459        let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1460        let start_info = lifecycle_startinfo(runtime_dir_server);
1461
1462        let runner = new_elf_runner_for_test();
1463        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1464            Arc::new(SecurityPolicy::default()),
1465            Moniker::root(),
1466        ));
1467        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1468
1469        runner.start(start_info, server_controller).await;
1470
1471        let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>().unwrap();
1472        let mut event_stream = controller.take_event_stream();
1473        match event_stream.try_next().await {
1474            Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1475                payload:
1476                    ComponentDiagnostics {
1477                        tasks:
1478                            Some(ComponentTasks {
1479                                component_task: Some(DiagnosticsTask::Job(job)), ..
1480                            }),
1481                        ..
1482                    },
1483            })) => {
1484                assert_eq!(job_id, job.get_koid().unwrap().raw_koid());
1485            }
1486            other => panic!("unexpected event result: {:?}", other),
1487        }
1488
1489        controller.stop().expect("Stop request failed");
1490        // Wait for the process to exit so the test doesn't pagefault due to an invalid stdout
1491        // handle.
1492        controller.on_closed().await.expect("failed waiting for channel to close");
1493
1494        Ok(())
1495    }
1496
1497    async fn expect_diagnostics_event(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1498        let event = event_stream.try_next().await;
1499        assert_matches!(
1500            event,
1501            Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1502                payload: ComponentDiagnostics {
1503                    tasks: Some(ComponentTasks {
1504                        component_task: Some(DiagnosticsTask::Job(_)),
1505                        ..
1506                    }),
1507                    ..
1508                },
1509            }))
1510        );
1511    }
1512
1513    async fn expect_on_stop(
1514        event_stream: &mut fcrunner::ComponentControllerEventStream,
1515        expected_status: zx::Status,
1516        expected_exit_code: Option<i64>,
1517    ) {
1518        let event = event_stream.try_next().await;
1519        assert_matches!(
1520            event,
1521            Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
1522                payload: fcrunner::ComponentStopInfo { termination_status: Some(s), exit_code, .. },
1523            }))
1524            if s == expected_status.into_raw() &&
1525                exit_code == expected_exit_code
1526        );
1527    }
1528
1529    async fn expect_channel_closed(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1530        let event = event_stream.try_next().await;
1531        match event {
1532            Ok(None) => {}
1533            other => panic!("Expected channel closed error, got {:?}", other),
1534        }
1535    }
1536
1537    /// An implementation of launcher that sends a complete launch request payload back to
1538    /// a test through an mpsc channel.
1539    struct LauncherConnectorForTest {
1540        sender: mpsc::UnboundedSender<LaunchPayload>,
1541    }
1542
1543    /// Contains all the information passed to fuchsia.process.Launcher before and up to calling
1544    /// Launch/CreateWithoutStarting.
1545    #[derive(Default)]
1546    struct LaunchPayload {
1547        launch_info: Option<fproc::LaunchInfo>,
1548        args: Vec<Vec<u8>>,
1549        environ: Vec<Vec<u8>>,
1550        name_info: Vec<fproc::NameInfo>,
1551        handles: Vec<fproc::HandleInfo>,
1552        options: u32,
1553    }
1554
1555    impl Connect for LauncherConnectorForTest {
1556        type Proxy = fproc::LauncherProxy;
1557
1558        fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
1559            let sender = self.sender.clone();
1560            let payload = Arc::new(Mutex::new(LaunchPayload::default()));
1561
1562            Ok(spawn_stream_handler(move |launcher_request| {
1563                let sender = sender.clone();
1564                let payload = payload.clone();
1565                async move {
1566                    let mut payload = payload.lock().await;
1567                    match launcher_request {
1568                        fproc::LauncherRequest::Launch { info, responder } => {
1569                            let process = create_child_process(&info.job, "test_process");
1570                            responder.send(zx::Status::OK.into_raw(), Some(process)).unwrap();
1571
1572                            let mut payload =
1573                                std::mem::replace(&mut *payload, LaunchPayload::default());
1574                            payload.launch_info = Some(info);
1575                            sender.unbounded_send(payload).unwrap();
1576                        }
1577                        fproc::LauncherRequest::CreateWithoutStarting { info: _, responder: _ } => {
1578                            unimplemented!()
1579                        }
1580                        fproc::LauncherRequest::AddArgs { mut args, control_handle: _ } => {
1581                            payload.args.append(&mut args);
1582                        }
1583                        fproc::LauncherRequest::AddEnvirons { mut environ, control_handle: _ } => {
1584                            payload.environ.append(&mut environ);
1585                        }
1586                        fproc::LauncherRequest::AddNames { mut names, control_handle: _ } => {
1587                            payload.name_info.append(&mut names);
1588                        }
1589                        fproc::LauncherRequest::AddHandles { mut handles, control_handle: _ } => {
1590                            payload.handles.append(&mut handles);
1591                        }
1592                        fproc::LauncherRequest::SetOptions { options, .. } => {
1593                            payload.options = options;
1594                        }
1595                    }
1596                }
1597            }))
1598        }
1599    }
1600
1601    #[fuchsia::test]
1602    async fn process_created_with_utc_clock_from_numbered_handles() -> Result<(), Error> {
1603        let (payload_tx, mut payload_rx) = mpsc::unbounded();
1604
1605        let connector = LauncherConnectorForTest { sender: payload_tx };
1606        let runner = ElfRunner::new(
1607            job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
1608            Box::new(connector),
1609            Some(new_utc_clock_for_tests()),
1610            CrashRecords::new(),
1611        );
1612        let policy_checker = ScopedPolicyChecker::new(
1613            Arc::new(SecurityPolicy::default()),
1614            Moniker::try_from(["foo"]).unwrap(),
1615        );
1616
1617        // Create a clock and pass it to the component as the UTC clock through numbered_handles.
1618        let clock = zx::SyntheticClock::create(
1619            zx::ClockOpts::AUTO_START | zx::ClockOpts::MONOTONIC | zx::ClockOpts::MAPPABLE,
1620            None,
1621        )?;
1622        let clock_koid = clock.get_koid().unwrap();
1623
1624        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1625        let mut start_info = hello_world_startinfo(runtime_dir_server);
1626        start_info.numbered_handles = Some(vec![fproc::HandleInfo {
1627            handle: clock.into_handle(),
1628            id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
1629        }]);
1630
1631        // Start the component.
1632        let _ = runner
1633            .start_component(start_info, &policy_checker)
1634            .await
1635            .context("failed to start component")?;
1636
1637        let payload = payload_rx.next().await.unwrap();
1638        assert!(
1639            payload
1640                .handles
1641                .iter()
1642                .any(|handle_info| handle_info.handle.get_koid().unwrap() == clock_koid)
1643        );
1644
1645        Ok(())
1646    }
1647
1648    /// Test visiting running components using [`ComponentSet`].
1649    #[fuchsia::test]
1650    async fn test_enumerate_components() {
1651        use std::sync::atomic::{AtomicUsize, Ordering};
1652
1653        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1654        let start_info = lifecycle_startinfo(runtime_dir_server);
1655
1656        let runner = new_elf_runner_for_test();
1657        let components = runner.components.clone();
1658
1659        // Initially there are zero components.
1660        let count = Arc::new(AtomicUsize::new(0));
1661        components.clone().visit(|_, _| {
1662            count.fetch_add(1, Ordering::SeqCst);
1663        });
1664        assert_eq!(count.load(Ordering::SeqCst), 0);
1665
1666        // Run a component.
1667        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1668            Arc::new(SecurityPolicy::default()),
1669            Moniker::root(),
1670        ));
1671        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1672        runner.start(start_info, server_controller).await;
1673
1674        // There should now be one component in the set.
1675        let count = Arc::new(AtomicUsize::new(0));
1676        components.clone().visit(|elf_component: &ElfComponentInfo, _| {
1677            assert_eq!(
1678                elf_component.get_url().as_str(),
1679                "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm"
1680            );
1681            count.fetch_add(1, Ordering::SeqCst);
1682        });
1683        assert_eq!(count.load(Ordering::SeqCst), 1);
1684
1685        // Stop the component.
1686        controller.stop().unwrap();
1687        controller.on_closed().await.unwrap();
1688
1689        // There should now be zero components in the set.
1690        // Keep retrying until the component is asynchronously deregistered.
1691        loop {
1692            let count = Arc::new(AtomicUsize::new(0));
1693            components.clone().visit(|_, _| {
1694                count.fetch_add(1, Ordering::SeqCst);
1695            });
1696            let count = count.load(Ordering::SeqCst);
1697            assert!(count == 0 || count == 1);
1698            if count == 0 {
1699                break;
1700            }
1701            // Yield to the executor once so that we are not starving the
1702            // asynchronous deregistration task from running.
1703            yield_to_executor().await;
1704        }
1705    }
1706
1707    async fn yield_to_executor() {
1708        let mut done = false;
1709        futures::future::poll_fn(|cx| {
1710            if done {
1711                Poll::Ready(())
1712            } else {
1713                done = true;
1714                cx.waker().wake_by_ref();
1715                Poll::Pending
1716            }
1717        })
1718        .await;
1719    }
1720
1721    /// Creates start info for a component which runs immediately escrows its
1722    /// outgoing directory and then exits.
1723    pub fn immediate_escrow_startinfo(
1724        outgoing_dir: ServerEnd<fio::DirectoryMarker>,
1725        runtime_dir: ServerEnd<fio::DirectoryMarker>,
1726    ) -> fcrunner::ComponentStartInfo {
1727        let ns = vec![
1728            pkg_dir_namespace_entry(),
1729            // Give the test component LogSink.
1730            svc_dir_namespace_entry(),
1731        ];
1732
1733        fcrunner::ComponentStartInfo {
1734            resolved_url: Some("#meta/immediate_escrow_component.cm".to_string()),
1735            program: Some(fdata::Dictionary {
1736                entries: Some(vec![
1737                    fdata::DictionaryEntry {
1738                        key: "binary".to_string(),
1739                        value: Some(Box::new(fdata::DictionaryValue::Str(
1740                            "bin/immediate_escrow".to_string(),
1741                        ))),
1742                    },
1743                    fdata::DictionaryEntry {
1744                        key: "lifecycle.stop_event".to_string(),
1745                        value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
1746                    },
1747                ]),
1748                ..Default::default()
1749            }),
1750            ns: Some(ns),
1751            outgoing_dir: Some(outgoing_dir),
1752            runtime_dir: Some(runtime_dir),
1753            component_instance: Some(zx::Event::create()),
1754            ..Default::default()
1755        }
1756    }
1757
1758    /// Test that an ELF component can send an `OnEscrow` event on its lifecycle
1759    /// channel and this event is forwarded to the `ComponentController`.
1760    #[fuchsia::test]
1761    async fn test_lifecycle_on_escrow() {
1762        let (outgoing_dir_client, outgoing_dir_server) =
1763            fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1764        let (_, runtime_dir_server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1765        let start_info = immediate_escrow_startinfo(outgoing_dir_server, runtime_dir_server);
1766
1767        let runner = new_elf_runner_for_test();
1768        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1769            Arc::new(SecurityPolicy::default()),
1770            Moniker::root(),
1771        ));
1772        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1773
1774        runner.start(start_info, server_controller).await;
1775
1776        let mut event_stream = controller.take_event_stream();
1777
1778        expect_diagnostics_event(&mut event_stream).await;
1779
1780        match event_stream.try_next().await {
1781            Ok(Some(fcrunner::ComponentControllerEvent::OnEscrow {
1782                payload: fcrunner::ComponentControllerOnEscrowRequest { outgoing_dir, .. },
1783            })) => {
1784                let outgoing_dir_server = outgoing_dir.unwrap();
1785
1786                assert_eq!(
1787                    outgoing_dir_client.basic_info().unwrap().koid,
1788                    outgoing_dir_server.basic_info().unwrap().related_koid
1789                );
1790            }
1791            other => panic!("unexpected event result: {:?}", other),
1792        }
1793
1794        expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1795        expect_channel_closed(&mut event_stream).await;
1796    }
1797
1798    fn exit_with_code_startinfo(exit_code: i64) -> fcrunner::ComponentStartInfo {
1799        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1800        let ns = vec![pkg_dir_namespace_entry()];
1801
1802        fcrunner::ComponentStartInfo {
1803            resolved_url: Some(
1804                "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/exit-with-code.cm".to_string(),
1805            ),
1806            program: Some(fdata::Dictionary {
1807                entries: Some(vec![
1808                    fdata::DictionaryEntry {
1809                        key: "args".to_string(),
1810                        value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![format!(
1811                            "{}",
1812                            exit_code
1813                        )]))),
1814                    },
1815                    fdata::DictionaryEntry {
1816                        key: "binary".to_string(),
1817                        value: Some(Box::new(fdata::DictionaryValue::Str(
1818                            "bin/exit_with_code".to_string(),
1819                        ))),
1820                    },
1821                ]),
1822                ..Default::default()
1823            }),
1824            ns: Some(ns),
1825            outgoing_dir: None,
1826            runtime_dir: Some(runtime_dir_server),
1827            component_instance: Some(zx::Event::create()),
1828            ..Default::default()
1829        }
1830    }
1831
1832    #[fuchsia::test]
1833    async fn test_return_code_success() {
1834        let start_info = exit_with_code_startinfo(0);
1835
1836        let runner = new_elf_runner_for_test();
1837        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1838            Arc::new(SecurityPolicy::default()),
1839            Moniker::root(),
1840        ));
1841        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1842        runner.start(start_info, server_controller).await;
1843
1844        let mut event_stream = controller.take_event_stream();
1845        expect_diagnostics_event(&mut event_stream).await;
1846        expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1847        expect_channel_closed(&mut event_stream).await;
1848    }
1849
1850    #[fuchsia::test]
1851    async fn test_return_code_failure() {
1852        let start_info = exit_with_code_startinfo(123);
1853
1854        let runner = new_elf_runner_for_test();
1855        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1856            Arc::new(SecurityPolicy::default()),
1857            Moniker::root(),
1858        ));
1859        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1860        runner.start(start_info, server_controller).await;
1861
1862        let mut event_stream = controller.take_event_stream();
1863        expect_diagnostics_event(&mut event_stream).await;
1864        let s = zx::Status::from_raw(
1865            i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
1866        );
1867        expect_on_stop(&mut event_stream, s, Some(123)).await;
1868        expect_channel_closed(&mut event_stream).await;
1869    }
1870}