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_instant_mono =
507            process.info().map_err(StartComponentError::ProcessInfoFailed)?.start_time;
508        runtime_dir.add_process_start_time(process_start_instant_mono.into_nanos());
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 maybe_time_utc = mono_to_clock_transformation
539                .map(|t| t.apply(process_start_instant_mono))
540                .map(|time_boot| clock_transformation.apply(time_boot));
541
542            if let Some(utc_timestamp) = maybe_time_utc {
543                let utc_time_ns = utc_timestamp.into_nanos();
544                let seconds = (utc_time_ns / 1_000_000_000) as i64;
545                let nanos = (utc_time_ns % 1_000_000_000) as u32;
546                let dt = DateTime::from_timestamp(seconds, nanos).unwrap();
547
548                // If any of the above values are unavailable (unlikely), then this
549                // does not happen.
550                runtime_dir.add_process_start_time_utc_estimate(dt.to_string())
551            }
552        };
553
554        Ok(ElfComponent::new(
555            runtime_dir,
556            moniker,
557            job,
558            process,
559            lifecycle_client,
560            program_config.main_process_critical,
561            local_scope,
562            resolved_url.clone(),
563            outgoing_directory,
564            program_config,
565            start_info.component_instance.ok_or(StartComponentError::StartInfoError(
566                StartInfoError::MissingComponentInstanceToken,
567            ))?,
568        ))
569    }
570
571    pub fn get_scoped_runner(
572        self: Arc<Self>,
573        checker: ScopedPolicyChecker,
574    ) -> Arc<ScopedElfRunner> {
575        Arc::new(ScopedElfRunner { runner: self, checker })
576    }
577
578    pub fn serve_memory_reporter(&self, stream: fattribution::ProviderRequestStream) {
579        self.memory_reporter.serve(stream);
580    }
581}
582
583pub struct ScopedElfRunner {
584    runner: Arc<ElfRunner>,
585    checker: ScopedPolicyChecker,
586}
587
588impl ScopedElfRunner {
589    pub fn serve(&self, mut stream: fcrunner::ComponentRunnerRequestStream) {
590        let runner = self.runner.clone();
591        let checker = self.checker.clone();
592        self.scope().spawn(async move {
593            while let Ok(Some(request)) = stream.try_next().await {
594                match request {
595                    fcrunner::ComponentRunnerRequest::Start { start_info, controller, .. } => {
596                        start(&runner, checker.clone(), start_info, controller).await;
597                    }
598                    fcrunner::ComponentRunnerRequest::_UnknownMethod { ordinal, .. } => {
599                        warn!(ordinal:%; "Unknown ComponentRunner request");
600                    }
601                }
602            }
603        });
604    }
605
606    pub async fn start(
607        &self,
608        start_info: fcrunner::ComponentStartInfo,
609        server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
610    ) {
611        start(&self.runner, self.checker.clone(), start_info, server_end).await
612    }
613
614    pub(crate) fn scope(&self) -> &ExecutionScope {
615        &self.runner.scope
616    }
617}
618
619/// Starts a component by creating a new Job and Process for the component.
620async fn start(
621    runner: &ElfRunner,
622    checker: ScopedPolicyChecker,
623    start_info: fcrunner::ComponentStartInfo,
624    server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
625) {
626    let resolved_url = start_info.resolved_url.clone().unwrap_or_else(|| "<unknown>".to_string());
627
628    let elf_component = match runner.start_component(start_info, &checker).await {
629        Ok(elf_component) => elf_component,
630        Err(err) => {
631            runner::component::report_start_error(
632                err.as_zx_status(),
633                format!("{}", err),
634                &resolved_url,
635                server_end,
636            );
637            return;
638        }
639    };
640
641    let (termination_tx, termination_rx) = oneshot::channel::<StopInfo>();
642    // This function waits for something from the channel and
643    // returns it or Error::Internal if the channel is closed
644    let termination_fn = Box::pin(async move {
645        termination_rx
646            .await
647            .unwrap_or_else(|_| {
648                warn!("epitaph oneshot channel closed unexpectedly");
649                StopInfo::from_error(fcomp::Error::Internal, None)
650            })
651            .into()
652    });
653
654    let Some(proc_copy) = elf_component.copy_process() else {
655        runner::component::report_start_error(
656            zx::Status::from_raw(
657                i32::try_from(fcomp::Error::InstanceCannotStart.into_primitive()).unwrap(),
658            ),
659            "Component unexpectedly had no process".to_string(),
660            &resolved_url,
661            server_end,
662        );
663        return;
664    };
665
666    let component_diagnostics = elf_component
667        .info()
668        .copy_job_for_diagnostics()
669        .map(|job| ComponentDiagnostics {
670            tasks: Some(ComponentTasks {
671                component_task: Some(DiagnosticsTask::Job(job.into())),
672                ..Default::default()
673            }),
674            ..Default::default()
675        })
676        .map_err(|error| {
677            warn!(error:%; "Failed to copy job for diagnostics");
678            ()
679        })
680        .ok();
681
682    let (server_stream, control) = server_end.into_stream_and_control_handle();
683
684    // Spawn a future that watches for the process to exit
685    runner.scope.spawn({
686        let resolved_url = resolved_url.clone();
687        async move {
688            fasync::OnSignals::new(&proc_copy.as_handle_ref(), zx::Signals::PROCESS_TERMINATED)
689                .await
690                .map(|_: fidl::Signals| ()) // Discard.
691                .unwrap_or_else(|error| warn!(error:%; "error creating signal handler"));
692            // Process exit code '0' is considered a clean return.
693            // TODO(https://fxbug.dev/42134825) If we create an epitaph that indicates
694            // intentional, non-zero exit, use that for all non-0 exit
695            // codes.
696            let stop_info = match proc_copy.info() {
697                Ok(zx::ProcessInfo { return_code, .. }) => {
698                    match return_code {
699                        0 => StopInfo::from_ok(Some(return_code)),
700                        // Don't log SYSCALL_KILL codes because they are expected in the course
701                        // of normal operation. When elf_runner process a `Kill` method call for
702                        // a component it makes a zx_task_kill syscall which sets this return code.
703                        zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL => StopInfo::from_error(
704                            fcomp::Error::InstanceDied.into(),
705                            Some(return_code),
706                        ),
707                        _ => {
708                            warn!(url:% = resolved_url, return_code:%;
709                                "process terminated with abnormal return code");
710                            StopInfo::from_error(fcomp::Error::InstanceDied, Some(return_code))
711                        }
712                    }
713                }
714                Err(error) => {
715                    warn!(error:%; "Unable to query process info");
716                    StopInfo::from_error(fcomp::Error::Internal, None)
717                }
718            };
719            termination_tx.send(stop_info).unwrap_or_else(|_| warn!("error sending done signal"));
720        }
721    });
722
723    let mut elf_component = elf_component;
724    runner.components.clone().add(&mut elf_component);
725
726    // Create a future which owns and serves the controller
727    // channel. The `epitaph_fn` future completes when the
728    // component's main process exits. The controller then sets the
729    // epitaph on the controller channel, closes it, and stops
730    // serving the protocol.
731    runner.scope.spawn(async move {
732        if let Some(component_diagnostics) = component_diagnostics {
733            control.send_on_publish_diagnostics(component_diagnostics).unwrap_or_else(
734                |error| warn!(url:% = resolved_url, error:%; "sending diagnostics failed"),
735            );
736        }
737        runner::component::Controller::new(elf_component, server_stream, control)
738            .serve(termination_fn)
739            .await;
740    });
741}
742
743#[cfg(test)]
744mod tests {
745    use super::runtime_dir::RuntimeDirectory;
746    use super::*;
747    use anyhow::{Context, Error};
748    use assert_matches::assert_matches;
749    use cm_config::{AllowlistEntryBuilder, JobPolicyAllowlists, SecurityPolicy};
750    use fidl::endpoints::{DiscoverableProtocolMarker, Proxy, create_endpoints, create_proxy};
751    use fidl_connector::Connect;
752    use fidl_fuchsia_component_runner::Task as DiagnosticsTask;
753    use fidl_fuchsia_logger::{LogSinkMarker, LogSinkRequestStream};
754    use fidl_fuchsia_process_lifecycle::LifecycleProxy;
755    use fidl_test_util::spawn_stream_handler;
756    use fuchsia_component::server::{ServiceFs, ServiceObjLocal};
757    use futures::channel::mpsc;
758    use futures::lock::Mutex;
759    use futures::{StreamExt, join};
760    use runner::component::Controllable;
761    use std::task::Poll;
762    use zx::{self as zx, Task};
763    use {
764        fidl_fuchsia_component as fcomp, fidl_fuchsia_component_runner as fcrunner,
765        fidl_fuchsia_data as fdata, fidl_fuchsia_io as fio, fuchsia_async as fasync,
766    };
767
768    pub enum MockServiceRequest {
769        LogSink(LogSinkRequestStream),
770    }
771
772    pub type MockServiceFs<'a> = ServiceFs<ServiceObjLocal<'a, MockServiceRequest>>;
773
774    /// Create a new local fs and install a mock LogSink service into.
775    /// Returns the created directory and corresponding namespace entries.
776    pub fn create_fs_with_mock_logsink()
777    -> Result<(MockServiceFs<'static>, Vec<fcrunner::ComponentNamespaceEntry>), Error> {
778        let (dir_client, dir_server) = create_endpoints::<fio::DirectoryMarker>();
779
780        let mut dir = ServiceFs::new_local();
781        dir.add_fidl_service_at(LogSinkMarker::PROTOCOL_NAME, MockServiceRequest::LogSink);
782        dir.serve_connection(dir_server).context("Failed to add serving channel.")?;
783
784        let namespace = vec![fcrunner::ComponentNamespaceEntry {
785            path: Some("/svc".to_string()),
786            directory: Some(dir_client),
787            ..Default::default()
788        }];
789
790        Ok((dir, namespace))
791    }
792
793    // Provide a UTC clock to avoid reusing the system UTC clock in tests, which may
794    // limit the changes that are allowed to be made to this code. We create this clock
795    // here, and start it from current time.
796    pub fn new_utc_clock_for_tests() -> Arc<UtcClock> {
797        let reference_now = zx::BootInstant::get();
798        let system_utc_clock = duplicate_utc_clock_handle(zx::Rights::SAME_RIGHTS).unwrap();
799        let utc_now = system_utc_clock.read().unwrap();
800
801        let utc_clock_for_tests =
802            Arc::new(UtcClock::create(zx::ClockOpts::MAPPABLE, /*backstop=*/ None).unwrap());
803        // This will start the test-only UTC clock.
804        utc_clock_for_tests
805            .update(zx::ClockUpdate::builder().absolute_value(reference_now, utc_now.into()))
806            .unwrap();
807        utc_clock_for_tests
808    }
809
810    pub fn new_elf_runner_for_test() -> Arc<ElfRunner> {
811        Arc::new(ElfRunner::new(
812            job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
813            Box::new(process_launcher::BuiltInConnector {}),
814            Some(new_utc_clock_for_tests()),
815            CrashRecords::new(),
816        ))
817    }
818
819    fn namespace_entry(path: &str, flags: fio::Flags) -> fcrunner::ComponentNamespaceEntry {
820        // Get a handle to /pkg
821        let ns_path = path.to_string();
822        let ns_dir = fuchsia_fs::directory::open_in_namespace(path, flags).unwrap();
823        let client_end = ns_dir.into_client_end().unwrap();
824        fcrunner::ComponentNamespaceEntry {
825            path: Some(ns_path),
826            directory: Some(client_end),
827            ..Default::default()
828        }
829    }
830
831    fn pkg_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
832        namespace_entry("/pkg", fio::PERM_READABLE | fio::PERM_EXECUTABLE)
833    }
834
835    fn svc_dir_namespace_entry() -> fcrunner::ComponentNamespaceEntry {
836        namespace_entry("/svc", fio::PERM_READABLE)
837    }
838
839    fn hello_world_startinfo(
840        runtime_dir: ServerEnd<fio::DirectoryMarker>,
841    ) -> fcrunner::ComponentStartInfo {
842        let ns = vec![pkg_dir_namespace_entry()];
843
844        fcrunner::ComponentStartInfo {
845            resolved_url: Some(
846                "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/hello-world-rust.cm".to_string(),
847            ),
848            program: Some(fdata::Dictionary {
849                entries: Some(vec![
850                    fdata::DictionaryEntry {
851                        key: "args".to_string(),
852                        value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
853                            "foo".to_string(),
854                            "bar".to_string(),
855                        ]))),
856                    },
857                    fdata::DictionaryEntry {
858                        key: "binary".to_string(),
859                        value: Some(Box::new(fdata::DictionaryValue::Str(
860                            "bin/hello_world_rust".to_string(),
861                        ))),
862                    },
863                ]),
864                ..Default::default()
865            }),
866            ns: Some(ns),
867            outgoing_dir: None,
868            runtime_dir: Some(runtime_dir),
869            component_instance: Some(zx::Event::create()),
870            ..Default::default()
871        }
872    }
873
874    /// ComponentStartInfo that points to a non-existent binary.
875    fn invalid_binary_startinfo(
876        runtime_dir: ServerEnd<fio::DirectoryMarker>,
877    ) -> fcrunner::ComponentStartInfo {
878        let ns = vec![pkg_dir_namespace_entry()];
879
880        fcrunner::ComponentStartInfo {
881            resolved_url: Some(
882                "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/does-not-exist.cm".to_string(),
883            ),
884            program: Some(fdata::Dictionary {
885                entries: Some(vec![fdata::DictionaryEntry {
886                    key: "binary".to_string(),
887                    value: Some(Box::new(fdata::DictionaryValue::Str(
888                        "bin/does_not_exist".to_string(),
889                    ))),
890                }]),
891                ..Default::default()
892            }),
893            ns: Some(ns),
894            outgoing_dir: None,
895            runtime_dir: Some(runtime_dir),
896            component_instance: Some(zx::Event::create()),
897            ..Default::default()
898        }
899    }
900
901    /// Creates start info for a component which runs until told to exit. The
902    /// ComponentController protocol can be used to stop the component when the
903    /// test is done inspecting the launched component.
904    pub fn lifecycle_startinfo(
905        runtime_dir: ServerEnd<fio::DirectoryMarker>,
906    ) -> fcrunner::ComponentStartInfo {
907        let ns = vec![pkg_dir_namespace_entry()];
908
909        fcrunner::ComponentStartInfo {
910            resolved_url: Some(
911                "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm".to_string(),
912            ),
913            program: Some(fdata::Dictionary {
914                entries: Some(vec![
915                    fdata::DictionaryEntry {
916                        key: "args".to_string(),
917                        value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
918                            "foo".to_string(),
919                            "bar".to_string(),
920                        ]))),
921                    },
922                    fdata::DictionaryEntry {
923                        key: "binary".to_string(),
924                        value: Some(Box::new(fdata::DictionaryValue::Str(
925                            "bin/lifecycle_placeholder".to_string(),
926                        ))),
927                    },
928                    fdata::DictionaryEntry {
929                        key: "lifecycle.stop_event".to_string(),
930                        value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
931                    },
932                ]),
933                ..Default::default()
934            }),
935            ns: Some(ns),
936            outgoing_dir: None,
937            runtime_dir: Some(runtime_dir),
938            component_instance: Some(zx::Event::create()),
939            ..Default::default()
940        }
941    }
942
943    fn create_child_process(job: &zx::Job, name: &str) -> zx::Process {
944        let (process, _vmar) = job
945            .create_child_process(zx::ProcessOptions::empty(), name.as_bytes())
946            .expect("could not create process");
947        process
948    }
949
950    fn make_default_elf_component(
951        lifecycle_client: Option<LifecycleProxy>,
952        critical: bool,
953    ) -> (scoped_task::Scoped<zx::Job>, ElfComponent) {
954        let job = scoped_task::create_child_job().expect("failed to make child job");
955        let process = create_child_process(&job, "test_process");
956        let job_copy =
957            job.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("job handle duplication failed");
958        let component = ElfComponent::new(
959            RuntimeDirectory::empty(),
960            Moniker::default(),
961            Job::Single(job_copy),
962            process,
963            lifecycle_client,
964            critical,
965            ExecutionScope::new(),
966            "".to_string(),
967            None,
968            Default::default(),
969            zx::Event::create(),
970        );
971        (job, component)
972    }
973
974    // TODO(https://fxbug.dev/42073224): A variation of this is used in a couple of places. We should consider
975    // refactoring this into a test util file.
976    async fn read_file<'a>(root_proxy: &'a fio::DirectoryProxy, path: &'a str) -> String {
977        let file_proxy =
978            fuchsia_fs::directory::open_file_async(&root_proxy, path, fuchsia_fs::PERM_READABLE)
979                .expect("Failed to open file.");
980        let res = fuchsia_fs::file::read_to_string(&file_proxy).await;
981        res.expect("Unable to read file.")
982    }
983
984    #[fuchsia::test]
985    async fn test_runtime_dir_entries() -> Result<(), Error> {
986        let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
987        let start_info = lifecycle_startinfo(runtime_dir_server);
988
989        let runner = new_elf_runner_for_test();
990        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
991            Arc::new(SecurityPolicy::default()),
992            Moniker::root(),
993        ));
994        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
995
996        runner.start(start_info, server_controller).await;
997
998        // Verify that args are added to the runtime directory.
999        assert_eq!("foo", read_file(&runtime_dir, "args/0").await);
1000        assert_eq!("bar", read_file(&runtime_dir, "args/1").await);
1001
1002        // Process Id, Process Start Time, Job Id will vary with every run of this test. Here we
1003        // verify that they exist in the runtime directory, they can be parsed as integers,
1004        // they're greater than zero and they are not the same value. Those are about the only
1005        // invariants we can verify across test runs.
1006        let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1007        let process_start_time =
1008            read_file(&runtime_dir, "elf/process_start_time").await.parse::<i64>()?;
1009        let process_start_time_utc_estimate =
1010            read_file(&runtime_dir, "elf/process_start_time_utc_estimate").await;
1011        let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>()?;
1012        assert!(process_id > 0);
1013        assert!(process_start_time > 0);
1014        assert!(process_start_time_utc_estimate.contains("UTC"));
1015        assert!(job_id > 0);
1016        assert_ne!(process_id, job_id);
1017
1018        controller.stop().expect("Stop request failed");
1019        // Wait for the process to exit so the test doesn't pagefault due to an invalid stdout
1020        // handle.
1021        controller.on_closed().await.expect("failed waiting for channel to close");
1022        Ok(())
1023    }
1024
1025    #[fuchsia::test]
1026    async fn test_kill_component() -> Result<(), Error> {
1027        let (job, mut component) = make_default_elf_component(None, false);
1028
1029        let job_info = job.info()?;
1030        assert!(!job_info.exited);
1031
1032        component.kill().await;
1033
1034        let h = job.as_handle_ref();
1035        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1036            .await
1037            .expect("failed waiting for termination signal");
1038
1039        let job_info = job.info()?;
1040        assert!(job_info.exited);
1041        Ok(())
1042    }
1043
1044    #[fuchsia::test]
1045    fn test_stop_critical_component() -> Result<(), Error> {
1046        let mut exec = fasync::TestExecutor::new();
1047        // Presence of the Lifecycle channel isn't used by ElfComponent to sense
1048        // component exit, but it does modify the stop behavior and this is
1049        // what we want to test.
1050        let (lifecycle_client, _lifecycle_server) = create_proxy::<LifecycleMarker>();
1051        let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1052        let process = component.copy_process().unwrap();
1053        let job_info = job.info()?;
1054        assert!(!job_info.exited);
1055
1056        // Ask the runner to stop the component, it returns a future which
1057        // completes when the component closes its side of the lifecycle
1058        // channel
1059        let mut completes_when_stopped = component.stop();
1060
1061        // The returned future shouldn't complete because we're holding the
1062        // lifecycle channel open.
1063        match exec.run_until_stalled(&mut completes_when_stopped) {
1064            Poll::Ready(_) => {
1065                panic!("runner should still be waiting for lifecycle channel to stop");
1066            }
1067            _ => {}
1068        }
1069        assert_eq!(process.kill(), Ok(()));
1070
1071        exec.run_singlethreaded(&mut completes_when_stopped);
1072
1073        // Check that the runner killed the job hosting the exited component.
1074        let h = job.as_handle_ref();
1075        let termination_fut = async move {
1076            fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1077                .await
1078                .expect("failed waiting for termination signal");
1079        };
1080        exec.run_singlethreaded(termination_fut);
1081
1082        let job_info = job.info()?;
1083        assert!(job_info.exited);
1084        Ok(())
1085    }
1086
1087    #[fuchsia::test]
1088    fn test_stop_noncritical_component() -> Result<(), Error> {
1089        let mut exec = fasync::TestExecutor::new();
1090        // Presence of the Lifecycle channel isn't used by ElfComponent to sense
1091        // component exit, but it does modify the stop behavior and this is
1092        // what we want to test.
1093        let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1094        let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1095
1096        let job_info = job.info()?;
1097        assert!(!job_info.exited);
1098
1099        // Ask the runner to stop the component, it returns a future which
1100        // completes when the component closes its side of the lifecycle
1101        // channel
1102        let mut completes_when_stopped = component.stop();
1103
1104        // The returned future shouldn't complete because we're holding the
1105        // lifecycle channel open.
1106        match exec.run_until_stalled(&mut completes_when_stopped) {
1107            Poll::Ready(_) => {
1108                panic!("runner should still be waiting for lifecycle channel to stop");
1109            }
1110            _ => {}
1111        }
1112        drop(lifecycle_server);
1113
1114        match exec.run_until_stalled(&mut completes_when_stopped) {
1115            Poll::Ready(_) => {}
1116            _ => {
1117                panic!("runner future should have completed, lifecycle channel is closed.");
1118            }
1119        }
1120        // Check that the runner killed the job hosting the exited component.
1121        let h = job.as_handle_ref();
1122        let termination_fut = async move {
1123            fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1124                .await
1125                .expect("failed waiting for termination signal");
1126        };
1127        exec.run_singlethreaded(termination_fut);
1128
1129        let job_info = job.info()?;
1130        assert!(job_info.exited);
1131        Ok(())
1132    }
1133
1134    /// Stopping a component which doesn't have a lifecycle channel should be
1135    /// equivalent to killing a component directly.
1136    #[fuchsia::test]
1137    async fn test_stop_component_without_lifecycle() -> Result<(), Error> {
1138        let (job, mut component) = make_default_elf_component(None, false);
1139
1140        let job_info = job.info()?;
1141        assert!(!job_info.exited);
1142
1143        component.stop().await;
1144
1145        let h = job.as_handle_ref();
1146        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1147            .await
1148            .expect("failed waiting for termination signal");
1149
1150        let job_info = job.info()?;
1151        assert!(job_info.exited);
1152        Ok(())
1153    }
1154
1155    #[fuchsia::test]
1156    async fn test_stop_critical_component_with_closed_lifecycle() -> Result<(), Error> {
1157        let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1158        let (job, mut component) = make_default_elf_component(Some(lifecycle_client), true);
1159        let process = component.copy_process().unwrap();
1160        let job_info = job.info()?;
1161        assert!(!job_info.exited);
1162
1163        // Close the lifecycle channel
1164        drop(lifecycle_server);
1165        // Kill the process because this is what ElfComponent monitors to
1166        // determine if the component exited.
1167        process.kill()?;
1168        component.stop().await;
1169
1170        let h = job.as_handle_ref();
1171        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1172            .await
1173            .expect("failed waiting for termination signal");
1174
1175        let job_info = job.info()?;
1176        assert!(job_info.exited);
1177        Ok(())
1178    }
1179
1180    #[fuchsia::test]
1181    async fn test_stop_noncritical_component_with_closed_lifecycle() -> Result<(), Error> {
1182        let (lifecycle_client, lifecycle_server) = create_proxy::<LifecycleMarker>();
1183        let (job, mut component) = make_default_elf_component(Some(lifecycle_client), false);
1184
1185        let job_info = job.info()?;
1186        assert!(!job_info.exited);
1187
1188        // Close the lifecycle channel
1189        drop(lifecycle_server);
1190        // Kill the process because this is what ElfComponent monitors to
1191        // determine if the component exited.
1192        component.stop().await;
1193
1194        let h = job.as_handle_ref();
1195        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1196            .await
1197            .expect("failed waiting for termination signal");
1198
1199        let job_info = job.info()?;
1200        assert!(job_info.exited);
1201        Ok(())
1202    }
1203
1204    /// Dropping the component should kill the job hosting it.
1205    #[fuchsia::test]
1206    async fn test_drop() -> Result<(), Error> {
1207        let (job, component) = make_default_elf_component(None, false);
1208
1209        let job_info = job.info()?;
1210        assert!(!job_info.exited);
1211
1212        drop(component);
1213
1214        let h = job.as_handle_ref();
1215        fasync::OnSignals::new(&h, zx::Signals::TASK_TERMINATED)
1216            .await
1217            .expect("failed waiting for termination signal");
1218
1219        let job_info = job.info()?;
1220        assert!(job_info.exited);
1221        Ok(())
1222    }
1223
1224    fn with_mark_vmo_exec(
1225        mut start_info: fcrunner::ComponentStartInfo,
1226    ) -> fcrunner::ComponentStartInfo {
1227        start_info.program.as_mut().map(|dict| {
1228            dict.entries.as_mut().map(|entry| {
1229                entry.push(fdata::DictionaryEntry {
1230                    key: "job_policy_ambient_mark_vmo_exec".to_string(),
1231                    value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1232                });
1233                entry
1234            })
1235        });
1236        start_info
1237    }
1238
1239    fn with_main_process_critical(
1240        mut start_info: fcrunner::ComponentStartInfo,
1241    ) -> fcrunner::ComponentStartInfo {
1242        start_info.program.as_mut().map(|dict| {
1243            dict.entries.as_mut().map(|entry| {
1244                entry.push(fdata::DictionaryEntry {
1245                    key: "main_process_critical".to_string(),
1246                    value: Some(Box::new(fdata::DictionaryValue::Str("true".to_string()))),
1247                });
1248                entry
1249            })
1250        });
1251        start_info
1252    }
1253
1254    #[fuchsia::test]
1255    async fn vmex_security_policy_denied() -> Result<(), Error> {
1256        let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1257        let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1258
1259        // Config does not allowlist any monikers to have access to the job policy.
1260        let runner = new_elf_runner_for_test();
1261        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1262            Arc::new(SecurityPolicy::default()),
1263            Moniker::root(),
1264        ));
1265        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1266
1267        // Attempting to start the component should fail, which we detect by looking for an
1268        // ACCESS_DENIED epitaph on the ComponentController's event stream.
1269        runner.start(start_info, server_controller).await;
1270        assert_matches!(
1271            controller.take_event_stream().try_next().await,
1272            Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1273        );
1274
1275        Ok(())
1276    }
1277
1278    #[fuchsia::test]
1279    async fn vmex_security_policy_allowed() -> Result<(), Error> {
1280        let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1281        let start_info = with_mark_vmo_exec(lifecycle_startinfo(runtime_dir_server));
1282
1283        let policy = SecurityPolicy {
1284            job_policy: JobPolicyAllowlists {
1285                ambient_mark_vmo_exec: vec![AllowlistEntryBuilder::new().exact("foo").build()],
1286                ..Default::default()
1287            },
1288            ..Default::default()
1289        };
1290        let runner = new_elf_runner_for_test();
1291        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1292            Arc::new(policy),
1293            Moniker::try_from(["foo"]).unwrap(),
1294        ));
1295        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1296        runner.start(start_info, server_controller).await;
1297
1298        // Runtime dir won't exist if the component failed to start.
1299        let process_id = read_file(&runtime_dir, "elf/process_id").await.parse::<u64>()?;
1300        assert!(process_id > 0);
1301        // Component controller should get shutdown normally; no ACCESS_DENIED epitaph.
1302        controller.kill().expect("kill failed");
1303
1304        // We expect the event stream to have closed, which is reported as an
1305        // error and the value of the error should match the epitaph for a
1306        // process that was killed.
1307        let mut event_stream = controller.take_event_stream();
1308        expect_diagnostics_event(&mut event_stream).await;
1309
1310        let s = zx::Status::from_raw(
1311            i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
1312        );
1313        expect_on_stop(&mut event_stream, s, Some(zx::sys::ZX_TASK_RETCODE_SYSCALL_KILL)).await;
1314        expect_channel_closed(&mut event_stream).await;
1315        Ok(())
1316    }
1317
1318    #[fuchsia::test]
1319    async fn critical_security_policy_denied() -> Result<(), Error> {
1320        let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1321        let start_info = with_main_process_critical(hello_world_startinfo(runtime_dir_server));
1322
1323        // Default policy does not allowlist any monikers to be marked as critical
1324        let runner = new_elf_runner_for_test();
1325        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1326            Arc::new(SecurityPolicy::default()),
1327            Moniker::root(),
1328        ));
1329        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1330
1331        // Attempting to start the component should fail, which we detect by looking for an
1332        // ACCESS_DENIED epitaph on the ComponentController's event stream.
1333        runner.start(start_info, server_controller).await;
1334        assert_matches!(
1335            controller.take_event_stream().try_next().await,
1336            Err(fidl::Error::ClientChannelClosed { status: zx::Status::ACCESS_DENIED, .. })
1337        );
1338
1339        Ok(())
1340    }
1341
1342    #[fuchsia::test]
1343    #[should_panic]
1344    async fn fail_to_launch_critical_component() {
1345        let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1346
1347        // ElfRunner should fail to start the component because this start_info points
1348        // to a binary that does not exist in the test package.
1349        let start_info = with_main_process_critical(invalid_binary_startinfo(runtime_dir_server));
1350
1351        // Policy does not allowlist any monikers to be marked as critical without being
1352        // allowlisted, so make sure we permit this one.
1353        let policy = SecurityPolicy {
1354            job_policy: JobPolicyAllowlists {
1355                main_process_critical: vec![AllowlistEntryBuilder::new().build()],
1356                ..Default::default()
1357            },
1358            ..Default::default()
1359        };
1360        let runner = new_elf_runner_for_test();
1361        let runner =
1362            runner.get_scoped_runner(ScopedPolicyChecker::new(Arc::new(policy), Moniker::root()));
1363        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1364
1365        runner.start(start_info, server_controller).await;
1366
1367        controller
1368            .take_event_stream()
1369            .try_next()
1370            .await
1371            .map(|_: Option<fcrunner::ComponentControllerEvent>| ()) // Discard.
1372            .unwrap_or_else(|error| warn!(error:%; "error reading from event stream"));
1373    }
1374
1375    fn hello_world_startinfo_forward_stdout_to_log(
1376        runtime_dir: ServerEnd<fio::DirectoryMarker>,
1377        mut ns: Vec<fcrunner::ComponentNamespaceEntry>,
1378    ) -> fcrunner::ComponentStartInfo {
1379        ns.push(pkg_dir_namespace_entry());
1380
1381        fcrunner::ComponentStartInfo {
1382            resolved_url: Some(
1383                "fuchsia-pkg://fuchsia.com/hello-world-rust#meta/hello-world-rust.cm".to_string(),
1384            ),
1385            program: Some(fdata::Dictionary {
1386                entries: Some(vec![
1387                    fdata::DictionaryEntry {
1388                        key: "binary".to_string(),
1389                        value: Some(Box::new(fdata::DictionaryValue::Str(
1390                            "bin/hello_world_rust".to_string(),
1391                        ))),
1392                    },
1393                    fdata::DictionaryEntry {
1394                        key: "forward_stdout_to".to_string(),
1395                        value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1396                    },
1397                    fdata::DictionaryEntry {
1398                        key: "forward_stderr_to".to_string(),
1399                        value: Some(Box::new(fdata::DictionaryValue::Str("log".to_string()))),
1400                    },
1401                ]),
1402                ..Default::default()
1403            }),
1404            ns: Some(ns),
1405            outgoing_dir: None,
1406            runtime_dir: Some(runtime_dir),
1407            component_instance: Some(zx::Event::create()),
1408            ..Default::default()
1409        }
1410    }
1411
1412    // TODO(https://fxbug.dev/42148789): Following function shares a lot of code with
1413    // //src/sys/component_manager/src/model/namespace.rs tests. Shared
1414    // functionality should be refactored into a common test util lib.
1415    #[fuchsia::test]
1416    async fn enable_stdout_and_stderr_logging() -> Result<(), Error> {
1417        let (mut dir, ns) = create_fs_with_mock_logsink()?;
1418
1419        let run_component_fut = async move {
1420            let (_runtime_dir, runtime_dir_server) = create_endpoints::<fio::DirectoryMarker>();
1421            let start_info = hello_world_startinfo_forward_stdout_to_log(runtime_dir_server, ns);
1422
1423            let runner = new_elf_runner_for_test();
1424            let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1425                Arc::new(SecurityPolicy::default()),
1426                Moniker::root(),
1427            ));
1428            let (client_controller, server_controller) =
1429                create_proxy::<fcrunner::ComponentControllerMarker>();
1430
1431            runner.start(start_info, server_controller).await;
1432            let mut event_stream = client_controller.take_event_stream();
1433            expect_diagnostics_event(&mut event_stream).await;
1434            expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1435            expect_channel_closed(&mut event_stream).await;
1436        };
1437
1438        // Just check for connection count, other integration tests cover decoding the actual logs.
1439        let service_fs_listener_fut = async {
1440            let mut requests = Vec::new();
1441            while let Some(MockServiceRequest::LogSink(r)) = dir.next().await {
1442                // The client is expecting us to send OnInit, but we're not testing that, so just
1443                // park the requests.
1444                requests.push(r);
1445            }
1446            requests.len()
1447        };
1448
1449        let connection_count = join!(run_component_fut, service_fs_listener_fut).1;
1450
1451        assert_eq!(connection_count, 1);
1452        Ok(())
1453    }
1454
1455    #[fuchsia::test]
1456    async fn on_publish_diagnostics_contains_job_handle() -> Result<(), Error> {
1457        let (runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1458        let start_info = lifecycle_startinfo(runtime_dir_server);
1459
1460        let runner = new_elf_runner_for_test();
1461        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1462            Arc::new(SecurityPolicy::default()),
1463            Moniker::root(),
1464        ));
1465        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1466
1467        runner.start(start_info, server_controller).await;
1468
1469        let job_id = read_file(&runtime_dir, "elf/job_id").await.parse::<u64>().unwrap();
1470        let mut event_stream = controller.take_event_stream();
1471        match event_stream.try_next().await {
1472            Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1473                payload:
1474                    ComponentDiagnostics {
1475                        tasks:
1476                            Some(ComponentTasks {
1477                                component_task: Some(DiagnosticsTask::Job(job)), ..
1478                            }),
1479                        ..
1480                    },
1481            })) => {
1482                assert_eq!(job_id, job.get_koid().unwrap().raw_koid());
1483            }
1484            other => panic!("unexpected event result: {:?}", other),
1485        }
1486
1487        controller.stop().expect("Stop request failed");
1488        // Wait for the process to exit so the test doesn't pagefault due to an invalid stdout
1489        // handle.
1490        controller.on_closed().await.expect("failed waiting for channel to close");
1491
1492        Ok(())
1493    }
1494
1495    async fn expect_diagnostics_event(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1496        let event = event_stream.try_next().await;
1497        assert_matches!(
1498            event,
1499            Ok(Some(fcrunner::ComponentControllerEvent::OnPublishDiagnostics {
1500                payload: ComponentDiagnostics {
1501                    tasks: Some(ComponentTasks {
1502                        component_task: Some(DiagnosticsTask::Job(_)),
1503                        ..
1504                    }),
1505                    ..
1506                },
1507            }))
1508        );
1509    }
1510
1511    async fn expect_on_stop(
1512        event_stream: &mut fcrunner::ComponentControllerEventStream,
1513        expected_status: zx::Status,
1514        expected_exit_code: Option<i64>,
1515    ) {
1516        let event = event_stream.try_next().await;
1517        assert_matches!(
1518            event,
1519            Ok(Some(fcrunner::ComponentControllerEvent::OnStop {
1520                payload: fcrunner::ComponentStopInfo { termination_status: Some(s), exit_code, .. },
1521            }))
1522            if s == expected_status.into_raw() &&
1523                exit_code == expected_exit_code
1524        );
1525    }
1526
1527    async fn expect_channel_closed(event_stream: &mut fcrunner::ComponentControllerEventStream) {
1528        let event = event_stream.try_next().await;
1529        match event {
1530            Ok(None) => {}
1531            other => panic!("Expected channel closed error, got {:?}", other),
1532        }
1533    }
1534
1535    /// An implementation of launcher that sends a complete launch request payload back to
1536    /// a test through an mpsc channel.
1537    struct LauncherConnectorForTest {
1538        sender: mpsc::UnboundedSender<LaunchPayload>,
1539    }
1540
1541    /// Contains all the information passed to fuchsia.process.Launcher before and up to calling
1542    /// Launch/CreateWithoutStarting.
1543    #[derive(Default)]
1544    struct LaunchPayload {
1545        launch_info: Option<fproc::LaunchInfo>,
1546        args: Vec<Vec<u8>>,
1547        environ: Vec<Vec<u8>>,
1548        name_info: Vec<fproc::NameInfo>,
1549        handles: Vec<fproc::HandleInfo>,
1550        options: u32,
1551    }
1552
1553    impl Connect for LauncherConnectorForTest {
1554        type Proxy = fproc::LauncherProxy;
1555
1556        fn connect(&self) -> Result<Self::Proxy, anyhow::Error> {
1557            let sender = self.sender.clone();
1558            let payload = Arc::new(Mutex::new(LaunchPayload::default()));
1559
1560            Ok(spawn_stream_handler(move |launcher_request| {
1561                let sender = sender.clone();
1562                let payload = payload.clone();
1563                async move {
1564                    let mut payload = payload.lock().await;
1565                    match launcher_request {
1566                        fproc::LauncherRequest::Launch { info, responder } => {
1567                            let process = create_child_process(&info.job, "test_process");
1568                            responder.send(zx::Status::OK.into_raw(), Some(process)).unwrap();
1569
1570                            let mut payload =
1571                                std::mem::replace(&mut *payload, LaunchPayload::default());
1572                            payload.launch_info = Some(info);
1573                            sender.unbounded_send(payload).unwrap();
1574                        }
1575                        fproc::LauncherRequest::CreateWithoutStarting { info: _, responder: _ } => {
1576                            unimplemented!()
1577                        }
1578                        fproc::LauncherRequest::AddArgs { mut args, control_handle: _ } => {
1579                            payload.args.append(&mut args);
1580                        }
1581                        fproc::LauncherRequest::AddEnvirons { mut environ, control_handle: _ } => {
1582                            payload.environ.append(&mut environ);
1583                        }
1584                        fproc::LauncherRequest::AddNames { mut names, control_handle: _ } => {
1585                            payload.name_info.append(&mut names);
1586                        }
1587                        fproc::LauncherRequest::AddHandles { mut handles, control_handle: _ } => {
1588                            payload.handles.append(&mut handles);
1589                        }
1590                        fproc::LauncherRequest::SetOptions { options, .. } => {
1591                            payload.options = options;
1592                        }
1593                    }
1594                }
1595            }))
1596        }
1597    }
1598
1599    #[fuchsia::test]
1600    async fn process_created_with_utc_clock_from_numbered_handles() -> Result<(), Error> {
1601        let (payload_tx, mut payload_rx) = mpsc::unbounded();
1602
1603        let connector = LauncherConnectorForTest { sender: payload_tx };
1604        let runner = ElfRunner::new(
1605            job_default().duplicate(zx::Rights::SAME_RIGHTS).unwrap(),
1606            Box::new(connector),
1607            Some(new_utc_clock_for_tests()),
1608            CrashRecords::new(),
1609        );
1610        let policy_checker = ScopedPolicyChecker::new(
1611            Arc::new(SecurityPolicy::default()),
1612            Moniker::try_from(["foo"]).unwrap(),
1613        );
1614
1615        // Create a clock and pass it to the component as the UTC clock through numbered_handles.
1616        let clock = zx::SyntheticClock::create(
1617            zx::ClockOpts::AUTO_START | zx::ClockOpts::MONOTONIC | zx::ClockOpts::MAPPABLE,
1618            None,
1619        )?;
1620        let clock_koid = clock.get_koid().unwrap();
1621
1622        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1623        let mut start_info = hello_world_startinfo(runtime_dir_server);
1624        start_info.numbered_handles = Some(vec![fproc::HandleInfo {
1625            handle: clock.into_handle(),
1626            id: HandleInfo::new(HandleType::ClockUtc, 0).as_raw(),
1627        }]);
1628
1629        // Start the component.
1630        let _ = runner
1631            .start_component(start_info, &policy_checker)
1632            .await
1633            .context("failed to start component")?;
1634
1635        let payload = payload_rx.next().await.unwrap();
1636        assert!(
1637            payload
1638                .handles
1639                .iter()
1640                .any(|handle_info| handle_info.handle.get_koid().unwrap() == clock_koid)
1641        );
1642
1643        Ok(())
1644    }
1645
1646    /// Test visiting running components using [`ComponentSet`].
1647    #[fuchsia::test]
1648    async fn test_enumerate_components() {
1649        use std::sync::atomic::{AtomicUsize, Ordering};
1650
1651        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1652        let start_info = lifecycle_startinfo(runtime_dir_server);
1653
1654        let runner = new_elf_runner_for_test();
1655        let components = runner.components.clone();
1656
1657        // Initially there are zero components.
1658        let count = Arc::new(AtomicUsize::new(0));
1659        components.clone().visit(|_, _| {
1660            count.fetch_add(1, Ordering::SeqCst);
1661        });
1662        assert_eq!(count.load(Ordering::SeqCst), 0);
1663
1664        // Run a component.
1665        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1666            Arc::new(SecurityPolicy::default()),
1667            Moniker::root(),
1668        ));
1669        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1670        runner.start(start_info, server_controller).await;
1671
1672        // There should now be one component in the set.
1673        let count = Arc::new(AtomicUsize::new(0));
1674        components.clone().visit(|elf_component: &ElfComponentInfo, _| {
1675            assert_eq!(
1676                elf_component.get_url().as_str(),
1677                "fuchsia-pkg://fuchsia.com/lifecycle-example#meta/lifecycle.cm"
1678            );
1679            count.fetch_add(1, Ordering::SeqCst);
1680        });
1681        assert_eq!(count.load(Ordering::SeqCst), 1);
1682
1683        // Stop the component.
1684        controller.stop().unwrap();
1685        controller.on_closed().await.unwrap();
1686
1687        // There should now be zero components in the set.
1688        // Keep retrying until the component is asynchronously deregistered.
1689        loop {
1690            let count = Arc::new(AtomicUsize::new(0));
1691            components.clone().visit(|_, _| {
1692                count.fetch_add(1, Ordering::SeqCst);
1693            });
1694            let count = count.load(Ordering::SeqCst);
1695            assert!(count == 0 || count == 1);
1696            if count == 0 {
1697                break;
1698            }
1699            // Yield to the executor once so that we are not starving the
1700            // asynchronous deregistration task from running.
1701            yield_to_executor().await;
1702        }
1703    }
1704
1705    async fn yield_to_executor() {
1706        let mut done = false;
1707        futures::future::poll_fn(|cx| {
1708            if done {
1709                Poll::Ready(())
1710            } else {
1711                done = true;
1712                cx.waker().wake_by_ref();
1713                Poll::Pending
1714            }
1715        })
1716        .await;
1717    }
1718
1719    /// Creates start info for a component which runs immediately escrows its
1720    /// outgoing directory and then exits.
1721    pub fn immediate_escrow_startinfo(
1722        outgoing_dir: ServerEnd<fio::DirectoryMarker>,
1723        runtime_dir: ServerEnd<fio::DirectoryMarker>,
1724    ) -> fcrunner::ComponentStartInfo {
1725        let ns = vec![
1726            pkg_dir_namespace_entry(),
1727            // Give the test component LogSink.
1728            svc_dir_namespace_entry(),
1729        ];
1730
1731        fcrunner::ComponentStartInfo {
1732            resolved_url: Some("#meta/immediate_escrow_component.cm".to_string()),
1733            program: Some(fdata::Dictionary {
1734                entries: Some(vec![
1735                    fdata::DictionaryEntry {
1736                        key: "binary".to_string(),
1737                        value: Some(Box::new(fdata::DictionaryValue::Str(
1738                            "bin/immediate_escrow".to_string(),
1739                        ))),
1740                    },
1741                    fdata::DictionaryEntry {
1742                        key: "lifecycle.stop_event".to_string(),
1743                        value: Some(Box::new(fdata::DictionaryValue::Str("notify".to_string()))),
1744                    },
1745                ]),
1746                ..Default::default()
1747            }),
1748            ns: Some(ns),
1749            outgoing_dir: Some(outgoing_dir),
1750            runtime_dir: Some(runtime_dir),
1751            component_instance: Some(zx::Event::create()),
1752            ..Default::default()
1753        }
1754    }
1755
1756    /// Test that an ELF component can send an `OnEscrow` event on its lifecycle
1757    /// channel and this event is forwarded to the `ComponentController`.
1758    #[fuchsia::test]
1759    async fn test_lifecycle_on_escrow() {
1760        let (outgoing_dir_client, outgoing_dir_server) =
1761            fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1762        let (_, runtime_dir_server) = fidl::endpoints::create_endpoints::<fio::DirectoryMarker>();
1763        let start_info = immediate_escrow_startinfo(outgoing_dir_server, runtime_dir_server);
1764
1765        let runner = new_elf_runner_for_test();
1766        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1767            Arc::new(SecurityPolicy::default()),
1768            Moniker::root(),
1769        ));
1770        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1771
1772        runner.start(start_info, server_controller).await;
1773
1774        let mut event_stream = controller.take_event_stream();
1775
1776        expect_diagnostics_event(&mut event_stream).await;
1777
1778        match event_stream.try_next().await {
1779            Ok(Some(fcrunner::ComponentControllerEvent::OnEscrow {
1780                payload: fcrunner::ComponentControllerOnEscrowRequest { outgoing_dir, .. },
1781            })) => {
1782                let outgoing_dir_server = outgoing_dir.unwrap();
1783
1784                assert_eq!(
1785                    outgoing_dir_client.basic_info().unwrap().koid,
1786                    outgoing_dir_server.basic_info().unwrap().related_koid
1787                );
1788            }
1789            other => panic!("unexpected event result: {:?}", other),
1790        }
1791
1792        expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1793        expect_channel_closed(&mut event_stream).await;
1794    }
1795
1796    fn exit_with_code_startinfo(exit_code: i64) -> fcrunner::ComponentStartInfo {
1797        let (_runtime_dir, runtime_dir_server) = create_proxy::<fio::DirectoryMarker>();
1798        let ns = vec![pkg_dir_namespace_entry()];
1799
1800        fcrunner::ComponentStartInfo {
1801            resolved_url: Some(
1802                "fuchsia-pkg://fuchsia.com/elf_runner_tests#meta/exit-with-code.cm".to_string(),
1803            ),
1804            program: Some(fdata::Dictionary {
1805                entries: Some(vec![
1806                    fdata::DictionaryEntry {
1807                        key: "args".to_string(),
1808                        value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![format!(
1809                            "{}",
1810                            exit_code
1811                        )]))),
1812                    },
1813                    fdata::DictionaryEntry {
1814                        key: "binary".to_string(),
1815                        value: Some(Box::new(fdata::DictionaryValue::Str(
1816                            "bin/exit_with_code".to_string(),
1817                        ))),
1818                    },
1819                ]),
1820                ..Default::default()
1821            }),
1822            ns: Some(ns),
1823            outgoing_dir: None,
1824            runtime_dir: Some(runtime_dir_server),
1825            component_instance: Some(zx::Event::create()),
1826            ..Default::default()
1827        }
1828    }
1829
1830    #[fuchsia::test]
1831    async fn test_return_code_success() {
1832        let start_info = exit_with_code_startinfo(0);
1833
1834        let runner = new_elf_runner_for_test();
1835        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1836            Arc::new(SecurityPolicy::default()),
1837            Moniker::root(),
1838        ));
1839        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1840        runner.start(start_info, server_controller).await;
1841
1842        let mut event_stream = controller.take_event_stream();
1843        expect_diagnostics_event(&mut event_stream).await;
1844        expect_on_stop(&mut event_stream, zx::Status::OK, Some(0)).await;
1845        expect_channel_closed(&mut event_stream).await;
1846    }
1847
1848    #[fuchsia::test]
1849    async fn test_return_code_failure() {
1850        let start_info = exit_with_code_startinfo(123);
1851
1852        let runner = new_elf_runner_for_test();
1853        let runner = runner.get_scoped_runner(ScopedPolicyChecker::new(
1854            Arc::new(SecurityPolicy::default()),
1855            Moniker::root(),
1856        ));
1857        let (controller, server_controller) = create_proxy::<fcrunner::ComponentControllerMarker>();
1858        runner.start(start_info, server_controller).await;
1859
1860        let mut event_stream = controller.take_event_stream();
1861        expect_diagnostics_event(&mut event_stream).await;
1862        let s = zx::Status::from_raw(
1863            i32::try_from(fcomp::Error::InstanceDied.into_primitive()).unwrap(),
1864        );
1865        expect_on_stop(&mut event_stream, s, Some(123)).await;
1866        expect_channel_closed(&mut event_stream).await;
1867    }
1868}