lucet_validate/
lib.rs

1mod moduletype;
2mod types;
3
4use std::path::Path;
5use std::rc::Rc;
6use thiserror::Error;
7use wasmparser;
8use witx::{self, Id, Module};
9
10pub use self::moduletype::ModuleType;
11pub use self::types::{FuncSignature, ImportFunc};
12pub use witx::{AtomType, Document, WitxError};
13
14#[derive(Debug, Error)]
15pub enum Error {
16    #[error("WebAssembly validation error at offset {1}: {0}")]
17    WasmValidation(&'static str, usize),
18    #[error("Unsupported: {0}")]
19    Unsupported(String),
20    #[error("Module not found: {0}")]
21    ModuleNotFound(String),
22    #[error("Import not found: {module}::{field}")]
23    ImportNotFound { module: String, field: String },
24    #[error("Export not found: {field}")]
25    ExportNotFound { field: String },
26    #[error("Import type error: for {module}::{field}, expected {expected:?}, got {got:?}")]
27    ImportTypeError {
28        module: String,
29        field: String,
30        expected: FuncSignature,
31        got: FuncSignature,
32    },
33    #[error("Export type error: for {field}, expected {expected:?}, got {got:?}")]
34    ExportTypeError {
35        field: String,
36        expected: FuncSignature,
37        got: FuncSignature,
38    },
39}
40
41impl From<wasmparser::BinaryReaderError> for Error {
42    fn from(e: wasmparser::BinaryReaderError) -> Error {
43        Error::WasmValidation(e.message, e.offset)
44    }
45}
46
47pub struct Validator {
48    witx: Document,
49    wasi_exe: bool,
50}
51
52impl Validator {
53    pub fn new(witx: Document, wasi_exe: bool) -> Self {
54        Self { witx, wasi_exe }
55    }
56
57    pub fn parse(source: &str) -> Result<Self, WitxError> {
58        let witx = witx::parse(source)?;
59        Ok(Self {
60            witx,
61            wasi_exe: false,
62        })
63    }
64
65    pub fn load<P: AsRef<Path>>(source_path: P) -> Result<Self, WitxError> {
66        let witx = witx::load(&[source_path.as_ref()])?;
67        Ok(Self {
68            witx,
69            wasi_exe: false,
70        })
71    }
72
73    pub fn wasi_exe(&mut self, check: bool) {
74        self.wasi_exe = check;
75    }
76
77    pub fn with_wasi_exe(mut self, check: bool) -> Self {
78        self.wasi_exe(check);
79        self
80    }
81
82    pub fn validate(&self, module_contents: &[u8]) -> Result<(), Error> {
83        wasmparser::validate(module_contents, None)?;
84
85        let moduletype = ModuleType::parse_wasm(module_contents)?;
86
87        for import in moduletype.imports() {
88            let func = self
89                .witx_module(&import.module)?
90                .func(&Id::new(&import.field))
91                .ok_or_else(|| Error::ImportNotFound {
92                    module: import.module.clone(),
93                    field: import.field.clone(),
94                })?;
95            let spec_type = FuncSignature::from(func.core_type());
96            if spec_type != import.ty {
97                Err(Error::ImportTypeError {
98                    module: import.module,
99                    field: import.field,
100                    got: import.ty,
101                    expected: spec_type,
102                })?;
103            }
104        }
105
106        if self.wasi_exe {
107            self.check_wasi_start_func(&moduletype)?;
108        }
109
110        Ok(())
111    }
112
113    fn witx_module(&self, module: &str) -> Result<Rc<Module>, Error> {
114        self.witx
115            .module(&Id::new(module))
116            .ok_or_else(|| Error::ModuleNotFound(module.to_string()))
117    }
118
119    fn check_wasi_start_func(&self, moduletype: &ModuleType) -> Result<(), Error> {
120        let start_name = "_start";
121        let expected = FuncSignature {
122            args: vec![],
123            ret: None,
124        };
125        if let Some(startfunc) = moduletype.export(start_name) {
126            if startfunc != &expected {
127                Err(Error::ExportTypeError {
128                    field: start_name.to_string(),
129                    expected,
130                    got: startfunc.clone(),
131                })
132            } else {
133                Ok(())
134            }
135        } else {
136            Err(Error::ExportNotFound {
137                field: start_name.to_string(),
138            })
139        }
140    }
141}