Vulnerabilities in Nissan infotainment system manufactured by BOSCH

Discovered in Nissan Leaf ZE1 manufactured in 2020

Read time:00:35

Release date:6.3.2025

Product description

PCAutomotive team verified all the findings from this advisory on the model Nissan Leaf ZE1 manufactured in 2020:

A white car with blue trim

Nissan Leaf ZE1 2020 appearance

The car was equipped with an infotainment ECU (Electronic Control Unit) manufactured by BOSCH:

Nissan infotainment system. Source: pcmag.com

The IVI exposes Wi-Fi (in client mode), Bluetooth, and USB connections. At the same time, the IVI is connected to other in-vehicle ECUs (Electronic Control Units) via a CAN bus. The IVI also has access to private information about the car owner. This makes the IVI an interesting target for would-be attackers.

The CAN bus interface of the IVI is handled by RH850 CPU core, which is responsible for sending/receiving CAN messages to/from corresponding CAN buses connected to the IVI.

The infotainment system installed in Nissan Leaf ZE1 vehicle under assessment had software version 283C30861E. Thus, the research focused on this version of firmware.

Nissan Leaf IVI appearance

Summary

PCAutomotive team identified multiple vulnerabilities that allow an attacker to obtain full control over the IVI via 1-time Bluetooth access. From the IVI, it was possible to control multiple car body functions:

  • Steering wheel control (at speed lower than ~10 km/h)
  • Opening/closing central door lock
  • Folding in and out rear-view mirrors
  • Opening/closing car windows.

In addition, it was possible to control less critical functions such as wipers, washers, and horn.

An attacker could also leverage full control over the IVI to:

  • Exfiltrate driver’s private data, such as location
  • Eavesdrop via in-vehicle microphone
  • Control the IVI screen
  • Control in-vehicle sound system.

The impact demonstration video is available on YouTube.

The list of vulnerabilities identified by PCAutomotive team:

CVE IDTitleCVSS 3.1 Score
CVE-2025-32056

Anti-theft bypass for infotainment ECU

4.0 (Medium)

CVE-2025-32057

Misconfigured SSL/TLS communication of Redbend service for infotainment ECU

6.5 (Medium)

CVE-2025-32058

Stack overflow in processing requests over INC interface on RH850 side of infotainment ECU

9.3 (Critical)

CVE-2025-32059

Stack overflow leading to RCE in Bluetooth stack of infotainment ECU [0]

8.8 (High)

CVE-2025-32060

Absence of Linux kernel module signature verification on infotainment ECU

6.7 (Medium)

CVE-2025-32061

Stack overflow leading to RCE in Bluetooth stack of infotainment ECU [1]

8.8 (High)

CVE-2025-32062

Stack overflow leading to RCE in Bluetooth stack of infotainment ECU [2]

8.8 (High)

CVE-2025-32063

Enabling SSH server on infotainment ECU

6.8 (Medium)

Additionally, the IVI appeared vulnerable to the following already known security issues:

CVE IDTitleCVSS 3.1 Score
CVE-2017-7932

Stack overflow in HAB of i.MX 6 leading to the Secure Boot Bypass on infotainment ECU

6.0 (Medium)

CVE-2017-9832

Integer overflow leading to code execution in processing MTP on infotainment ECU

6.8 (Medium)

Disclosure timeline

DateDescription

02.08.2023

PCAutomotive sends the advisory to Nissan Cybersecurity Team

09.08.2023 - 11.12.2023

Email discussion about the findings’ criticality

04.01.2024

PCAutomotive sends a video demonstration of the full attack chain; asks about CVE registration; notifies about publication plans

26.01.2024

Nissan Cybersecurity Team confirms the vulnerabilities; starts planning their mitigations; notifies us to register CVE by ourselves; accepted the publication plans

25.04.2024

PCAutomotive requests CVE registration from MITRE

19.05.2024

MITRE forwards us to Bosch PSIRT

10.09.2024

PCAutomotive sends Bosch PSIRT a request to register CVE

11.09.2024

Bosch PSIRT responds that they didn’t receive any information about vulnerabilities from Nissan Cybersecurity Team

12.09.2024

PCAutomotive notifies Nissan Cybersecurity Team about the communication with Bosch PSIRT

23.09.2024

PCAutomotive sends the advisory to Bosch PSIRT

06.11.2024

PCAutomotive notifies Bosch PSIRT about the publication plans

11.03.2025

Bosch PSIRT accepts the publication, declines to register CVE and forward us to ASRG

18.03.2025

PCAutomotive requests CVE registration from ASRG

03.04.2025

ASRG registered CVE for vulnerabilities

Technical details

CVE-2025-32056: Anti-theft bypass for infotainment ECU

Description

When disconnected from an original vehicle, the IVI activates a special anti-theft protection mechanism.

When the head unit is switched on, it sends a diagnostic CAN message with CAN-ID 0x71e and waits for a specific response from CAN-ID 0x72e. Messages from the head unit differ at every power cycle, and depending on them, the expected response changes. If the head unit receives a correct answer, it sends another message with CAN-ID 0x71e and data 24c76c9a9889ffff, then it waits for a response with CAN-ID 0x72e and the same data 24c76c9a9889ffff, after which the anti-theft check will be passed.

If an incorrect response is received, the head unit goes into anti-theft with status RED. If no response is received – anti-theft with status GREEN.

Example of triggered anti-theft

For example, in the traffic from the vehicle, one can see the following messages:

CAN-IDMessage
0x71e (Head Unit)14 03 f05bb5 17 ffff
0x72e14 c826e381 66 ffff
0x71e24 c76c9a98 89 ffff
0x72e24 c76c9a98 89 ffff

Another example of the CAN traffic:

CAN-IDMessage
0x71e14 06 f05bb5 1a ffff
0x72e14 e907c2a0 66 ffff
0x71e24 c76c9a98 89 ffff
0x72e24 c76c9a98 89 ffff

The following list of CAN message correspondences can be compiled (sorted by seed number):

0x71e0x72e
14 00 f05bb5 14 ffff14 50505050 54 ffff
14 01 f05bb5 15 ffff14 efefefef d0 ffff
14 02 f05bb5 16 ffff14 10101010 54 ffff
14 03 f05bb5 17 ffff14 3d3d3d3d 08 ffff
14 04 f05bb5 18 ffff14 7a7a7a7a fc ffff
14 05 f05bb5 19 ffff14 d6d6d6d6 6c ffff
14 06 f05bb5 1a ffff14 1c1c1c1c 84 ffff
14 07 f05bb5 1b ffff14 90909090 54 ffff
14 08 f05bb5 1c ffff14 a2a2a2a2 9c ffff
14 09 f05bb5 1d ffff14 fdfdfdfd 08 ffff
14 0a f05bb5 1e ffff14 1f1f1f1f 90 ffff
14 0b f05bb5 1f ffff14 bdbdbdbd 08 ffff
14 0c f05bb5 20 ffff14 51515151 58 ffff
14 0d f05bb5 21 ffff14 71717171 d8 ffff
14 0e f05bb5 22 ffff14 abababab c0 ffff
14 0f f05bb5 23 ffff14 fefefefe 0c ffff
14 10 f05bb5 24 ffff14 f0f0f0f0 d4 ffff
14 11 f05bb5 25 ffff14 6c6c6c6c c4 ffff
14 12 f05bb5 26 ffff14 e3e3e3e3 a0 ffff
14 13 f05bb5 27 ffff14 41414141 18 ffff
14 14 f05bb5 28 ffff14 bebebebe 0c ffff
14 15 f05bb5 29 ffff14 25252525 a8 ffff
14 16 f05bb5 2a ffff14 5c5c5c5c 84 ffff
14 17 f05bb5 2b ffff14 d7d7d7d7 70 ffff
14 18 f05bb5 2c ffff14 42424242 1c ffff
14 19 f05bb5 2d ffff14 bbbbbbbb 00 ffff
14 1a f05bb5 2e ffff14 26262626 ac ffff
14 1b f05bb5 2f ffff14 08080808 34 ffff
14 1c f05bb5 30 ffff14 d5d5d5d5 68 ffff
14 1d f05bb5 31 ffff14 9d9d9d9d 88 ffff
14 1e f05bb5 32 ffff14 4a4a4a4a 3c ffff
14 1f f05bb5 33 ffff14 a1a1a1a1 98 ffff
14 20 f05bb5 34 ffff14 50505050 54 ffff - loop

The messages in the listing and traffic samples above are separated into pieces by whitespaces, following the format of those messages. More specifically, messages with CAN-ID 0x71e have the following format:

FunctionSeedConst0Const1Const2ChksumConstFFConstFF
1401f05bb515ffff

The checksum field “Chksum” is calculated as follows: (0x14 + 0x01 + 0xF0 + 0x5B + 0xB5) && 0x0FF = 0x15.

Messages with CAN-ID 0x72e have a slightly different format:

FunctionRes0Res1Res2Res3ChksumConstFFConstFF
14efefefefd0ffff

The checksum for messages with CAN-ID 0x72E is calculated in the same way as for messages with CAN-ID 0x71E.

There are 32 requests from the head unit in total. It is possible to reveal all 32 corresponding responses by sniffing CAN traffic or by pre-calculating the values, which will bypass the protection.

Credits

The vulnerability was identified by Polina Smirnova from PCAutomotive Security Assessment team.


CVE-2025-32057: Misconfigured SSL/TLS communication of Redbend service for infotainment ECU

Description

The IVI uses Redbend service for provisioning and firmware updates. For that, IVI initiates SSL connection as client to the Redbend server on the internet.

Critical information on the IVI (such as private keys for SSL) is stored in the wrapped mode - encrypted by CAAM (Cryptographic Accelerator and Assurance Module) crypto device. Therefore, in the SSL handshake logic to unwrap (decrypt) the private keys, the mechanism of callbacks inside the OpenSSL engine is used. Because of a logic issue based on the setup configuration and flow of callbacks, a self-signed certificate of the back-end server can be used for HTTPS communication.

An attacker can act as a legit Redbend back-end server and provide fake provisioning data and updates.

There are two functions inside the app_redbend_out.out which perform certificate verification during the handshake process:

  • sub_46754 (assigned in SSL_CTX_set_cert_verify_callback) - validates a certificate by a host name.
  • sdc_ecm_load_client_cert (it's a part of sdc-ecm engine assigned in SSL_CTX_set_client_cert_engine) - validates a server certificate by the local storage and then loads a client one.

Function sub_46754 looks the following way:

int __fastcall sub_46754(int a1, const char *a2) 
{ 
  int v4; // r0 
  int v5; // r0 
  int v6; // r0 
  int v8; // r0 
 
  v4 = _vdmfile__( 
         "/home/umi1cob/samba/views/umi1cob_AI_PRJ_RN_AIVI_18.12V10.vws/ai_ota/nissan_ncg3/swmc/redbend/sdk/source/comm/v" 
         "dm_comm_pl_conn.c"); 
  VDM_logDebug_Func(v4, 1434, 3, "%s", "SDCECM_verify_callback"); 
  v5 = _vdmfile__( 
         "/home/umi1cob/samba/views/umi1cob_AI_PRJ_RN_AIVI_18.12V10.vws/ai_ota/nissan_ncg3/swmc/redbend/sdk/source/comm/v" 
         "dm_comm_pl_conn.c"); 
  VDM_logError_Func(v5, 1435, 3, "SDCECM_verify_callback: HostName: %s", a2); 
  if ( X509_check_host(*(_DWORD *)(a1 + 8), a2, 0, 0, 0) == 1 ) 
  { 
    v8 = _vdmfile__( 
           "/home/umi1cob/samba/views/umi1cob_AI_PRJ_RN_AIVI_18.12V10.vws/ai_ota/nissan_ncg3/swmc/redbend/sdk/source/comm" 
           "/vdm_comm_pl_conn.c"); 
    VDM_logDebug_Func(v8, 1444, 3, "%s", "HostName verification success"); 
    return 0; 
  } 
  else 
  { 
    ENGINE_finish(gpSdcEcmEngine); 
    v6 = _vdmfile__( 
           "/home/umi1cob/samba/views/umi1cob_AI_PRJ_RN_AIVI_18.12V10.vws/ai_ota/nissan_ncg3/swmc/redbend/sdk/source/comm" 
           "/vdm_comm_pl_conn.c"); 
    VDM_logError_Func(v6, 1441, 3, "%s", "HostName verification failed"); 
    return 1; 
  } 
}`

For a self-signed certificate, it calls ENGINE_finish (turns off the engine) and returns 0. There are notes regarding this API function, which is used for handler registration:

callback should return 1 to indicate verification success and 0 to indicate verification failure. 
In server mode, a return value of 0 leads to handshake failure. In client mode, the behaviour is 
as follows. All values, including 0, are ignored if the verification mode is SSL_VERIFY_NONE. 
Otherwise, when the return value is less than or equal to 0, the handshake will fail.

The SSL engine runs in a client mode, hence, in case of SSL_VERIFY_NONE all the security measures can be bypassed using a self-signed certificate.

By default, the SSL_VERIFY_NONE is used:

Programming considerations 
1. To use this function, you must include the library that is specified in the prototype in your makefile.
2. The default value for the verify mode is SSL_VERIFY_NONE when the CTX structure is created. You must issue the SSL_set_verify function or the SSL_CTX_set_verify function with the SSL_VERIFY_PEER option if you want to authenticate remote peers when SSL sessions are started. 

As a result, an attacker can perform a MiTM attack:

The result of a performed MiTM attack

Credits

The vulnerability was identified by Radu Motspan from PCAutomotive Security Assessment team.


CVE-2025-32058: Stack overflow in processing requests over INC interface on RH850 side of infotainment ECU

Description

CSM (CAN Proxy on IMX6 – main CPU of the IVI running Linux OS) service uses INC interface to broadcast signal messages to the CAN busses. In fact, the signal messages are CAN messages, but their CAN IDs are restricted. RH850 can send messages with CAN IDs that are hardcoded in the firmware.

During the processing of the signal messages over the INC interface, there is a stack-based overflow vulnerability. It allows an attacker to perform code execution on RH850 module. Moreover, using this vulnerability an attacker with code execution on the IVI can send raw CAN messages over IDT and MPDT buses. An attacker can fully control the ID and content of a CAN message.

In case of requests to the NET_BROADCAST_PORT of inc-scc interface, the following handler is used:

    0002b420 00 00 00 00 00  SCC_HANDLER_DESC               [15]
             00 00 00 00 00 
             00 00 00 00 00
    0002b420 00 00 00 00     ddw   0h                      field0_0x0
    0002b424 00 00 00 00     addr  00000000                field1_0x4
    0002b428 00 00 00 00     addr  00000000                field2_0x8
    0002b42c 00 00 00 00     addr  00000000                field3_0xc
    0002b430 08 9a 06 00     addr  prepareNetBroadcast     pfnPrepareIn
    0002b434 6c 99 06 00     addr  fillNetBroadcast        pfnFillInput
    0002b438 46 99 06 00     addr  processNetBroadcast     pfnProcessRe
    0002b43c b0 99 06 00     addr  FUN_000699b0            field7_0x1c
    0002b440 fe 99 06 00     addr  FUN_000699fe            field8_0x20
    0002b444 44 ae df fe     addr  DAT_fedfae44            field9_0x24 = ??

Responsibilities of these functions:

  • prepareNetBroadcast: prepares a global buffer for an incoming request:
undefined4 prepareNetBroadcast(undefined4 param_1,int param_2) 
{ 
  undefined4 uVar1; 
 
  if ((*(ushort *)(param_2 + 4) < 0x65) && (DAT_fede6a60 == '\0')) { 
    gwNetBroadcastEnd = 0; 
    gpNetBroadcastPacket1 = gsNetBroadcastPacket; 
    uVar1 = 0; 
    gpNetBroadcastPacket2 = (dword *)gsNetBroadcastPacket; 
    DAT_fede6a60 = '\x01'; 
    gwBroadcastPacketLength = *(ushort *)(param_2 + 4); 
  } 
  else { 
    uVar1 = 1; 
  } 
  return uVar1; 
} 
  • fillNetBroadcast: copies data from a request to the previously prepared buffer:
int fillNetBroadcast(undefined4 param_1,undefined4 *param_2) 
{ 
  bool bVar1; 
  int in_r10; 
  int iVar2; 
 
  bVar1 = 99 < (uint)gwNetBroadcastEnd + (uint)*(ushort *)(param_2 + 1); 
  iVar2 = (uint)bVar1 + in_r10 * (uint)!bVar1; 
  if (!bVar1) { 
    memcpy(gpNetBroadcastPacket2,(undefined4 *)*param_2,(uint)*(ushort *)(param_2 + 1)); 
    iVar2 = 0; 
    gwNetBroadcastEnd = gwNetBroadcastEnd + *(ushort *)(param_2 + 1); 
    gpNetBroadcastPacket2 = (dword *)((int)gpNetBroadcastPacket2 + (uint)*(ushort *)(param_2 + 1)); 
  } 
  return iVar2; 
} 
  • processNetBroadcast -> processNetBroadcastInternal

In case of data packet type (0x50), the packet data should be copied on the stack buffer with the size of 0x20 bytes.

void processNetBroadcastInternal(undefined4 param_1,char i_dPacketSize,char *i_pPacket) 
{ 
  ...   
  iVar6 = FUN_00068fa6(); 
  if (iVar6 == 1) { 
    if (cRamfedf9834 == '\x01') { 
      if (*i_pPacket == 0x50) { 
        _local_30 = 0; 
        uStack_2c = 0; 
        uStack_28 = 0; 
        uStack_24 = 0; 
        pdVar7 = &local_38; 
        dID = *(undefined4 *)(i_pPacket + 8); 
        local_38 = 0; 
        local_34 = 0; 
        uVar5 = (uint)(byte)(i_dPacketSize - 0xdU); 
        uVar8 = 0; 
        if (uVar5 != 0) { 
          pcVar9 = i_pPacket + uVar5 + 0xc; 
          do { 
            cVar1 = *pcVar9; 
            pcVar9 = pcVar9 + -1; 
            uVar8 = uVar8 + 1; 
            *(char *)pdVar7 = cVar1;               // here copied on stack 
            pdVar7 = (dword *)((int)pdVar7 + 1); 
          } while (uVar8 < uVar5); 
        } 
        iVar6 = FUN_0005f360(dID,&local_38,i_dPacketSize - 0xdU,i_pPacket[0xc]); 
        if (-1 < iVar6) { 
          return; 
        } 
  ... 
} 

However, the previous step allows a transfer request with a data length that can exceed 0x20 bytes. As a result, a stack-based buffer overflow occurs.

Credits

The vulnerability was identified by Radu Motspan from PCAutomotive Security Assessment team.


CVE-2025-32059, CVE-2025-32061, CVE-2025-32062: Multiple stack overflows in Bluetooth stack of infotainment ECU leading to RCE

Description

Notice: The vulnerabilities CVE-2025-32059, CVE-2025-32061 and CVE-2025-32062 are similar.

Hands-Free control (HFP) is the entity responsible for Hands-Free unit specific control signalling; this signalling is an AT command based. HFP is one of L2CAP Upper Layers usually named as Profiles.

Hands-free profile architecture

A common scenario would be a mobile phone used together with a wireless headset or a Head Unit. The headset will connect to the mobile phone and can be used to place and receive phone calls. The HFP defines two roles, that of an Audio Gateway (HFP-AG) and a Hands-Free unit:

  • Audio Gateway (AG) – This is the device that is the gateway of the audio, both for input and output. Typical devices acting a- s Audio Gateways are cellular phones.
  • Hands-Free unit (HF) – This is the device acting as the Audio Gateway’s remote audio input and output mechanism. It also provides some remote control means.

Hands-free protocol stack

Therefore, HFP is used to mirror the audio from the AG to HF, plus some extra controlling messages to manage the communication process. Upon a user action or an internal event, either the HF or the AG may initiate a Service Level Connection establishment procedure. A Service Level Connection establishment requires the existence of a RFCOMM connection, that is, a RFCOMM data link channel between the HF and the AG.

When an RFCOMM connection has been established, the Service Level Connection Initialization procedure shall be executed. First, in the initialization procedure, the HF shall send the AT+BRSF=<HF supported features> command to the AG to both notify the AG of the supported features in the HF, as well as to retrieve the supported features in the AG using the +BRSF result code.

Service level connection establishment

HFP supports various standard AT commands in correspondence to Hands-Free Profile Bluetooth® Profile Specification. However, some vendors might add extra AT commands which are not included in the specification. Nissan Leaf infotainment ECU is not an exception here. A few additional AT commands were discovered such as +ANDROID, +APLSIRI, +APLNRSTAT and others.

PCAutomotive specialists believe that these commands are somehow related to the mobile Nissan application.

One of this custom AT command contains multiple buffer overflow vulnerabilities that may lead to RCE (Remote Code Execution) on the infotainment system:

The result of exploitation and gaining a reverse root shell from the IVI

The Bluetooth stack on Nissan Leaf infotainment ECU is implemented in several libraries and executable files.

  • libevo_stack.so: The actual low-level Bluetooth stack implementation. Reading data from the baseband on top of UART bus and the further parsing.
  • libevo_btappl.so: Application-level library that uses libevo_stack.so to issue low-level procedures such as sending SDP requests.
  • evo_hli_socket: Executable file that starts the BT stack.
  • evo_genivi_bt: Somehow connected with evo_hli_socket. This file wasn't fully analyzed.
  • Other libraries.

HFP functionality is in libevo_stack.so library. The initialization part of the HFP profile will be omitted here as irrelevant to the vulnerabilities presented.

When a new data portion is received, HF_ReceiveDataInd is executed as an indicator of the incoming data that must be processed.

int __fastcall HF_ReceiveDataInd(RfDlc *dlc, BtvBuf *pkt) 
{ 
  ...... 
      if ( v11 ) 
      { 
        len = (unsigned __int16)(*(_WORD *)(v11 + 16) + pkt->len); 
        v13 = j_BT_pAllocBuffer(len, 1);        //  
                                                // if it's the first HF frame in the packet, allocate 
                                                // a buffer of the corresponding size 
        v14 = (unsigned __int8)target[0]; 
        v13->pDataCur = v13->data; 
        v15 = v13; 
        v16 = &dword_48657E14[133 * v14]; 
        v17 = (char *)memcpy(v13->data, *(const void **)(v16[127] + 12), *(unsigned __int16 *)(v16[127] + 16)); 
        memcpy(&v17[*(unsigned __int16 *)(v16[127] + 16)], pkt->pDataCur, pkt->len); 
        j_BT_vFreeBuffer(pkt); 
        j_BT_vFreeBuffer((BtvBuf *)dword_48657E14[133 * (unsigned __int8)target[0] + 127]); 
        v4 = (unsigned __int8)target[0]; 
        v5 = 4 * (unsigned __int8)target[0]; 
        v6 = (unsigned __int8)target[0] << 7; 
        dword_48657E14[v5 + 127 + v6 + (unsigned __int8)target[0]] = v10; 
      } 
      else 
      { 
        len = pkt->len; 
        v15 = pkt; 
      } 
      j_BTOS_UnlockMutex(28, dword_48657E14[v5 + 108 + v6 + v4]); 
      j_GPP_GetParse4HFP((size_t (__fastcall **)(char *, unsigned __int8 *, size_t))&target[1]); 
      if ( !*(_DWORD *)&target[1] ) 
        goto LABEL_20; 
      v18 = (*(int (__fastcall **)(RfDlc *, unsigned __int8 *, unsigned int))&target[1])(dlc, v15->pDataCur, len);//  
                                                // HF_ParseRsp 
  ...... 
} 

The incoming frame is copied to the buffer allocated via j_BT_pAllocBuffer. Then, when a frame is ready, HF_ParseRsp is called to parse the received data. The mentioned HF_ParseRsp is responsible for AT commands responses handling contained in the packet. The buffer overflow vulnerability is in the custom +ANDROID AT command. Here is the code snippet with the relevant parts of the code:

size_t __fastcall HF_ParseRsp(RfDlc *dlc, unsigned __int8 *rxbf, size_t rxlen) 
{ 
  ... 
  unsigned __int16 probe_lens[2]; // [sp+4Ch] [bp-D4h] BYREF 
  ... 
  size_t params[10]; // [sp+8Ch] [bp-94h] BYREF 
  char v86[68]; // [sp+B4h] [bp-6Ch] BYREF 
 
    ............. 
                    if ( j_CmpBuffer(rxbf + 2, "+ANDROID:") ) 
                    { 
                      if ( rxbf != (unsigned __int8 *)-11 ) 
                      { 
                        v30 = rxbf + 10; 
                        v31 = 0; 
                        space_len = 0; 
                        do 
                        { 
                          v33 = *++v30; 
                          ++v31; 
                          if ( v33 != ' ' ) 
                            break; 
                          space_len = (unsigned __int8)v31; 
                        } 
                        while ( cmdLenAdv - 11 > v31 ); 
                        v34 = j_CmpBuffer(&rxbf[space_len + 11], "probe"); 
                        if ( v34 ) 
                        { 
                          v35 = (unsigned __int8)(space_len + 17); 
                          probe_params = 0; 
                          probe_bf = &rxbf[v35]; 
                          proble_len = cmdLenAdv - v35; 
                          v78 = 0; 
                          probe_lens[0] = 0; 
                          probe_lens[1] = 0; 
                          memset(tmp_params, 0, 13); 
                          memset(params, 0, 13); 
                          LOWORD(tmp_lens) = 0; 
                          v80 = 0; 
                          if ( j_AC_IsOpenLayer(32) ) 
                            j_AlpsCloud_LogPrint(2u, 32829, 7395, 2, 0); 
                          j_BT_LocalTraceLogPrintEx(1, 0x803D1CE3, 32, 8, 0, 0); 
                          if ( !probe_bf ) 
                          { 
                            v13 = 1; 
                            j_BT_LocalTraceLogPrintEx(16, 0x803D1CE6, 32, 4, 1u, 0); 
                            goto LABEL_27; 
                          } 
                          v37 = j_GetParameters( 
                                  probe_bf, 
                                  (unsigned __int16)(proble_len - 2), 
                                  &probe_params, 
                                  probe_lens, 
                                  2u); 
                          switch ( v37 ) 
                          { 
                            case 0: 
                              LOWORD(v38) = 7405; 
                              goto LABEL_150; 
                            case 2:             // we have 2 parameters 
                              if ( (unsigned int)probe_lens[1] - 2 <= 0xC ) 
                              { 
                                v40 = probe_lens[0]; 
                                memcpy(params, probe_params, probe_lens[0]);//  
                                                // BUG: stack buffer overflow 
                                                // no boundary checks on the first param 
    ............ 
} 

+ANDROID might contain various sub-commands such as probe, handshake, audiosource and vds. The vulnerabilities CVE-2025-32059, CVE-2025-32061 and CVE-2025-32062 exist during processing subcommands probe, handshake, audiosource and vds correspondently. The vulnerability in probe sub-command will be described.

After the command response +ANDROID is encountered, all the space characters are skipped. Then, a sub-command string is validated. If it is probe, then proceed to the parameters reading. Parameters are named variables having the following format:

\r +ANDROID <sub-command>="parameter 0","parameter 1",<...>

Double quotes are optional. j_GetParameters function reads the parameters presented in the sub-command and returns them as the two buffers: probe_params and probe_lens. The former contains the pointers to the parameters in the sub-command strings, the latter is their corresponding lengths. Notice that lengths are stored in uint16_t type, therefore one parameter might be up to 65535 bytes in length.

For a reference, there is the implementation of j_GetParameters:

int __fastcall GetParameters( 
        unsigned __int8 *rxbf, 
        int rxlen, 
        _DWORD *params, 
        _WORD *lens, 
        unsigned __int16 max_param_cnt) 
{ 
  bool tmp; // zf 
  int v10; // r3 
  unsigned __int8 *curbf; // r0 
  char param_brkt_cnt; // r9 
  int param_cnt; // r1 
  int flag_add; // r12 
  unsigned __int8 *endbf; // lr 
  unsigned __int8 *param; // r7 
  int chr; // t1 MAPDST 
  unsigned __int16 param_cnt_adv; // r2 
  int v20; // r2 
 
  if ( j_AC_IsOpenLayer(32) ) 
    j_AlpsCloud_LogPrint(2u, 32829, 5851, 2, 2u); 
  j_BT_LocalTraceLogPrintEx(1, 0x803D16DB, 32, 8, 0, 2u); 
  tmp = lens == 0; 
  if ( lens ) 
    tmp = params == 0; 
  v10 = tmp; 
  if ( !rxbf ) 
    v10 |= 1u; 
  if ( v10 ) 
  { 
    j_BT_LocalTraceLogPrintEx(16, 0x803D16DF, 32, 1, rxlen, 0); 
    return 0; 
  } 
  else 
  { 
    if ( !rxlen ) 
      return 0; 
    curbf = rxbf; 
    *params = 0; 
    param_brkt_cnt = 0; 
    *lens = 0; 
    param_cnt = 0; 
    flag_add = 1; 
    endbf = &curbf[(unsigned __int16)(rxlen - 1) + 1]; 
    while ( 1 ) 
    { 
      param = curbf; 
      chr = *curbf++; 
      if ( chr == 0x22 )                        // '"' 
        break; 
      if ( chr != 0x2C )                        // ',' 
        goto add_chr; 
      param_cnt_adv = param_cnt + 1; 
      if ( (param_brkt_cnt & 0xFD) != 0 )       //  
                                                // first of all we need to encounter a " symbol to start 
                                                // counting the length of a parameter 
      { 
add_chr: 
        tmp = flag_add == 1; 
        flag_add = 0; 
        ++lens[param_cnt]; 
        if ( tmp ) 
          params[param_cnt] = param; 
        if ( curbf == endbf ) 
          return (unsigned __int16)(param_cnt + 1); 
      } 
      else 
      { 
        param_cnt = param_cnt_adv; 
        flag_add = 1; 
        param_brkt_cnt = 0; 
        tmp = max_param_cnt == param_cnt_adv; 
        v20 = param_cnt_adv; 
        if ( tmp ) 
          return 0; 
        params[param_cnt] = 0; 
        lens[v20] = 0; 
        if ( curbf == endbf ) 
          return (unsigned __int16)(param_cnt + 1); 
      } 
    } 
    ++param_brkt_cnt; 
    goto add_chr; 
  } 
} 

When parameters are read, they are ready to be copied into params variable which is a buffer located in the stack frame of the current HF_ParseRsp function. There are no boundaries check when the copying takes place, hence an attacker can overflow stack buffer and overwrite the return value of the stack frame. In libevo_stack.so there are no stack canaries presented as a security measure to prevent such kind of attack. Eventually, an attacker can take the execution control and spawn a remote shell.

Such an attack requires an attacker to be paired with the head unit prior to the HFP channel communication. In the case of Nissan Leaf, the pairing process can be accomplished entirely unnoticed by the end-user with no interactions except clicking the Add New button in the Bluetooth submenu.

Credits

The vulnerabilities were identified by Mikhail Evdokimov from PCAutomotive Security Assessment team.


CVE-2025-32060: Absence of Linux kernel module signature verification on infotainment ECU

Description

Kernel modules are pieces of code that can be loaded and unloaded into the kernel upon demand. They extend the functionality of the kernel without the need to reboot the system. Nissan Leaf infotainment ECU has Linux as its main Operating System, therefore kernel modules uploading is available there.

The kernel module signing facility cryptographically signs modules during installation and then checks the signature upon loading the module. This allows increased kernel security by disallowing the loading of unsigned modules or modules signed with an invalid key. Module signing increases security by making it harder to load a malicious module into the kernel. The module signature checking is done by the kernel so that it is not necessary to have trusted user-space bits.

The module signing facility is enabled using CONFIG_MODULE_SIG kernel config line. Due to absence of the kernel module signing facility, a malicious user that have a code execution on behalf of root user, can upload custom kernel modules affecting the entire system.

As an example, an attacker can disable exchnd built-in kernel module to obtain a reliable debugging feature on the system. This can be done by uploading a custom kernel module that will call an exit function of exchnd module, thus disabling it completely.

dmsg with the exchnd module removed log message

Credits

The vulnerability was identified by Mikhail Evdokimov from PCAutomotive Security Assessment team.


CVE-2025-32063: Enabling SSH server on infotainment ECU

Description

There is an ALD service in the infotainment ECU manufactured by BOSCH. Inside, this service can turn off security measures that are legitimately designed by the developers. This functionality can be achieved using a private RSA key. But in this case, using a vulnerability in the configuration, an attacker can force the system to disable the firewall and start the SSH server during the start process. An attacker should have local access to the IVI beforehand.

Initially, all the incoming connections are restricted by the firewall, which is started according to the following config:

[Unit] 
Description=Firewall configuration 
DefaultDependencies=no 
## dynamic partition is needed because of below Condition statements 
After=pretty-early.target rbcm-mount-dynamic.target 
OnFailure=firewall-emergency.service 
#none of the following files should exist 
ConditionPathExists=|/var/opt/bosch/dynamic/ald/FWdisabled 
ConditionPathExists=|/var/run/ald/FWdisabled 

[Service] 
RemainAfterExit=yes 
Type=oneshot 
ExecStart=/etc/network/setup_firewall.sh /etc/network/iptables_cache/ipt_productive.v4 /etc/network/iptables_cache/ipt_productive.v6 

[Install] 
RequiredBy=firewall.target 

Omitting the comments, the firewall will start only if both files do not exist. Otherwise, the firewall will be disabled:

root@MYCAR:~## iptables -L 
Chain INPUT (policy ACCEPT) 
target     prot opt source               destination 
​ 
Chain FORWARD (policy ACCEPT) 
target     prot opt source               destination 
​ 
Chain OUTPUT (policy ACCEPT) 
target     prot opt source               destination 

In the folder /lib/systemd/system there is a service sshd@.service registered with the following description:

[Unit] 
Description=OpenSSH Per-Connection Daemon (AIVI) 
## as the service depends on existing files, the partition need to be available 
After=syslog.target rbcm-mount-dynamic.target ald_once.service tty-ssh-checker.service 
Wants=tty-ssh-checker.service 
DefaultDependencies=no## this service is protected by ALD! 
## it only starts, if FEATURE is either enabled permanently or (usage of |) temporarily 
ConditionPathExists=|/var/run/ald/SSHenabled 
ConditionPathExists=|/var/opt/bosch/dynamic/ald/SSHenabled 
## the existence of the ald folders is ensured by the dependency to ald.service ## in disabled state, the ald folders contain only FEATdisabled files. 
## in virgin startup, the ald-folders do not contain FEATdisabled files. 
## on update startup, the dyn/ald-folder contains no FEATxxabled files, then, the service shall not start, too. ## NOTE: the ideal of negative logic, referring to "disabled" files does not work out fine, as for features that are temporarily enabled, the dynamic partition contains a "disabled" file. [Service] 
Environment="SSHD_OPTS=" 
EnvironmentFile=-/etc/default/ssh 
ExecStart=-/usr/sbin/sshd -i $SSHD_OPTS 
ExecReload=/bin/kill -HUP $MAINPID 
StandardInput=socket 
StandardError=syslog 
​ 
######################### 
## ADIT add-on for debugging: 
## 
## Specifies whether to send SIGKILL to remaining processes after a 
## timeout, if the normal shutdown procedure left processes of the 
## service around. Takes a boolean value. 
## Defaults to "yes". 
#SendSIGKILL=no 
​ 
​ 
## Specifies how processes of this service shall be killed. One of 
## control-group, process, none. 
## Defaults to "control-group" 
## "control-group" all remaining processes in the control group will be terminated ## "process" only the main process itself is killed 
## "none" no process is killed. 
KillMode=process 

According to it, the service will be started if the file/var/opt/bosch/dynamic/ald/SSHenabled exists.

In file /bin/ald_prepRootLogin.sh, there is a check for file /var/opt/bosch/dynamic/ald/rootLogindisabled existence:

#!/bin/bash 
## assumption: dynamic partition target reached in systemd!  
## assumption: an /etc/shadow, there exists a second shadow file - allowing root 
## assumption: the shadow in productive configuration is the default, the rooted one will be overloaded by mount -B. 
## assumption: in dynamic partition: existence of rootLoginenabled indicates an unlocked root login 
## assumption: in dynamic partition: existence of rootLogindisabled indicates a locked root login 
## assumption: in dynamic partition: no rootLoginxxx file => assume an update occured or the firmware is fresh => ALD-udpate will trigger the ALD to repair its state (update) or set it to open (initial flash) => start with locked root login. 
##             sideeffect: if ALD not started/existing, root login does not get unlocked 
##             workarounds: TTY can be used to open root login temporarily by "mount -B /etc/shadow /etc/" 
##                          or TTY can be used to "touch /var/opt/bosch/dynamic/ald/FWenabled" 
## before start of firewall service, update default settings, use bind-mount ## if dyn/FEATUREdisabled exists, it is disabled, i.e. normal productive config, i.e. best performance needed. 
if [ ! -f /var/opt/bosch/dynamic/ald/rootLogindisabled ] || [ -f /var/run/ald/rootLoginenabled ] ; then  
    ## note: FEATUREdisabled indicates FEATURE (root login) is not enabled, i.e. FEATURE is productive/locked/disabled #For reason of a systemd-service restart: check whether productive rules are already overloaded! If yes, remove the mounts! 
    mount=`grep "/etc/shadow" /proc/mounts` 
    if [ -n "${mount}" ]; then 
        /bin/umount /etc/shadow 
    fi## note: missing FEATUREdisabled means root-login-unlock is maybe enabled <=> root login is maybe in unlocked state, or target ALD status is "not yet defined"  
    if [ -f /var/opt/bosch/dynamic/ald/rootLoginenabled ] || [ -f /var/run/ald/rootLoginenabled ]; then  
        ## now we are sure, the root login unlock is enabled 
        /bin/mount -B /etc/shadow_w_root /etc/shadow 
##   else 
        ## target is in ALD status "not yet defined"  
        ## root login unlock is not disabled, nor enabled => original flashing or image. 
        ## root login should be productive first. 
        ## ALD is responsible to open the root login with virgin level change. 
    fi 
fi 

Therefore, performing the following actions the SSH server can be accessed from a Wi-Fi network:

rm /var/opt/bosch/dynamic/ald/SSHdisabled 
rm /var/opt/bosch/dynamic/ald/rootLogindisabled 
touch /var/opt/bosch/dynamic/ald/SSHenabled 
touch /var/opt/bosch/dynamic/ald/rootLoginenabled 
rm /var/opt/bosch/dynamic/ald/FWdisabled 

The preceding description is true for an older version of the Nissan Leaf ZE1 - 2020 IVI (for example version 283c36786r). For a modern firmware (version 283c30861e), the service tty-ssh-checker.service was added. Internally, the service calls /bin/tty_ssh_level_recheck.sh script:

#!/bin/bash 
Marker_Path=/var/opt/bosch/dynamic/ald 
ALD_Level=$(dbus-send --system --dest=com.adit.de.ALD --print-reply /com/adit/de/ALD/level_status org.freedesktop.DBus.Properties.Get string:'com.adit.de.ALD.level_status' string:'level' | sed -n '${s/.* //; p}') 
if [ ${ALD_Level} -le 30 ]; 
then 
    if [ -f ${Marker_Path}/TTYenabled ]; 
    then 
        rm ${Marker_Path}/TTYenabled 
        touch ${Marker_Path}/TTYdisabled 
    fi 
fi 
if [ ${ALD_Level} -lt 30 ]; 
then 
    if [ -f ${Marker_Path}/SSHenabled ]; 
    then 
        rm ${Marker_Path}/SSHenabled 
        touch ${Marker_Path}/SSHdisabled  
    fi 
fi 
sync 
exit 0 

As a result, sshd service does not start.

The presented patch can be bypassed, by creating a symbolic link SSHdisabled -> SSHenabled using the following command:

ln -s SSHenabled SSHdisabled

Eventually, in the script /bin/tty_ssh_level_recheck.sh, it first deletes SSHenabled and then performs touch SSHdisabled. The latter transforms into touch SSHenabled; therefore, this file will be created again.

Hence, if an attacker adds the controlled Wi-Fi network to the list of favourite Wi-Fi networks, the IVI will connect to it during the startup. After that, an attacker will be able to access the SSH server launched by the IVI after reboot.

Credits

The vulnerability was identified by Radu Motspan from PCAutomotive Security Assessment team.


CVE-2017-7932: Stack Overflow in HAB of i.MX 6 leading to the Secure Boot Bypass on infotainment ECU

Description

The infotainment ECU manufactured by BOSCH is vulnerable to the known bug CVE-2017-7932, which was found by QuarksLab and fully detailed in their blog.

Proof-of-Concept for this vulnerability can be found in usbarmory project.

During the secure boot process, boot arguments from DTB (Device Tree Blob) are passed to the Linux kernel as command line. Further, dm-verity sub-system is used to mount root filesystem. Before loading, the filesystem is checked using the hashes in kernel command line.

Using this vulnerability, an attacker can create a fake DTB on mmcblk1boot0 that bypasses the sign mechanism and modify boot arguments from DTB passed to the Linux kernel. This leads to disabling dm-verity mechanism and modifying the filesystem. Finally, this step allows an attacker to gain persistence on the infotainment ECU.

Through the following value of boot arguments attacker can disable verity checks:

bootargs3 = "console=ttymxc3,115200n8 root=/dev/dm-0 dm=\"vroot,,rw, 0 6262736 verity 1 179:26 179:26 4096 4096 … sha256 … 1 ignore_corruption\" rootwait ro maxcpus=4 printk.time=1 ip=off consoleblank=0 lpj=7905280 quiet oops=panic panic=1 vmalloc=384m dm_verity.prefetch_cluster=131072 dmwait"; 

Patched version of DTB should be written to /dev/mmcblk1boot0 using the following commands:

echo 0 > /sys/block/mmcblk1boot0/force_ro 

dd if=/tmp/mmcblk1boot0_poc of=/dev/mmcblk1boot0 bs=4096 count=8160 

echo 1 > /sys/block/mmcblk1boot0/force_ro 

After system rebooting, root filesystem can be remounted with write privileges and modified:

Modification of the root file system of the IVI Linux OS

CVE-2017-9832: Integer Overflow leading to code execution in processing MTP on infotainment ECU

Description

The infotainment ECU manufactured by BOSCH is vulnerable to the known bug CVE-2017-9832.

The Media Transfer Protocol (MTP) is an extension to the Picture Transfer Protocol (PTP) communications protocol that allows media files to be transferred automatically to and from portable devices. Media Transfer Protocol allows the transfer of music files on digital audio players and media files on portable media players, as well as personal information on personal digital assistants. According to its specification, the main purpose of MTP is to facilitate communication between media devices with transient connection.

The media player functionality is implemented in executable file /opt/bosch/mediaplayer/bin/ccamediaplayer_out.out that has various library dependencies. MTP is one of such a library that implements its core functionality and resides in /usr/lib/libmtp.so. libmtp is an open-source library that implements an MTP initiator, which means it initiate MTP sessions with devices. The devices responding are known as MTP responders. libmtp runs on something with a USB host controller interface, using libusb to access the host controller.

There is a known integer overflow vulnerability under the ID of CVE-2017-9832 which is also present in libmtp.so in the IVI firmware. An integer overflow vulnerability in ptp-pack.c (ptp_unpack_OPL function) of libmtp (version 1.1.12 and below) allows attackers to cause a denial of service (out-of-bounds memory access) or remote code execution by inserting a mobile device into a personal computer through a USB cable.

PCAutomotive specialists were able to develop a trigger that takes advantage of the vulnerability and crashes the Head Unit. Furthermore, the resulting trigger could be improved to obtain code execution on the IVI. This was confirmed on test Raspberry Pi device (setup for debugging), which uses the same version of libmtp.so

The vulnerability is triggered automatically when the HU examines the MTP objects tree of the device connected through USB cable.

get_all_metadata_fast command gets all handles and stuff by FAST directory retrieval which is available by getting all metadata for object 0xffffffff which simply means "all metadata for all objects". This works on the vast majority of MTP devices (there are exceptions!) and is quite quick.

Each object has a metadata information stored in the property list. The mentioned data is retrieved in ptp_mtp_getobjectproplist.

uint16_t 
ptp_mtp_getobjectproplist (PTPParams* params, uint32_t handle, MTPProperties **props, int *nrofprops) 
{ 
    uint16_t ret; 
    PTPContainer ptp; 
    unsigned char* opldata = NULL; 
    unsigned int oplsize; 
 
    PTP_CNT_INIT(ptp); 
    ptp.Code = PTP_OC_MTP_GetObjPropList; 
    ptp.Param1 = handle; 
    ptp.Param2 = 0x00000000U;  /* 0x00000000U should be "all formats" */ 
    ptp.Param3 = 0xFFFFFFFFU;  /* 0xFFFFFFFFU should be "all properties" */ 
    ptp.Param4 = 0x00000000U; 
    ptp.Param5 = 0xFFFFFFFFU;  /* means - return full tree below the Param1 handle */ 
    ptp.Nparam = 5; 
    ret = ptp_transaction(params, &ptp, PTP_DP_GETDATA, 0, &opldata, &oplsize);   
    if (ret == PTP_RC_OK) *nrofprops = ptp_unpack_OPL(params, opldata, props, oplsize); 
    if (opldata != NULL) 
        free(opldata); 
    return ret; 
} 

The variable opldata is a byte blob fetched from the connected USB device controlled by an attacker. In that case, in ptp_unpack_OPL function which is called at the end of ptp_mtp_getobjectproplist, the integer overflow happens:

static inline int 
ptp_unpack_OPL (PTPParams *params, unsigned char* data, MTPProperties **pprops, unsigned int len) 
{  
    uint32_t prop_count = dtoh32a(data); // [1] 
    MTPProperties *props = NULL; 
    int offset = 0, i; 
 
    if (prop_count == 0) { 
        *pprops = NULL; 
        return 0; 
    } 
    ptp_debug (params ,"Unpacking MTP OPL, size %d (prop_count %d)", len, prop_count); 
    data += sizeof(uint32_t); 
    len -= sizeof(uint32_t); 
    props = malloc(prop_count * sizeof(MTPProperties)); // [2] 
    if (!props) return 0; 
    for (i = 0; i < prop_count; i++) { 
        if (len <= 0) { 
            ptp_debug (params ,"short MTP Object Property List at property %d (of %d)", i, prop_count); 
            ptp_debug (params ,"device probably needs DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST_ALL", i); 
            ptp_debug (params ,"or even DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST", i); 
            qsort (props, i, sizeof(MTPProperties),_compare_func); 
            *pprops = props; 
            return i; 
        } 
        props[i].ObjectHandle = dtoh32a(data); // [3] 
        data += sizeof(uint32_t); 
        len -= sizeof(uint32_t); 
 
        props[i].property = dtoh16a(data); // [3] 
        data += sizeof(uint16_t); 
        len -= sizeof(uint16_t); 
 
        props[i].datatype = dtoh16a(data); // [3] 
        data += sizeof(uint16_t); 
        len -= sizeof(uint16_t); 
 
        offset = 0; 
        ptp_unpack_DPV(params, data, &offset, len, &props[i].propval, props[i].datatype); 
        data += offset; 
        len -= offset; 
    } 
    qsort (props, prop_count, sizeof(MTPProperties),_compare_func); 
    *pprops = props; 
    return prop_count; 
} 
  1. uint32_t value is obtained from the attacker's provided data. There are no integer validations presented in the function.
  2. Property list is allocated as the multiplication of prop_count and the size of MTPProperties structure. Due to the absence of validations, the integer overflow vulnerability takes place here. It leads to an allocated chunk of a less size than is expected.
  3. Finally, after the allocation, the function iterates over the property list passed to the function and assigns the controlled header values to the allocated chunk. If the target chunk's size is less than the property list size, the heap buffer overflow can happen by the property objects.

Vulnerability chaining

The following figure summarizes the impact achieved and the way how identified vulnerabilities can be chained with each other. Arrows identify vulnerabilities, while rectangles describe achieved impact.

Vulnerability chaining and impact

Vulnerabilities CVE-2025-32059, CVE-2025-32061, CVE-2025-32062 and CVE-2017-9832 allow a would-be attacker to gain root access to the infotainment system either via Bluetooth or USB. With those privileges, a would-be attacker can utilize CVE-2025-32058 to achieve complete control over the RH850 CPU and send arbitrary commands to in-vehicle CAN bus. When combined with CVE-2017-7932 and CVE-2025-32063, this chain becomes even more dangerous, as a would-be attacker will have persistent control over the vehicle and will be able to trigger an attack compromising vehicle owner’s safety at any arbitrary moment in time, even if the IVI ECU will be turned off and on again.

Related publications

PCA made a talk about discovered vulnerabilities at BlackHat Asia 2025.

Article tags

nissan

vehicle penetration testing

bosch

infotainment system vulnerability

Popular tags

security advisory

skoda

pcautomotive

nissan

vehicle penetration testing

bosch

infotainment system vulnerability

vw

mib3 infotainment unit

preh car connect gmbh

Credits

Undefined

Polina Smirnova

Senior Security Researcher

Undefined (1)

Radu Motspan

Senior Security Researcher

Mikhail Evdokimov

Mikhail Evdokimov

Senior Security Researcher