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