Header bannerHeader banner
January 9, 2023

Multiple Vulnerabilities in Qualcomm and Lenovo ARM-based Devices

Binarly Team

Binarly researchers conduct a deep-dive investigation into Lenovo’s LEN-94952 bulletin and find that two vulnerabilities -- CVE-2022-3430 and CVE-2022-3431 -- remain unfixed one month after their official disclosure.

In today's disclosure we opened Pandora's box of ARM devices with UEFI firmware vulnerabilities impacting enterprise vendors. As far as we know, this is the first major vulnerability disclosure related to UEFI firmware on ARM. The big part of vulnerabilities disclosed today related to Qualcomm’s reference code for Snapdragon chips. The vulnerabilities in reference code are usually one of the most impactful since they tend to affect the whole ecosystem and not just a single vendor. Due to the complexity of the UEFI firmware supply chain, these vulnerabilities often create additional impact. UEFI's unified specification not only brings consistency to the firmware development process, but also to the attack surface. This consistency creates cross-platform attacks, so many attack vectors from the x86 ecosystem will also be available on ARM, though exploitation specifics will differ.

BRLY ID Type Vendor CVE ID CVSS score CWE
BRLY-2022-029
BRLY-2022-030
BRLY-2022-033
Stack overflow
via double
GetVariable in
DXE driver
Qualcomm
Qualcomm
Qualcomm
CVE-2022-40516
CVE-2022-40517
CVE-2022-40520
8.2 (HIGH)
8.2 (HIGH)
8.2 (HIGH)
CWE-121:
Stack-based Buffer Overflow
BRLY-2022-031
BRLY-2022-032
BRLY-2022-034
BRLY-2022-035
BRLY-2022-036
BRLY-2022-037
Stack memory
leak
vulnerability in
DXE driver
Qualcomm
Lenovo
Lenovo
Lenovo
Qualcomm
Lenovo
CVE-2022-40518
CVE-2022-4432
CVE-2022-4433
CVE-2022-4434
CVE-2022-40519
CVE-2022-4435
4.9 (MEDIUM)
6.0 (MEDIUM)
6.0 (MEDIUM)
6.0 (MEDIUM)
6.0 (MEDIUM)
6.0 (MEDIUM)
CWE-125:
Out-of-bounds Read

Usually, UEFI firmware related vulnerabilities are disclosed from the perspective of the x86 ecosystem on Intel or AMD based devices. This is the first public disclosure in history of UEFI specification related to the ARM device ecosystem. It shows some of the attacks and classes of bugs can be the same on both ARM and x86 devices, but exploitation specifics will be different. These vulnerabilities are confirmed on Lenovo’s Thinkpad and Microsoft’s Surface devices during our research. Even the recently released development device Microsoft Windows Dev Kit 2023 (code name “Project Volterra”) is impacted.

“Providing technologies that support robust security and privacy is a priority for Qualcomm Technologies. We commend security researcher Alex Matrosov of Binarly for using industry-standard coordinated disclosure practices, and we have worked with Lenovo to address the reported boot issues. Patches were made available in November 2022, and we encourage affected end users to apply security updates when they become available from their device makers.” – Qualcomm Spokesperson

These three vulnerabilities BRLY-2022-029, BRLY-2022-030, BRLY-2022-033 have a high-impact CVSS score since they can lead to a secure boot bypass, and enable an attacker to gain persistence on a device by gaining sufficient privileges to write to the file system, thus allowing an attacker to cross an extra security boundary to simplify attacks on TrustZone. All three are impacting Qualcomm’s reference code and affect the entire ecosystem.

BRLY-2022-031 and BRLY-2022-036 are also related to Qualcomm's reference code, and BRLY-2022-032, BRLY-2022-034, BRLY-2022-035, and BRLY-2022-037 are specific to Lenovo. An attacker can gain read access to the privileged boot code through all of these vulnerabilities. Compared to the previous group of vulnerabilities with arbitrary code execution, these vulnerabilities only lead to privileged information disclosure, and so are less impactful.

Let's dive into each class of problems to gain more detailed information about each of them.

NVRAM-based memory disclosure vulnerabilities

Binarly REsearch team identified these vulnerabilities while analyzing the firmware for the Lenovo Thinkpad X13s device, we found six vulnerabilities that lead to writing of the stack contents to NVRAM. In our previous blog, we discussed in detail how NVRAM can be exploited by attackers. As a result, these vulnerabilities can be considered stack memory leaks or privileged information disclosures. Potential attackers can use these primitives to get advantage and exploit other vulnerabilities in the DXE boot phase.

In general terms, the vulnerable code pattern can be summarized as follows:

UINTN DataSize = N;
EFI_STATUS Status = EFI_SUCCESS;
Status = gRT->GetVariable(L"ExampleVariable", &gExampleVariableGuid, &Attributes, &DataSize, Data);
...
// Status has not been checked or has been compared to EFI_BUFFER_TOO_SMALL
gRT->SetVariable(L"ExampleVariable", &gExampleVariableGuid, Attributes, DataSize, Data);

After the first gRT->GetVariable() call, DataSize is not re-initialized prior to the call to gRT->SetVariable(). Thus, when the size of the existing NVRAM variable ExampleVariable is larger than the original DataSize value (N), the updated DataSize - N additional bytes will be written to NVRAM on the call to gRT->SetVariable.

In summary, if an attacker can control the value of the NVRAM variable, he can control the DataSize value, and if gRT->SetVariable() uses a DataSize value that is not checked or re-initialized, prior to the gRT->SetVariable() call, the NVRAM variable ExampleVariable will contain additional data from the stack to expose.

With this type of vulnerability, it should be noted that in some cases it may be possible to retrieve the contents of global memory or SMRAM, for example, when the vulnerable pattern is present in a SMI handler or Trust Zone application or privileged callback function.

Let’s dive into examples from the Qualcomm and Lenovo vulnerability disclosures.

BRLY-2022-031/CVE-2022-40518

Vulnerable code pattern in ResetRuntimeDxe driver:

Fig 1

As we can see from the pseudocode, the gRT->SetVariable() service is called with the DataSize value, which can be overwritten inside the gRT->GetVariable() service.

Thus, a potential attacker can write X - 4 bytes from the stack to NVRAM if writes any buffer of length X > 4 to the BSPowerCycles NVRAM variable. In order to fix this vulnerability, the DataSize variable must be initialized before gRT->SetVariable().

  SetVariable = gRT->SetVariable;
  VariableValue = val;
  DataSize = 4;
  return (SetVariable)(L"BSPowerCycles", &gVariableGuid, 3, DataSize, &VariableValue);

BRLY-2022-032/CVE-2022-4432

Vulnerable code pattern in PersistenceConfigDxe driver:

Fig 2

As we can see from the pseudocode, for the LenovoAbtStatus variable gRT->SetVariable() service is called with the DataSize value, which can be overwritten inside the gRT->GetVariable() service. Thus, a potential attacker can write X - 14 bytes from the stack to NVRAM if writes any buffer of length X > 14 to the LenovoAbtStatus NVRAM variable.

In order to fix this vulnerability, the DataSize variable must be initialized before gRT->SetVariable().

BRLY-2022-034/CVE-2022-4433

Vulnerable code pattern in LenovoSetupConfigDxe driver:

Fig 3

As we can see from the pseudocode, the gRT->SetVariable() service is called with the DataSize value, which can be overwritten inside the gRT->GetVariable() service. Thus, a potential attacker can write X - 49 bytes from the stack to NVRAM if writes any buffer of length X > 49 to the LenovoFunctionConfig NVRAM variable.

In order to fix this vulnerability, the DataSize variable must be initialized before gRT->SetVariable():

GetVariable = gRT->GetVariable;
  DataSize = 49;
  GetVariable(L"LenovoFunctionConfig", &gVariableGuid, &v24, &DataSize, Value);

  Value[0] = 0;
  DataSize = 49; // <--- added
  result = (gRT->SetVariable)(L"LenovoFunctionConfig", &gVariableGuid, v24, DataSize, Value);

BRLY-2022-035/CVE-2022-4434

Vulnerable code pattern in SystemErrorMenuDxe driver shown below:

Fig 5

We can generate the pseudocode manually because the IDA has difficulties with this code snippet to decompile it from the ARM assembly. Previously in one of our blog posts “ARM-Based Firmware Support In New efiXplorer V5.0 [LABScon Edition]” we already discussed ARM decompiler and code analysis limitations in IDA.

Fig 6

As we can see from the pseudocode, the gRT->SetVariable() service is called with the DataSize value, which can be overwritten inside the gRT->GetVariable() service. Thus, a potential attacker can write X - 8 bytes from the stack to NVRAM if writes any buffer of length X > 8 to the PwdUnlockErr NVRAM variable.

In order to fix this vulnerability, the DataSize variable must be initialized before gRT->SetVariable():

 DataSize = 8;
  if ( (gRT->GetVariable(L"PwdUnlockErr", &gVariableGuid, 0, &DataSize, Value) & 0x8000000000000000) != 0 )
    v10 = 1;
  else
    v10 = *(v6 + 1);
  SetVariable = gRT->SetVariable;
  Value[0] = v10;

  DataSize = 8; // <--- added
  SetVariable(L"PwdUnlockErr", &gVariableGuid, VARIABLE_ATTRIBUTE_NV_BS_RT, DataSize, Value);

BRLY-2022-036/CVE-2022-40519

The vulnerable code pattern based on QcomBds driver. And vulnerable functions are a part of EFI protocol with GUID d874d61a-4b87-7608-a00f-58add7052530. The pseudocode of the vulnerable code is shown below:

Fig 7

As we can see from the pseudocode, for the RunCycles variable gRT->SetVariable() service is called with the DataSize value, which can be overwritten inside the gRT->GetVariable() service. Thus, a potential attacker can write X - 4 bytes from the stack to NVRAM if writes any buffer of length X > 4 to the RunCycles NVRAM variable.

In order to fix this vulnerability, the DataSize variable must be initialized before gRT->SetVariable().

BRLY-2022-037/CVE-2022-4435

The vulnerable code pattern in LenovoRemoteConfigUpdateDxe driver. The vulnerable function is a part of the protocol with GUID d874d61a-4b87-7608-a00f-58add7052530.

The pseudocode of the vulnerable code pattern is shown below:

Fig 8

As we can see from the pseudocode, for the LenovoFunctionConfig  variable gRT->SetVariable() service is called with the DataSize value, which can be overwritten inside the gRT->GetVariable() service.

Thus, a potential attacker can write X - 49 bytes from the stack to NVRAM if writes any buffer of length X > 49 to the LenovoFunctionConfig NVRAM variable.

In order to fix this vulnerability, the DataSize variable must be initialized before gRT->SetVariable().

Clearly, the vulnerable code pattern illustrates the repeatable failures in the development process to detect obvious security issues with existing static analysis toolchains for product security needs. Currently, there are no static analysis tools that focus especially on firmware security since firmware applications have a lot of code specifics that do not exist in operating system applications, but open wide attack surfaces. This is exactly the reason why the Binarly team started working on the next generation tool to address firmware specific security issues at scale.

The impact of memory leaks is usually rated as medium since they do not provide an attacker with the ability to execute malicious code, but still expose sensitive information from privileged memory. Let's now investigate another class of vulnerabilities disclosed with Lenovo and Qualcomm, which can lead to arbitrary code execution with high severity.

NVRAM-based stack overflow vulnerabilities

To understand such class of the security problems in more detail, check our previous blog “efiXplorer: Hunting UEFI Firmware NVRAM Vulnerabilities”. The Binarly REsearch team discussed how to hunt NVRAM-based vulnerabilities using our IDA plugin called efiXplorer. More than a hundred security issues related to NVRAM stack overflows have already been reported by our team to enterprise device vendors and some of them disclosed at multiple security conferences including Black Hat. Let's take a closer look at the security problems disclosed this time.

BRLY-2022-029/CVE-2022-40516

Vulnerable code pattern in QcomChargerDxeWp driver shows a classic example of a vulnerability that arises from incorrect usage of gRT->GetVariable() API , which leads to a stack buffer overflow during execution.

Fig 9

As we can see from the pseudocode, DataSize is initialized only once (before the first call to gRT->GetVariable()).

Thus, if the data size of the variable in NVRAM is greater than 1 (for any NVRAM variable: DISABLEBATTERY, PrintChargerAppDbgMsg, ChargerPDLogLevel, ChargerPDLogTimer, ForcePowerTesting), DataSize will be overwritten (technically all of them, can be reported as separate vulnerabilities). Thus, the next call to gRT-GetVariable() may cause a stack overflow, and subsequently allow execution of arbitrary code.

BRLY-2022-030/CVE-2022-40517

Vulnerable code pattern in PILDxe driver:

Fig 10

As we can see from the pseudocode, DataSize does not initialized before each call to gRT->GetVariable()(technically all of them can be reported as separate vulnerabilities). In this case a potential attacker can trigger the stack buffer overflow and execute the arbitrary code. This requires changing two NVRAM variable values: the first to override DataSize and the second to overwrite the return address.

In order to fix this vulnerability, the DataSize variable must be initialized before each call to gRT->GetVariable().

BRLY-2022-033/CVE-2022-40520

Another vulnerable code pattern in UsbConfigDxe driver demonstrating repeatable failures appearance:

Fig 11

As we can see from the pseudocode, DataSize is initialized only once (before the first call to gRT->GetVariable()). Thus, if the data size of the variable in NVRAM is greater than 1, DataSize will be overwritten. Thus, the next call to gRT-GetVariable() may cause an overflow on the stack (and subsequent execution of arbitrary code).

In order to fix this vulnerability, the DataSize variable must be (re)initialized before each call to gRT->GetVariable():

DataSize = 1;
  result = gRT->GetVariable(L"UsbConfigPrimaryPort", &gVariableGuid, 0, &DataSize, &Value);
  if ( result >= 0 )
    LODWORD(v32) = sub_6528(Value);
  else
    LODWORD(v32) = 32;

  DataSize = 1; // <--- added
  result = gRT->GetVariable(L"UsbConfigSecondaryPort", &gVariableGuid, 0, &DataSize, &Value);

The coordinated disclosure with both Qualcomm and Lenovo took only two months to release the fixes and secure their supply chain after we reported reference code vulnerabilities in October. With such a broad impact to the entire UEFI ARM-based ecosystem it's an unprecedented timeline we didn't see from other vendors before. In spite of this, the ecosystem is affected much more broadly, and the fixes will take months to be reflected in firmware updates industry-wide. Binarly team already discussed the firmware supply chain complexity topics regarding the firmware update delivery and how the timing plays a negative role to give an attackers advantage to adopt already known vulnerabilities (N-days) to their attacks (“The Firmware Supply-Chain Security Is Broken: Can We Fix It?”). The silicon vendor reference code vulnerabilities are always the worst since impacting the whole industry and all the device vendors have used the same chips on their devices. That’s been the first public disclosure related to ARM ecosystem by Binarly REsearch team but not the leas, so stay tuned with us.  

Closer collaboration between the vendor and researcher can significantly reduce the disclosure timeline and assist industry in recovering from repeatable firmware security failures.