55//! Specification Type 2 requirements for UKI boot entries, including extraction of boot
66//! labels from os-release information embedded in the UKI binary.
77
8+ use std:: io:: { Read , Seek , SeekFrom } ;
89use thiserror:: Error ;
910use zerocopy:: {
1011 FromBytes , Immutable , KnownLayout ,
@@ -62,17 +63,20 @@ struct SectionHeader {
6263}
6364
6465/// Errors that can occur when parsing UKI files.
65- #[ derive( Debug , Error , PartialEq ) ]
66+ #[ derive( Debug , Error ) ]
6667pub enum UkiError {
68+ /// IO Error while reading or seeking
69+ #[ error( "IO Error" ) ]
70+ Io ( #[ from] std:: io:: Error ) ,
6771 /// The file is not a valid Portable Executable (PE/EFI) format
6872 #[ error( "UKI is not valid EFI executable" ) ]
6973 PortableExecutableError ,
7074 /// A required PE section is missing from the UKI
7175 #[ error( "UKI doesn't contain a '{0}' section" ) ]
72- MissingSection ( & ' static str ) ,
76+ MissingSection ( String ) ,
7377 /// A PE section contains invalid UTF-8
7478 #[ error( "UKI section '{0}' is not UTF-8" ) ]
75- UnicodeError ( & ' static str ) ,
79+ UnicodeError ( String ) ,
7680 /// The .osrel section lacks name information
7781 #[ error( "No name information found in .osrel section" ) ]
7882 NoName ,
@@ -97,7 +101,19 @@ pub fn get_text_section<'a>(
97101 section_name : & ' static str ,
98102) -> Result < & ' a str , UkiError > {
99103 let bytes = get_section ( image, section_name) . ok_or ( UkiError :: PortableExecutableError ) ??;
100- std:: str:: from_utf8 ( bytes) . or ( Err ( UkiError :: UnicodeError ( section_name) ) )
104+ std:: str:: from_utf8 ( bytes) . or ( Err ( UkiError :: UnicodeError ( section_name. into ( ) ) ) )
105+ }
106+
107+ /// Buffered version of [`get_text_section`].
108+ ///
109+ /// See [`get_text_section`] for details. This version works with any [`Read`] + [`Seek`]
110+ /// source instead of requiring the entire image in memory.
111+ pub fn get_text_section_buffered < ' a , R : Read + Seek > (
112+ image : & ' a mut R ,
113+ section_name : & ' a str ,
114+ ) -> Result < String , UkiError > {
115+ let bytes = get_section_buffered ( image, section_name) ?;
116+ String :: from_utf8 ( bytes) . or ( Err ( UkiError :: UnicodeError ( section_name. into ( ) ) ) )
101117}
102118
103119/// Extracts a raw section from a UKI PE file by name.
@@ -158,7 +174,64 @@ pub fn get_section<'a>(
158174 }
159175 }
160176
161- Some ( Err ( UkiError :: MissingSection ( section_name) ) )
177+ Some ( Err ( UkiError :: MissingSection ( section_name. into ( ) ) ) )
178+ }
179+
180+ /// Buffered version of [`get_section`].
181+ ///
182+ /// See [`get_section`] for details. This version works with any [`Read`] + [`Seek`]
183+ /// source and returns owned data instead of borrowed slices.
184+ pub fn get_section_buffered < R : Read + Seek > (
185+ image : & mut R ,
186+ section_name : & str ,
187+ ) -> Result < Vec < u8 > , UkiError > {
188+ use std:: io:: Error as IOError ;
189+
190+ // Turn the section_name ".osrel" into a section_key b".osrel\0\0".
191+ // This will panic if section_name.len() > 8, which is what we want.
192+ let mut section_key = [ 0u8 ; 8 ] ;
193+ section_key[ ..section_name. len ( ) ] . copy_from_slice ( section_name. as_bytes ( ) ) ;
194+
195+ // Skip the DOS stub
196+ let mut buf: Vec < u8 > = vec ! [ 0 ; std:: mem:: size_of:: <DosStub >( ) ] ;
197+ image. read_exact ( & mut buf) ?;
198+ let dos_stub =
199+ DosStub :: ref_from_bytes ( & buf) . map_err ( |e| UkiError :: Io ( IOError :: other ( e. to_string ( ) ) ) ) ?;
200+ image. seek ( SeekFrom :: Start ( dos_stub. pe_offset . get ( ) as u64 ) ) ?;
201+
202+ // Get the PE header
203+ let mut buf: Vec < u8 > = vec ! [ 0 ; std:: mem:: size_of:: <PeHeader >( ) ] ;
204+ image. read_exact ( & mut buf) ?;
205+ let pe_header =
206+ PeHeader :: ref_from_bytes ( & buf) . map_err ( |e| UkiError :: Io ( IOError :: other ( e. to_string ( ) ) ) ) ?;
207+ if pe_header. pe_magic != PE_MAGIC {
208+ return Err ( UkiError :: PortableExecutableError ) ;
209+ }
210+
211+ // Skip the optional header
212+ image. seek ( SeekFrom :: Current (
213+ pe_header. coff_file_header . size_of_optional_header . get ( ) as i64 ,
214+ ) ) ?;
215+
216+ // Try to load the section headers
217+ let n_sections = pe_header. coff_file_header . number_of_sections . get ( ) as usize ;
218+ let mut sections = vec ! [ 0 ; std:: mem:: size_of:: <SectionHeader >( ) * n_sections] ;
219+ image. read_exact ( & mut sections) ?;
220+ let sections = <[ SectionHeader ] >:: ref_from_bytes_with_elems ( & sections, n_sections)
221+ . map_err ( |e| UkiError :: Io ( IOError :: other ( e. to_string ( ) ) ) ) ?;
222+
223+ for section in sections {
224+ if section. name != section_key {
225+ continue ;
226+ }
227+
228+ let mut buffer = vec ! [ 0 ; section. virtual_size. get( ) as usize ] ;
229+ image. seek ( SeekFrom :: Start ( section. pointer_to_raw_data . get ( ) as u64 ) ) ?;
230+ image. read_exact ( & mut buffer) ?;
231+ return Ok ( buffer) ;
232+ }
233+
234+ Err ( UkiError :: MissingSection ( section_name. to_string ( ) ) )
162235}
163236
164237/// Gets an appropriate label for display in the boot menu for the given UKI image, according to
@@ -189,11 +262,26 @@ pub fn get_boot_label(image: &[u8]) -> Result<String, UkiError> {
189262 . ok_or ( UkiError :: NoName )
190263}
191264
265+ /// Buffered version of [`get_boot_label`].
266+ ///
267+ /// See [`get_boot_label`] for details. This version works with any [`Read`] + [`Seek`] source.
268+ pub fn get_boot_label_buffered < R : Read + Seek > ( image : & mut R ) -> Result < String , UkiError > {
269+ let osrel = get_text_section_buffered ( image, ".osrel" ) ?;
270+ OsReleaseInfo :: parse ( & osrel)
271+ . get_boot_label ( )
272+ . ok_or ( UkiError :: NoName )
273+ }
274+
192275/// Gets the contents of the .cmdline section of a UKI.
193276pub fn get_cmdline ( image : & [ u8 ] ) -> Result < & str , UkiError > {
194277 get_text_section ( image, ".cmdline" )
195278}
196279
280+ /// Buffered version of [`get_cmdline`]. See [`get_cmdline`] for details.
281+ pub fn get_cmdline_buffered < R : Read + Seek > ( image : & mut R ) -> Result < String , UkiError > {
282+ get_text_section_buffered ( image, ".cmdline" )
283+ }
284+
197285#[ cfg( test) ]
198286mod test {
199287 use core:: mem:: size_of;
@@ -273,10 +361,16 @@ ID=pretty-os
273361 #[ test]
274362 fn test_bad_pe ( ) {
275363 fn pe_err ( img : & [ u8 ] ) {
276- assert_eq ! ( get_boot_label( img) , Err ( UkiError :: PortableExecutableError ) ) ;
364+ assert ! ( matches!(
365+ get_boot_label( img) ,
366+ Err ( UkiError :: PortableExecutableError )
367+ ) ) ;
277368 }
278369 fn no_sec ( img : & [ u8 ] ) {
279- assert_eq ! ( get_boot_label( img) , Err ( UkiError :: MissingSection ( ".osrel" ) ) ) ;
370+ assert ! ( matches!(
371+ get_boot_label( img) ,
372+ Err ( UkiError :: MissingSection ( s) ) if s == ".osrel"
373+ ) ) ;
280374 }
281375
282376 pe_err ( b"" ) ;
0 commit comments