elf/
endian.rs

1//! An all-safe-code endian-aware integer parsing implementation via the
2//! [EndianParse] trait.
3//!
4//! This module provides four endian parsing implementations optimized to support the different
5//! common use-cases for an ELF parsing library.  Each trait impl represents a
6//! specification that encapsulates an interface for parsing integers from some
7//! set of allowed byte orderings.
8//!
9//! * [AnyEndian]: Dynamically parsing either byte order at runtime based on the type of ELF object being parsed.
10//! * [BigEndian]/[LittleEndian]: For tools that know they only want to parse a single given byte order known at compile time.
11//! * [type@NativeEndian]: For tools that know they want to parse the same byte order as the target's byte order.
12//
13// Note:
14//   I'd love to see this get replaced with safe transmutes, if that RFC ever gets formalized.
15//   Until then, this crate serves as an example implementation for what's possible with purely safe rust.
16use crate::abi;
17use crate::parse::ParseError;
18
19/// This macro writes out safe code to get a subslice from the the byte slice $data
20/// at the given $off as a [u8; size_of<$typ>], then calls the corresponding safe
21/// endian-aware conversion on it.
22///
23/// This uses safe integer math and returns a ParseError on overflow or if $data did
24/// not contain enough bytes at $off to perform the conversion.
25macro_rules! safe_from {
26    ( $self:ident, $typ:ty, $off:ident, $data:ident) => {{
27        const SIZE: usize = core::mem::size_of::<$typ>();
28
29        let end = (*$off)
30            .checked_add(SIZE)
31            .ok_or(ParseError::IntegerOverflow)?;
32
33        let buf: [u8; SIZE] = $data
34            .get(*$off..end)
35            .ok_or(ParseError::SliceReadError((*$off, end)))?
36            .try_into()?;
37
38        *$off = end;
39
40        // Note: This check evaluates to a constant true/false for the "fixed" types
41        // so the compiler should optimize out the check (LittleEndian, BigEndian, NativeEndian)
42        if $self.is_little() {
43            Ok(<$typ>::from_le_bytes(buf))
44        } else {
45            Ok(<$typ>::from_be_bytes(buf))
46        }
47    }};
48}
49
50/// An all-safe-code endian-aware integer parsing trait.
51///
52/// These methods use safe code to get a subslice from the the byte slice $data
53/// at the given $off as a [u8; size_of<$typ>], then calls the corresponding safe
54/// endian-aware conversion on it.
55///
56/// These use checked integer math and returns a ParseError on overflow or if $data did
57/// not contain enough bytes at $off to perform the conversion.
58pub trait EndianParse: Clone + Copy + Default + PartialEq + Eq {
59    fn parse_u8_at(self, offset: &mut usize, data: &[u8]) -> Result<u8, ParseError> {
60        safe_from!(self, u8, offset, data)
61    }
62
63    fn parse_u16_at(self, offset: &mut usize, data: &[u8]) -> Result<u16, ParseError> {
64        safe_from!(self, u16, offset, data)
65    }
66
67    fn parse_u32_at(self, offset: &mut usize, data: &[u8]) -> Result<u32, ParseError> {
68        safe_from!(self, u32, offset, data)
69    }
70
71    fn parse_u64_at(self, offset: &mut usize, data: &[u8]) -> Result<u64, ParseError> {
72        safe_from!(self, u64, offset, data)
73    }
74
75    fn parse_i32_at(self, offset: &mut usize, data: &[u8]) -> Result<i32, ParseError> {
76        safe_from!(self, i32, offset, data)
77    }
78
79    fn parse_i64_at(self, offset: &mut usize, data: &[u8]) -> Result<i64, ParseError> {
80        safe_from!(self, i64, offset, data)
81    }
82
83    /// Get an endian-aware integer parsing spec for an ELF [FileHeader](crate::file::FileHeader)'s
84    /// `ident[EI_DATA]` byte.
85    ///
86    /// Returns an [UnsupportedElfEndianness](ParseError::UnsupportedElfEndianness) if this spec
87    /// doesn't support parsing the byte-order represented by ei_data. If you're
88    /// seeing this error, are you trying to read files of any endianness? i.e.
89    /// did you want to use AnyEndian?
90    fn from_ei_data(ei_data: u8) -> Result<Self, ParseError>;
91
92    fn is_little(self) -> bool;
93
94    #[inline(always)]
95    fn is_big(self) -> bool {
96        !self.is_little()
97    }
98}
99
100/// An endian parsing type that can choose at runtime which byte order to parse integers as.
101/// This is useful for scenarios where a single compiled binary wants to dynamically
102/// interpret ELF files of any byte order.
103#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
104pub enum AnyEndian {
105    /// Used for a little-endian ELF structures that have been parsed with AnyEndian
106    #[default]
107    Little,
108    /// Used for a big-endian ELF structures that have been parsed with AnyEndian
109    Big,
110}
111
112/// A zero-sized type that always parses integers as if they're in little-endian order.
113/// This is useful for scenarios where a combiled binary knows it only wants to interpret
114/// little-endian ELF files and doesn't want the performance penalty of evaluating a match
115/// each time it parses an integer.
116#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
117pub struct LittleEndian;
118
119/// A zero-sized type that always parses integers as if they're in big-endian order.
120/// This is useful for scenarios where a combiled binary knows it only wants to interpret
121/// big-endian ELF files and doesn't want the performance penalty of evaluating a match
122/// each time it parses an integer.
123#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
124pub struct BigEndian;
125
126/// A zero-sized type that always parses integers as if they're in the compilation target's native-endian order.
127/// This is useful for toolchain scenarios where a combiled binary knows it only wants to interpret
128/// ELF files compiled for the same target and doesn't want the performance penalty of evaluating a match
129/// each time it parses an integer.
130#[cfg(target_endian = "little")]
131pub type NativeEndian = LittleEndian;
132
133#[cfg(target_endian = "little")]
134#[allow(non_upper_case_globals)]
135#[doc(hidden)]
136pub const NativeEndian: LittleEndian = LittleEndian;
137
138/// A zero-sized type that always parses integers as if they're in the compilation target's native-endian order.
139/// This is useful for toolchain scenarios where a combiled binary knows it only wants to interpret
140/// ELF files compiled for the same target and doesn't want the performance penalty of evaluating a match
141/// each time it parses an integer.
142#[cfg(target_endian = "big")]
143pub type NativeEndian = BigEndian;
144
145#[cfg(target_endian = "big")]
146#[allow(non_upper_case_globals)]
147#[doc(hidden)]
148pub const NativeEndian: BigEndian = BigEndian;
149
150impl EndianParse for LittleEndian {
151    fn from_ei_data(ei_data: u8) -> Result<Self, ParseError> {
152        match ei_data {
153            abi::ELFDATA2LSB => Ok(LittleEndian),
154            _ => Err(ParseError::UnsupportedElfEndianness(ei_data)),
155        }
156    }
157
158    #[inline(always)]
159    fn is_little(self) -> bool {
160        true
161    }
162}
163
164impl EndianParse for BigEndian {
165    fn from_ei_data(ei_data: u8) -> Result<Self, ParseError> {
166        match ei_data {
167            abi::ELFDATA2MSB => Ok(BigEndian),
168            _ => Err(ParseError::UnsupportedElfEndianness(ei_data)),
169        }
170    }
171
172    #[inline(always)]
173    fn is_little(self) -> bool {
174        false
175    }
176}
177
178impl EndianParse for AnyEndian {
179    fn from_ei_data(ei_data: u8) -> Result<Self, ParseError> {
180        match ei_data {
181            abi::ELFDATA2LSB => Ok(AnyEndian::Little),
182            abi::ELFDATA2MSB => Ok(AnyEndian::Big),
183            _ => Err(ParseError::UnsupportedElfEndianness(ei_data)),
184        }
185    }
186
187    #[inline(always)]
188    fn is_little(self) -> bool {
189        match self {
190            AnyEndian::Little => true,
191            AnyEndian::Big => false,
192        }
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    macro_rules! parse_test {
201        ( $endian:expr, $res_typ:ty, $method:ident, $expect:expr) => {{
202            let bytes = [
203                0x01u8, 0x02u8, 0x03u8, 0x04u8, 0x05u8, 0x06u8, 0x07u8, 0x08u8,
204            ];
205            let mut offset = 0;
206            let result = $endian.$method(&mut offset, &bytes).unwrap();
207            assert_eq!(result, $expect);
208            assert_eq!(offset, core::mem::size_of::<$res_typ>());
209        }};
210    }
211
212    macro_rules! fuzz_too_short_test {
213        ( $endian:expr, $res_typ:ty, $method:ident) => {{
214            let bytes = [
215                0x01u8, 0x02u8, 0x03u8, 0x04u8, 0x05u8, 0x06u8, 0x07u8, 0x08u8,
216            ];
217            let size = core::mem::size_of::<$res_typ>();
218            for n in 0..size {
219                let buf = bytes.split_at(n).0.as_ref();
220                let mut offset: usize = 0;
221                let error = $endian
222                    .$method(&mut offset, buf)
223                    .expect_err("Expected an error, but parsed: ");
224                assert!(
225                    matches!(error, ParseError::SliceReadError(_)),
226                    "Unexpected Error type found: {error}"
227                );
228            }
229        }};
230    }
231
232    #[test]
233    fn parse_u8_at() {
234        parse_test!(LittleEndian, u8, parse_u8_at, 0x01u8);
235        parse_test!(BigEndian, u8, parse_u8_at, 0x01u8);
236        parse_test!(AnyEndian::Little, u8, parse_u8_at, 0x01u8);
237        parse_test!(AnyEndian::Big, u8, parse_u8_at, 0x01u8);
238    }
239
240    #[test]
241    fn parse_u16_at() {
242        parse_test!(LittleEndian, u16, parse_u16_at, 0x0201u16);
243        parse_test!(BigEndian, u16, parse_u16_at, 0x0102u16);
244        parse_test!(AnyEndian::Little, u16, parse_u16_at, 0x0201u16);
245        parse_test!(AnyEndian::Big, u16, parse_u16_at, 0x0102u16);
246    }
247
248    #[test]
249    fn parse_u32_at() {
250        parse_test!(LittleEndian, u32, parse_u32_at, 0x04030201u32);
251        parse_test!(BigEndian, u32, parse_u32_at, 0x01020304u32);
252        parse_test!(AnyEndian::Little, u32, parse_u32_at, 0x04030201u32);
253        parse_test!(AnyEndian::Big, u32, parse_u32_at, 0x01020304u32);
254    }
255
256    #[test]
257    fn parse_u64_at() {
258        parse_test!(LittleEndian, u64, parse_u64_at, 0x0807060504030201u64);
259        parse_test!(BigEndian, u64, parse_u64_at, 0x0102030405060708u64);
260        parse_test!(AnyEndian::Little, u64, parse_u64_at, 0x0807060504030201u64);
261        parse_test!(AnyEndian::Big, u64, parse_u64_at, 0x0102030405060708u64);
262    }
263
264    #[test]
265    fn parse_i32_at() {
266        parse_test!(LittleEndian, i32, parse_i32_at, 0x04030201i32);
267        parse_test!(BigEndian, i32, parse_i32_at, 0x01020304i32);
268        parse_test!(AnyEndian::Little, i32, parse_i32_at, 0x04030201i32);
269        parse_test!(AnyEndian::Big, i32, parse_i32_at, 0x01020304i32);
270    }
271
272    #[test]
273    fn parse_i64_at() {
274        parse_test!(LittleEndian, i64, parse_i64_at, 0x0807060504030201i64);
275        parse_test!(BigEndian, i64, parse_i64_at, 0x0102030405060708i64);
276        parse_test!(AnyEndian::Little, i64, parse_i64_at, 0x0807060504030201i64);
277        parse_test!(AnyEndian::Big, i64, parse_i64_at, 0x0102030405060708i64);
278    }
279
280    #[test]
281    fn fuzz_u8_too_short() {
282        fuzz_too_short_test!(LittleEndian, u8, parse_u8_at);
283        fuzz_too_short_test!(BigEndian, u8, parse_u8_at);
284        fuzz_too_short_test!(AnyEndian::Little, u8, parse_u8_at);
285        fuzz_too_short_test!(AnyEndian::Big, u8, parse_u8_at);
286    }
287
288    #[test]
289    fn fuzz_u16_too_short() {
290        fuzz_too_short_test!(LittleEndian, u16, parse_u16_at);
291        fuzz_too_short_test!(BigEndian, u16, parse_u16_at);
292        fuzz_too_short_test!(AnyEndian::Little, u16, parse_u16_at);
293        fuzz_too_short_test!(AnyEndian::Big, u16, parse_u16_at);
294    }
295
296    #[test]
297    fn fuzz_u32_too_short() {
298        fuzz_too_short_test!(LittleEndian, u32, parse_u32_at);
299        fuzz_too_short_test!(BigEndian, u32, parse_u32_at);
300        fuzz_too_short_test!(AnyEndian::Little, u32, parse_u32_at);
301        fuzz_too_short_test!(AnyEndian::Big, u32, parse_u32_at);
302    }
303
304    #[test]
305    fn fuzz_i32_too_short() {
306        fuzz_too_short_test!(LittleEndian, i32, parse_i32_at);
307        fuzz_too_short_test!(BigEndian, i32, parse_i32_at);
308        fuzz_too_short_test!(AnyEndian::Little, i32, parse_i32_at);
309        fuzz_too_short_test!(AnyEndian::Big, i32, parse_i32_at);
310    }
311
312    #[test]
313    fn fuzz_u64_too_short() {
314        fuzz_too_short_test!(LittleEndian, u64, parse_u64_at);
315        fuzz_too_short_test!(BigEndian, u64, parse_u64_at);
316        fuzz_too_short_test!(AnyEndian::Little, u64, parse_u64_at);
317        fuzz_too_short_test!(AnyEndian::Big, u64, parse_u64_at);
318    }
319
320    #[test]
321    fn fuzz_i64_too_short() {
322        fuzz_too_short_test!(LittleEndian, i64, parse_i64_at);
323        fuzz_too_short_test!(BigEndian, i64, parse_i64_at);
324        fuzz_too_short_test!(AnyEndian::Little, i64, parse_i64_at);
325        fuzz_too_short_test!(AnyEndian::Big, i64, parse_i64_at);
326    }
327}