--
--
Chromium OS Developers mailing list: chromiu...@chromium.org
View archives, change email options, or unsubscribe:
https://groups.google.com/a/chromium.org/group/chromium-os-dev
--
--
Chromium OS Developers mailing list: chromiu...@chromium.org
View archives, change email options, or unsubscribe:
https://groups.google.com/a/chromium.org/group/chromium-os-dev
---
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-os-d...@chromium.org.
I'm using this:
Acer CB3-131
That's not in the list you provided, but I did find there this close cousin:
Acer CB3-531
This email provides the following sections below:
System Info
This identifies the ChromeOS system I have; it’s mainly useful to help users find this email.
Using RW_LEGACY
This is a kind of tutorial that shows others how to fix and use the RW_LEGACY region by means of their own knowledge and tools.
Firmware Code Paths
This documents the relevant code in the firmware of my computer; it is based on a reading of the source code.
I hope it’s useful to someone else.
Sincerely,
Michael Witten
Grant (“ggg”) wrote:
I’m using this:
Acer CB3-131
That’s not in the list you provided, but I did find there this close cousin:
Acer CB3-531
They are both Baytrail SoC - so likely running the same FW too.
To identify which board this is, run any one of these in developer mode:
- dmesg | fgrep DMI:
- dmidecode -s baseboard-product-name
- crossystem hwid" (or fwid)
Here’s my output:
$ | dmesg | fgrep DMI: | |
[ 0.000000] DMI: GOOGLE Gnawty, BIOS Google_Gnawty.5216.239.156 12/03/2017 | ||
$ | sudo /usr/sbin/dmidecode -s baseboard-product-name | |
$ | echo $? | |
0 | ||
$ | sudo /usr/sbin/dmidecode | sed -ne '/^BIOS/,/Family:/p; /Family:/q' | |
BIOS Information Vendor: coreboot Version: Google_Gnawty.5216.239.156 Release Date: 12/03/2017 ROM Size: 8192 kB Characteristics: PCI is supported PC Card (PCMCIA) is supported BIOS is upgradeable Selectable boot is supported ACPI is supported Targeted content distribution is supported BIOS Revision: 4.0 Firmware Revision: 0.0 Handle 0x0001, DMI type 1, 27 bytes System Information Manufacturer: GOOGLE Product Name: Gnawty Version: 1.0 Serial Number: 123456789 UUID: Not Settable Wake-up Type: Reserved SKU Number: Not Specified Family: Not Specified |
This is the important info:
Version | Google_Gnawty.5216.239.156 |
---|---|
Release Date | 12/03/2017 |
I will add the following interesting bit of information:
$ | crossystem ro_fwid; echo |
Google_Gnawty.5216.239.34 |
If that last number (34) is the “build” number, then it can be seen that there have been quite a few iterations between these 2 times:
The time my computer system was manufactured.
The time the modifiable firmware was last updated (to build number 156).
dragon788 wrote:
Some devices have dummy data there, if a functional firmware has been created for RW_LEGACY for your device you could try the MrChromebox.tech script to install it.
The flash storage has a region named “RW_LEGACY”, which is useful for running one’s own software during the early process of booting a ChromeOS system that is running in Developer Mode.
Unfortunately, in my system (and apparently many similar systems of its generation), the RW_LEGACY region was not prepared for easy use; in fact, all of its bits were set to the value 1.
This section explains the steps you can take to fix it:
0. | Put ChromeOS in Developer Mode |
The instructions are given here. In particular:
Enter Recovery Mode by pressing the following on the keyboard:
[esc][⟳][⏻] | ([esc][refresh][power]) |
Next, enter Developer Mode by pressing the following on the keyboard:
[ctrl][d]
[enter]
1. | Open a shell command-line prompt |
The instructions are given here or here. In particular:
Log into your account.
Press the following on the keyboard:
[ctrl][alt][t]
Type “shell” at the prompt, and then press:
[enter]
2. | Create an old-style CBFS in RW_LEGACY |
At a shell command-line prompt, run the following commands (the “$ ” at the beginning of a line indicates the prompt).
Create a directory to work in:
$ | mkdir /tmp/flash && cd /tmp/flash |
Grab a copy of what’s stored in the flash storage:
$ | sudo /usr/sbin/flashrom -r ./00-flashrom |
flashrom 0.9.9 : a5cb316d : Nov 13 2020 18:00:28 UTC on Linux 4.4.237-19358-g17705661373a (x86_64) flashrom is free software, get the source code at https://flashrom.org Using default programmer "internal" with arguments "". Calibrating delay loop... OK. coreboot table found at 0x7ae6e000. Found chipset "Intel Bay Trail". Enabling flash write... Warning: Setting BIOS Control at 0x0 from 0x0b to 0x09 failed. New value is 0x0b. SPI Configuration is locked down. FREG0: Flash Descriptor region (0x00000000-0x00000fff) is read-only. FREG2: Management Engine region (0x00001000-0x001fffff) is locked. Not all flash regions are freely accessible by flashrom. This is most likely due to an active ME. Please see https://flashrom.org/ME for details. At least some flash regions are read protected. You have to use a flash layout and include only accessible regions. For write operations, you'll additionally need the --noverify-all switch. See manpage for more details. OK. Found Winbond flash chip "W25Q64.W" (8192 kB, SPI) mapped at physical address 0x00000000ff800000. Block protection could not be disabled! Reading flash... SUCCESS |
Change the ownership of the resulting file:
$ | sudo chown chronos:chronos ./00-flashrom |
Get the size of the RW_LEGACY region:
$ | cbfstool ./00-flashrom layout | grep RW_LEGACY |
'RW_LEGACY' (CBFS, size 2097152, offset 4194304) |
Create a fresh old-style CBFS of the same size:
$ | cbfstool ./01-empty-cbfs create -m x86 -s 2097152 |
Created CBFS (capacity = 2097048 bytes) |
Overwrite the RW_LEGACY region with this empty CBFS:
$ | cbfstool ./00-flashrom write -F -r RW_LEGACY -f ./01-empty-cbfs |
Ensure that RW_LEGACY now contains an empty CBFS:
$ | cbfstool ./00-flashrom print -r RW_LEGACY | |||||||||||||||
|
Now that RW_LEGACY contains an old-style CBFS, it can be manipulated as intended by all the usual commands.
3. | Get a program of your choice |
You can place into RW_LEGACY’s CBFS any program you want to run when booting up your computer. For example, here is a simple program composed of three x86 instructions:
$ | printf '\xfa\xf4\xeb\xfd' > ./02-program |
That little 4-byte program does the following:
• | It turns off interrupts. | (0xfa | : | cli | ) |
• | It halts the logical processor. | (0xf4 | : | hlt | ) |
• | It jumps back to hlt if the processing is triggered again by some interrupt signal that cannot be ignored. | (0xeb 0xfd | : | jmp -3 | ) |
If you have a disassembler handy, you can view these facts for yourself; for instance, you can use the objdump tool from the binutils project:
$ | objdump --disassemble-all --target=binary --architecture=i386 ./02-program | sed -ne '/0:/,$p' |
0: fa cli 1: f4 hlt 2: eb fd jmp 0x1 |
The disassembler has chosen to interpret “eb fd” as:
jump to address 1
However, that’s not strictly correct; they actually mean:
jump back 3 bytes
Anyway, when this program is run, it should basically ensure that your computer does absolutely nothing interesting; it should just sit there quietly.
Therefore, it might be a good idea to find a more useful program somewhere else; a common choice is SeaBIOS, an open-source implementation of the BIOS software that was traditionally used to boot a PC.
4. | Add the program to the CBFS as the “payload” |
A component in the CBFS is essentially a file in a file system.
When this computer turns on, it runs a piece of software known as “the firmware”, which is stored in a flash storage chip. In this computer, this particular version of the firmware can be told to run some software from the RW_LEGACY region of the flash storage; the firmware looks for a component named “payload" in the CBFS that is stored in the RW_LEGACY region.
This “payload” component cannot be just the raw instructions of the program; the instructions must be encoded in a format known as the “Simple Executable Loader Format” (“Simple ELF” or “SELF”), which is a variation of “ELF” (the “Executable and Linkable Format”), which is a common format for executables on Linux systems.
The SELF encoding provides the firmware with 2 very important pieces of information:
Load | Address | The location in memory where the program should be placed. |
---|---|---|
Entry | Address | The location in memory where the initial instruction of the program can be found, after the whole program is placed at the Load Address. |
At the time the program is loaded, the CPU has been put in 32-bit Protected Mode, and thus the program can be placed anywhere in memory. For example, it can be loaded 256 MiB into memory, at the following address:
Load Address | = | 256 * 1024 * 1024 |
= | 268435456 | |
= | 0x10000000 |
If the program is simple enough, then the initial instruction of the program can be located at that very same address:
Entry Address | = | Load Address |
= | 0x10000000 |
Fortunately, all of these details can be handled by one command:
$ | cbfstool ./00-flashrom add-flat-binary -r RW_LEGACY -f ./02-program -n payload -l 0x10000000 -e 0x10000000 |
W: Compression failed or would make the data bigger - disabled. |
As you can see, cbfstool tries to compress the program, but decides that this is pointless; the warning is of no consequence.
Confirm that the program is in its proper place:
$ | cbfstool ./00-flashrom print -r RW_LEGACY | ||||||||||||||||||||
|
The “payload” component is located at the very top of the CBFS (at offset 0); the component is a “simple elf”; it’s size is “60” bytes; and its compression is “none”, meaning that it has not been compressed, just as cbfstool warned us.
Now, a little trick:
(Skip this unless you are feeling playful)
The SELF payload can be extracted:
$ | cbfstool ./00-flashrom extract -r RW_LEGACY -n payload -m x86 -f ./03-payload |
Found file payload at 0x0, type simple elf, compressed 60, size 60 |
Notice, though, that the extracted file is quite a bit larger than 60 bytes:
$ | du -h ./03-payload |
12K ./03-payload |
The reason for this is that cbfstool packages the payload as a full ELF program, meaning that the extracted program is in fact a program that Linux would be happy to run.
That is one reason why the Load Address was deliberately chosen to have these properties:
It is aligned to 4096 bytes (the memory page size that Linux likes).
It is relatively high up there in the address space, so Linux won’t complain about permissions.
In order to run this program, it must be marked as executable:
$ | chmod u+x ./03-payload |
Also, by default, ChromeOS does not allow executing programs that are located in the /tmp directory; for security, the file system located there has been mounted without the necessary permission. Thus, the /tmp mount point must be remounted with the proper “exec” option:
$ | sudo mount -o remount,exec /tmp |
Now, run the program:
$ ./03-payload | |
Segmentation fault (core dumped) |
Woops! The kernel will have reported the problem:
$ | dmesg | grep 03-payload |
[ 7906.089053] traps: 03-payload[4678] general protection ip:10000000 sp:ff915c30 error:0 in 03-payload[10000000+1000] |
The CPU raised the “general protection” exception when it was told to run the “cli” instruction; that’s because the privilege level is not sufficient to run the “cli” instruction. Even if the CPU could have gotten past the “cli” instruction, the “hlt” instruction requires the highest hardware-level privilege; this program cannot be run in “user space”.
However, you could try all this again with an alternative program that does not require these privileged instructions. For example, consider this program instead:
$ | printf '\x04\x01\xeb\xfc' > ./04-program |
That program simply keeps adding 1 to the “AL” register:
$ | objdump --disassemble-all --target=binary --architecture=i386 ./04-program | sed -ne '/0:/,$p' |
0: 04 01 add $0x1,%al 2: eb fc jmp 0x0 |
Remove the existing payload:
$ | cbfstool ./00-flashrom remove -r RW_LEGACY -n payload |
Add the new program:
$ | cbfstool ./00-flashrom add-flat-binary -r RW_LEGACY -f ./04-program -n payload -l 0x10000000 -e 0x10000000 |
W: Compression failed or would make the data bigger - disabled. |
Extract the payload:
$ | cbfstool ./00-flashrom extract -r RW_LEGACY -n payload -m x86 -f ./05-payload |
Found file payload at 0x0, type simple elf, compressed 60, size 60 |
Marked it as executable:
$ | chmod u+x ./05-payload |
Run it:
$ ./05-payload |
Your CPU will be pegged to 100% as it furiously increments the AL register; kill the program by typing:
[ctrl][c]
You can run it in the background, and then use the top command to see the vigor with which that program performs arithmetic:
$ ./05-payload & | |
[1] 5204 | |
$ top |
You should see some lines like this in top:
PID | USER | PR | NI | VIRT | RES | SHR | S | %CPU | %MEM | TIME+ | COMMAND |
---|---|---|---|---|---|---|---|---|---|---|---|
5182 | chronos | 20 | 0 | 152 | 4 | 0 | R | 77.5 | 0.0 | 0:10.09 | 05-payload |
Press [q] or [ctrl][c] to quit top, and then for the sake of your computer, please kill the payload program:
$ pkill 05-payload | |
[1]+ Terminated ./05-payload |
At this point, you’d better put the more respectable program back in place:
$ | cbfstool ./00-flashrom remove -r RW_LEGACY -n payload |
$ | cbfstool ./00-flashrom add-flat-binary -r RW_LEGACY -f ./02-program -n payload -l 0x10000000 -e 0x10000000 |
W: Compression failed or would make the data bigger - disabled. |
5. | Write the data to the flash storage |
Currently, you’ve been manipulating only your copy of the data that are in the flash storage; you need to write your copy of the data to the flash storage itself. Specifically, you need to overwrite the RW_LEGACY region:
$ | sudo /usr/sbin/flashrom -w ./00-flashrom -i RW_LEGACY |
flashrom 0.9.9 : a5cb316d : Nov 13 2020 18:00:28 UTC on Linux 4.4.237-19358-g17705661373a (x86_64) flashrom is free software, get the source code at https://flashrom.org Using default programmer "internal" with arguments "". Calibrating delay loop... OK. coreboot table found at 0x7ae6e000. Found chipset "Intel Bay Trail". Enabling flash write... Warning: Setting BIOS Control at 0x0 from 0x0b to 0x09 failed. New value is 0x0b. SPI Configuration is locked down. FREG0: Flash Descriptor region (0x00000000-0x00000fff) is read-only. FREG2: Management Engine region (0x00001000-0x001fffff) is locked. Not all flash regions are freely accessible by flashrom. This is most likely due to an active ME. Please see https://flashrom.org/ME for details. At least some flash regions are read protected. You have to use a flash layout and include only accessible regions. For write operations, you'll additionally need the --noverify-all switch. See manpage for more details. OK. Found Winbond flash chip "W25Q64.W" (8192 kB, SPI) mapped at physical address 0x00000000ff800000. Block protection could not be disabled! |
spi_write_cmd failed during command execution at address 0x0 spi_simple_write_cmd failed during command execution spi_simple_write_cmd failed during command execution Erasing and writing flash chip... Verifying flash... VERIFIED. SUCCESS |
6. | Permit the firmware to run that payload |
The firmware must be granted permission to load and run the program from the RW_LEGACY region of the flash storage; permission is granted by setting the following flag:
dev_boot_legacy
If that flag is set to “0”, then the firmware has not yet been granted permission:
$ | sudo crossystem dev_boot_legacy; echo |
0 |
In that case, set it to “1”:
$ | sudo crossystem dev_boot_legacy=1 |
Check that the flag is properly set:
$ | sudo crossystem dev_boot_legacy; echo |
1 |
7. | Reboot the computer |
Turn it off. Turn it back on.
8. | Press [ctrl][l] (L) to run the program |
When in Developer Mode, the firmware will boot to a warning screen; you can press the following keys on the keyboard to load and run the program from the RW_LEGACY region:
[ctrl][l] (L)
If there is a problem loading the program, the firmware will beep twice at you. Otherwise, the program will be run.
In the case of the little program installed above, your computer should essentially just freeze; there will be no beeps, and most other input (e.g., from the keyboard) will have no effect.
Press the power button on the keyboard to turn the machine off, and then sit back in your chair, look out the window, and wonder to yourself “What am I doing with my life? Seriously. What am I doing?”
9. | ??? |
A great time to think about the possibilities is while showering. Make sure you keep the water hot, and use high-quality, sensual soaps.
10. | Profit! |
Please share some of your gains with a worthy computing project.
I found these primary documents useful in getting a quick overview of how the firmware operates:
(2014) Chrome OS Overview by Duncan Laurie. Firmware Summit.
(2017) Chrome OS Firmware by Duncan Laurie.
These source code repositories provided details:
In short:
At power-on, the “RO” firmware runs; it loads, verifies, and runs some “RW” firmware.
The RW firmware initializes the hardware and then runs a “payload” called “Depthcharge”.
In Developer Mode, Depthcharge waits for the user to tell it what to do; if things were configured to allow the user to do so, the user may press [ctrl][l] (L) on the keyboard to run the program stored in the “RW_LEGACY” region (or area) of the flash storage.
In more detail:
At power-on, a ChromeOS “device” (by which Google means “computer”) runs the read-only (RO) firmware; this RO firmware is basically meant to be never altered for the life of the computer (though it can be overwritten if the computer is mechanically manipulated to allow doing so, thereby voiding any warranty).
The RO firmware verifies that one of the 2 read-write (RW; updatable) firmware images is cryptographically signed by Google; if so, the RO firmware runs the chosen RW firmware.
The RW firmware image constitutes a coreboot “file system” (CBFS); the coreboot software initializes the hardware, and then loads a payload from the CBFS and runs it.
The payload in question is Google’s “Depthcharge” software, which is designed to load, possibly verify, and then run one of 2 Linux kernels that are stored in the computer’s non-volatile storage. Under normal circumstances, Depthcharge verifies that the kernel is cryptographically signed by Google and then runs it; however, when a ChromeOS system is configured to run in “Developer Mode”, Depthcharge pauses the booting process to seek interaction from the user of the computer.
In Developer Mode, Depthcharge displays some information on the screen and then waits for user input to decide what to load and run; or, after a timeout, a kernel is loaded as usual.
If the user presses [ctrl][d] on the keyboard, Depthcharge loads and runs the kernel as usual, though verification of the kernel may not be performed if the following flag is set to 0:
dev_boot_signed_only
If there is no user input within a certain amount of time, then Depthcharge behaves as though the user pressed [ctrl][d] on the keyboard.
If the user presses [ctrl][l] (L) on the keyboard, and the flag to allow “legacy” booting is set (i.e., the flag dev_boot_legacy must be non-zero), then Depthcharge loads a program from the flash storage, and runs it.
The flash storage has its data laid out in regions (or areas) described by an “FMAP” data structure.
The program in question is stored in the “RW_LEGACY” region of the flash storage: The data in the RW_LEGACY region constitute a CBFS data structure; at least one “component” of the CBFS data structure constitutes the program. In the case of my computer’s official firmware, the program must be located in the component named “payload”.
The program is encoded in the “Simple Executable Loader Format” (“Simple ELF” or “SELF”), which is a variation of “ELF” (the “Executable and Linkable Format”), which is a common format for executables on Linux-based operating systems.
The following is the function call sequence when the user succesfully gets Depthcharge to run the program in the RW_LEGACY region:
Software | Function | Source |
---|---|---|
Depthcharge | main | src/vboot/{main.c,rw_main.c} |
Depthcharge | vboot_select_and_load_kernel | src/vboot/stages.c |
vboot_fw.a | VbSelectAndLoadKernel | firmware/lib/vboot_api_kernel.c |
vboot_fw.a | VbBootDeveloper | firmware/lib/vboot_api_kernel.c |
Depthcharge | VbExLegacy | src/vboot/callbacks/legacy.c |
Depthcharge | load_payload_and_run | src/vboot/callbacks/legacy.c |
RW_LEGACY | *payload_entry | WHATEVER YOU WANT!!!111 |
A particular sticking point for me was the fact that the CBFS in the RW_LEGACY region is intended to be the old-style data structure, which has a master header at the end of it; I had actually managed to shoehorn a new-style CBFS into the region, and everything about it worked just dandy, except the firmware refused to touch it:
Software | Function | Source |
---|---|---|
Depthcharge | main | src/vboot/{main.c,rw_main.c} |
Depthcharge | vboot_select_and_load_kernel | src/vboot/stages.c |
vboot_fw.a | VbSelectAndLoadKernel | firmware/lib/vboot_api_kernel.c |
vboot_fw.a | VbBootDeveloper | firmware/lib/vboot_api_kernel.c |
Depthcharge | VbExLegacy | src/vboot/callbacks/legacy.c |
libpayload | cbfs_load_payload | payloads/libpayload/libcbfs/cbfs.c |
libpayload | cbfs_get_file_content | payloads/libpayload/libcbfs/cbfs_core.c |
libpayload | cbfs_get_file | payloads/libpayload/libcbfs/cbfs_core.c |
libpayload | cbfs_get_header | payloads/libpayload/libcbfs/cbfs_core.c |
The following is the point of failure if there is no master header (specifically, if there is no “magic” string of bytes to indicate the likely presence of a master header):
The result is this:
cbfs_get_header() returns CBFS_HEADER_INVALID_ADDRESS.
cbfs_get_file() returns NULL.
cbfs_get_file_content() returns NULL.
cbfs_load_payload() returns NULL.
VbExLegacy() returns 1.
VbBootDeveloper() beeps twice at you and then waits for more input (keyboard combinations) from the user:
As an aside, I’ll note that it was not by looking at this code that I figured out the problem with using a new-style CBFS (one without a master header); rather, I took a sneak peek at MrChomebox’s repository for the SeaBIOS code. In that repository, there is a script that creates the CBFS used to store SeaBIOS in the RW_LEGACY region of a Chromebook based on a Bay Trail SoC; in particular, that script creates an old-style CBFS:
$ | cbfstool ${filename} create -m x86 -s 0x00200000 |
It was that single line that made it clear to me that a CBFS without a master header will not work after all.