1use crate::{MountAction, parse_rlimits, run_component_features};
6use anyhow::{Context, Error, anyhow, bail};
7use fidl::AsyncChannel;
8use fidl::endpoints::{RequestStream, ServerEnd};
9use fidl_fuchsia_component as fcomponent;
10use fidl_fuchsia_component_runner::{
11 ComponentControllerMarker, ComponentControllerRequest, ComponentControllerRequestStream,
12 ComponentStartInfo,
13};
14use fidl_fuchsia_io as fio;
15use fidl_fuchsia_process as fprocess;
16use fuchsia_async as fasync;
17use fuchsia_runtime::{HandleInfo, HandleType};
18use futures::channel::oneshot;
19use futures::{FutureExt, StreamExt};
20use rand::distr::Alphanumeric;
21use rand::{RngExt as _, rng};
22use serde::Deserialize;
23use serde::de::Error as _;
24use starnix_core::execution::{create_init_child_process, execute_task_with_prerun_result};
25use starnix_core::fs::fuchsia::{RemoteFs, SyslogFile, create_file_from_handle};
26use starnix_core::task::{CurrentTask, ExitStatus, Task};
27use starnix_core::vfs::fs_args::MountParams;
28use starnix_core::vfs::{
29 FdNumber, FdTable, FileSystemOptions, FsString, LookupContext, NamespaceNode, WhatToMount,
30};
31use starnix_core::{security, signals};
32use starnix_logging::{log_debug, log_error, log_info, log_warn};
33use starnix_sync::{ComponentMountRecordLock, LockDepMutex};
34use starnix_task_command::TaskCommand;
35use starnix_uapi::auth::{Capabilities, Credentials};
36use starnix_uapi::device_id::DeviceId;
37use starnix_uapi::errors::{EEXIST, ENOTDIR, Errno};
38use starnix_uapi::file_mode::mode;
39use starnix_uapi::mount_flags::{MountFlags, MountpointFlags};
40use starnix_uapi::open_flags::OpenFlags;
41use starnix_uapi::signals::{SIGINT, SIGKILL};
42use starnix_uapi::unmount_flags::UnmountFlags;
43use starnix_uapi::{errno, rlimit};
44use std::ffi::CString;
45
46use std::os::unix::ffi::OsStrExt;
47use std::path::Path;
48use std::sync::{Arc, Weak};
49
50const COMPONENT_EXIT_CODE_BASE: i32 = 1024;
56
57#[derive(Debug, Deserialize)]
58#[serde(deny_unknown_fields)]
59struct ComponentProgram {
60 binary: CString,
61
62 #[serde(default)]
63 args: Vec<String>,
64
65 #[serde(default)]
66 environ: Vec<String>,
67
68 #[serde(default)]
69 cwd: Option<String>,
70
71 #[serde(default)]
72 uid: Option<runner::serde::StoreAsString<u32>>,
73
74 #[serde(default)]
75 component_mounts: Vec<String>,
76
77 #[serde(default)]
78 features: Vec<String>,
79
80 #[serde(default)]
81 rlimits: Vec<String>,
82
83 #[serde(default, deserialize_with = "parse_capabilities")]
84 capabilities: Option<Capabilities>,
85
86 #[serde(default)]
87 seclabel: Option<CString>,
88
89 #[serde(default, rename(deserialize = "test_target_kernel"))]
90 _test_target_kernel: Option<String>,
91}
92
93impl ComponentProgram {
94 fn resolve_templates(&mut self, component_path: &str, pkg_path: &str) {
95 let resolve_template = |values: &mut Vec<String>| {
96 for val in values {
97 *val = val
98 .replace("{pkg_path}", &pkg_path)
99 .replace("{component_path}", &component_path);
100 }
101 };
102
103 resolve_template(&mut self.args);
104 resolve_template(&mut self.environ);
105 }
106}
107
108fn parse_capabilities<'de, D>(deserializer: D) -> Result<Option<Capabilities>, D::Error>
109where
110 D: serde::Deserializer<'de>,
111{
112 let mut capabilities = Capabilities::empty();
113 for cap in Vec::<String>::deserialize(deserializer)? {
114 capabilities |= cap.parse().map_err(D::Error::custom)?;
115 }
116 Ok(Some(capabilities))
117}
118
119pub async fn start_component(
129 mut start_info: ComponentStartInfo,
130 controller: ServerEnd<ComponentControllerMarker>,
131 system_task: &CurrentTask,
132) -> Result<(), Error> {
133 let url = start_info.resolved_url.clone().unwrap_or_else(|| "<unknown>".to_string());
134
135 let (task_complete_sender, task_complete) = oneshot::channel::<TaskResult>();
136
137 let weak_task = system_task.override_creds(
138 security::creds_start_internal_operation(system_task),
139 || {
140 let component_path = generate_component_path(system_task)?;
142 let pkg_path = format!("{component_path}/pkg");
143
144 let mount_record =
145 Arc::new(LockDepMutex::<_, ComponentMountRecordLock>::new(MountRecord::default()));
146
147 let ns = start_info.ns.take().ok_or_else(|| anyhow!("Missing namespace"))?;
148
149 let program = start_info.program.as_ref().context("reading program block")?;
150 let mut program: ComponentProgram =
151 runner::serde::deserialize_program(program).context("parsing program block")?;
152 let rlimits = parse_rlimits(&program.rlimits)?;
153 program.resolve_templates(&component_path, &pkg_path);
154 log_debug!("start_component: {}\n{:#?}", url, program);
155
156 let ns_mount_options = system_task.kernel().features.default_ns_mount_options.as_ref();
157 let mut maybe_pkg = None;
158 let mut maybe_svc = None;
159 for entry in ns {
160 if let (Some(dir_path), Some(dir_handle)) = (entry.path, entry.directory) {
161 let dir_path_str = dir_path.as_str();
162 let mount_options = ns_mount_options
163 .and_then(|mount_options| mount_options.get(dir_path_str).cloned());
164
165 match dir_path_str {
166 "/svc" => {
167 maybe_svc = Some(fio::DirectoryProxy::new(AsyncChannel::from_channel(
168 dir_handle.into_channel(),
169 )));
170 }
171 "/custom_artifacts" => {
172 let dir_proxy =
175 fio::DirectorySynchronousProxy::new(dir_handle.into_channel());
176 mount_record
177 .lock()
178 .mount_remote(
179 system_task,
180 &dir_proxy,
181 &dir_path,
182 mount_options.as_ref(),
183 )
184 .with_context(|| {
185 format!("failed to mount_remote on path {}", dir_path)
186 })?;
187 }
188 _ => {
189 let dir_proxy =
190 fio::DirectorySynchronousProxy::new(dir_handle.into_channel());
191 mount_record
192 .lock()
193 .mount_remote(
194 system_task,
195 &dir_proxy,
196 &format!("{component_path}/{dir_path}"),
197 mount_options.as_ref(),
198 )
199 .with_context(|| {
200 format!(
201 "failed to mount_remote on path {component_path}/{dir_path}"
202 )
203 })?;
204 if dir_path == "/pkg" {
205 maybe_pkg = Some(dir_proxy);
206 }
207 }
208 }
209 }
210 }
211
212 let pkg = maybe_pkg.ok_or_else(|| anyhow!("Missing /pkg entry in namespace"))?;
213
214 let uid = program
215 .uid
216 .map(|uid| uid.0)
217 .unwrap_or_else(|| system_task.kernel().features.default_uid);
218
219 let mut credentials = Credentials::with_ids(uid, uid);
220 if let Some(capabilities) = program.capabilities {
221 credentials.cap_permitted = capabilities;
222 credentials.cap_effective = capabilities;
223 credentials.cap_inheritable = capabilities;
224 credentials.cap_ambient = capabilities;
225 }
226
227 run_component_features(system_task.kernel(), &program.features, maybe_svc)
228 .unwrap_or_else(|e| {
229 log_error!("failed to set component features for {} - {:?}", url, e);
230 });
231
232 let current_task = create_init_child_process(
233 system_task.kernel(),
234 TaskCommand::new(program.binary.as_bytes()),
235 credentials,
236 program.seclabel.as_ref(),
237 )?;
238 {
242 let mut limits = current_task.thread_group().limits.lock();
243 for (resource, limit) in rlimits {
244 limits.set(resource, rlimit { rlim_cur: limit, rlim_max: limit });
245 }
246 }
247
248 execute_task_with_prerun_result(
249 current_task,
250 {
251 let mount_record = mount_record.clone();
252 move |current_task| {
253 let cwd_path = FsString::from(program.cwd.unwrap_or(pkg_path));
254 let cwd = current_task.lookup_path(
255 &mut LookupContext::default(),
256 current_task.fs().root(),
257 cwd_path.as_ref(),
258 )?;
259 current_task.fs().chdir(current_task, cwd)?;
260
261 for mount in &program.component_mounts {
262 let Some(action) = MountAction::from_spec(current_task, &pkg, mount)
263 .map_err(|e| {
264 log_error!("Error while mounting the filesystems: {e:?}");
265 errno!(EINVAL)
266 })?
267 else {
268 continue;
269 };
270 let mount_point =
271 current_task.lookup_path_from_root(action.path.as_ref())?;
272 mount_record.lock().mount(
273 mount_point,
274 WhatToMount::Fs(action.fs),
275 action.flags,
276 )?;
277 }
278
279 let files = current_task.files();
280 parse_numbered_handles(current_task, start_info.numbered_handles, &files)
281 .map_err(|e| {
282 log_error!("Error while parsing the numbered handles: {e:?}");
283 errno!(EINVAL)
284 })?;
285
286 let mut argv = vec![program.binary.clone()];
287 for arg in program.args {
288 argv.push(CString::new(arg).map_err(|_| errno!(EINVAL))?);
289 }
290
291 let mut environ = vec![];
292 for env in program.environ {
293 environ.push(CString::new(env).map_err(|_| errno!(EINVAL))?);
294 }
295
296 let executable = current_task.open_file_for_exec(
297 FdNumber::AT_FDCWD,
298 program.binary.as_bytes().into(),
299 OpenFlags::empty(),
300 )?;
301 current_task.exec(executable, program.binary, argv, environ)?;
302
303 Ok(Arc::downgrade(¤t_task.task))
304 }
305 },
306 move |result| {
307 std::mem::drop(mount_record);
309
310 let _ = task_complete_sender.send(result);
313 },
314 )
315 .map_err(anyhow::Error::from)
316 },
317 )?;
318
319 let controller = controller.into_stream();
320 fasync::Task::local(serve_component_controller(controller, weak_task, task_complete)).detach();
321
322 Ok(())
323}
324
325type TaskResult = Result<ExitStatus, Error>;
326
327async fn serve_component_controller(
336 controller: ComponentControllerRequestStream,
337 task: Weak<Task>,
338 task_complete: oneshot::Receiver<TaskResult>,
339) {
340 let controller_handle = controller.control_handle();
341
342 enum Event<T, U> {
343 Controller(T),
344 Completion(U),
345 }
346
347 let mut stream = futures::stream::select(
348 controller.map(Event::Controller),
349 task_complete.into_stream().map(Event::Completion),
350 );
351
352 while let Some(event) = stream.next().await {
353 match event {
354 Event::Controller(request) => match request {
355 Ok(ComponentControllerRequest::Stop { .. }) => {
356 if let Some(task) = task.upgrade() {
357 signals::send_standard_signal(
358 task.as_ref(),
359 signals::SignalInfo::kernel(SIGINT),
360 );
361 log_info!("Sent SIGINT to program {}", task.command());
362 }
363 }
364 Ok(ComponentControllerRequest::Kill { .. }) => {
365 if let Some(task) = task.upgrade() {
366 signals::send_standard_signal(&task, signals::SignalInfo::forced(SIGKILL));
367 log_info!("Sent SIGKILL to program {}", task.command());
368 controller_handle.shutdown_with_epitaph(zx::Status::err_from_raw(
369 fcomponent::Error::InstanceDied.into_primitive() as i32,
370 ));
371 }
372 return;
373 }
374 Ok(ComponentControllerRequest::_UnknownMethod { ordinal, .. }) => {
375 log_warn!("Unknown ComponentController request: {ordinal}");
376 }
377 Err(_) => {
378 return;
379 }
380 },
381 Event::Completion(result) => match result {
382 Ok(Ok(ExitStatus::Exit(0))) => controller_handle.shutdown_with_epitaph(Ok(())),
383 Ok(Ok(ExitStatus::Exit(n))) => {
384 let epitaph =
385 zx::Status::try_from_raw(COMPONENT_EXIT_CODE_BASE + n as i32).unwrap();
386 controller_handle.shutdown_with_epitaph(Err(epitaph));
387 }
388 _ => {
389 let epitaph = zx::Status::try_from_raw(
390 fcomponent::Error::InstanceDied.into_primitive() as i32,
391 )
392 .unwrap();
393 controller_handle.shutdown_with_epitaph(Err(epitaph));
394 }
395 },
396 }
397 }
398}
399
400fn generate_component_path(system_task: &CurrentTask) -> Result<String, Error> {
402 let mount_point = system_task.lookup_path_from_root("/container/component/".into())?;
405
406 let component_path = loop {
408 let random_string: String =
409 rng().sample_iter(&Alphanumeric).take(10).map(char::from).collect();
410
411 match mount_point.create_node(
414 system_task,
415 random_string.as_str().into(),
416 mode!(IFDIR, 0o755),
417 DeviceId::NONE,
418 ) {
419 Ok(_) => break format!("/container/component/{random_string}"),
420 Err(errno) if errno == EEXIST => {}
421 Err(e) => bail!(e),
422 };
423 };
424
425 Ok(component_path)
426}
427
428pub fn parse_numbered_handles(
436 current_task: &CurrentTask,
437 numbered_handles: Option<Vec<fprocess::HandleInfo>>,
438 files: &FdTable,
439) -> Result<(), Error> {
440 if let Some(numbered_handles) = numbered_handles {
441 for numbered_handle in numbered_handles {
442 let info = HandleInfo::try_from(numbered_handle.id)?;
443 if info.handle_type() == HandleType::FileDescriptor {
444 let file = create_file_from_handle(current_task, numbered_handle.handle)?;
445 files.insert(current_task, FdNumber::from_raw(info.arg().into()), file)?;
446 }
447 }
448 }
449
450 let stdio = SyslogFile::new_file(current_task);
451 for i in [0, 1, 2] {
453 if files.get(FdNumber::from_raw(i)).is_err() {
454 files.insert(current_task, FdNumber::from_raw(i), stdio.clone())?;
455 }
456 }
457
458 Ok(())
459}
460
461#[derive(Default)]
465struct MountRecord {
466 mounts: Vec<NamespaceNode>,
468}
469
470impl MountRecord {
471 fn mount(
472 &mut self,
473 mount_point: NamespaceNode,
474 what: WhatToMount,
475 flags: MountpointFlags,
476 ) -> Result<(), Errno> {
477 mount_point.mount(what, flags.into())?;
478 self.mounts.push(mount_point);
479 Ok(())
480 }
481
482 fn mount_remote(
483 &mut self,
484 system_task: &CurrentTask,
485 directory: &fio::DirectorySynchronousProxy,
486 path: &str,
487 mount_options: Option<&String>,
488 ) -> Result<(), Error> {
489 let mut current_node =
493 system_task.lookup_path_from_root(".".into()).context("looking up '.'")?;
494 let mut context = LookupContext::default();
495
496 let path = if let Some(path) = path.strip_prefix('/') { path } else { path };
500
501 for sub_dir in Path::new(path).components() {
502 let sub_dir_bytes = sub_dir.as_os_str().as_bytes();
503 current_node = match current_node.create_node(
504 system_task,
505 sub_dir_bytes.into(),
506 mode!(IFDIR, 0o755),
507 DeviceId::NONE,
508 ) {
509 Ok(node) => node,
510 Err(errno) if errno == EEXIST || errno == ENOTDIR => current_node
511 .lookup_child(system_task, &mut context, sub_dir_bytes.into())
512 .with_context(|| format!("looking up {sub_dir:?}"))?,
513 Err(e) => bail!(e),
514 };
515 }
516
517 let flags = directory
518 .get_flags(zx::MonotonicInstant::INFINITE)
519 .context("transport error")?
520 .map_err(zx::Status::err_from_raw)
521 .context("get_flags")?;
522 let rights = flags.intersection(fio::MASK_KNOWN_PERMISSIONS);
523
524 let (client_end, server_end) = zx::Channel::create();
525 directory.clone(ServerEnd::new(server_end)).context("cloning directory")?;
526
527 let params = if let Some(mount_options) = mount_options {
530 MountParams::parse(mount_options.as_str().into())
531 .expect("failed to parse default_ns_mount_options")
532 } else {
533 MountParams::default()
534 };
535
536 let fs = RemoteFs::new_fs(
537 system_task.kernel(),
538 client_end,
539 FileSystemOptions { source: path.into(), params, ..Default::default() },
540 rights,
541 )
542 .context("making remote fs")?;
543
544 security::file_system_resolve_security(system_task, &fs).context("resolving security")?;
545
546 let flags = MountFlags::NOSUID | MountFlags::NODEV | MountFlags::RELATIME;
549 current_node.mount(WhatToMount::Fs(fs), flags.mountpoint_flags()).context("mounting fs")?;
550 self.mounts.push(current_node);
551
552 Ok(())
553 }
554
555 fn unmount(&mut self) -> Result<(), Errno> {
556 while let Some(node) = self.mounts.pop() {
557 node.unmount(UnmountFlags::DETACH)?;
558 }
559 Ok(())
560 }
561}
562
563impl Drop for MountRecord {
564 fn drop(&mut self) {
565 match self.unmount() {
566 Ok(()) => {}
567 Err(e) => log_error!("failed to unmount during component exit: {:?}", e),
568 }
569 }
570}
571
572#[cfg(test)]
573mod test {
574 use super::ComponentProgram;
575 use fidl_fuchsia_data as fdata;
576 use std::ffi::CString;
577
578 #[fuchsia::test]
579 fn test_deserialize_component_program_rlimits() {
580 let program_with_rlimits = fdata::Dictionary {
581 entries: Some(vec![
582 fdata::DictionaryEntry {
583 key: "binary".to_string(),
584 value: Some(Box::new(fdata::DictionaryValue::Str("/bin/sh".to_string()))),
585 },
586 fdata::DictionaryEntry {
587 key: "rlimits".to_string(),
588 value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
589 "RLIMIT_NOFILE=131072".to_string(),
590 "RLIMIT_RTPRIO=10".to_string(),
591 ]))),
592 },
593 ]),
594 ..Default::default()
595 };
596 let parsed: ComponentProgram =
597 runner::serde::deserialize_program(&program_with_rlimits).expect("deserialize_program");
598 assert_eq!(parsed.binary, CString::new("/bin/sh").unwrap());
599 assert_eq!(
600 parsed.rlimits,
601 vec!["RLIMIT_NOFILE=131072".to_string(), "RLIMIT_RTPRIO=10".to_string()]
602 );
603
604 let program_without_rlimits = fdata::Dictionary {
605 entries: Some(vec![fdata::DictionaryEntry {
606 key: "binary".to_string(),
607 value: Some(Box::new(fdata::DictionaryValue::Str("/bin/sh".to_string()))),
608 }]),
609 ..Default::default()
610 };
611 let parsed: ComponentProgram = runner::serde::deserialize_program(&program_without_rlimits)
612 .expect("deserialize_program");
613 assert!(parsed.rlimits.is_empty());
614 }
615}