[QPSIIR-80] Qualcomm TrustZone Integer Signedness bug

Posted on Thu 18 December 2014 in Advisory


Qualcomm TrustZone is prone to an integer signedness bug that may allow to write NULL words to barely controllable locations in memory.

The vulnerability can be triggered from Non-Secure World through the TrustZone call "tzbsp_smmu_fault_regs_dump".

This issue has been discovered in Samsung Galaxy S5 firmware, but other devices can be affected as well.


This vulnerability has been discovered in TrustZone binary of Samsung Galaxy S5 firmware, version 4.4.2.
The tzbsp_smmu_fault_regs_dump function can be called from Non-Secure World through the SMC instruction. It takes 4 arguments passed in R0-R3 registers.
When called with argument R0 > 1, nested function subfunc_1 is called with arguments (R0 = 0xFFFFFFFF, R1) :
FE84B9B6 tzbsp_smmu_fault_regs_dump
FE84B9B6     PUSH.W   {R4-R8,LR}
FE84B9BA     MOVS     R6, R2
FE84B9BC     MOV      R8, R1
FE84B9BE     MOV      R7, R3
FE84B9C0     MOV.W    R4, #0xFFFFFFFF
FE84B9C4     MOV      R5, #0xFFFFFFEE
FE84B9C8     BEQ      loc_FE84BA1A
FE84B9CA     CBZ      R0, loc_FE84B9D2 ; R0 > 1 : branch not taken
FE84B9CC     CMP      R0, #1
FE84B9CE     BEQ      loc_FE84B9D6 ; R0 > 1 : branch not taken
FE84B9D0     B        loc_FE84B9D8 ; branch
FE84B9D2 ; ---------------------------------------------------------
FE84B9D2 loc_FE84B9D2        ; if R0 == 0
FE84B9D2     MOVS     R4, #1
FE84B9D4     B        loc_FE84B9D8
FE84B9D6 ; ---------------------------------------------------------
FE84B9D6 loc_FE84B9D6        ; if R0 == 1
FE84B9D6     MOVS     R4, #0
FE84B9D8 loc_FE84B9D8        ; for any value of R0
FE84B9D8     MOVS     R0, #1
FE84B9DA     BL       subfunc_0 ; kind of "is retail hardware?" test
FE84B9DE     CBZ      R0, loc_FE84B9EE ; not taken
FE84B9E0     MOV      R1, R8
FE84B9E2     MOV      R0, R4 ; R4 == #0xFFFFFFFF
FE84B9E4     BL       subfunc_1
FE84B9E8     [...]
Then subfunc_1 checks if R0 value is valid. It will Branch and return if R0 is Greater than or Equal to 2. However, BGE instruction operates on signed integers. So R0 == -1 < 2 will pass the test and the execution will continue :
FE853124 subfunc_1
FE853124     CMP      R0, #2 ; R0 == #0xFFFFFFFF
FE853126     BGE      locret_FE85314C ; signed comparison :
FE853126                              ; R0(-1) < 2 so branch not taken
FE853128     MOVW     R2, #0x9EE0
FE85312C     ADD.W    R3, R0, R0,LSL#1
FE853130     MOVT.W   R2, #0xFE82
FE853134     ADD.W    R0, R3, R0,LSL#3
FE853138     LDR      R2, [R2,#(dword_FE829EE4 - 0xFE829EE0)]
FE85313A     ADD.W    R0, R2, R0,LSL#4
FE85313E     LDRB     R2, [R0,#4]
FE853140     CMP      R2, R1 ; with R1 < R2
FE853142     BLS      locret_FE85314C
FE853144     LDR      R0, [R0]
FE853146     MOVS     R2, #0
FE853148     B.W      sub_FE856C84 ; write NULL DWORD to a barely arbitrary address (derived from R1 value)
FE85314C     [...]
Finally, the last nested function could allow to write NULL words to a limited range of memory locations.


This bug is fixed in Lolipop version of the firmware. Several changes have been made. First, subfunc_1 function is not reachable anymore with an invalid R0 value:
FE84B970 tzbsp_smmu_fault_regs_dump
FE84B970     PUSH.W   {R4-R8,LR}
FE84B974     MOVS     R6, R2
FE84B976     MOV      R8, R1
FE84B978     MOV      R7, R3
FE84B97A     MOV      R5, #0xFFFFFFEE
FE84B97E     BEQ      loc_FE84B9D2
FE84B980     CBZ      R0, loc_FE84B98A
FE84B982     CMP      R0, #1
FE84B984     BEQ      loc_FE84B98E
FE84B986     ADDS     R0, R5, #2
FE84B988     B        locret_FE84B7AA ; branch if R0 > 1
FE84B98A ; ---------------------------------------------------------
FE84B98A loc_FE84B98A        ; if R0 == 0
FE84B98A     MOVS     R4, #1
FE84B98C     B        loc_FE84B990
FE84B98E ; ---------------------------------------------------------
FE84B98E loc_FE84B98E        ; if R0 == 1
FE84B98E     MOVS     R4, #0
FE84B990 loc_FE84B990        ; if 0 <= R0 < 2
FE84B990     MOVS     R0, #1
FE84B992     BL       subfunc_0 ; kind of "is retail hardware?" test
FE84B996     CBZ      R0, loc_FE84B9A6 ; not taken
FE84B998     MOV      R1, R8
FE84B99A     MOV      R0, R4 ; R4 is either 0 or 1
FE84B99C     BL       subfunc_1
FE84B9A0     [...]
Then in (sub)subfunc_1, R0 value is now tested with an unsigned comparison:
FE852B94 (sub)subfunc_1
FE852B94     CMP      R0, #2
FE852B96     BCS      loc_FE852BBA ;unsigned comparison: branch if R0 > 1
FE852B98     MOVW     R2, #0x9F38
FE852B9C     [...]
The bug can no longer be triggered.

CVSS Version 2 Metrics

Access Vector: Local

Access Complexity: High

Authentication: Single

Confidentiality Impact: Complete

Integrity Impact: Complete

Availability Impact: Complete

Disclosure Timeline

2014-08-28 Intial vendor notification

2014-09-03 Vendor reply; severity of the issue rated as high

2014-00-00 Vendor has notified all OEMs

2014-12-18 Public advisory