lucet_module/
signature.rs

1use crate::error::Error::{self, IOError, ModuleSignatureError};
2use crate::module::{SerializedModule, LUCET_MODULE_SYM};
3use crate::module_data::MODULE_DATA_SYM;
4use crate::ModuleData;
5use byteorder::{ByteOrder, LittleEndian};
6use memoffset::offset_of;
7pub use minisign::{PublicKey, SecretKey};
8use minisign::{SignatureBones, SignatureBox};
9use object::*;
10use std::fs::{File, OpenOptions};
11use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};
12use std::path::Path;
13
14pub struct ModuleSignature;
15
16impl ModuleSignature {
17    pub fn verify<P: AsRef<Path>>(
18        so_path: P,
19        pk: &PublicKey,
20        module_data: &ModuleData,
21    ) -> Result<(), Error> {
22        let signature_box: SignatureBox =
23            SignatureBones::from_bytes(&module_data.get_module_signature())
24                .map_err(|e| ModuleSignatureError(e))?
25                .into();
26
27        let mut raw_module_and_data =
28            RawModuleAndData::from_file(&so_path).map_err(|e| IOError(e))?;
29        let cleared_module_data_bin =
30            ModuleData::clear_module_signature(raw_module_and_data.module_data_bin())?;
31        raw_module_and_data.patch_module_data(&cleared_module_data_bin);
32
33        minisign::verify(
34            &pk,
35            &signature_box,
36            Cursor::new(&raw_module_and_data.obj_bin),
37            true,
38            false,
39        )
40        .map_err(|e| ModuleSignatureError(e))
41    }
42
43    pub fn sign<P: AsRef<Path>>(path: P, sk: &SecretKey) -> Result<(), Error> {
44        let raw_module_and_data = RawModuleAndData::from_file(&path).map_err(|e| IOError(e))?;
45        let signature_box = minisign::sign(
46            None,
47            sk,
48            Cursor::new(&raw_module_and_data.obj_bin),
49            true,
50            None,
51            None,
52        )
53        .map_err(|e| ModuleSignatureError(e))?;
54        let signature_bones: SignatureBones = signature_box.into();
55        let patched_module_data_bin = ModuleData::patch_module_signature(
56            raw_module_and_data.module_data_bin(),
57            &signature_bones.to_bytes(),
58        )?;
59        raw_module_and_data
60            .write_patched_module_data(&path, &patched_module_data_bin)
61            .map_err(|e| IOError(e))?;
62        Ok(())
63    }
64}
65
66#[allow(dead_code)]
67struct SymbolData {
68    offset: usize,
69    len: usize,
70}
71
72struct RawModuleAndData {
73    pub obj_bin: Vec<u8>,
74    pub module_data_offset: usize,
75    pub module_data_len: usize,
76}
77
78impl RawModuleAndData {
79    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, io::Error> {
80        let mut obj_bin: Vec<u8> = Vec::new();
81        File::open(&path)?.read_to_end(&mut obj_bin)?;
82
83        let native_data_symbol_data =
84            Self::symbol_data(&obj_bin, LUCET_MODULE_SYM, true)?.ok_or(io::Error::new(
85                io::ErrorKind::InvalidInput,
86                format!("`{}` symbol not present", LUCET_MODULE_SYM),
87            ))?;
88
89        // While `module_data` is the first field of the `SerializedModule` that `lucet_module` points
90        // to, it is a virtual address, not a file offset. The translation is somewhat tricky at
91        // the moment, so just look at the corresponding `lucet_module_data` symbol for now.
92        let module_data_symbol_data =
93            Self::symbol_data(&obj_bin, MODULE_DATA_SYM, true)?.ok_or(io::Error::new(
94                io::ErrorKind::InvalidInput,
95                format!("`{}` symbol not present", MODULE_DATA_SYM),
96            ))?;
97
98        let module_data_len = LittleEndian::read_u64(
99            &obj_bin[(native_data_symbol_data.offset
100                + offset_of!(SerializedModule, module_data_len))..],
101        ) as usize;
102
103        Ok(RawModuleAndData {
104            obj_bin,
105            module_data_offset: module_data_symbol_data.offset,
106            module_data_len: module_data_len,
107        })
108    }
109
110    pub fn module_data_bin(&self) -> &[u8] {
111        &self.obj_bin[self.module_data_offset as usize
112            ..self.module_data_offset as usize + self.module_data_len]
113    }
114
115    pub fn module_data_bin_mut(&mut self) -> &mut [u8] {
116        &mut self.obj_bin[self.module_data_offset as usize
117            ..self.module_data_offset as usize + self.module_data_len]
118    }
119
120    pub fn patch_module_data(&mut self, module_data_bin: &[u8]) {
121        self.module_data_bin_mut().copy_from_slice(&module_data_bin);
122    }
123
124    pub fn write_patched_module_data<P: AsRef<Path>>(
125        &self,
126        path: P,
127        patched_module_data_bin: &[u8],
128    ) -> Result<(), io::Error> {
129        let mut fp = OpenOptions::new()
130            .write(true)
131            .create_new(false)
132            .open(&path)?;
133        fp.seek(SeekFrom::Start(self.module_data_offset as u64))?;
134        fp.write_all(&patched_module_data_bin)?;
135        Ok(())
136    }
137
138    // Retrieving the offset of a symbol is not supported by the object crate.
139    // In Mach-O, actual file offsets are encoded, whereas Elf encodes virtual
140    // addresses, requiring extra steps to retrieve the section, its base
141    // address as well as the section offset.
142
143    // Elf
144    #[cfg(all(target_family = "unix", not(target_os = "macos")))]
145    fn symbol_data(
146        obj_bin: &[u8],
147        symbol_name: &str,
148        _mangle: bool,
149    ) -> Result<Option<SymbolData>, io::Error> {
150        let obj = object::ElfFile::parse(obj_bin)
151            .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
152        let symbol_map = obj.symbol_map();
153        for symbol in symbol_map
154            .symbols()
155            .iter()
156            .filter(|sym| sym.kind() == SymbolKind::Data)
157            .filter(|sym| sym.name() == Some(symbol_name))
158        {
159            if let Some(section_index) = symbol.section_index() {
160                let section = &obj.elf().section_headers[section_index.0];
161                let offset = (symbol.address() - section.sh_addr + section.sh_offset) as usize;
162                let len = symbol.size() as usize;
163                return Ok(Some(SymbolData { offset, len }));
164            }
165        }
166        Ok(None)
167    }
168
169    // Mach-O
170    #[cfg(target_os = "macos")]
171    fn symbol_data(
172        obj_bin: &[u8],
173        symbol_name: &str,
174        mangle: bool,
175    ) -> Result<Option<SymbolData>, io::Error> {
176        let obj = object::File::parse(obj_bin)
177            .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
178        let symbol_map = obj.symbol_map();
179        let mangled_symbol_name = format!("_{}", symbol_name);
180        let symbol_name = if mangle {
181            &mangled_symbol_name
182        } else {
183            symbol_name
184        };
185        if let Some(symbol) = symbol_map
186            .symbols()
187            .iter()
188            .filter(|sym| sym.kind() == SymbolKind::Data || sym.kind() == SymbolKind::Unknown)
189            .find(|sym| sym.name() == Some(symbol_name))
190        {
191            let offset = symbol.address() as usize;
192            let len = symbol.size() as usize;
193            return Ok(Some(SymbolData { offset, len }));
194        }
195        Ok(None)
196    }
197}