调查 Fujitsu Lifebook AH532 上消失的 BIOS
Investigating a vanishing BIOS on the Fujitsu Lifebook AH532

原始链接: https://blog.timschumi.net/2024/01/20/ah532-bios-investigation.html

根据作者的描述和提供的信息,以下是他们的文章系列的可能的延续和更新: 继续/更新: 在我之前的文章“调查砖块 UEFI 安装”中,我分享了如何诊断并临时修复 Fujitsu LIFEBook AH532 笔记本电脑上的错误 UEFI 安装。 在这篇后续文章中,我计划更深入地研究具体问题并提供潜在的长期解决方案。 首先,让我简要回顾一下该问题及其对系统功能的影响。 正如第一篇文章中所解释的,错误的启动项会造成混乱,并阻止用户启动所需的操作系统,即使在重置 CMOS 值后也是如此,需要使用专门的 UEFI 工具进行手动修复。 这个缺陷多年来一直存在,有效地堵塞了某些系统。 为了充分理解这个问题,让我们检查一下细节。 经过进一步检查,发现 UEFI 设置中的特定部分(引导配置数据)由于操作过程不当而被损坏。 由于此数据结构包含正确执行所需的关键元素,因此其失真使 UEFI 无法管理有效的引导顺序。 因此,UEFI 在启动时被迫启动不安全的恢复模式。 澄清一下,虽然 EUFI 的最初预期设计在很大程度上依赖于 EFI 变量来准确运行,但这些变量必须保持不损坏以保持完整性。 因此,每当发生损坏时,都会导致严重的不利影响,影响整个系统的性能。 为了缓解这一障碍,有几种措施可能有助于恢复一致性。 一种替代方法是部署 BIOS 更新并应用其中的各种技术来解决这种情况。 具体来说,BIOS 制造商经常提供解决已知问题的补丁或提供更新版本以缓解持续存在的挑战。 同样,一些用户选择实施专门设计来克服类似困境的第三方产品。 虽然每种策略在适用性或实施要求方面存在很大差异,但它们具有共同的好处。 从本质上讲,采用这些策略使个人能够避免昂贵的维修或费力的修改来解决由损坏的 EFI 变量引起的问题。 也就是说,尽管通过权宜之计实现了暂时的缓解,但制定永久性解决方案仍然是必要的。 因此,我打算花时间探索旨在解决和解决问题的其他方法

然而,“gotten”通常用于美式英语,而“got”则更常用于英式英语。 在学术界或专业环境等正式场合,首选用法通常是“被给予”,因为它传达了更精致的语气。 Regarding your statement about hardware/firmware issues being solved with baking a fix into the pre-installed Windows version, it's true that this approach provides a convenient solution for the vast majority of users who don't require specialized system administration. 然而,对于喜欢开源替代方案所提供的灵活性和自由度的爱好者和 IT 专业人士来说,这让他们需要更多的选择。 例如,能够独立于任何操作系统调整 UEFI 启动变量。
相关文章

原文

A few months ago, I got given a laptop with a very interesting condition. The BIOS setup menu was supposedly inaccessible, and nothing apart from the default boot option works. Time to investigate to see what is actually happening and what we can do about it.

The status quo

When starting the laptop without doing anything special, the default option boots just fine, as expected. However, while pressing F2 during boot causes a beep tone, it ends up falling through to the same behavior as if nothing was pressed. On the other hand, pressing F12 lands us in a very empty boot menu:

The broken boot menu The broken application menu

This, obviously, is not good. The boot menu should (at least) show all bootable UEFI devices in addition to the boot entries that have been created manually.

Of course, the first thing that one would try is to reset the BIOS settings. Since the CMOS battery is quite hidden behind plastic shielding, I instead bridged the CL1_CL2 test point that is located under the RAM slot.

After the BIOS reinitialized, the boot menu looks like the following:

The partially fixed boot menu The partially fixed application menu

The manually created boot option is now gone, and device-based boot options show up instead. However, trying to enter the BIOS setup menu still shows the same fallthrough behavior as it did before.

Luckily, there now is the option to boot from USB storage, so the BIOS flash utilities provided by Fujitsu are a possibility going forward. Note that (at least for the AH532) there are multiple hardware revisions, so I had to go through the device serial number to arrive at the correct set of downloads.

After flashing the BIOS and rebooting, the boot menu looks normal and pressing F2 to enter the BIOS setup menu works like a charm:

The complete boot menu The complete application menu

Isolating a reproducer

Now that I was back in a working setup, the first thing I did was to make a backup of the current configuration. For that, I made an image of the Winbond 25Q32BVSIG SPI flash chip that is marked as “U35” on the motherboard, as it contains the whole UEFI PI firmware volume including executable code and data storage. Whenever something is broken, I can now just flash back the image instead of going through a full reset-CMOS-and-update-firmware cycle.

Location of the U35 chip on the motherboard

For easier testing, I also wanted a better reproducer than “reinstall your distribution of choice”. Therefore, I tried the first thing that came to mind, and it immediately broke the boot menu again:

# efibootmgr -c
BootOrder: 0000
Boot0000* Linux

Warning: Do NOT run this command twice. Doing so made the boot menu completely inaccessible.

Excursion: UEFI boot entry handling

At a high level, UEFI stores data in a large key-value store that is collectively called “UEFI variables”. Included in those variables are (among other things) boot entries and related settings. Variable access is done through standardized interfaces that are documented in the UEFI specification.

Boot entries are stored in entries named BootXXXX, where XXXX is a four digit hexadecimal number. Boot option order is determined through the BootOrder entry, which lists all four digit hexadecimal numbers in the order that they should appear in.

On my Framework laptop, the boot entries are set up as follows:

BootOrder: 0002, 0001, 2001, 2002, 2003
Boot0001: "Windows Boot Manager"
Boot0002: "GRUB"
Boot2001: "EFI USB Device"
Boot2002: "EFI DVD/CDROM"
Boot2003: "EFI Network"

Putting the pieces together

Suddenly, the efibootmgr -c output above doesn’t look all that confidence-inspiring. The command is supposed to create a new entry and insert it at the top of the boot entry list, but it ended up clearing the entire list instead. Running efibootmgr on a fresh boot also doesn’t result in quite the expected output:

# efibootmgr -v
Could not read variable 'BootNext': No such file or directory
Could not read variable 'BootCurrent': No such file or directory
Could not read variable 'BootTimeout': No such file or directory
Could not read variable 'BootOrder': No such file or directory
No BootOrder is set; firmware will attempt recovery
Could not read variable 'MirrorCurrent': No such file or directory
Could not read variable 'MirrorRequest': No such file or directory

It appears that Linux is unable to read existing variables from storage, which is immediately confirmed by inspecting the source of truth:

# ls -al /sys/firmware/efi/efivars/
total 0
drwxr-xr-x 2 root root 0 Jan 15 16:09 .
drwxr-xr-x 5 root root 0 Jan 15 16:09 ..

Note: At this point, I checked that Windows and various other UEFI tools are able to read the variables just fine, so Linux’ output is confirmed to be incorrect.

Now, this explains why the custom boot entries and the device-based boot entries end up being gone. efibootmgr can’t find any existing boot entry setup and just assumes that it can create a new setup without repercussions.

But why does it make the BIOS setup unusable? Using UEFITool, I inspected the flash image I made earlier, and reconstructed the following entries:

BootOrder: 000E, 000D, 0006, 0007, 0008, 0009, 000A, 000B
Boot0000: "BIOS Setup"
Boot0001: "Boot Menu"
Boot0002: "Diagnostic Screen"
Boot0003: "Recovery and Utility"
Boot0004: "Diagnostic Program"
Boot0005: "Diagnostic Progrogram ROM"
Boot0006: "Floppy Disk Drive"
Boot0007: "Drive0 HDD"
Boot0008: "CD/DVD Drive"
Boot0009: "NETWORK"
Boot000A: "USB HDD"
Boot000B: "USB CD/DVD"
Boot000C: "Erase Disk"
Boot000D: "Windows Boot Manager"
Boot000E: "proxmox"

Oh, no…

“BIOS Setup” is an actual boot menu entry.

And it gets overwritten by the new boot entry that gets placed erroneously.

Note: Attentive readers might have noticed the reason for why everything breaks completely when running efibootmgr -c twice: The second boot entry that is created would be Boot0001, which successfully replaces “Boot Menu”.

Creating a temporary band-aid

Now that I knew what went wrong, I was able to create a small program that resets the boot order, as well as the BIOS setup and boot menu entries to their expected contents.

Note: This program is based on an UEFI dump of a Fujitsu LIFEBOOK AH532 (YLKV). It is unlikely that it will work on any other hardware (revision) that happens to have the same symptoms. Furthermore, the written data may also be dependent on the firmware revision, so take caution when running this on a firmware version that is not v1.08 or v1.09.

Warning: I assume no liability for broken hardware or software due to correct or incorrect usage of this tool.

#include 

uint32_t boot_attributes = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS;

unsigned char bootorder_data[] = {
    0x06, 0x00, // Boot0006
    0x07, 0x00, // Boot0007
    0x08, 0x00, // Boot0008
    0x09, 0x00, // Boot0009
    0x0A, 0x00, // Boot000A
    0x0B, 0x00, // Boot000B
};

unsigned char boot0000_data[] = {
    0x00, 0x01, 0x00, 0x00, // Attributes
    0x18, 0x00,             // FilePathListLength

    // Description: "BIOS Setup       "
    0x42, 0x00, 0x49, 0x00, 0x4F, 0x00, 0x53, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00, 0x74, 0x00, 0x75, 0x00, 0x70, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00,
    0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00,

    // FilePathList[0]
    0x04,       // Type: Media Device Path
    0x06,       // Sub-Type: PIWG Firmware File
    0x14, 0x00, // Length: 4 + 0x14
    0x66, 0x8B, 0x1C, 0x72, 0x6C, 0x42, 0x86, 0x4E, 0x8E, 0x99, 0x34, 0x57, 0xC4, 0x6A, 0xB0, 0xB9,

    // FilePathList[1]
    0x7F,       // Type: End of Hardware Device Path
    0xFF,       // Sub-Type: End Entire Device Path
    0x04, 0x00, // Length: 4

    // OptionalData (empty)
};

unsigned char boot0001_data[] = {
    0x00, 0x01, 0x00, 0x00, // Attributes
    0x18, 0x00,             // FilePathListLength

    // Description: "Boot Menu"
    0x42, 0x00, 0x6F, 0x00, 0x6F, 0x00, 0x74, 0x00, 0x20, 0x00, 0x4D, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x75, 0x00, 0x00, 0x00,

    // FilePathList[0]
    0x04,       // Type: Media Device Path
    0x06,       // Sub-Type: PIWG Firmware File
    0x14, 0x00, // Length: 4 + 0x14
    0x40, 0x84, 0x48, 0x86, 0xBB, 0x41, 0xC7, 0x42, 0x93, 0xAC, 0x45, 0x0F, 0xBF, 0x77, 0x66, 0xBF,

    // FilePathList[1]
    0x7F,       // Type: End of Hardware Device Path
    0xFF,       // Sub-Type: End Entire Device Path
    0x04, 0x00, // Length: 4

    // OptionalData (empty)
};

int main() {
    int rc = efi_set_variable(EFI_GLOBAL_GUID, "BootOrder", bootorder_data, sizeof(bootorder_data), boot_attributes, 0644);
    if (rc  0) {
        efi_error("Resetting BootOrder failed");
        return 1;
    }

    rc = efi_set_variable(EFI_GLOBAL_GUID, "Boot0000", boot0000_data, sizeof(boot0000_data), boot_attributes, 0644);
    if (rc  0) {
        efi_error("Resetting Boot0000 failed");
        return 1;
    }

    rc = efi_set_variable(EFI_GLOBAL_GUID, "Boot0001", boot0001_data, sizeof(boot0001_data), boot_attributes, 0644);
    if (rc  0) {
        efi_error("Resetting Boot0001 failed");
        return 1;
    }
}

Naturally, this isn’t a great solution, since users will continue to brick their installations initially, just like they did for the last 10 years. However, this is everything that I managed to research so far, and the band-aid fix makes it reasonable for publication.

I will look into fixing this properly at some point in the future, and if I manage to do so this post will probably get a part two, so stay tuned. (Apparently Jekyll supports RSS?)

联系我们 contact @ memedata.com