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