detcore/in_guest_site_patching.rs
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! The check that an in-guest Tool image runs with syscall site patching off,
10//! and collects the statistics it reports at exit, when the host asked for it
11//! (`Config::in_guest_site_patching_off`, set for
12//! `hermit --backend=in-guest-trap`).
13//!
14//! The in-guest LiteInst runtime takes both settings from the guest
15//! environment while it installs the Tool: `REVERIE_LITEINST_SITE_PATCHING` and
16//! the statistics coordinator's variable. Hermit sets them, but code that runs
17//! in the guest before the runtime's constructor (a constructor of one of the
18//! program's own shared libraries), or code the runtime calls through
19//! interposable symbols during installation, could change them. So the check
20//! does not read the environment: the Tool's constructor records the host's
21//! request, which arrived in the configuration handshake that guest code
22//! cannot change ([`require_site_patching_off`]), and once installation has
23//! returned the runtime's preload constructor (`detcore_liteinst_initialize`)
24//! compares it with the settings the runtime actually captured and refuses to
25//! run the program when they differ ([`violation`]).
26
27use std::sync::atomic::AtomicBool;
28use std::sync::atomic::Ordering;
29
30static REQUIRED: AtomicBool = AtomicBool::new(false);
31
32/// Records that the host asked this Tool image to run with syscall site
33/// patching off and statistics on. Called from the Tool's constructor.
34pub fn require_site_patching_off() {
35 REQUIRED.store(true, Ordering::Release);
36}
37
38/// Whether the host asked this Tool image to run with syscall site patching
39/// off ([`require_site_patching_off`]).
40pub fn site_patching_off_required() -> bool {
41 REQUIRED.load(Ordering::Acquire)
42}
43
44/// Why an image whose runtime captured `runtime_patches` (whether it patches
45/// syscall sites) and `runtime_collects` (whether it collects its exit
46/// statistics) breaks the host's request, or `None` when it does not or when
47/// nothing was requested (`required` false).
48pub fn violation(required: bool, runtime_patches: bool, runtime_collects: bool) -> Option<String> {
49 if !required {
50 return None;
51 }
52 if runtime_patches {
53 return Some(
54 "--backend=in-guest-trap runs with syscall site patching off, but the in-guest \
55 runtime installed this process with site patching on: it reads \
56 REVERIE_LITEINST_SITE_PATCHING from the environment, and code in the guest that \
57 ran before it (a shared library's constructor, or a function the runtime calls \
58 while installing) changed the value Hermit set; run this program with \
59 --backend=liteinst"
60 .to_owned(),
61 );
62 }
63 (!runtime_collects).then(|| {
64 "--backend=in-guest-trap needs every process's syscall site statistics, but the \
65 in-guest runtime installed this process without collecting them: it reads its \
66 statistics socket from the environment, and code in the guest that ran before it \
67 removed the variable Hermit set, so this process could not show that it patched \
68 nothing; run this program with --backend=liteinst"
69 .to_owned()
70 })
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn only_the_captured_settings_decide_and_only_when_requested() {
79 assert_eq!(violation(true, false, true), None);
80 let patches = violation(true, true, true).expect("a patching runtime is refused");
81 assert!(
82 patches.contains("installed this process with site patching on"),
83 "{patches}"
84 );
85 assert!(patches.contains("--backend=liteinst"), "{patches}");
86 // Patching is the first reason even when statistics are missing too.
87 assert_eq!(violation(true, true, false), Some(patches));
88 let silent = violation(true, false, false).expect("an image that reports nothing");
89 assert!(silent.contains("without collecting them"), "{silent}");
90 for (patches, collects) in [(true, true), (true, false), (false, false)] {
91 assert_eq!(violation(false, patches, collects), None);
92 }
93 }
94}