1 // Copyright 2023, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use core::ffi::CStr;
16 use core::fmt::Write;
17 use core::str::from_utf8;
18 
19 use bootconfig::{BootConfigBuilder, BootConfigError};
20 use bootimg::{BootImage, VendorImageHeader};
21 use efi::{efi_print, efi_println, exit_boot_services, EfiEntry};
22 use fdt::Fdt;
23 use gbl_storage::AsMultiBlockDevices;
24 use misc::{AndroidBootMode, BootloaderMessage};
25 
26 use crate::error::{EfiAppError, GblEfiError, Result};
27 use crate::utils::{
28     aligned_subslice, cstr_bytes_to_str, find_gpt_devices, get_efi_fdt, usize_add, usize_roundup,
29     EfiMultiBlockDevices,
30 };
31 
32 use crate::avb::GblEfiAvbOps;
33 use avb::{slot_verify, HashtreeErrorMode, Ops, SlotVerifyFlags};
34 
35 // Linux kernel requires 2MB alignment.
36 const KERNEL_ALIGNMENT: usize = 2 * 1024 * 1024;
37 // libfdt requires FDT buffer to be 8-byte aligned.
38 const FDT_ALIGNMENT: usize = 8;
39 
40 /// A helper macro for creating a null-terminated string literal as CStr.
41 macro_rules! cstr_literal {
42     ( $( $x:expr ),* $(,)?) => {
43        CStr::from_bytes_until_nul(core::concat!($($x),*, "\0").as_bytes()).unwrap()
44     };
45 }
46 
47 /// Helper function for performing libavb verification.
avb_verify_slot<'a, 'b, 'c>( gpt_dev: &'b mut EfiMultiBlockDevices, kernel: &'b [u8], vendor_boot: &'b [u8], init_boot: &'b [u8], bootconfig_builder: &'b mut BootConfigBuilder<'c>, ) -> Result<()>48 fn avb_verify_slot<'a, 'b, 'c>(
49     gpt_dev: &'b mut EfiMultiBlockDevices,
50     kernel: &'b [u8],
51     vendor_boot: &'b [u8],
52     init_boot: &'b [u8],
53     bootconfig_builder: &'b mut BootConfigBuilder<'c>,
54 ) -> Result<()> {
55     let preloaded = [("boot", kernel), ("vendor_boot", vendor_boot), ("init_boot", init_boot)];
56     let mut avb_ops = GblEfiAvbOps::new(gpt_dev, Some(&preloaded));
57     let avb_state = match avb_ops.read_is_device_unlocked()? {
58         true => "orange",
59         _ => "green",
60     };
61 
62     let res = slot_verify(
63         &mut avb_ops,
64         &[cstr_literal!("boot"), cstr_literal!("vendor_boot"), cstr_literal!("init_boot")],
65         Some(cstr_literal!("_a")),
66         SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE,
67         // For demo, we use the same setting as Cuttlefish u-boot.
68         HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
69     )
70     .map_err(|e| Into::<GblEfiError>::into(e.without_verify_data()))?;
71 
72     // Append avb generated bootconfig.
73     for cmdline_arg in res.cmdline().to_str().unwrap().split(' ') {
74         write!(bootconfig_builder, "{}\n", cmdline_arg).map_err(|_| EfiAppError::BufferTooSmall)?;
75     }
76 
77     // Append "androidboot.verifiedbootstate="
78     write!(bootconfig_builder, "androidboot.verifiedbootstate={}\n", avb_state)
79         .map_err(|_| EfiAppError::BufferTooSmall)?;
80     Ok(())
81 }
82 
83 /// Loads Android images from disk and fixes up bootconfig, commandline, and FDT.
84 ///
85 /// A number of simplifications are made:
86 ///
87 ///   * No A/B slot switching is performed. It always boot from *_a slot.
88 ///   * No dynamic partitions.
89 ///   * Only support V3/V4 image and Android 13+ (generic ramdisk from the "init_boot" partition)
90 ///   * Only support booting recovery from boot image
91 ///
92 /// # Returns
93 ///
94 /// Returns a tuple of 4 slices corresponding to:
95 ///   (ramdisk load buffer, FDT load buffer, kernel load buffer, unused buffer).
load_android_simple<'a>( efi_entry: &EfiEntry, load: &'a mut [u8], ) -> Result<(&'a mut [u8], &'a mut [u8], &'a mut [u8], &'a mut [u8])>96 pub fn load_android_simple<'a>(
97     efi_entry: &EfiEntry,
98     load: &'a mut [u8],
99 ) -> Result<(&'a mut [u8], &'a mut [u8], &'a mut [u8], &'a mut [u8])> {
100     let mut gpt_devices = find_gpt_devices(efi_entry)?;
101 
102     const PAGE_SIZE: usize = 4096; // V3/V4 image has fixed page size 4096;
103 
104     let (bcb_buffer, load) = load.split_at_mut(BootloaderMessage::SIZE_BYTES);
105     gpt_devices.read_gpt_partition("misc", 0, bcb_buffer)?;
106     let bcb = BootloaderMessage::from_bytes_ref(bcb_buffer)?;
107     let boot_mode = bcb.boot_mode()?;
108     efi_println!(efi_entry, "boot mode from BCB: {}", boot_mode);
109 
110     // Parse boot header.
111     let (boot_header_buffer, load) = load.split_at_mut(PAGE_SIZE);
112     gpt_devices.read_gpt_partition("boot_a", 0, boot_header_buffer)?;
113     let boot_header = BootImage::parse(boot_header_buffer)?;
114     let (kernel_size, cmdline, kernel_hdr_size) = match boot_header {
115         BootImage::V3(ref hdr) => (hdr.kernel_size as usize, &hdr.cmdline[..], PAGE_SIZE),
116         BootImage::V4(ref hdr) => {
117             (hdr._base.kernel_size as usize, &hdr._base.cmdline[..], PAGE_SIZE)
118         }
119         _ => {
120             efi_println!(efi_entry, "V0/V1/V2 images are not supported");
121             return Err(GblEfiError::EfiAppError(EfiAppError::Unsupported));
122         }
123     };
124     efi_println!(efi_entry, "boot image size: {}", kernel_size);
125     efi_println!(efi_entry, "boot image cmdline: \"{}\"", from_utf8(cmdline).unwrap());
126 
127     // Parse vendor boot header.
128     let (vendor_boot_header_buffer, load) = load.split_at_mut(PAGE_SIZE);
129     gpt_devices.read_gpt_partition("vendor_boot_a", 0, vendor_boot_header_buffer)?;
130     let vendor_boot_header = VendorImageHeader::parse(vendor_boot_header_buffer)?;
131     let (vendor_ramdisk_size, vendor_hdr_size, vendor_cmdline) = match vendor_boot_header {
132         VendorImageHeader::V3(ref hdr) => (
133             hdr.vendor_ramdisk_size as usize,
134             usize_roundup(hdr.bytes().len(), hdr.page_size)?,
135             &hdr.cmdline[..],
136         ),
137         VendorImageHeader::V4(ref hdr) => (
138             hdr._base.vendor_ramdisk_size as usize,
139             usize_roundup(hdr.bytes().len(), hdr._base.page_size)?,
140             &hdr._base.cmdline[..],
141         ),
142     };
143     efi_println!(efi_entry, "vendor ramdisk size: {}", vendor_ramdisk_size);
144     efi_println!(efi_entry, "vendor cmdline: \"{}\"", from_utf8(vendor_cmdline).unwrap());
145 
146     // Parse init_boot header
147     let init_boot_header_buffer = &mut load[..PAGE_SIZE];
148     gpt_devices.read_gpt_partition("init_boot_a", 0, init_boot_header_buffer)?;
149     let init_boot_header = BootImage::parse(init_boot_header_buffer)?;
150     let (generic_ramdisk_size, init_boot_hdr_size) = match init_boot_header {
151         BootImage::V3(ref hdr) => (hdr.ramdisk_size as usize, PAGE_SIZE),
152         BootImage::V4(ref hdr) => (hdr._base.ramdisk_size as usize, PAGE_SIZE),
153         _ => {
154             efi_println!(efi_entry, "V0/V1/V2 images are not supported");
155             return Err(GblEfiError::EfiAppError(EfiAppError::Unsupported));
156         }
157     };
158     efi_println!(efi_entry, "init_boot image size: {}", generic_ramdisk_size);
159 
160     // Load and prepare various images.
161     let images_buffer = aligned_subslice(load, KERNEL_ALIGNMENT)?;
162     let load = &mut images_buffer[..];
163 
164     // Load kernel
165     // Kernel may need to reserve additional memory after itself. To avoid the risk of this
166     // memory overlapping with ramdisk. We place kernel after ramdisk. We first load it to the tail
167     // of the buffer and move it forward as much as possible after ramdisk and fdt are loaded,
168     // fixed-up and finalized.
169     let kernel_load_offset = {
170         let off = load.len().checked_sub(kernel_size).ok_or_else(|| EfiAppError::BufferTooSmall)?;
171         off.checked_sub(load[off..].as_ptr() as usize % KERNEL_ALIGNMENT)
172             .ok_or_else(|| EfiAppError::BufferTooSmall)?
173     };
174     let (load, kernel_tail_buffer) = load.split_at_mut(kernel_load_offset);
175     gpt_devices.read_gpt_partition(
176         "boot_a",
177         kernel_hdr_size.try_into().unwrap(),
178         &mut kernel_tail_buffer[..kernel_size],
179     )?;
180 
181     // Load vendor ramdisk
182     let mut ramdisk_load_curr = 0;
183     gpt_devices.read_gpt_partition(
184         "vendor_boot_a",
185         vendor_hdr_size.try_into().unwrap(),
186         &mut load[ramdisk_load_curr..][..vendor_ramdisk_size],
187     )?;
188     ramdisk_load_curr = usize_add(ramdisk_load_curr, vendor_ramdisk_size)?;
189 
190     // Load generic ramdisk
191     gpt_devices.read_gpt_partition(
192         "init_boot_a",
193         init_boot_hdr_size.try_into().unwrap(),
194         &mut load[ramdisk_load_curr..][..generic_ramdisk_size],
195     )?;
196     ramdisk_load_curr = usize_add(ramdisk_load_curr, generic_ramdisk_size)?;
197 
198     // Prepare partition data for avb verification
199     let (vendor_boot_load_buffer, remains) = load.split_at_mut(vendor_ramdisk_size);
200     let (init_boot_load_buffer, remains) = remains.split_at_mut(generic_ramdisk_size);
201     // Prepare a BootConfigBuilder to add avb generated bootconfig.
202     let mut bootconfig_builder = BootConfigBuilder::new(remains)?;
203     // Perform avb verification.
204     avb_verify_slot(
205         &mut gpt_devices,
206         kernel_tail_buffer,
207         vendor_boot_load_buffer,
208         init_boot_load_buffer,
209         &mut bootconfig_builder,
210     )?;
211 
212     // Add slot index
213     bootconfig_builder.add("androidboot.slot_suffix=_a\n")?;
214 
215     match boot_mode {
216         // TODO(b/329716686): Support bootloader mode
217         AndroidBootMode::Normal | AndroidBootMode::BootloaderBootOnce => {
218             bootconfig_builder.add("androidboot.force_normal_boot=1\n")?
219         }
220         _ => {
221             // Do nothing
222         }
223     }
224 
225     // V4 image has vendor bootconfig.
226     if let VendorImageHeader::V4(ref hdr) = vendor_boot_header {
227         let mut bootconfig_offset: usize = vendor_hdr_size;
228         for image_size in
229             [hdr._base.vendor_ramdisk_size, hdr._base.dtb_size, hdr.vendor_ramdisk_table_size]
230         {
231             bootconfig_offset =
232                 usize_add(bootconfig_offset, usize_roundup(image_size, hdr._base.page_size)?)?;
233         }
234         bootconfig_builder.add_with(|out| {
235             gpt_devices
236                 .read_gpt_partition(
237                     "vendor_boot_a",
238                     bootconfig_offset.try_into().unwrap(),
239                     &mut out[..hdr.bootconfig_size as usize],
240                 )
241                 .map_err(|_| BootConfigError::GenericReaderError(-1))?;
242             Ok(hdr.bootconfig_size as usize)
243         })?;
244     }
245     // Check if there is a device specific bootconfig partition.
246     match gpt_devices.find_partition("bootconfig").and_then(|v| v.size()) {
247         Ok(sz) => {
248             bootconfig_builder.add_with(|out| {
249                 // For proof-of-concept only, we just load as much as possible and figure out the
250                 // actual bootconfig string length after. This however, can introduce large amount
251                 // of unnecessary disk access. In real implementation, we might want to either read
252                 // page by page or find way to know the actual length first.
253                 let max_size = core::cmp::min(sz.try_into().unwrap(), out.len());
254                 gpt_devices
255                     .read_gpt_partition("bootconfig", 0, &mut out[..max_size])
256                     .map_err(|_| BootConfigError::GenericReaderError(-1))?;
257                 // Compute the actual config string size. The config is a null-terminated string.
258                 Ok(CStr::from_bytes_until_nul(&out[..])
259                     .map_err(|_| BootConfigError::GenericReaderError(-1))?
260                     .to_bytes()
261                     .len())
262             })?;
263         }
264         _ => {}
265     }
266     efi_println!(efi_entry, "final bootconfig: \"{}\"", bootconfig_builder);
267     ramdisk_load_curr = usize_add(ramdisk_load_curr, bootconfig_builder.config_bytes().len())?;
268 
269     // Prepare FDT.
270 
271     // For cuttlefish, FDT comes from EFI vendor configuration table installed by u-boot. In real
272     // product, it may come from vendor boot image.
273     let (_, fdt_bytes) = get_efi_fdt(&efi_entry).ok_or_else(|| EfiAppError::NoFdt)?;
274     let fdt_origin = Fdt::new(fdt_bytes)?;
275 
276     // Use the remaining load buffer for updating FDT.
277     let (ramdisk_load_buffer, load) = load.split_at_mut(ramdisk_load_curr);
278     let load = aligned_subslice(load, FDT_ALIGNMENT)?;
279     let mut fdt = Fdt::new_from_init(&mut load[..], fdt_bytes)?;
280 
281     // Add ramdisk range to FDT
282     let ramdisk_addr: u64 = (ramdisk_load_buffer.as_ptr() as usize).try_into().unwrap();
283     let ramdisk_end: u64 = ramdisk_addr + u64::try_from(ramdisk_load_buffer.len()).unwrap();
284     fdt.set_property(
285         "chosen",
286         CStr::from_bytes_with_nul(b"linux,initrd-start\0").unwrap(),
287         &ramdisk_addr.to_be_bytes(),
288     )?;
289     fdt.set_property(
290         "chosen",
291         CStr::from_bytes_with_nul(b"linux,initrd-end\0").unwrap(),
292         &ramdisk_end.to_be_bytes(),
293     )?;
294     efi_println!(&efi_entry, "linux,initrd-start: {:#x}", ramdisk_addr);
295     efi_println!(&efi_entry, "linux,initrd-end: {:#x}", ramdisk_end);
296 
297     // Concatenate kernel commandline and add it to FDT.
298     let bootargs_prop = CStr::from_bytes_with_nul(b"bootargs\0").unwrap();
299     let all_cmdline = [
300         cstr_bytes_to_str(fdt_origin.get_property("chosen", bootargs_prop).unwrap_or(&[0]))?,
301         " ",
302         cstr_bytes_to_str(cmdline)?,
303         " ",
304         cstr_bytes_to_str(vendor_cmdline)?,
305         "\0",
306     ];
307     let mut all_cmdline_len = 0;
308     all_cmdline.iter().for_each(|v| all_cmdline_len += v.len());
309     let cmdline_payload = fdt.set_property_placeholder("chosen", bootargs_prop, all_cmdline_len)?;
310     let mut cmdline_payload_off: usize = 0;
311     for ele in all_cmdline {
312         cmdline_payload[cmdline_payload_off..][..ele.len()].clone_from_slice(ele.as_bytes());
313         cmdline_payload_off += ele.len();
314     }
315     efi_println!(&efi_entry, "final cmdline: \"{}\"", from_utf8(cmdline_payload).unwrap());
316 
317     // Finalize FDT to actual used size.
318     fdt.shrink_to_fit()?;
319 
320     // Move the kernel backward as much as possible to preserve more space after it. This is
321     // necessary in case the input buffer is at the end of address space.
322     let kernel_tail_buffer_size = kernel_tail_buffer.len();
323     let ramdisk_load_buffer_size = ramdisk_load_buffer.len();
324     let fdt_len = fdt.header_ref()?.actual_size();
325     // Split out the ramdisk.
326     let (ramdisk, remains) = images_buffer.split_at_mut(ramdisk_load_buffer_size);
327     // Split out the fdt.
328     let (fdt, kernel) = aligned_subslice(remains, FDT_ALIGNMENT)?.split_at_mut(fdt_len);
329     // Move the kernel backward as much as possible.
330     let kernel = aligned_subslice(kernel, KERNEL_ALIGNMENT)?;
331     let kernel_start = kernel.len().checked_sub(kernel_tail_buffer_size).unwrap();
332     kernel.copy_within(kernel_start..kernel_start.checked_add(kernel_size).unwrap(), 0);
333     // Split out the remaining buffer.
334     let (kernel, remains) = kernel.split_at_mut(kernel_size);
335 
336     Ok((ramdisk, fdt, kernel, remains))
337 }
338 
339 // The following implements a demo for booting Android from disk. It can be run from
340 // Cuttlefish by adding `--android_efi_loader=<path of this EFI binary>` to the command line.
341 //
342 // A number of simplifications are made (see `android_load::load_android_simple()`):
343 //
344 //   * No A/B slot switching is performed. It always boot from *_a slot.
345 //   * No AVB is performed.
346 //   * No dynamic partitions.
347 //   * Only support V3/V4 image and Android 13+ (generic ramdisk from the "init_boot" partition)
348 //
349 // The missing pieces above are currently under development as part of the full end-to-end boot
350 // flow in libgbl, which will eventually replace this demo. The demo is currently used as an
351 // end-to-end test for libraries developed so far.
android_boot_demo(entry: EfiEntry) -> Result<()>352 pub fn android_boot_demo(entry: EfiEntry) -> Result<()> {
353     efi_println!(entry, "Try booting as Android");
354 
355     // Allocate buffer for load.
356     let mut load_buffer = vec![0u8; 128 * 1024 * 1024]; // 128MB
357 
358     let (ramdisk, fdt, kernel, remains) = load_android_simple(&entry, &mut load_buffer[..])?;
359 
360     efi_println!(&entry, "");
361     efi_println!(
362         &entry,
363         "Booting kernel @ {:#x}, ramdisk @ {:#x}, fdt @ {:#x}",
364         kernel.as_ptr() as usize,
365         ramdisk.as_ptr() as usize,
366         fdt.as_ptr() as usize
367     );
368     efi_println!(&entry, "");
369 
370     #[cfg(target_arch = "aarch64")]
371     {
372         let _ = exit_boot_services(entry, remains)?;
373         // SAFETY: We currently targets at Cuttlefish emulator where images are provided valid.
374         unsafe { boot::aarch64::jump_linux_el2_or_lower(kernel, ramdisk, fdt) };
375     }
376 
377     #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
378     {
379         let fdt = fdt::Fdt::new(&fdt[..])?;
380         let efi_mmap = exit_boot_services(entry, remains)?;
381         // SAFETY: We currently target at Cuttlefish emulator where images are provided valid.
382         unsafe {
383             boot::x86::boot_linux_bzimage(
384                 kernel,
385                 ramdisk,
386                 fdt.get_property(
387                     "chosen",
388                     core::ffi::CStr::from_bytes_with_nul(b"bootargs\0").unwrap(),
389                 )
390                 .unwrap(),
391                 |e820_entries| {
392                     // Convert EFI memory type to e820 memory type.
393                     if efi_mmap.len() > e820_entries.len() {
394                         return Err(boot::BootError::E820MemoryMapCallbackError(-1));
395                     }
396                     for (idx, mem) in efi_mmap.into_iter().enumerate() {
397                         e820_entries[idx] = boot::x86::e820entry {
398                             addr: mem.physical_start,
399                             size: mem.number_of_pages * 4096,
400                             type_: crate::utils::efi_to_e820_mem_type(mem.memory_type),
401                         };
402                     }
403                     Ok(efi_mmap.len().try_into().unwrap())
404                 },
405                 0x9_0000,
406             )?;
407         }
408         unreachable!();
409     }
410 
411     #[cfg(target_arch = "riscv64")]
412     {
413         let boot_hart_id = entry
414             .system_table()
415             .boot_services()
416             .find_first_and_open::<efi::protocol::riscv::RiscvBootProtocol>()?
417             .get_boot_hartid()?;
418         efi_println!(entry, "riscv boot_hart_id: {}", boot_hart_id);
419         let _ = exit_boot_services(entry, remains)?;
420         // SAFETY: We currently target at Cuttlefish emulator where images are provided valid.
421         unsafe { boot::riscv64::jump_linux(kernel, boot_hart_id, fdt) };
422     }
423 }
424