AbstractThis document describes the layout of data on the disk drive for a Chromium OS device and the process by which the OS is booted.Goals for the drive partitioning scheme are as follows:
Boot processChromium OS is essentially a specially-tailored GNU/Linux distribution. We want to make as few modifications to the upstream kernel as possible, ideally none. But as with any other GNU/Linux system, the pre-kernel boot process is unavoidably dependent on the hardware, BIOS, and bootloader.U-BootBoth ARM and (recent) x86 devices use U-Boot as their bootloader. On x86 we use Coreboot to set up RAM and load U-Boot. You can find an overview of the verified boot process in the U-Boot Porting Guide. U-Boot still uses the EFI partition table described below.x86 legacy BIOSLegacy boot for x86 Linux has three steps:
Virtualized systems (vmware, qemu, etc.) typically have their own legacy BIOS implementations and will use this method to boot Chromium OS images. x86 EFI BIOSThe Extensible Firmware Interface is a BIOS replacement originally developed by Intel® for its Itanium® systems and later expanded to include x86 and other architectures. While not enthusiastically embraced by the Linux kernel developers, it offers some advantages over legacy BIOS and is becoming more widely used, especially for 64-bit x86 systems.EFI BIOS boots like this:
The Chromium OS build process creates an EFI System Partition (partition 12) and installs a 64-bit version of grub2 as the bootloader (/efi/boot/bootx64.efi), along with its config file (/efi/boot/grub.cfg). 64-bit EFI BIOSes will use this bootloader. It is possible to also install a 32-bit bootloader in the same partition, but we currently do not do that. To change the boot partition, we just need to edit the grub.cfg file. Note that different EFI BIOSes may have different requirements for the pathname of the bootloader.
Most EFI BIOSes contain a "Compatibility Support Module" component which makes them act like legacy BIOSes, so they may boot either way. Google Chrome OS devicesGoogle Chrome OS devices (x86/x86_64/arm) have custom BIOSes that use yet another boot method to ensure that the user is running only the bits that are intended. Instead of a separate bootloader and kernel, there is one binary blob contained in its own GPT partition. That blob is cryptographically signed and the signature is verified before booting. Under normal conditions, the process is:
Which kernel?For any booting (x86) configuration, there are at least three separate kernels (along with their command lines) on the disk image. Legacy BIOS will use syslinux, which uses its own copy of the chosen kernel that's kept in partition 12. EFI BIOSes will use /boot/vmlinuz from the target rootfs. ChromeOS BIOS uses the signed kernel embedded in its own partition. Our build and update process is carefully crafted to try to keep all three of these kernels in sync. However, if you're fiddling with the kernel and commandline, you may find that your changes are being ignored. This is usually an indication that you're modifying the wrong one. In /proc/cmdline, you should see one of the strings "cros_legacy", "cros_efi", or "cros_secure". These identify which method the kernel used to boot (and that's all they do - we don't use them for any run-time decisions AFAIK).Drive contentsBootable Chromium OS drives (removable or not) share a common drive format. In the discussion that follows, “sector” refers to a 512-byte disk sector, addressed by its Logical Block Address (LBA). Although the UEFI specs allow for disk sectors of other sizes, in practice 512 bytes is the norm. We do not use the old Cylinder-Head-Sector addresses at all.Protective master boot recordThe master boot record is the first sector on the hard drive (LBA 0). As mentioned above, legacy BIOSes will boot from this sector.To protect the GUID partitions on the drive from legacy OSes, the MBR partition table normally contains a single partition entry of type 0xEE, filling the entire drive. GUID Partition Table (GPT)The second sector (LBA 1) contains the primary GPT header, followed immediately by 16K (32 sectors) of the primary GUID Partition Entry Array. In conformance with the EFI spec, another copy of these data should be located at the end of the disk as well, with the secondary GPT header in the last accessible sector and the secondary GUID Partition Entry Array immediately preceding it.Drive partitionsGPT allows a large number of partitions on a drive. In an attempt to reduce the effect that later partitioning changes might have on deployed systems, we are trying to enumerate the known partitions first, while leaving room for future growth. Here’s the current layout:
Note that the reserved partitions will actually be present on the image, so that the partition numbering remains constant from now on. Each minimal-size partition (including the C kernel and C rootfs) is only 512 bytes, and is shoved into some space lost to filesystem alignment (between the primary partition table and the stateful partition). 64M of empty space is set aside for use by those reserved partitions if they ever need it. Bootable USB keys have the same layout, except that kernel B and rootfs B are minimal-size, and partition 1 is limited to 720M. The total USB image size is around 1.5G. When the USB image is installed on a fixed drive, the B image is duplicated from the A image, and partition 1 is made as large as possible so that the entire disk is in use. The exact sizes and layouts are managed by a json file. See the Disk Layout Format page for more information. Partition typesEach GPT Partition Entry contains a PartitionTypeGUID to identify the purpose of the partition, a UniquePartitionGUID which is specific to an individual partition on an individual drive, a PartitionName (not the same as the filesystem's label, and apparently unused by the Linux kernel or userspace), and some Attributes bits that the Chrome OS BIOS will use to select the bootable image.There are several standard PartitionTypeGUIDs. We use two of them, and we’ve created three new ones to identify the Chrome OS kernel and rootfs partitions and to reserve partitions for future use.
Partition namesAt various times, Linux has used a number of means to refer to disk partitions. For the kernel command line, it may be by means of parameters like this:root=/dev/sda3 root=LABEL=C-ROOT root=UUID=86f0f84d-e2rd0-41e7-ad44-df4faad61e73 For userspace mount points, those may correspond to paths like this: /dev/sda3 /dev/disk/by-label/C-ROOT /dev/disk/by-uuid/86f0f84d-e2rd0-41e7-ad44-df4faad61e73 In those examples, when the kernel refers to a partition by its UUID, that UUID doesn’t come from the GPT. Each filesystem has its own UUID (and label), and that’s what the kernel looks at. Typically using the UUID notation requires starting udev in an initramfs, which takes extra time. For legacy or standard EFI BIOSes, the /dev/fooN format is used, to keep boot times to a minimum. This must be specified in the bootloader config file. The Chrome OS BIOS and bootstub passes an additional argument on the kernel command line: kern_guid=064af864-4b97-40c1-95ab-fec261760a19 This allows the kernel to identify the GPT partition from which it was loaded. The root partition is the next higher partition. Partition alignmentThe filesystem and kernel partitions are all 2MB aligned and sized. However, in the future we may move down to 1MB to be in sync with what other OSes are doing.Layout on diskThe physical layout of the partitions does not have to match their order in the partition table. In fact, there are reasons why it might be advantageous that it doesn't. For example it may be necessary to resize some partitions, which is made much easier with certain physical layouts. Refer to the Partition Resizing document for details.Here’s the current fixed-disk layout: Secure bootOnly Chrome OS BIOS will implement secure boot from first power-on. Portions of the firmware are read-only, forming the basis of trust to validate the read/write portions of the firmware. Once the firmware has been validated, we will continue the boot process by reading the kernel from the disk.Trusting the GPTIt is not possible to sign the GPT using public key encryption. The contents of the GPT (in particular, the partition-dependent attributes fields for the kernel GPT entries) will change as autoupdate applies updates and devices reboot and attempt to use newly updated partitions. Since the GPT is not signed and thus cannot be trusted, all firmware or software that accesses the GPT must pass security review.Firmware needs to sanity-check all GPT values before using them. Most forms of corrupted or damaged partition tables will just cause the firmware to read a portion of the drive that doesn't contain a valid kernel signature header, in which case the firmware initiates recovery mode. But we must also protect against malicious GPT entries that might open security holes, so if the GPT is suspicious or corrupted in ways that can’t be repaired, we can’t boot this device. Selecting the kernelThere are at least two kernel partitions, to support autoupdate and accidental corruption. Each kernel partition is paired with a rootfs partition; kernel A should only boot rootfs A, kernel B should only boot rootfs B, etc. The kernel partition is separate from the rootfs partition so that:
Chrome OS Kernel partitions use the following attribute flags:
Kernel partitions can be in the following states:
Using a Google-supplied library (in src/platform/vboot_reference/firmware), the BIOS searches the GPT to find the Chrome OS kernel with the highest Priority value and then runs the following checks on it:
After the OS finishes booting successfully, it will modify its partition table entry, ensuring that Successful Boot Flag == 1 and Tries Remaining == 0. We can edit the other attribute fields manually if we need to change the primary boot partition. Here’s the flow in graphical form: Kernel partition formatThe same library that sanity-checks the GPT and selects the kernel partition also checks the kernel’s cryptographic signature. The kernel partition consists of the following structure:The first 64K bytes are the cryptographic signature header blob, which contains the keys and signatures needed to verify the rest of the kernel blob (plus a few pointers and version numbers). The kernel blob consists of the 32-bit part of the Linux kernel, a config file (just the kernel command line string at the moment), a mostly-complete zeropage table, and our bootloader stub to complete the transition from BIOS to kernel. As it’s verified, the kernel blob is copied into RAM starting at the 32-bit kernel entry location of 0x100000 on x86 (for ARM the address varies by sub-architecture). Once the verification is complete, the bootloader stub is invoked, which finishes initializing the params table and jumps to the kernel. Quick developmentDevelopers may want to do a rapid turnaround of the kernel only. This suggested procedure may help:1. Install some image onto the hard disk. 2. Reboot. It should boot the kernel from partition 4 and mount the rootfs from partition 5, but it could also use partitions 2 and 3, respectively. 3. Check this from a console by running rootdev -s 4. Build the new kernel using emerge-x86-generic kernel or similar. You'll need the bzImage (aka vmlinuz) file to create the signed kernel partition image. It's usually left in /build/x86-generic/boot/
5. You'll also need a config.txt file, which will specify the kernel command line. You can make your own, or just reuse the one that's left in src/build/images/<board>/latest/ by the last build_image run. 6. Create and sign the kernel partition image like this (in chroot): vbutil_kernel --pack new_kern.bin \ --keyblock /usr/share/vboot/devkeys/kernel.keyblock \ --signprivate /usr/share/vboot/devkeys/kernel_data_key.vbprivk \ --version 1 \ --config config.txt \ --bootloader /lib64/bootstub/bootstub.efi \ --vmlinuz /build/x86-generic/boot/vmlinuz scp USER@SOMEWHERE:new_kern.bin /tmp sudo dd if=/tmp/new_kern.bin /dev/sda4 uname -a Changing the kernel command lineSometimes all one needs is to change the kernel command line, for instance to enable or disable the verified rootfs. This can be done as follows (moving the kernel blob between the target and host is required in case the keys are not available on the target): Move the kernel which needs modifying into a file (using the appropriate source device, <src_part> below is most likely to be sda2, sda4, or sdb2 ): sudo dd if=/dev/<src_part> of=/tmp/kernel.old Save the old kernel command line to a file: vbutil_kernel --verify /tmp/kernel.old --verbose | \ tail -1 > /tmp/cmd.line.old Modify the command line as required and save it in a file (say vbutil_kernel --repack /tmp/kernel.new \ --config /tmp/cmd.line.new \ --signprivate <private_key> \ --oldblob /tmp/kern.old For the recovery kernel on a removeable device, <private_key> above is recovery_kernel_data_key.vbprivk and for the main kernel on the hard drive, the <private_key> is kernel_data_key.vbprivk. The full path to the key file is required, of course. Then verify things look OK: vbutil_kernel --verify /tmp/kernel.new --verbose Finally get your kernel back to the device it came from: sudo dd if=/tmp/kernel.new of=/dev/<src_part> |
Chromium OS > Design Documents >