Skip to main content

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}