1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "missing_efi.h"
10 #define DOS_FILE_MAGIC "MZ"
11 #define PE_FILE_MAGIC "PE\0\0"
12 #define MAX_SECTIONS 96
15 # define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_IA32
16 # define TARGET_MACHINE_TYPE_COMPATIBILITY EFI_IMAGE_MACHINE_X64
17 #elif defined(__x86_64__)
18 # define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_X64
19 #elif defined(__aarch64__)
20 # define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_AARCH64
21 #elif defined(__arm__)
22 # define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_ARMTHUMB_MIXED
23 #elif defined(__riscv) && __riscv_xlen == 64
24 # define TARGET_MACHINE_TYPE EFI_IMAGE_MACHINE_RISCV64
26 # error Unknown EFI arch
29 #ifndef TARGET_MACHINE_TYPE_COMPATIBILITY
30 # define TARGET_MACHINE_TYPE_COMPATIBILITY 0
33 typedef struct DosFileHeader
{
51 uint16_t reserved2
[10];
53 } _packed_ DosFileHeader
;
55 typedef struct CoffFileHeader
{
57 uint16_t NumberOfSections
;
58 uint32_t TimeDateStamp
;
59 uint32_t PointerToSymbolTable
;
60 uint32_t NumberOfSymbols
;
61 uint16_t SizeOfOptionalHeader
;
62 uint16_t Characteristics
;
63 } _packed_ CoffFileHeader
;
65 #define OPTHDR32_MAGIC 0x10B /* PE32 OptionalHeader */
66 #define OPTHDR64_MAGIC 0x20B /* PE32+ OptionalHeader */
68 typedef struct PeOptionalHeader
{
73 uint32_t SizeOfInitializedData
;
74 uint32_t SizeOfUninitializeData
;
75 uint32_t AddressOfEntryPoint
;
82 uint64_t ImageBase64
; /* PE32+ */
84 uint32_t SectionAlignment
;
85 uint32_t FileAlignment
;
86 uint16_t MajorOperatingSystemVersion
;
87 uint16_t MinorOperatingSystemVersion
;
88 uint16_t MajorImageVersion
;
89 uint16_t MinorImageVersion
;
90 uint16_t MajorSubsystemVersion
;
91 uint16_t MinorSubsystemVersion
;
92 uint32_t Win32VersionValue
;
94 uint32_t SizeOfHeaders
;
97 uint16_t DllCharacteristics
;
98 /* fields with different sizes for 32/64 omitted */
99 } _packed_ PeOptionalHeader
;
101 typedef struct PeFileHeader
{
103 CoffFileHeader FileHeader
;
104 PeOptionalHeader OptionalHeader
;
105 } _packed_ PeFileHeader
;
107 typedef struct PeSectionHeader
{
109 uint32_t VirtualSize
;
110 uint32_t VirtualAddress
;
111 uint32_t SizeOfRawData
;
112 uint32_t PointerToRawData
;
113 uint32_t PointerToRelocations
;
114 uint32_t PointerToLinenumbers
;
115 uint16_t NumberOfRelocations
;
116 uint16_t NumberOfLinenumbers
;
117 uint32_t Characteristics
;
118 } _packed_ PeSectionHeader
;
120 static inline bool verify_dos(const DosFileHeader
*dos
) {
122 return memcmp(dos
->Magic
, DOS_FILE_MAGIC
, STRLEN(DOS_FILE_MAGIC
)) == 0;
125 static inline bool verify_pe(const PeFileHeader
*pe
, bool allow_compatibility
) {
127 return memcmp(pe
->Magic
, PE_FILE_MAGIC
, STRLEN(PE_FILE_MAGIC
)) == 0 &&
128 (pe
->FileHeader
.Machine
== TARGET_MACHINE_TYPE
||
129 (allow_compatibility
&& pe
->FileHeader
.Machine
== TARGET_MACHINE_TYPE_COMPATIBILITY
)) &&
130 pe
->FileHeader
.NumberOfSections
> 0 &&
131 pe
->FileHeader
.NumberOfSections
<= MAX_SECTIONS
&&
132 IN_SET(pe
->OptionalHeader
.Magic
, OPTHDR32_MAGIC
, OPTHDR64_MAGIC
);
135 static inline UINTN
section_table_offset(const DosFileHeader
*dos
, const PeFileHeader
*pe
) {
138 return dos
->ExeHeader
+ offsetof(PeFileHeader
, OptionalHeader
) + pe
->FileHeader
.SizeOfOptionalHeader
;
141 static void locate_sections(
142 const PeSectionHeader section_table
[],
144 const char * const sections
[],
149 assert(section_table
);
154 size_t prev_section_addr
= 0;
156 for (UINTN i
= 0; i
< n_table
; i
++) {
157 const PeSectionHeader
*sect
= section_table
+ i
;
160 if (prev_section_addr
> sect
->VirtualAddress
)
161 log_error_stall(u
"Overlapping PE sections detected. Boot may fail due to image memory corruption!");
162 prev_section_addr
= sect
->VirtualAddress
+ sect
->VirtualSize
;
165 for (UINTN j
= 0; sections
[j
]; j
++) {
166 if (memcmp(sect
->Name
, sections
[j
], strlen8(sections
[j
])) != 0)
169 offsets
[j
] = in_memory
? sect
->VirtualAddress
: sect
->PointerToRawData
;
170 sizes
[j
] = sect
->VirtualSize
;
175 static uint32_t get_compatibility_entry_address(const DosFileHeader
*dos
, const PeFileHeader
*pe
) {
176 UINTN addr
= 0, size
= 0;
177 static const char *sections
[] = { ".compat", NULL
};
179 /* The kernel may provide alternative PE entry points for different PE architectures. This allows
180 * booting a 64bit kernel on 32bit EFI that is otherwise running on a 64bit CPU. The locations of any
181 * such compat entry points are located in a special PE section. */
183 locate_sections((const PeSectionHeader
*) ((const uint8_t *) dos
+ section_table_offset(dos
, pe
)),
184 pe
->FileHeader
.NumberOfSections
,
196 uint16_t machine_type
;
197 uint32_t entry_point
;
198 } _packed_ LinuxPeCompat1
;
200 while (size
>= sizeof(LinuxPeCompat1
) && addr
% __alignof__(LinuxPeCompat1
) == 0) {
201 LinuxPeCompat1
*compat
= (LinuxPeCompat1
*) ((uint8_t *) dos
+ addr
);
203 if (compat
->type
== 0 || compat
->size
== 0 || compat
->size
> size
)
206 if (compat
->type
== 1 &&
207 compat
->size
>= sizeof(LinuxPeCompat1
) &&
208 compat
->machine_type
== TARGET_MACHINE_TYPE
)
209 return compat
->entry_point
;
211 addr
+= compat
->size
;
212 size
-= compat
->size
;
218 EFI_STATUS
pe_kernel_info(
220 uint32_t *ret_entry_point_address
,
221 uint32_t *ret_size_of_image
,
222 uint32_t *ret_section_alignment
) {
224 const DosFileHeader
*dos
;
225 const PeFileHeader
*pe
;
228 assert(ret_entry_point_address
);
230 dos
= (const DosFileHeader
*) base
;
231 if (!verify_dos(dos
))
232 return EFI_LOAD_ERROR
;
234 pe
= (const PeFileHeader
*) ((const uint8_t *) base
+ dos
->ExeHeader
);
235 if (!verify_pe(pe
, /* allow_compatibility= */ true))
236 return EFI_LOAD_ERROR
;
238 /* Support for LINUX_INITRD_MEDIA_GUID was added in kernel stub 1.0. */
239 if (pe
->OptionalHeader
.MajorImageVersion
< 1)
240 return EFI_UNSUPPORTED
;
242 uint32_t entry_address
= pe
->OptionalHeader
.AddressOfEntryPoint
;
244 /* Look for a compat entry point. */
245 if (pe
->FileHeader
.Machine
!= TARGET_MACHINE_TYPE
) {
246 entry_address
= get_compatibility_entry_address(dos
, pe
);
247 if (entry_address
== 0)
248 /* Image type not supported and no compat entry found. */
249 return EFI_UNSUPPORTED
;
252 *ret_entry_point_address
= entry_address
;
253 if (ret_size_of_image
)
254 *ret_size_of_image
= pe
->OptionalHeader
.SizeOfImage
;
255 if (ret_section_alignment
)
256 *ret_section_alignment
= pe
->OptionalHeader
.SectionAlignment
;
260 EFI_STATUS
pe_memory_locate_sections(const void *base
, const char * const sections
[], UINTN
*addrs
, UINTN
*sizes
) {
261 const DosFileHeader
*dos
;
262 const PeFileHeader
*pe
;
270 dos
= (const DosFileHeader
*) base
;
271 if (!verify_dos(dos
))
272 return EFI_LOAD_ERROR
;
274 pe
= (const PeFileHeader
*) ((uint8_t *) base
+ dos
->ExeHeader
);
275 if (!verify_pe(pe
, /* allow_compatibility= */ false))
276 return EFI_LOAD_ERROR
;
278 offset
= section_table_offset(dos
, pe
);
279 locate_sections((PeSectionHeader
*) ((uint8_t *) base
+ offset
),
280 pe
->FileHeader
.NumberOfSections
,
289 EFI_STATUS
pe_file_locate_sections(
291 const char16_t
*path
,
292 const char * const sections
[],
295 _cleanup_free_ PeSectionHeader
*section_table
= NULL
;
296 _cleanup_(file_closep
) EFI_FILE
*handle
= NULL
;
299 UINTN len
, section_table_len
;
308 err
= dir
->Open(dir
, &handle
, (char16_t
*) path
, EFI_FILE_MODE_READ
, 0ULL);
309 if (err
!= EFI_SUCCESS
)
313 err
= handle
->Read(handle
, &len
, &dos
);
314 if (err
!= EFI_SUCCESS
)
316 if (len
!= sizeof(dos
) || !verify_dos(&dos
))
317 return EFI_LOAD_ERROR
;
319 err
= handle
->SetPosition(handle
, dos
.ExeHeader
);
320 if (err
!= EFI_SUCCESS
)
324 err
= handle
->Read(handle
, &len
, &pe
);
325 if (err
!= EFI_SUCCESS
)
327 if (len
!= sizeof(pe
) || !verify_pe(&pe
, /* allow_compatibility= */ false))
328 return EFI_LOAD_ERROR
;
330 section_table_len
= pe
.FileHeader
.NumberOfSections
* sizeof(PeSectionHeader
);
331 section_table
= xmalloc(section_table_len
);
333 return EFI_OUT_OF_RESOURCES
;
335 err
= handle
->SetPosition(handle
, section_table_offset(&dos
, &pe
));
336 if (err
!= EFI_SUCCESS
)
339 len
= section_table_len
;
340 err
= handle
->Read(handle
, &len
, section_table
);
341 if (err
!= EFI_SUCCESS
)
343 if (len
!= section_table_len
)
344 return EFI_LOAD_ERROR
;
346 locate_sections(section_table
, pe
.FileHeader
.NumberOfSections
,
347 sections
, offsets
, sizes
, /*in_memory=*/false);