Summary
An attacker with local access to a system (either through a disk or external drive) can present a modified XFS partition to grub-legacy in such a way to exploit a memory corruption in grub’s XFS file system implementation. This may allow attackers to execute arbitrary code undetectably. This affects variants of grub-legacy that run disk or from UEFI firmware. Other (less severe) memory corruption vulnerabilities exist, which may lead to the same outcome if exploited under the right conditions.
Severity
High - grub-legacy is used as a bootloader in certain embedded devices. There, plugging in a drive or accessing the system drive is trivial, either through physical access or through being root on the device.
Proof of Concept
Use dd to copy the malicious partition to any partition in a disk image. Do not mount the partition but instead, use dd to overwrite one of the drive’s/image’s partitions. Example:
dd if=fxs_small.bin of=/dev/sdb2
For demonstration purposes, we suggest using a virtual machine running Ubuntu 9.04.
For our PoC, it is sufficient to run any kind of grub-legacy loader and interrupt it so that it’s possible to run commands. Using the “root” command, we can set the base drive that contains our malicious partition. Once the drive and partition have been selected (e.g. root(hd1,0) ) it is possible to trigger the bug by reading any file, for example by executing the cat command as shown in below screenshot.
You can run the ubuntu virtual machine in a similar fashion, like so:
$ qemu-system-x86_64 -nographic -m 2048 -serial
telnet:localhost:8888,server,nowait -enable-kvm -device ahci,id=ahci0,bus=pci.0
-drive file=./ubuntu9.04.qcow2,if=none,id=drive-sata-disk0,format=qcow2 -drive
file=./xfs_small.bin,if=none,id=drive-sata-disk1,format=raw -device
ide-hd,bus=ahci0.0,drive=drive-sata-disk0,id=drive-sata-disk0 -device
ide-hd,bus=ahci0.1,drive=drive-sata-disk1,id=drive-sata-disk1
Similar to what is shown in our screenshot, this will add a separate drive which contains the payload. When booting, interrupt the bootloader using the ESC button and switch to a console by typing ‘c’. Now, type
$ root (hd1,0)
$ cat /asdf
And notice that the bootloader crashes and the system resets.
Further Analysis
By abusing a stack-based buffer overflow in grub-legacy (hereafter: grub), it is possible to execute arbitrary code in all known versions of grub-legacy that support the XFS file system. This can be exploited without user interaction, for example by using only root access, by modifying the hard drive given physical access or by plugging in a specially prepared drive. The exact conditions vary from system to system, however the vulnerability can affect both systems that boot automatically (with no interactive grub console) and systems that allow users to interact with grub.
The vulnerability lies in grub’s XFS file system implementation and triggers automatically when grub tries to access any kind of file. As long as an attacker can modify the partition header and some portion of the partition on disk, it is possible to exploit the buffer overflow and overwrite the return value of the xfs_dir function such that attacker code (as read from the disk) is executed.
When grub’s file system backend tries to open a file on a XFS partition (for example via grub_open), it calls xfs_dir and that function allocates linkbuf on the stack. That buffer is used to store data used to implement symbolic links. Importantly, that buffer is dynamically sized based on the xfs.bsize variable that is read from the superblock in xfs_mount. Information in the superblock is not strictly validated when the partition is mounted and should be considered untrusted since attackers with access to the disk can modify xfs.bsize.
The function initially reads the first inode. Since the inode could be a link to another inode, a link_count counter keeps track of the number of followed links and restricts that number to 8 recursions - the function exits when exceeding the maximum link count. Else, di_size (read from inode) is compared to attacker-controlled xfs.bsize subtracted by 1. If di_size is smaller, xfs_read is called, which may end up calling grub_memmove() with linkbuf as destination and data from the disk (located in grub’s scratch buffer) as source.
Adversaries can set xfs.bsize to 0 - in that case linkbuf will have no length and di_size will always be smaller than (xfs.bsize - 1 = 0xffffffff). This way, adversaries can overflow linkbuf and control the length of the data that is written into the stack. Since the data is read from FSYS_BUF and FSYS_BUF is filled using large reads from the disk, adversaries can control the contents of all data written into the stack. This way, it is possible to overwrite all variables appearing in the stack prior to linkbuf and further, base pointer and return address of xfs_dir (and stack frames from calling functions) can be modified.
Overall, grub-legacy’s code quality is significantly worse than grub2's. Only limited time could be spent on triaging or exploiting the identified bugs - we conclude that more vulnerabilities should exist in grub-legacy.
Timeline
Date reported: 07/05/2023
Date fixed:
Date disclosed: 11/10/2023
Summary
An attacker with local access to a system (either through a disk or external drive) can present a modified XFS partition to grub-legacy in such a way to exploit a memory corruption in grub’s XFS file system implementation. This may allow attackers to execute arbitrary code undetectably. This affects variants of grub-legacy that run disk or from UEFI firmware. Other (less severe) memory corruption vulnerabilities exist, which may lead to the same outcome if exploited under the right conditions.
Severity
High - grub-legacy is used as a bootloader in certain embedded devices. There, plugging in a drive or accessing the system drive is trivial, either through physical access or through being root on the device.
Proof of Concept
Use dd to copy the malicious partition to any partition in a disk image. Do not mount the partition but instead, use dd to overwrite one of the drive’s/image’s partitions. Example:
For demonstration purposes, we suggest using a virtual machine running Ubuntu 9.04.
For our PoC, it is sufficient to run any kind of grub-legacy loader and interrupt it so that it’s possible to run commands. Using the “root” command, we can set the base drive that contains our malicious partition. Once the drive and partition have been selected (e.g. root(hd1,0) ) it is possible to trigger the bug by reading any file, for example by executing the cat command as shown in below screenshot.
You can run the ubuntu virtual machine in a similar fashion, like so:
Similar to what is shown in our screenshot, this will add a separate drive which contains the payload. When booting, interrupt the bootloader using the ESC button and switch to a console by typing ‘c’. Now, type
And notice that the bootloader crashes and the system resets.
Further Analysis
By abusing a stack-based buffer overflow in grub-legacy (hereafter: grub), it is possible to execute arbitrary code in all known versions of grub-legacy that support the XFS file system. This can be exploited without user interaction, for example by using only root access, by modifying the hard drive given physical access or by plugging in a specially prepared drive. The exact conditions vary from system to system, however the vulnerability can affect both systems that boot automatically (with no interactive grub console) and systems that allow users to interact with grub.
The vulnerability lies in grub’s XFS file system implementation and triggers automatically when grub tries to access any kind of file. As long as an attacker can modify the partition header and some portion of the partition on disk, it is possible to exploit the buffer overflow and overwrite the return value of the xfs_dir function such that attacker code (as read from the disk) is executed.
When grub’s file system backend tries to open a file on a XFS partition (for example via grub_open), it calls xfs_dir and that function allocates linkbuf on the stack. That buffer is used to store data used to implement symbolic links. Importantly, that buffer is dynamically sized based on the xfs.bsize variable that is read from the superblock in xfs_mount. Information in the superblock is not strictly validated when the partition is mounted and should be considered untrusted since attackers with access to the disk can modify xfs.bsize.
The function initially reads the first inode. Since the inode could be a link to another inode, a link_count counter keeps track of the number of followed links and restricts that number to 8 recursions - the function exits when exceeding the maximum link count. Else, di_size (read from inode) is compared to attacker-controlled xfs.bsize subtracted by 1. If di_size is smaller, xfs_read is called, which may end up calling grub_memmove() with linkbuf as destination and data from the disk (located in grub’s scratch buffer) as source.
Adversaries can set xfs.bsize to 0 - in that case linkbuf will have no length and di_size will always be smaller than (xfs.bsize - 1 = 0xffffffff). This way, adversaries can overflow linkbuf and control the length of the data that is written into the stack. Since the data is read from FSYS_BUF and FSYS_BUF is filled using large reads from the disk, adversaries can control the contents of all data written into the stack. This way, it is possible to overwrite all variables appearing in the stack prior to linkbuf and further, base pointer and return address of xfs_dir (and stack frames from calling functions) can be modified.
Overall, grub-legacy’s code quality is significantly worse than grub2's. Only limited time could be spent on triaging or exploiting the identified bugs - we conclude that more vulnerabilities should exist in grub-legacy.
Timeline
Date reported: 07/05/2023
Date fixed:
Date disclosed: 11/10/2023