1use anyhow::{Error, anyhow, bail};
6use elf_parse::{Elf64Headers, SegmentType};
7use fidl_next::{Request, Responder, ServerDispatcher, ServerEnd};
8use fidl_next_fuchsia_boot::UserbootPostBootHandlesRequest;
9use fidl_next_fuchsia_ldsvc as fuchsia_ldsvc;
10use fuchsia_bootfs::{
11 BootfsParser, ZeroCopyBootfsEntry, zbi_bootfs_is_aligned, zbi_bootfs_page_align,
12};
13use fuchsia_ldsvc::loader::{Clone, Config, LoadObject};
14use fuchsia_ldsvc::{Loader, LoaderLoadObjectResponse, LoaderServerHandler};
15use fuchsia_runtime::{HandleInfo, HandleType};
16use process_builder::elf_load::load_elf;
17use process_builder::{Message, MessageContents, StartupHandle, compute_initial_stack_pointer};
18use std::ffi::CString;
19use std::fmt::Write as _;
20use std::str;
21use zx::sys::{ZX_RSRC_SYSTEM_POWER_BASE, ZX_RSRC_SYSTEM_VMEX_BASE};
22use zx::{
23 Channel, DebugLog, Job, Name, NullableHandle, ObjectType, Process, ProcessOptions, Resource,
24 ResourceKind, Rights, Socket, Status, Vmar, VmarFlags, Vmo, VmoChildOptions,
25};
26use zx_libc::sanitizer::Log;
27
28const FDIO_FLAG_USE_FOR_STDIO: u16 = 0x8000;
31
32pub enum SystemLog {
34 DebugLog(DebugLog),
36 Socket(Socket),
38}
39
40impl SystemLog {
41 fn to_handle(&self, rights: Rights) -> Result<NullableHandle, Status> {
42 match self {
43 Self::DebugLog(h) => h.duplicate_handle(rights).map(|h| h.into_handle()),
44 Self::Socket(h) => h.duplicate_handle(rights).map(|h| h.into_handle()),
45 }
46 }
47
48 fn duplicate(&self, rights: Rights) -> Result<Self, Status> {
49 match self {
50 Self::DebugLog(h) => Ok(Self::DebugLog(h.duplicate_handle(rights)?)),
51 Self::Socket(h) => Ok(Self::Socket(h.duplicate_handle(rights)?)),
52 }
53 }
54}
55
56pub struct SystemHandles {
59 pub root_job: Job,
61 pub vmex_resource: Resource,
63 pub power_resource: Option<Resource>,
65 pub vdso_vmo: Vmo,
67 pub zbi_vmo: Vmo,
69 pub log: SystemLog,
71 pub startup_handles: Vec<StartupHandle>,
73}
74
75impl SystemHandles {
76 pub fn from_handles(handles: impl IntoIterator<Item = zx::HandleInfo>) -> Result<Self, Error> {
78 let mut root_job = None;
79 let mut vmex_resource = None;
80 let mut power_resource = None;
81 let mut vdso_vmo = None;
82 let mut zbi_vmo = None;
83 let mut log = None;
84
85 let mut startup_handles = Vec::new();
86 let mut vdso_count = 0u16;
87 let mut kernel_file_count = 0u16;
88
89 for zx::HandleInfo { object_type, handle, .. } in handles {
90 match (object_type, handle.get_name()) {
91 (ObjectType::JOB, Ok(name)) => {
92 if name == "root" {
93 root_job = Some(Job::from(handle));
94 }
95 }
96 (ObjectType::RESOURCE, Ok(name)) => {
97 let htype = if name == "mmio" {
98 HandleType::MmioResource
99 } else if name == "irq" {
100 HandleType::IrqResource
101 } else if name == "io_port" {
102 HandleType::IoportResource
103 } else if name == "smc" {
104 HandleType::SmcResource
105 } else if name == "system" {
106 let resource = handle.as_handle_ref().cast::<Resource>();
107 if vmex_resource.is_none() {
108 vmex_resource = resource
109 .create_child(
110 ResourceKind::SYSTEM,
111 None,
112 ZX_RSRC_SYSTEM_VMEX_BASE,
113 1,
114 b"vmex",
115 )
116 .ok();
117 }
118 if power_resource.is_none() {
119 power_resource = resource
120 .create_child(
121 ResourceKind::SYSTEM,
122 None,
123 ZX_RSRC_SYSTEM_POWER_BASE,
124 1,
125 b"power",
126 )
127 .ok();
128 }
129 HandleType::SystemResource
130 } else if name == "vmex" {
131 vmex_resource = Some(Resource::from(handle));
132 continue;
133 } else if name == "power" {
134 power_resource = Some(Resource::from(handle));
135 continue;
136 } else {
137 continue;
138 };
139 startup_handles.push(StartupHandle { handle, info: HandleInfo::new(htype, 0) });
140 }
141 (ObjectType::VMO, Ok(name)) => {
142 if handle.as_handle_ref().cast::<Vmo>().get_size() == Ok(0) {
144 continue;
145 }
146 if name == "zbi" {
147 zbi_vmo = Some(Vmo::from(handle));
148 } else if name.as_bstr().starts_with(b"vdso/") {
149 if vdso_vmo.is_none() {
150 vdso_vmo =
151 Some(Vmo::from(handle.duplicate_handle(Rights::SAME_RIGHTS)?));
152 }
153 startup_handles.push(StartupHandle {
154 handle,
155 info: HandleInfo::new(HandleType::VdsoVmo, vdso_count),
156 });
157 vdso_count += 1;
158 } else {
159 startup_handles.push(StartupHandle {
160 handle,
161 info: HandleInfo::new(HandleType::KernelFileVmo, kernel_file_count),
162 });
163 kernel_file_count += 1;
164 }
165 }
166 (ObjectType::DEBUGLOG, _) => {
167 log = Some(SystemLog::DebugLog(DebugLog::from(handle)));
168 }
169 (ObjectType::SOCKET, _) => {
170 log = Some(SystemLog::Socket(Socket::from(handle)));
171 }
172 _ => {}
173 }
174 }
175
176 let root_job = root_job.ok_or_else(|| anyhow!("Root job handle not found"))?;
177 let vmex_resource =
178 vmex_resource.ok_or_else(|| anyhow!("VMEX resource handle not found"))?;
179 let vdso_vmo = vdso_vmo.ok_or_else(|| anyhow!("vDSO VMO handle not found"))?;
180 let zbi_vmo = zbi_vmo.ok_or_else(|| anyhow!("ZBI VMO handle not found"))?;
181 let log = log.ok_or_else(|| anyhow!("DebugLog/Socket handle not found"))?;
182
183 Ok(SystemHandles {
184 root_job,
185 vmex_resource,
186 power_resource,
187 vdso_vmo,
188 zbi_vmo,
189 log,
190 startup_handles,
191 })
192 }
193
194 pub fn duplicate(&self) -> Result<Self, Status> {
196 Ok(Self {
197 root_job: self.root_job.duplicate_handle(Rights::SAME_RIGHTS)?,
198 vmex_resource: self.vmex_resource.duplicate_handle(Rights::SAME_RIGHTS)?,
199 power_resource: self
200 .power_resource
201 .as_ref()
202 .map(|p| p.duplicate_handle(Rights::SAME_RIGHTS))
203 .transpose()?,
204 vdso_vmo: self.vdso_vmo.duplicate_handle(Rights::SAME_RIGHTS)?,
205 zbi_vmo: self.zbi_vmo.duplicate_handle(Rights::SAME_RIGHTS)?,
206 log: self.log.duplicate(Rights::SAME_RIGHTS)?,
207 startup_handles: self
208 .startup_handles
209 .iter()
210 .map(|h| {
211 Ok(StartupHandle {
212 handle: h.handle.duplicate_handle(Rights::SAME_RIGHTS)?,
213 info: h.info,
214 })
215 })
216 .collect::<Result<Vec<_>, Status>>()?,
217 })
218 }
219
220 pub fn take_boot_handles(&mut self) -> Result<UserbootPostBootHandlesRequest, Status> {
223 let mut out = UserbootPostBootHandlesRequest {
224 zbi_vmo: Some(self.zbi_vmo.duplicate_handle(Rights::SAME_RIGHTS)?),
225 ..Default::default()
226 };
227 let mut kernel_files = Vec::new();
228 let mut remaining = Vec::with_capacity(self.startup_handles.len());
229 for sh in self.startup_handles.drain(..) {
230 match sh.info.handle_type() {
231 HandleType::SystemResource => out.system_resource = Some(Resource::from(sh.handle)),
232 HandleType::MmioResource => out.mmio_resource = Some(Resource::from(sh.handle)),
233 HandleType::IrqResource => out.irq_resource = Some(Resource::from(sh.handle)),
234 HandleType::IoportResource => out.ioport_resource = Some(Resource::from(sh.handle)),
235 HandleType::SmcResource => out.smc_resource = Some(Resource::from(sh.handle)),
236 HandleType::KernelFileVmo => kernel_files.push(Vmo::from(sh.handle)),
237 _ => remaining.push(sh),
238 }
239 }
240 if !kernel_files.is_empty() {
241 out.kernel_files = Some(kernel_files);
242 }
243 self.startup_handles = remaining;
244 Ok(out)
245 }
246}
247
248fn reserve_low_address_space(root_vmar: &Vmar) -> Result<Vmar, Error> {
251 let info = root_vmar.info()?;
252 let page_size = zx::system_get_page_size() as usize;
253 let reserve_len = (info.len / 2) & !(page_size - 1);
254 let (vmar, addr) = root_vmar.allocate(0, reserve_len, VmarFlags::SPECIFIC)?;
255 if addr != info.base {
256 bail!("zx_vmar_allocate gave wrong address for low address space reservation");
257 }
258 Ok(vmar)
259}
260
261fn create_bootfs_file_vmo(
270 bootfs_vmo: &Vmo,
271 offset: u64,
272 size: u64,
273 vmex_resource: &Resource,
274 name: &Name,
275 bootfs_entries: Option<&mut Vec<(u32, Vmo)>>,
276) -> Result<Vmo, Error> {
277 if let Some(entries) = bootfs_entries {
278 let offset_u32 = u32::try_from(offset)?;
279 if !zbi_bootfs_is_aligned(offset_u32) {
280 bail!("BOOTFS entry at offset {:#x} is not page-aligned", offset_u32);
281 }
282 if let Some((_, existing_vmo)) = entries.iter().find(|(off, _)| *off == offset_u32) {
283 return Ok(existing_vmo.duplicate_handle(Rights::SAME_RIGHTS)?);
284 }
285 let aligned_size = u64::from(zbi_bootfs_page_align(u32::try_from(size)?));
286 let vmo = Vmo::create(size)?;
287 if aligned_size > 0 {
288 vmo.transfer_data(
289 zx::TransferDataOptions::empty(),
290 0,
291 aligned_size,
292 bootfs_vmo,
293 offset,
294 )?;
295 }
296 vmo.set_name(name)?;
297 let vmo = vmo
298 .replace_handle(Rights::VMO_DEFAULT - Rights::WRITE)?
299 .replace_as_executable(vmex_resource)?;
300 entries.push((offset_u32, vmo.duplicate_handle(Rights::SAME_RIGHTS)?));
301 Ok(vmo)
302 } else {
303 let vmo = bootfs_vmo
304 .create_child(
305 VmoChildOptions::SNAPSHOT_AT_LEAST_ON_WRITE | VmoChildOptions::NO_WRITE,
306 offset,
307 size,
308 )?
309 .replace_as_executable(vmex_resource)?;
310 vmo.set_name(name)?;
311 Ok(vmo)
312 }
313}
314
315fn find_bootfs_entry<'a>(
318 bootfs_parser: &'a BootfsParser,
319 parts: &[&str],
320) -> Option<ZeroCopyBootfsEntry<'a>> {
321 bootfs_parser.zero_copy_iter().filter_map(Result::ok).find(|e| {
322 let mut rest = e.name;
323 for (i, &part) in parts.iter().rev().enumerate() {
324 if i > 0 {
325 let Some(r) = rest.strip_suffix('/') else {
326 return false;
327 };
328 rest = r;
329 }
330 let Some(r) = rest.strip_suffix(part) else {
331 return false;
332 };
333 rest = r;
334 }
335 rest.is_empty() || rest.ends_with('/')
336 })
337}
338
339pub async fn launch_program(
346 program_name: &str,
347 target_path: &str,
348 args: impl IntoIterator<Item = &str>,
349 bootfs_vmo: Vmo,
350 handles: SystemHandles,
351 mut bootfs_entries: Option<&mut Vec<(u32, Vmo)>>,
352 log: &mut Log,
353) -> Result<Process, Error> {
354 let SystemHandles {
355 root_job,
356 vmex_resource,
357 power_resource: _,
358 vdso_vmo,
359 zbi_vmo: _,
360 log: system_log,
361 startup_handles,
362 } = handles;
363
364 let (child_process, child_vmar) =
365 root_job.create_child_process(ProcessOptions::empty(), program_name.as_bytes())?;
366 let reserved_vmar = reserve_low_address_space(&child_vmar)?;
367 let child_thread = child_process.create_thread(program_name.as_bytes())?;
368
369 let bootfs_parser = BootfsParser::create_from_vmo(&bootfs_vmo)?;
370
371 let entry = bootfs_parser
372 .zero_copy_iter()
373 .filter_map(Result::ok)
374 .find(|e| e.name == target_path)
375 .ok_or_else(|| anyhow!("Program '{}' not found in bootfs", target_path))?;
376
377 let program_vmo = create_bootfs_file_vmo(
378 &bootfs_vmo,
379 entry.offset,
380 entry.size,
381 &vmex_resource,
382 &Name::new_lossy(program_name),
383 bootfs_entries.as_deref_mut(),
384 )?;
385
386 let headers = Elf64Headers::from_vmo(&program_vmo)?;
387
388 let (to_child_client, to_child_server) = Channel::create();
389 let mut loader_server = None;
390
391 let entry_point = if let Some(interp_hdr) =
392 headers.program_header_with_type(SegmentType::Interp)?
393 {
394 let mut interp_bytes = vec![0u8; interp_hdr.filesz as usize];
395 program_vmo.read(&mut interp_bytes, interp_hdr.offset as u64)?;
396 let interp_str =
397 str::from_utf8(&interp_bytes)?.trim_end_matches('\0').trim_start_matches('/');
398 let interp_name = interp_str.strip_prefix("lib/").unwrap_or(interp_str);
399
400 let interp_entry = find_bootfs_entry(&bootfs_parser, &["lib", interp_name])
401 .ok_or_else(|| anyhow!("Interpreter 'lib/{interp_name}' not found in bootfs"))?;
402
403 let interp_vmo = create_bootfs_file_vmo(
404 &bootfs_vmo,
405 interp_entry.offset,
406 interp_entry.size,
407 &vmex_resource,
408 &Name::new_lossy(interp_entry.name),
409 bootfs_entries.as_deref_mut(),
410 )?;
411
412 let interp_headers = Elf64Headers::from_vmo(&interp_vmo)?;
413 let loaded_interp = load_elf(&interp_vmo, &interp_headers, &child_vmar)?;
414
415 let (ldsvc_client, ldsvc_server) = Channel::create();
416 loader_server = Some(ldsvc_server);
417
418 let handles = vec![
419 StartupHandle {
420 handle: program_vmo.duplicate_handle(Rights::SAME_RIGHTS)?.into_handle().into(),
421 info: HandleInfo::new(HandleType::ExecutableVmo, 0),
422 },
423 StartupHandle {
424 handle: system_log.to_handle(Rights::SAME_RIGHTS)?,
425 info: HandleInfo::new(HandleType::FileDescriptor, FDIO_FLAG_USE_FOR_STDIO),
426 },
427 StartupHandle {
428 handle: child_process.duplicate_handle(Rights::SAME_RIGHTS)?.into_handle().into(),
429 info: HandleInfo::new(HandleType::ProcessSelf, 0),
430 },
431 StartupHandle {
432 handle: child_vmar.duplicate_handle(Rights::SAME_RIGHTS)?.into_handle().into(),
433 info: HandleInfo::new(HandleType::RootVmar, 0),
434 },
435 StartupHandle {
436 handle: loaded_interp.vmar.into_handle().into(),
437 info: HandleInfo::new(HandleType::LoadedVmar, 0),
438 },
439 StartupHandle {
440 handle: child_thread.duplicate_handle(Rights::SAME_RIGHTS)?.into_handle().into(),
441 info: HandleInfo::new(HandleType::ThreadSelf, 0),
442 },
443 StartupHandle {
444 handle: ldsvc_client.into_handle().into(),
445 info: HandleInfo::new(HandleType::LdsvcLoader, 0),
446 },
447 ];
448
449 let contents = MessageContents {
450 environment_vars: vec![CString::new("LD_DEBUG=1")?],
453 handles,
454 ..Default::default()
455 };
456
457 let message = Message::build(contents)?;
458 message.write(&to_child_client)?;
459 loaded_interp.entry
460 } else {
461 let loaded_elf = load_elf(&program_vmo, &headers, &child_vmar)?;
462 let mut initial_handles = vec![
463 system_log.to_handle(Rights::SAME_RIGHTS)?,
464 child_process.duplicate_handle(Rights::SAME_RIGHTS)?.into_handle(),
465 child_vmar.duplicate_handle(Rights::SAME_RIGHTS)?.into_handle(),
466 loaded_elf.vmar.into_handle(),
467 child_thread.duplicate_handle(Rights::SAME_RIGHTS)?.into_handle(),
468 ];
469 to_child_client.write(&[], &mut initial_handles)?;
470 loaded_elf.entry
471 };
472
473 let vdso_headers = Elf64Headers::from_vmo(&vdso_vmo)?;
474 let loaded_vdso = load_elf(&vdso_vmo, &vdso_headers, &child_vmar)?;
475 let vdso_base = loaded_vdso.vmar_base;
476
477 let stack_size: usize = 256 * 1024;
478 let stack_vmo = Vmo::create(stack_size as u64)?;
479 stack_vmo.set_name(&Name::new_lossy("userboot-child-initial-stack"))?;
480 let stack_base = child_vmar.map(
481 0,
482 &stack_vmo,
483 0,
484 stack_size,
485 VmarFlags::PERM_READ | VmarFlags::PERM_WRITE,
486 )?;
487 let sp = compute_initial_stack_pointer(stack_base, stack_size);
488
489 let mut handles = vec![
490 StartupHandle {
491 handle: child_process.duplicate_handle(Rights::SAME_RIGHTS)?.into_handle().into(),
492 info: HandleInfo::new(HandleType::ProcessSelf, 0),
493 },
494 StartupHandle {
495 handle: child_vmar.duplicate_handle(Rights::SAME_RIGHTS)?.into_handle().into(),
496 info: HandleInfo::new(HandleType::RootVmar, 0),
497 },
498 StartupHandle {
499 handle: child_thread.duplicate_handle(Rights::SAME_RIGHTS)?.into_handle().into(),
500 info: HandleInfo::new(HandleType::ThreadSelf, 0),
501 },
502 StartupHandle {
503 handle: root_job.duplicate_handle(Rights::SAME_RIGHTS)?.into_handle().into(),
504 info: HandleInfo::new(HandleType::DefaultJob, 0),
505 },
506 StartupHandle {
507 handle: system_log.to_handle(Rights::SAME_RIGHTS)?,
508 info: HandleInfo::new(HandleType::FileDescriptor, FDIO_FLAG_USE_FOR_STDIO),
509 },
510 ];
511 handles.extend(startup_handles);
512
513 let contents = MessageContents {
514 args: args.into_iter().map(CString::new).collect::<Result<Vec<_>, _>>()?,
515 handles,
516 namespace_paths: vec![CString::new("/svc")?],
517 ..Default::default()
518 };
519
520 let message = Message::build(contents)?;
521 message.write(&to_child_client)?;
522
523 unsafe {
530 reserved_vmar.destroy()?;
531 }
532
533 child_process.start(&child_thread, entry_point, sp, to_child_server.into(), vdso_base)?;
534 writeln!(log, "Started child process: {}", program_name)?;
535
536 if let Some(ldsvc_server) = loader_server {
537 writeln!(log, "Serving loader service for child process...")?;
538 let server = UserbootLoaderServer::new(
539 bootfs_parser,
540 bootfs_vmo,
541 vmex_resource,
542 bootfs_entries,
543 log,
544 );
545 let _ = ServerDispatcher::new(ServerEnd::<Loader, Channel>::from_untyped(ldsvc_server))
546 .run(server)
547 .await;
548 }
549
550 Ok(child_process)
551}
552
553struct UserbootLoaderServer<'a> {
555 bootfs_parser: BootfsParser,
557 bootfs_vmo: Vmo,
559 vmex_resource: Resource,
561 entries: Option<&'a mut Vec<(u32, Vmo)>>,
563 log: &'a mut Log,
565 subdir: String,
567 exclusive: bool,
569}
570
571impl<'a> UserbootLoaderServer<'a> {
572 fn new(
573 bootfs_parser: BootfsParser,
574 bootfs_vmo: Vmo,
575 vmex_resource: Resource,
576 entries: Option<&'a mut Vec<(u32, Vmo)>>,
577 log: &'a mut Log,
578 ) -> Self {
579 Self {
580 bootfs_parser,
581 bootfs_vmo,
582 vmex_resource,
583 entries,
584 log,
585 subdir: String::new(),
586 exclusive: false,
587 }
588 }
589}
590
591impl LoaderServerHandler<Channel> for UserbootLoaderServer<'_> {
592 async fn done(&mut self) {}
594
595 async fn load_object(
597 &mut self,
598 request: Request<LoadObject, Channel>,
599 responder: Responder<LoadObject, Channel>,
600 ) {
601 let name = &request.payload().object_name;
602 let mut found = None;
603 if !self.subdir.is_empty() {
604 found = find_bootfs_entry(&self.bootfs_parser, &["lib", &self.subdir, name]);
605 }
606 if found.is_none() && (!self.exclusive || self.subdir.is_empty()) {
607 found = find_bootfs_entry(&self.bootfs_parser, &["lib", name]);
608 }
609
610 let lib_vmo = found.as_ref().and_then(|entry| {
611 match create_bootfs_file_vmo(
612 &self.bootfs_vmo,
613 entry.offset,
614 entry.size,
615 &self.vmex_resource,
616 &Name::new_lossy(name),
617 self.entries.as_deref_mut(),
618 ) {
619 Ok(vmo) => Some(vmo),
620 Err(e) => {
621 let _ = writeln!(self.log, "Failed to load object '{name}': {e:?}");
622 None
623 }
624 }
625 });
626
627 let (rv, object) = lib_vmo.map_or((Err(Status::NOT_FOUND), None), |v| (Ok(()), Some(v)));
628 let _ = responder.respond(LoaderLoadObjectResponse { rv, object }).await;
629 }
630
631 async fn config(
633 &mut self,
634 request: Request<Config, Channel>,
635 responder: Responder<Config, Channel>,
636 ) {
637 let payload = request.payload();
638 let config_str = payload.config.as_str();
639 self.exclusive = config_str.ends_with('!');
640 self.subdir = config_str.strip_suffix('!').unwrap_or(config_str).to_string();
641 let _ = responder.respond(Ok(())).await;
642 }
643
644 async fn clone(
646 &mut self,
647 _request: Request<Clone, Channel>,
648 responder: Responder<Clone, Channel>,
649 ) {
650 let _ = responder.respond(Err(Status::NOT_SUPPORTED)).await;
651 }
652}
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657 use elf_parse::{
658 CURRENT_ARCH, ELF_MAGIC, Elf64FileHeader, Elf64ProgramHeader, ElfClass, ElfIdent, ElfType,
659 ElfVersion, NATIVE_ENCODING, SegmentFlags,
660 };
661 use fidl_fuchsia_kernel::{PowerResourceMarker, VmexResourceMarker};
662 use fuchsia_bootfs::bootfs::ZBI_BOOTFS_MAGIC;
663 use fuchsia_component::client::connect_to_protocol;
664 use fuchsia_runtime::{job_default, take_startup_handle};
665 use std::mem::size_of;
666 use std::sync::LazyLock;
667 use zerocopy::IntoBytes;
668
669 static VDSO_VMO: LazyLock<Vmo> = LazyLock::new(|| {
670 take_startup_handle(HandleInfo::new(HandleType::VdsoVmo, 0))
671 .map(Vmo::from)
672 .expect("expected real vDSO VMO handle from process startup handles")
673 });
674
675 async fn create_test_system_handles() -> SystemHandles {
676 let root_job = job_default().duplicate_handle(Rights::SAME_RIGHTS).unwrap();
677 let vmex_resource = connect_to_protocol::<VmexResourceMarker>()
678 .expect("failed to connect to VmexResource protocol")
679 .get()
680 .await
681 .expect("failed to get VmexResource");
682 let power_resource = connect_to_protocol::<PowerResourceMarker>()
683 .expect("failed to connect to PowerResource protocol")
684 .get()
685 .await
686 .expect("failed to get PowerResource");
687 let vdso_vmo = VDSO_VMO.duplicate_handle(Rights::SAME_RIGHTS).unwrap();
688 let zbi_vmo = Vmo::create(4096).unwrap();
689 let (s1, _s2) = zx::Socket::create_stream();
690 let log = SystemLog::Socket(s1);
691 let startup_handles = vec![];
692
693 SystemHandles {
694 root_job,
695 vmex_resource,
696 power_resource: Some(power_resource),
697 vdso_vmo,
698 zbi_vmo,
699 log,
700 startup_handles,
701 }
702 }
703
704 fn create_bootfs_vmo(files: &[(&str, &[u8])]) -> Vmo {
705 let mut total_dirsize = 0u32;
706 for (name, _) in files {
707 let name_len = (name.len() + 1) as u32;
708 let dirent_size = (12 + name_len + 3) & !3;
709 total_dirsize += dirent_size;
710 }
711
712 let data_start_offset = ((16 + total_dirsize + 4095) & !4095) as u64;
713 let mut vmo_bytes = vec![0u8; data_start_offset as usize];
714
715 vmo_bytes[0..4].copy_from_slice(&ZBI_BOOTFS_MAGIC.to_le_bytes());
717 vmo_bytes[4..8].copy_from_slice(&total_dirsize.to_le_bytes());
718
719 let mut current_dir_off = 16usize;
720 let mut current_data_off = data_start_offset;
721
722 for (name, data) in files {
723 let name_c = format!("{}\0", name);
724 let name_bytes = name_c.as_bytes();
725 let name_len = name_bytes.len() as u32;
726 let data_len = data.len() as u32;
727
728 vmo_bytes[current_dir_off..current_dir_off + 4]
729 .copy_from_slice(&name_len.to_le_bytes());
730 vmo_bytes[current_dir_off + 4..current_dir_off + 8]
731 .copy_from_slice(&data_len.to_le_bytes());
732 vmo_bytes[current_dir_off + 8..current_dir_off + 12]
733 .copy_from_slice(&(current_data_off as u32).to_le_bytes());
734 vmo_bytes[current_dir_off + 12..current_dir_off + 12 + name_bytes.len()]
735 .copy_from_slice(name_bytes);
736
737 let dirent_size = ((12 + name_len + 3) & !3) as usize;
738 current_dir_off += dirent_size;
739
740 if vmo_bytes.len() < (current_data_off as usize + data.len()) {
741 vmo_bytes.resize(current_data_off as usize + data.len(), 0);
742 }
743 vmo_bytes[current_data_off as usize..current_data_off as usize + data.len()]
744 .copy_from_slice(data);
745
746 let page_aligned_data_len = ((data.len() + 4095) & !4095) as u64;
747 current_data_off += page_aligned_data_len.max(4096);
748 }
749
750 let vmo = Vmo::create(vmo_bytes.len() as u64).unwrap();
751 vmo.write(&vmo_bytes, 0).unwrap();
752 vmo
753 }
754
755 fn create_elf_bytes(interp_path: Option<&str>) -> Vec<u8> {
757 let has_interp = interp_path.is_some();
758 let phnum = if has_interp { 2 } else { 1 };
759 let ehsize = size_of::<Elf64FileHeader>() as u16;
760 let phentsize = size_of::<Elf64ProgramHeader>() as u16;
761
762 let interp_str_bytes = interp_path.map(|p| format!("{}\0", p)).unwrap_or_default();
763 let interp_len = interp_str_bytes.len();
764
765 let phoff = ehsize as usize;
766 let interp_offset = phoff + (phnum as usize) * (phentsize as usize);
767
768 let file_header = Elf64FileHeader {
769 ident: ElfIdent {
770 magic: ELF_MAGIC,
771 class: ElfClass::Elf64 as u8,
772 data: NATIVE_ENCODING as u8,
773 version: ElfVersion::Current as u8,
774 osabi: 0,
775 abiversion: 0,
776 pad: [0; 7],
777 },
778 elf_type: ElfType::Executable as u16,
779 machine: CURRENT_ARCH as u16,
780 version: 1,
781 entry: 0x1000,
782 phoff,
783 shoff: 0,
784 flags: 0,
785 ehsize,
786 phentsize,
787 phnum,
788 shentsize: 0,
789 shnum: 0,
790 shstrndx: 0,
791 };
792
793 let mut bytes = Vec::new();
794 bytes.extend_from_slice(file_header.as_bytes());
795
796 let load_phdr = Elf64ProgramHeader {
798 segment_type: SegmentType::Load as u32,
799 flags: (SegmentFlags::READ | SegmentFlags::EXECUTE).bits(),
800 offset: 0,
801 vaddr: 0x1000,
802 paddr: 0x1000,
803 filesz: 4096,
804 memsz: 4096,
805 align: 4096,
806 };
807 bytes.extend_from_slice(load_phdr.as_bytes());
808
809 if let Some(_) = interp_path {
810 let interp_phdr = Elf64ProgramHeader {
811 segment_type: SegmentType::Interp as u32,
812 flags: SegmentFlags::READ.bits(),
813 offset: interp_offset,
814 vaddr: 0,
815 paddr: 0,
816 filesz: interp_len as u64,
817 memsz: interp_len as u64,
818 align: 1,
819 };
820 bytes.extend_from_slice(interp_phdr.as_bytes());
821 bytes.extend_from_slice(interp_str_bytes.as_bytes());
822 }
823
824 if bytes.len() < 4096 {
825 bytes.resize(4096, 0);
826 }
827
828 bytes
829 }
830
831 #[fuchsia::test]
832 async fn test_launch_program_invalid_bootfs() {
833 let handles = create_test_system_handles().await;
834 let invalid_bootfs = Vmo::create(4096).unwrap();
835 let mut log = Log::new();
836
837 let res = launch_program(
838 "test_prog",
839 "bin/test_prog",
840 ["arg1"],
841 invalid_bootfs,
842 handles,
843 None,
844 &mut log,
845 )
846 .await;
847
848 assert!(res.is_err());
849 }
850
851 #[fuchsia::test]
852 async fn test_launch_program_target_not_in_bootfs() {
853 let handles = create_test_system_handles().await;
854 let bootfs = create_bootfs_vmo(&[("bin/other", b"some_data")]);
855 let mut log = Log::new();
856
857 let res =
858 launch_program("test_prog", "bin/test_prog", ["arg1"], bootfs, handles, None, &mut log)
859 .await;
860
861 assert!(res.is_err());
862 let err_msg = res.unwrap_err().to_string();
863 assert!(
864 err_msg.contains("Program 'bin/test_prog' not found in bootfs"),
865 "unexpected err: {err_msg}"
866 );
867 }
868
869 #[fuchsia::test]
870 async fn test_launch_program_corrupt_elf() {
871 let handles = create_test_system_handles().await;
872 let bootfs = create_bootfs_vmo(&[("bin/test_prog", b"NOT_AN_ELF_BINARY")]);
873 let mut log = Log::new();
874
875 let res =
876 launch_program("test_prog", "bin/test_prog", [], bootfs, handles, None, &mut log).await;
877
878 assert!(res.is_err());
879 }
880
881 #[fuchsia::test]
882 async fn test_launch_program_missing_interpreter() {
883 let handles = create_test_system_handles().await;
884 let elf_data = create_elf_bytes(Some("lib/ld.so.1"));
885 let bootfs = create_bootfs_vmo(&[("bin/test_prog", &elf_data)]);
886 let mut log = Log::new();
887
888 let res = launch_program(
889 "test_prog",
890 "bin/test_prog",
891 ["--flag"],
892 bootfs,
893 handles,
894 None,
895 &mut log,
896 )
897 .await;
898
899 assert!(res.is_err());
900 let err_msg = res.unwrap_err().to_string();
901 assert!(
902 err_msg.contains("Interpreter 'lib/ld.so.1' not found in bootfs"),
903 "unexpected err: {err_msg}"
904 );
905 }
906
907 #[fuchsia::test]
908 async fn test_launch_program_interpreter_path_normalization() {
909 let handles = create_test_system_handles().await;
910 let elf_data = create_elf_bytes(Some("/lib/ld.so.1"));
912 let bootfs = create_bootfs_vmo(&[("bin/test_prog", &elf_data)]);
913 let mut log = Log::new();
914
915 let res =
916 launch_program("test_prog", "bin/test_prog", [], bootfs, handles, None, &mut log).await;
917
918 assert!(res.is_err());
919 let err_msg = res.unwrap_err().to_string();
920 assert!(
921 err_msg.contains("Interpreter 'lib/ld.so.1' not found in bootfs"),
922 "unexpected err: {err_msg}"
923 );
924
925 let handles2 = create_test_system_handles().await;
927 let elf_data2 = create_elf_bytes(Some("ld.so.1"));
928 let bootfs2 = create_bootfs_vmo(&[("bin/test_prog", &elf_data2)]);
929 let mut log2 = Log::new();
930
931 let res2 =
932 launch_program("test_prog", "bin/test_prog", [], bootfs2, handles2, None, &mut log2)
933 .await;
934
935 assert!(res2.is_err());
936 let err_msg2 = res2.unwrap_err().to_string();
937 assert!(
938 err_msg2.contains("Interpreter 'lib/ld.so.1' not found in bootfs"),
939 "unexpected err: {err_msg2}"
940 );
941 }
942
943 #[fuchsia::test]
944 async fn test_launch_program_success() {
945 let handles = create_test_system_handles().await;
946 let elf_data = create_elf_bytes(None);
947 let bootfs = create_bootfs_vmo(&[("bin/test_prog", &elf_data)]);
948 let bootfs_dup = bootfs.duplicate_handle(Rights::SAME_RIGHTS).unwrap();
949 let mut log = Log::new();
950
951 let mut bootfs_entries = Vec::new();
952 let res = launch_program(
953 "test_prog",
954 "bin/test_prog",
955 ["arg1", "arg2"],
956 bootfs,
957 handles,
958 Some(&mut bootfs_entries),
959 &mut log,
960 )
961 .await;
962
963 assert!(res.is_ok(), "expected launch_program to succeed, got: {:?}", res);
964 assert_eq!(bootfs_entries.len(), 1);
965 let (offset, ref entry_vmo) = bootfs_entries[0];
966 let mut transferred_bytes = vec![0u8; elf_data.len()];
967 entry_vmo.read(&mut transferred_bytes, 0).unwrap();
968 assert_eq!(transferred_bytes, elf_data);
969
970 let mut original_bytes = vec![0xffu8; elf_data.len()];
972 bootfs_dup.read(&mut original_bytes, offset as u64).unwrap();
973 assert!(original_bytes.iter().all(|&b| b == 0));
974 }
975
976 #[fuchsia::test]
977 async fn test_launch_program_arg_with_null_byte() {
978 let handles = create_test_system_handles().await;
979 let elf_data = create_elf_bytes(None);
980 let bootfs = create_bootfs_vmo(&[("bin/test_prog", &elf_data)]);
981 let mut log = Log::new();
982
983 let res = launch_program(
984 "test_prog",
985 "bin/test_prog",
986 ["arg1\0with_null"],
987 bootfs,
988 handles,
989 None,
990 &mut log,
991 )
992 .await;
993
994 assert!(res.is_err());
995 }
996
997 #[fuchsia::test]
998 async fn test_system_handles_success() {
999 let root_job = job_default().duplicate_handle(Rights::SAME_RIGHTS).unwrap();
1000 root_job.set_name(&Name::new("root").unwrap()).unwrap();
1001 let root_job_info =
1002 zx::HandleInfo::new(root_job.into_handle(), ObjectType::JOB, Rights::SAME_RIGHTS);
1003
1004 let vmex = connect_to_protocol::<VmexResourceMarker>().unwrap().get().await.unwrap();
1005 let vmex_info =
1006 zx::HandleInfo::new(vmex.into_handle(), ObjectType::RESOURCE, Rights::SAME_RIGHTS);
1007
1008 let power = connect_to_protocol::<PowerResourceMarker>().unwrap().get().await.unwrap();
1009 let power_info =
1010 zx::HandleInfo::new(power.into_handle(), ObjectType::RESOURCE, Rights::SAME_RIGHTS);
1011
1012 let vdso = VDSO_VMO.duplicate_handle(Rights::SAME_RIGHTS).unwrap();
1013 vdso.set_name(&Name::new("vdso/stable").unwrap()).unwrap();
1014 let vdso_info =
1015 zx::HandleInfo::new(vdso.into_handle(), ObjectType::VMO, Rights::SAME_RIGHTS);
1016
1017 let zbi = Vmo::create(4096).unwrap();
1018 zbi.set_name(&Name::new("zbi").unwrap()).unwrap();
1019 let zbi_info = zx::HandleInfo::new(zbi.into_handle(), ObjectType::VMO, Rights::SAME_RIGHTS);
1020
1021 let (s1, _s2) = zx::Socket::create_stream();
1022 let log_info =
1023 zx::HandleInfo::new(s1.into_handle(), ObjectType::SOCKET, Rights::SAME_RIGHTS);
1024
1025 let zero_vmo = Vmo::create(0).unwrap();
1026 zero_vmo.set_name(&Name::new("zero").unwrap()).unwrap();
1027 let zero_info =
1028 zx::HandleInfo::new(zero_vmo.into_handle(), ObjectType::VMO, Rights::SAME_RIGHTS);
1029
1030 let kfile_vmo = Vmo::create(4096).unwrap();
1031 kfile_vmo.set_name(&Name::new("kernel_file").unwrap()).unwrap();
1032 let kfile_info =
1033 zx::HandleInfo::new(kfile_vmo.into_handle(), ObjectType::VMO, Rights::SAME_RIGHTS);
1034
1035 let res = SystemHandles::from_handles(vec![
1036 root_job_info,
1037 vmex_info,
1038 power_info,
1039 vdso_info,
1040 zbi_info,
1041 log_info,
1042 zero_info,
1043 kfile_info,
1044 ]);
1045 let handles = res.expect("expected SystemHandles::from_handles to succeed");
1046 assert_eq!(handles.root_job.get_name().unwrap(), "root");
1047 assert_eq!(handles.vdso_vmo.get_name().unwrap(), "vdso/stable");
1048 assert_eq!(handles.zbi_vmo.get_name().unwrap(), "zbi");
1049 assert_eq!(handles.startup_handles.len(), 2);
1050 }
1051
1052 #[fuchsia::test]
1053 fn test_find_bootfs_entry() {
1054 let bootfs = create_bootfs_vmo(&[
1055 ("ld.so.1", b"root_file"),
1056 ("lib/asan/libc.so", b"asan_libc"),
1057 ("lib/ld.so.1", b"interp"),
1058 ("my_lib/foo.so", b"wrong_prefix"),
1059 ("zbi-test/lib/asan/libfoo.so", b"prefixed_asan"),
1060 ("zbi-test/lib/libbar.so", b"prefixed_lib"),
1061 ]);
1062 let parser = BootfsParser::create_from_vmo(&bootfs).unwrap();
1063
1064 let entry = find_bootfs_entry(&parser, &["lib", "ld.so.1"]).unwrap();
1066 assert_eq!(entry.name, "lib/ld.so.1");
1067
1068 let entry = find_bootfs_entry(&parser, &["lib", "asan", "libc.so"]).unwrap();
1069 assert_eq!(entry.name, "lib/asan/libc.so");
1070
1071 let entry = find_bootfs_entry(&parser, &["lib", "asan", "libfoo.so"]).unwrap();
1073 assert_eq!(entry.name, "zbi-test/lib/asan/libfoo.so");
1074
1075 let entry = find_bootfs_entry(&parser, &["lib", "libbar.so"]).unwrap();
1076 assert_eq!(entry.name, "zbi-test/lib/libbar.so");
1077
1078 assert!(find_bootfs_entry(&parser, &["lib", "foo.so"]).is_none());
1080 assert!(find_bootfs_entry(&parser, &["lib", "other", "libc.so"]).is_none());
1081 assert!(find_bootfs_entry(&parser, &["other", "ld.so.1"]).is_none());
1082 }
1083}