spin_componentize/
bugs.rs

1use crate::module_info::ModuleInfo;
2
3pub const EARLIEST_PROBABLY_SAFE_CLANG_VERSION: &str = "15.0.7";
4
5/// This error represents the likely presence of the allocation bug fixed in
6/// <https://github.com/WebAssembly/wasi-libc/pull/377> in a Wasm module.
7#[derive(Debug, PartialEq)]
8pub struct WasiLibc377Bug {
9    clang_version: Option<String>,
10}
11
12impl WasiLibc377Bug {
13    /// Detects the likely presence of this bug.
14    pub fn check(module_info: &ModuleInfo) -> Result<(), Self> {
15        if module_info.probably_uses_wit_bindgen() {
16            // Modules built with wit-bindgen are probably safe.
17            return Ok(());
18        }
19        if let Some(clang_version) = &module_info.clang_version {
20            // Clang/LLVM version is a good proxy for wasi-sdk
21            // version; the allocation bug was fixed in wasi-sdk-18
22            // and LLVM was updated to 15.0.7 in wasi-sdk-19.
23            if let Some((major, minor, patch)) = parse_clang_version(clang_version) {
24                let earliest_safe =
25                    parse_clang_version(EARLIEST_PROBABLY_SAFE_CLANG_VERSION).unwrap();
26                if (major, minor, patch) < earliest_safe {
27                    return Err(Self {
28                        clang_version: Some(clang_version.clone()),
29                    });
30                };
31            } else {
32                tracing::warn!(
33                    clang_version,
34                    "Unexpected producers.processed-by.clang version"
35                );
36            }
37        }
38        Ok(())
39    }
40}
41
42impl std::fmt::Display for WasiLibc377Bug {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        write!(
45            f,
46            "This Wasm module appears to have been compiled with wasi-sdk version <19 \
47            which contains a critical memory safety bug. For more information, see: \
48            https://github.com/spinframework/spin/issues/2552"
49        )
50    }
51}
52
53impl std::error::Error for WasiLibc377Bug {}
54
55fn parse_clang_version(ver: &str) -> Option<(u16, u16, u16)> {
56    // Strip optional trailing detail after space
57    let ver = ver.split(' ').next().unwrap();
58    // Strip optional -wasi-sdk suffix
59    let ver = ver.strip_suffix("-wasi-sdk").unwrap_or(ver);
60    let mut parts = ver.split('.');
61    let major = parts.next()?.parse().ok()?;
62    let minor = parts.next()?.parse().ok()?;
63    let patch = parts.next()?.parse().ok()?;
64    Some((major, minor, patch))
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn wasi_libc_377_detect() {
73        for (wasm, safe) in [
74            (r#"(module)"#, true),
75            (
76                r#"(module (func (export "cabi_realloc") (unreachable)))"#,
77                true,
78            ),
79            (
80                r#"(module (@producers (processed-by "clang" "16.0.0 extra-stuff")))"#,
81                true,
82            ),
83            (
84                r#"(module (@producers (processed-by "clang" "15.0.7")))"#,
85                true,
86            ),
87            (
88                r#"(module (@producers (processed-by "clang" "18.1.2-wasi-sdk (https://github.com/llvm/llvm-project 26a1d6601d727a96f4301d0d8647b5a42760ae0c)")))"#,
89                true,
90            ),
91            (
92                r#"(module (@producers (processed-by "clang" "15.0.6")))"#,
93                false,
94            ),
95            (
96                r#"(module (@producers (processed-by "clang" "14.0.0 extra-stuff")))"#,
97                false,
98            ),
99        ] {
100            eprintln!("WAT: {wasm}");
101            let module = wat::parse_str(wasm).unwrap();
102            let module_info = ModuleInfo::from_module(&module).unwrap();
103            let detected = WasiLibc377Bug::check(&module_info);
104            assert!(detected.is_ok() == safe, "{wasm} -> {detected:?}");
105        }
106    }
107}