Keywords
Security Vulnerabilities
Multiple vulnerabilities have been discovered in the Mazda Connectivity Master Unit (CMU) system installed in various models (such as the 2014-2021 Mazda 3). As is often the case, these vulnerabilities were caused by inadequate sanitization when processing input provided by attackers. Actual attackers can exploit these vulnerabilities by connecting specially crafted USB devices (such as an iPod or a mass storage device) to the target system. Successfully exploiting some of these vulnerabilities could lead to arbitrary code execution with root privileges.
Objective
The specific CMU device studied was manufactured by Visteon, and the software was originally developed by Johnson Controls (JCI). The research targeted the latest available software version (74.00.324A). Earlier software versions below 70.x may also be affected by these vulnerabilities.
It is noteworthy that the CMU’s “modification scene” is very active, with the community releasing various software “tweaks” to alter the operation of the device. Installing these tweaks usually relies on software vulnerabilities. As of the time of publication, no publicly known vulnerabilities were found in the latest firmware version.
Hardware Design
The device appears as follows:
There is a sticker on the bottom with some technical information about the device. The sticker notes that the specific model of the CMU is MAZDA_GEN_65_CMU.
The back of the device has multiple connectors for connecting power, audio input/output, USB and CAN signals, as well as a serial console.
Internally, the device is built on a printed circuit board that contains an application SoC (Freescale part number SCIMX06DAVT10AC), various memory ICs (serial flash on the back of the board, NAND flash, and eMMC), a secondary MCU (Renesas part number R5F35MCEJFF), and a Bluetooth module (Murata part number LBEE6Z2U0C-584).
This study did not explore the available functionalities of the edge connectors.
Typically, in designs with multiple processing units, a degree of separation is implemented. User-facing applications handled by rich operating systems (such as QNX, Linux, Android, etc.) run on the application SoC, while CAN connections are typically handled by an MCU running an RTOS. This device appears to be no exception.
Software Design
On the software side, the main SoC of the device is running a Linux-based operating system image, as evidenced by the output captured through the serial console (abbreviated here):
IBC embedded bootloader 1.68.21
(c) 2012 XS Embedded GmbH
Uncompressing Linux... done, booting the kernel.
00:00:01.226 LVDS[61] contrast : 54 -> 54
00:00:01.227 LVDS[61] (Defaulting) Speed Restriction: Enabled
00:00:01.227 LVDS[61] (Defaulting) Visteon Display.
…
00:00:01.428 LVDS[61] ChangeBrightness_Id:
00:00:01.433 LVDS[61] brightness : 5000 -> 5000
00:00:01.433 LVDS[61] BrightNessLevel = 5000.
FGSN: VPGJ3Fxxxxxxxxyy
cmu login:
Although there is a login prompt, the validation credentials for the latest software version are not publicly available. The console continues to provide a wealth of log output, which is useful for testing vulnerabilities.
Once fully booted, the system mounts the following filesystems:
rootfs on / type rootfs (rw)
none on /sys type sysfs (rw,relatime)
none on /proc type proc (rw,relatime)
none on /dev type devtmpfs (rw,relatime,size=8192k,nr_inodes=95316,mode=755)
/dev/ffx01p1 on / type relfs (ro,noatime)
none on /sys type sysfs (rw,relatime)
none on /proc type proc (rw,relatime)
none on /dev type devtmpfs (rw,relatime,size=8192k,nr_inodes=95316,mode=755)
none on /tmp type tmpfs (rw,relatime,size=95232k)
/dev/ffx01p4 on /tmp/mnt/data type relfs (rw,noatime)
/dev/mmcblk0p2 on /tmp/mnt/data_persist type relfs (rw,noatime)
/dev/mtdblock5 on /config-mfg type squashfs (ro,relatime)
none on /dev/pts type devpts (rw,relatime,mode=600)
none on /dev/mqueue type mqueue (rw,relatime)
none on /tmp/mnt/tmp/race/database type tmpfs (rw,relatime,size=30720k)
/dev/mmcblk0p1 on /tmp/mnt/resources type relfs (ro,noatime)
View rawmazda_connect-1.txt hosted with ❤ by GitHub
The operating system has access to all persistent memory storage. The serial flash exists as an mtdblock partition, NAND flash exists as an ffx01 partition, and eMMC flash exists as an mmcblk partition. Notably, several filesystems are read-write mounted, indicated by the rw string in parentheses.
Software Update Process
The software on the device can be updated via the diagnostic menu:
The update process requires finding a file with the .up extension on a mass storage device connected via USB. The dealer can then enter the diagnostic menu and initiate the update process.
The software update files are password-protected ZIP files containing multiple directories for different steps in the update process, along with an “instructions” file detailing how to perform the update. The latest update file contains 21 steps. Of particular note are the contents of the following directories:
– rootfs1upd contains a .tar.gz archive of the entire root filesystem, – linux1 contains the Linux kernel 3.0.35, – ibc1 and ibc2 contain a bootloader, – bootstrap contains another bootloader, – passwdupdate contains a replacement passwd file, – vip contains an update image for the auxiliary MCU.
Most of the update process is implemented in several shared objects. All business logic resides in svcjciupdatea.so and svcjciupdates.so, which use supporting code in libjcireflashua.so and libjcisecurity.so. The investigation of the update process was limited to signature verification. Further investigation would assume the ability to generate correctly signed updates or bypass signature verification. Unfortunately, the former did not exist at the time of this study, and the latter was not discovered in this research.
At a high level, the update verification process is contained within svcjciupdates.so:updates_sys_srv_ValidatePackageOperationThread(), which calls other functions to execute verification steps in the specified order:
– updates_sys_srv_LoadUP() parses the specified update file, extracts main_instructions.ini and versions.ini files, and stores them in memory for later use. – updates_sys_srv_ValidateUPStructure() verifies the internal structure of the update file against the expected layout. This is not an issue when the vulnerability is created according to the correct update file format. – updates_sys_srv_ExtractCertificates() extracts the signature certificate chain from the update file for update verification. This chain includes the publisher certificate used to sign the update and the intermediate CA certificate that issued the publisher certificate. The intermediate CA certificate is issued by the JCI root CA. – updates_sys_sec_VerifyUPCertificates() verifies the extracted certificate chain to ensure that it correctly terminates at the expected root CA stored on the device. – updates_sys_sec_VerifyUPSignature() verifies the signature of the specified update file based on the publisher certificate. The signature in this case is located at the end of the update file and is 256 bytes long. – updates_sys_srv_RemoveExtractedCertificates() performs cleanup after the verification process ends.
Notably, before performing integrity checks on user-provided files, the ups updates_sys_srv_LoadUP() function and its calling functions perform several operations on the provided update file.
Vulnerabilities
Multiple vulnerabilities were discovered in our research work, which can be combined to achieve comprehensive and persistent control over the infotainment system.
CVE-2024-8355/ZDI-24-1208: Device Manager iAP Serial Number SQL Injection
This vulnerability was the first one discovered during the research process, prompting further investigation into the device.
The specific vulnerability exists in the eInsertDeviceEntry() function of the /jci/devicemanager/libdevicemanager.so library. When attempting to insert a new Apple device into the DeviceDatabase.db SQLite database, several values are fetched from the device and used in the SQL statement without sanitization. See the following pseudocode implementation of the eInsertDeviceEntry() function:
int eInsertDeviceEntry(undefined4 param_1,int param_2)
{
int iVar1;
undefined auStack1052 [1024];
int local_1c;
local_1c = 0;
sqlite3_snprintf(0x400,auStack1052,
"INSERT INTO DeviceInfo (USBSERIAL,MACADDRESS, IAPSERIAL, UIDVALID) VALUES(\'%s\',%Q,\'%s\',%d)"
,param_2,param_2 + 100,param_2 + 200,*(undefined4 *)(param_2 + 300));
race_print_log(4,0x11,"eInsertDeviceEntry",0x4c0,"DEVMGR_DATABASE","Query is : %s\n",auStack1052);
iVar1 = sqlite3_exec(param_1,auStack1052,0,0,&local_1c);
if (iVar1 != 0) {
race_print_log(1,0x11,"eInsertDeviceEntry",0x4c5,"DEVMGR_DATABASE","SQL error: %s %d\n",local_1c
,iVar1);
if (local_1c != 0) {
sqlite3_free();
}
}
if (iVar1 != 0) {
iVar1 = 1;
}
return iVar1;
}
View rawmazda_connect-2.c hosted with ❤ by GitHub
As a result, when the infotainment system detects an Apple device connected and requests (for example) its iAP serial number, a spoofed iPod or other Apple device can respond with the following string:
' , 0); [ANY SQL STATEMENT];--
View rawmazda_connect-3.txt hosted with ❤ by GitHub
This causes the DeviceManager to execute the injected SQL statement with root privileges on the infotainment system. This can be used to manipulate the database itself, disclose information, create arbitrary files on the filesystem, and even potentially execute code. Due to the input length restriction of 0x36 bytes, the exploitation of this vulnerability is somewhat limited, but it could potentially be circumvented by having multiple spoofed iPods connect in succession, each with its own injected SQL statement replacing the serial number.
CVE-2024-8359/ZDI-24-1191: REFLASH_DDU_FindFile Command Injection RCE
The REFLASH_DDU_FindFile() function in the libjcireflashua.so shared object is one of many functions supporting the update process. For clarity, the pseudocode of this function is copied below:
undefined4 REFLASH_DDU_FindFile(char *_up_path,char *_file_name,int _is_gzipped)
{
int iVar1;
undefined4 uVar2;
char acStack_408 [1023];
undefined local_9;
memset(acStack_408,0,0x400);
if ((_up_path == (char *)0x0) || (_file_name == (char *)0x0)) {
REFLASH_UL_WriteLog("Core Reflash",0,0,"reflash_ddu.c","REFLASH_DDU_FindFile",0x181,
"Some of the input parameters are wrong!");
uVar2 = 0xffffd8f0;
}
else {
if (_is_gzipped == 1) {
if (reflash_ddu_password == 0) {
snprintf(acStack_408,0x400,"%s \"%s\" \"%s.gz\" > /dev/null","unzip -t",_up_path,_file_name)
;
}
else {
snprintf(acStack_408,0x400,"%s %s \"%s\" \"%s.gz\" > /dev/null","unzip -t -P",
reflash_ddu_password,_up_path,_file_name);
}
}
else if (reflash_ddu_password == 0) {
snprintf(acStack_408,0x400,"%s \"%s\" \"%s\" > /dev/null","unzip -t",_up_path,_file_name);
}
else {
snprintf(acStack_408,0x400,"%s %s \"%s\" \"%s\" > /dev/null","unzip -t -P",
reflash_ddu_password,_up_path,_file_name);
}
local_9 = 0;
iVar1 = system(acStack_408);
if (iVar1 == 0) {
uVar2 = 0;
}
else {
uVar2 = 0xffffd8fb;
}
}
return uVar2;
}
mazda_connect-4.c hosted with ❤ by GitHub
This function attempts to find a specific file in the update package specified by the provided file path. This is accomplished by interpolating the provided path into the command line of the decompressor. Unfortunately, no sanitization is performed beforehand, and attacker-controlled input (such as /dev/sda1/path/file.up) is passed to the system() function. This allows the attacker to inject arbitrary operating system commands, executed by the host operating system shell, thus fully compromising the system. The attacker can control both the path and file elements throughout the entire path.
The affected function can be reached through the following call graph:
– svcjciupdates.so’s UPDATES_SYS_IPC_INTERFACE_GetPackageInfo_svc– UPDATES_SYS_SRV_GetPackageInfo– Update_sys_srv_GetPackageInfoOperationThread– update_sys_srv_LoadUP– libjcireflashua.so’s REFLASH_UA_LoadUP– REFLASH_DDU_FindFile
CVE-2024-8360/ZDI-24-1192: REFLASH_DDU_ExtractFile Command Injection RCE
The REFLASH_DDU_ExtractFile() function in the libjcireflashua.so shared object is one of many functions supporting the update process. For clarity, the pseudocode of this function is copied below:
undefined4 REFLASH_DDU_ExtractFile(char *_up_path,char *_file_name,char *_dest_path,int _is_gzipped)
{
undefined4 uVar1;
char acStack_808 [1023];
undefined local_409;
char acStack_408 [1023];
undefined local_9;
memset(acStack_408,0,0x400);
memset(acStack_808,0,0x400);
if (((_up_path == (char *)0x0) || (_file_name == (char *)0x0)) || (_dest_path == (char *)0x0)) {
REFLASH_UL_WriteLog("Core Reflash",0,0,"reflash_ddu.c","REFLASH_DDU_ExtractFile",0xd4,
"Some of the input parameters are wrong!");
uVar1 = 0xffffd8f0;
}
else {
if (_is_gzipped == 1) {
if (reflash_ddu_password == 0) {
snprintf(acStack_408,0x400,"%s \"%s\" \"%s.gz\" | %s > \"%s\", "unzip -p",_up_path,
_file_name,"gzip -d -c",_dest_path);
}
else {
snprintf(acStack_408,0x400,"%s %s \"%s\" \"%s.gz\" | %s > \"%s\", "unzip -p -P",
reflash_ddu_password,_up_path,_file_name,"gzip -d -c",_dest_path);
snprintf(acStack_808,0x400,"%s %s \"%s\" \"%s.gz\" | %s > \"%s\", "unzip -p -P","<***>",
_up_path,_file_name,"gzip -d -c",_dest_path);
}
}
else if (reflash_ddu_password == 0) {
snprintf(acStack_408,0x400,"%s \"%s\" \"%s\" > \"%s\", "unzip -p",_up_path,_file_name,
_dest_path);
}
else {
snprintf(acStack_408,0x400,"%s %s \"%s\" \"%s\" > \"%s\", "unzip -p -P",reflash_ddu_password,
_up_path,_file_name,_dest_path);
snprintf(acStack_808,0x400,"%s %s \"%s\" \"%s\" > \"%s\", "unzip -p -P","<***>",_up_path,
_file_name,_dest_path);
}
local_9 = 0;
if (reflash_ddu_password == 0) {
uVar1 = REFLASH_ExecuteCommand(acStack_408,0);
}
else {
local_409 = 0;
uVar1 = REFLASH_ExecuteCommand(acStack_408,acStack_808);
}
}
return uVar1;
}
mazda_connect-5.c hosted with ❤ by GitHub
This function attempts to extract a specific file from the update package specified by the provided up_path parameter. This is accomplished by interpolating the provided path into the command line of the decompressor. Unfortunately, no sanitization is performed beforehand, and attacker-controlled input (such as /dev/sda1/path/file.up) is passed to the system() function. This allows the attacker to inject arbitrary operating system commands, executed by the host operating system shell, thus fully compromising the system.
The affected function can be reached through the following call graph:
– svcjciupdates.so’s UPDATES_SYS_IPC_INTERFACE_GetPackageInfo_svc– UPDATES_SYS_SRV_GetPackageInfo– Update_sys_srv_GetPackageInfoOperationThread– update_sys_srv_LoadUP– libjcireflashua.so’s REFLASH_UA_LoadUP– REFLASH_DDU_ExtractFile
CVE-2024-8358/ZDI-24-1190: UPDATES_ExtractFile Command Injection RCE
The UPDATES_ExtractFile() function in the svcjciupdates.so shared object is one of various functions supporting the update process. For clarity, part of the pseudocode for this function is copied below:
int UPDATES_ExtractFile(char *up_path,char *file_path,char *destFile,int gzipped,char *password)
{
int iVar1;
size_t sVar2;
size_t sVar3;
int iVar4;
char *pcVar5;
char *local_2060 [4];
int gzipped$1;
char *destFile$1;
char *file_path$1;
char *up_path$1;
char acStack_203c [4100];
char *pcStack_1038;
char cmd [4100];
char *local_30;
int local_2c;
size_t local_28;
int local_24;
local_24 = 100;
gzipped$1 = gzipped;
destFile$1 = destFile;
file_path$1 = file_path;
up_path$1 = up_path;
// ...
if (local_24 == 100) {
snprintf(acStack_203c,0x1001,"%s","unzip -p");
}
if ((local_24 == 100) && (password != (char *)0x0)) {
// append the unzip option and the password itself
}
if (local_24 == 100) {
if (gzipped$1 == 1) {
local_2060[0] = up_path$1;
local_2060[1] = file_path$1;
local_2060[2] = "gzip -d -c";
local_2060[3] = destFile$1;
snprintf(cmd,0x1001,"%s \"%s\" \"%s.gz\" | %s > \"%s"