Skip to content

Commit a6bc750

Browse files
statham-armtru
authored andcommitted
Retain all jump table range checks when using BTI.
This modifies the switch-statement generation in SelectionDAGBuilder, specifically the part that generates case clusters of type CC_JumpTable. A table-based branch of any kind is at risk of being a JOP gadget, if it doesn't range-check the offset into the table. For some types of table branch, such as Arm TBB/TBH, the impact of this is limited because the value loaded from the table is a relative offset of limited size; for others, such as a MOV PC,Rn computed branch into a table of further branch instructions, the gadget is fully general. When compiling for branch-target enforcement via Arm's BTI system, many of these table branch idioms use branch instructions of types that do not require a BTI instruction at the branch destination. This avoids the need to put a BTI at the start of each case handler, reducing the number of available gadgets //with// BTIs (i.e. ones which could be used by a JOP attack in spite of the BTI system). But without a range check, the use of a non-BTI-requiring branch also opens up a larger range of followup gadgets for an attacker's use. A defence against this is to avoid optimising away the range check on the table offset, even if the compiler believes that no out-of-range value should be able to reach the table branch. (Rationale: that may be true for values generated legitimately by the program, but not those generated maliciously by attackers who have already corrupted the control flow.) The effect of keeping the range check and branching to an unreachable block is that no actual code is generated at that block, so it will typically point at the end of the function. That may still cause some kind of unpredictable code execution (such as executing data as code, or falling through to the next function in the code section), but even if so, there will only be //one// possible invalid branch target, rather than giving an attacker the choice of many possibilities. This defence is enabled only when branch target enforcement is in use. Without branch target enforcement, the range check is easily bypassed anyway, by branching in to a location just after it. But with enforcement, the attacker will have to enter the jump table dispatcher at the initial BTI and then go through the range check. (Or, if they don't, it's because they //already// have a general BTI-bypassing gadget.) Reviewed By: MaskRay, chill Differential Revision: https://reviews.llvm.org/D155485 (cherry picked from commit 60b9836)
1 parent 1d07708 commit a6bc750

File tree

2 files changed

+155
-2
lines changed

2 files changed

+155
-2
lines changed

llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11310,8 +11310,32 @@ void SelectionDAGBuilder::lowerWorkItem(SwitchWorkListItem W, Value *Cond,
1131011310
}
1131111311
}
1131211312

11313-
if (FallthroughUnreachable)
11314-
JTH->FallthroughUnreachable = true;
11313+
// If the default clause is unreachable, propagate that knowledge into
11314+
// JTH->FallthroughUnreachable which will use it to suppress the range
11315+
// check.
11316+
//
11317+
// However, don't do this if we're doing branch target enforcement,
11318+
// because a table branch _without_ a range check can be a tempting JOP
11319+
// gadget - out-of-bounds inputs that are impossible in correct
11320+
// execution become possible again if an attacker can influence the
11321+
// control flow. So if an attacker doesn't already have a BTI bypass
11322+
// available, we don't want them to be able to get one out of this
11323+
// table branch.
11324+
if (FallthroughUnreachable) {
11325+
Function &CurFunc = CurMF->getFunction();
11326+
bool HasBranchTargetEnforcement = false;
11327+
if (CurFunc.hasFnAttribute("branch-target-enforcement")) {
11328+
HasBranchTargetEnforcement =
11329+
CurFunc.getFnAttribute("branch-target-enforcement")
11330+
.getValueAsBool();
11331+
} else {
11332+
HasBranchTargetEnforcement =
11333+
CurMF->getMMI().getModule()->getModuleFlag(
11334+
"branch-target-enforcement");
11335+
}
11336+
if (!HasBranchTargetEnforcement)
11337+
JTH->FallthroughUnreachable = true;
11338+
}
1131511339

1131611340
if (!JTH->FallthroughUnreachable)
1131711341
addSuccessorWithProb(CurMBB, Fallthrough, FallthroughProb);
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
;; When BTI is enabled, keep the range check for a jump table for hardening,
2+
;; even with an unreachable default.
3+
;;
4+
;; We check with and without the branch-target-enforcement module attribute,
5+
;; and in each case, try overriding it with the opposite function attribute.
6+
;; Expect to see a range check whenever there is BTI, and not where there
7+
;; isn't.
8+
9+
; RUN: sed s/SPACE/4/ %s | llc -mtriple=thumbv8.1m.main-linux-gnu -mattr=+pacbti -o - | FileCheck %s --check-prefix=BTI-TBB
10+
; RUN: sed s/SPACE/4/ %s | sed '/test_jumptable/s/{/#0 {/' | llc -mtriple=thumbv8.1m.main-linux-gnu -mattr=+pacbti -o - | FileCheck %s --check-prefix=NOBTI-TBB
11+
; RUN: sed s/SPACE/4/ %s | sed '/^..for-non-bti-build-sed-will-delete-everything-after-this-line/q' | llc -mtriple=thumbv8.1m.main-linux-gnu -mattr=+pacbti -o - | FileCheck %s --check-prefix=NOBTI-TBB
12+
; RUN: sed s/SPACE/4/ %s | sed '/test_jumptable/s/{/#1 {/' | sed '/^..for-non-bti-build-sed-will-delete-everything-after-this-line/q' | llc -mtriple=thumbv8.1m.main-linux-gnu -mattr=+pacbti -o - | FileCheck %s --check-prefix=BTI-TBB
13+
14+
; RUN: sed s/SPACE/400/ %s | llc -mtriple=thumbv8.1m.main-linux-gnu -mattr=+pacbti -o - | FileCheck %s --check-prefix=BTI-TBH
15+
; RUN: sed s/SPACE/400/ %s | sed '/test_jumptable/s/{/#0 {/' | llc -mtriple=thumbv8.1m.main-linux-gnu -mattr=+pacbti -o - | FileCheck %s --check-prefix=NOBTI-TBH
16+
; RUN: sed s/SPACE/400/ %s | sed '/^..for-non-bti-build-sed-will-delete-everything-after-this-line/q' | llc -mtriple=thumbv8.1m.main-linux-gnu -mattr=+pacbti -o - | FileCheck %s --check-prefix=NOBTI-TBH
17+
; RUN: sed s/SPACE/400/ %s | sed '/test_jumptable/s/{/#1 {/' | sed '/^..for-non-bti-build-sed-will-delete-everything-after-this-line/q' | llc -mtriple=thumbv8.1m.main-linux-gnu -mattr=+pacbti -o - | FileCheck %s --check-prefix=BTI-TBH
18+
19+
; RUN: sed s/SPACE/400000/ %s | llc -mtriple=thumbv8.1m.main-linux-gnu -mattr=+pacbti -o - | FileCheck %s --check-prefix=BTI-MOV
20+
; RUN: sed s/SPACE/400000/ %s | sed '/test_jumptable/s/{/#0 {/' | llc -mtriple=thumbv8.1m.main-linux-gnu -mattr=+pacbti -o - | FileCheck %s --check-prefix=NOBTI-MOV
21+
; RUN: sed s/SPACE/400000/ %s | sed '/^..for-non-bti-build-sed-will-delete-everything-after-this-line/q' | llc -mtriple=thumbv8.1m.main-linux-gnu -mattr=+pacbti -o - | FileCheck %s --check-prefix=NOBTI-MOV
22+
; RUN: sed s/SPACE/400000/ %s | sed '/test_jumptable/s/{/#1 {/' | sed '/^..for-non-bti-build-sed-will-delete-everything-after-this-line/q' | llc -mtriple=thumbv8.1m.main-linux-gnu -mattr=+pacbti -o - | FileCheck %s --check-prefix=BTI-MOV
23+
24+
declare i32 @llvm.arm.space(i32, i32)
25+
26+
attributes #0 = { "branch-target-enforcement"="false" }
27+
attributes #1 = { "branch-target-enforcement"="true" }
28+
29+
define ptr @test_jumptable(ptr %src, ptr %dst) {
30+
entry:
31+
%sw = load i32, ptr %src, align 4
32+
%src.postinc = getelementptr inbounds i32, ptr %src, i32 1
33+
switch i32 %sw, label %default [
34+
i32 0, label %sw.0
35+
i32 1, label %sw.1
36+
i32 2, label %sw.2
37+
i32 3, label %sw.3
38+
]
39+
40+
sw.0:
41+
%store.0 = call i32 @llvm.arm.space(i32 SPACE, i32 14142)
42+
store i32 %store.0, ptr %dst, align 4
43+
br label %exit
44+
45+
sw.1:
46+
%store.1 = call i32 @llvm.arm.space(i32 SPACE, i32 31415)
47+
%dst.1 = getelementptr inbounds i32, ptr %dst, i32 1
48+
store i32 %store.1, ptr %dst.1, align 4
49+
br label %exit
50+
51+
sw.2:
52+
%store.2 = call i32 @llvm.arm.space(i32 SPACE, i32 27182)
53+
%dst.2 = getelementptr inbounds i32, ptr %dst, i32 2
54+
store i32 %store.2, ptr %dst.2, align 4
55+
br label %exit
56+
57+
sw.3:
58+
%store.3 = call i32 @llvm.arm.space(i32 SPACE, i32 16180)
59+
%dst.3 = getelementptr inbounds i32, ptr %dst, i32 3
60+
store i32 %store.3, ptr %dst.3, align 4
61+
br label %exit
62+
63+
default:
64+
unreachable
65+
66+
exit:
67+
ret ptr %src.postinc
68+
}
69+
70+
; NOBTI-TBB: test_jumptable:
71+
; NOBTI-TBB-NEXT: .fnstart
72+
; NOBTI-TBB-NEXT: @ %bb
73+
; NOBTI-TBB-NEXT: ldr [[INDEX:r[0-9]+]], [r0], #4
74+
; NOBTI-TBB-NEXT: .LCPI
75+
; NOBTI-TBB-NEXT: tbb [pc, [[INDEX]]]
76+
77+
; BTI-TBB: test_jumptable:
78+
; BTI-TBB-NEXT: .fnstart
79+
; BTI-TBB-NEXT: @ %bb
80+
; BTI-TBB-NEXT: bti
81+
; BTI-TBB-NEXT: ldr [[INDEX:r[0-9]+]], [r0], #4
82+
; BTI-TBB-NEXT: cmp [[INDEX]], #3
83+
; BTI-TBB-NEXT: bhi .LBB
84+
; BTI-TBB-NEXT: @ %bb
85+
; BTI-TBB-NEXT: .LCPI
86+
; BTI-TBB-NEXT: tbb [pc, [[INDEX]]]
87+
88+
; NOBTI-TBH: test_jumptable:
89+
; NOBTI-TBH-NEXT: .fnstart
90+
; NOBTI-TBH-NEXT: @ %bb
91+
; NOBTI-TBH-NEXT: ldr [[INDEX:r[0-9]+]], [r0], #4
92+
; NOBTI-TBH-NEXT: .LCPI
93+
; NOBTI-TBH-NEXT: tbh [pc, [[INDEX]], lsl #1]
94+
95+
; BTI-TBH: test_jumptable:
96+
; BTI-TBH-NEXT: .fnstart
97+
; BTI-TBH-NEXT: @ %bb
98+
; BTI-TBH-NEXT: bti
99+
; BTI-TBH-NEXT: ldr [[INDEX:r[0-9]+]], [r0], #4
100+
; BTI-TBH-NEXT: cmp [[INDEX]], #3
101+
; BTI-TBH-NEXT: bhi.w .LBB
102+
; BTI-TBH-NEXT: @ %bb
103+
; BTI-TBH-NEXT: .LCPI
104+
; BTI-TBH-NEXT: tbh [pc, [[INDEX]], lsl #1]
105+
106+
; NOBTI-MOV: test_jumptable:
107+
; NOBTI-MOV-NEXT: .fnstart
108+
; NOBTI-MOV-NEXT: @ %bb
109+
; NOBTI-MOV-NEXT: ldr [[INDEX:r[0-9]+]], [r0], #4
110+
; NOBTI-MOV-NEXT: adr.w [[ADDR:r[0-9]+]], .LJTI
111+
; NOBTI-MOV-NEXT: add.w [[ADDR]], [[ADDR]], [[INDEX]], lsl #2
112+
; NOBTI-MOV-NEXT: mov pc, [[ADDR]]
113+
114+
; BTI-MOV: test_jumptable:
115+
; BTI-MOV-NEXT: .fnstart
116+
; BTI-MOV-NEXT: @ %bb
117+
; BTI-MOV-NEXT: bti
118+
; BTI-MOV-NEXT: ldr [[INDEX:r[0-9]+]], [r0], #4
119+
; BTI-MOV-NEXT: cmp [[INDEX]], #3
120+
; BTI-MOV-NEXT: bls .LBB
121+
; BTI-MOV-NEXT: b.w .LBB
122+
; BTI-MOV-NEXT: .LBB
123+
; BTI-MOV-NEXT: adr.w [[ADDR:r[0-9]+]], .LJTI
124+
; BTI-MOV-NEXT: add.w [[ADDR]], [[ADDR]], [[INDEX]], lsl #2
125+
; BTI-MOV-NEXT: mov pc, [[ADDR]]
126+
127+
; for-non-bti-build-sed-will-delete-everything-after-this-line
128+
!llvm.module.flags = !{!0}
129+
!0 = !{i32 8, !"branch-target-enforcement", i32 1}

0 commit comments

Comments
 (0)