[PATCH] Implement Control Flow Integrity for virtual calls.
Peter Collingbourne
peter at pcc.me.uk
Fri Feb 6 13:19:53 PST 2015
I've verified that this scheme does not inhibit the current scheme of devirtualization via constant propagation. For example, in the following program:
struct A {
virtual void f() = 0;
};
struct B : A {
virtual void f();
};
void B::f() {}
A* ptr() {
return new B;
}
int main() {
ptr()->f();
}
the main function looks like this:
define i32 @main() #1 {
br i1 true, label %2, label %1
; <label>:1 ; preds = %0
call void @llvm.trap()
unreachable
; <label>:2 ; preds = %0
ret i32 0
}
You will also notice that the optimization I just implemented on the LLVM side of things eliminated the check. We might want to play with the pass ordering so that the CFG is simplified in this case (it doesn't have any effect on the generated code in this case though:)
00000000004006a0 <main>:
4006a0: 55 push %rbp
4006a1: 48 89 e5 mov %rsp,%rbp
4006a4: 31 c0 xor %eax,%eax
4006a6: 5d pop %rbp
4006a7: c3 retq
4006a8: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
4006af: 00
Here's a more complex example where we might want to do partial devirtualization:
bool p;
struct A {
virtual void f() = 0;
};
struct B : A {
virtual void f();
};
struct C : A {
virtual void f();
};
void B::f() {}
void C::f() {}
A* ptr() {
return ((long)(&p)> 42) ? (A *)new B : (A *)new C;
}
int main() {
ptr()->f();
}
We can eliminate the check here as well:
define i32 @main() #2 {
%1 = tail call noalias i8* @_Znwm(i64 8) #5
%2 = bitcast i8* %1 to i32 (...)***
store i32 (...)** select (i1 icmp sgt (i64 ptrtoint (i8* @p to i64), i64 42), i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @_ZTV1B, i64 0, i64 2) to i32 (...)**), i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @_ZTV1C, i64 0, i64 2) to i32 (...)**)), i32 (...)*** %2, align 8, !tbaa !1
br i1 true, label %4, label %3
; <label>:3 ; preds = %0
tail call void @llvm.trap()
unreachable
; <label>:4 ; preds = %0
%5 = bitcast i8* %1 to %struct.A*
%6 = load void (%struct.A*)** bitcast (i32 (...)** select (i1 icmp sgt (i64 ptrtoint (i8* @p to i64), i64 42), i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @_ZTV1B, i64 0, i64 2) to i32 (...)**), i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @_ZTV1C, i64 0, i64 2) to i32 (...)**)) to void (%struct.A*)**), align 8
tail call void %6(%struct.A* %5)
ret i32 0
}
After further offline discussion we came up with the idea of introducing an intrinsic that protects calls via a supplied function pointer. To give one example:
%vptr = load ...
%fptrptr = gep %vptr, ...
%fptr = load %fptrptr
%checked_fptr = call @llvm.bitset.cficheck(%vptr, "bitset1", %fptr)
call %checked_fptr(...)
The semantics of the intrinsic are such that the intrinsic performs the check and returns its third argument, otherwise it aborts. We can probably teach the relevant parts of the optimizer that @llvm.bitset.cficheck is essentially an identity operation over its third argument.
I am reasonably sure that such a scheme or something like it would permit devirtualization while allowing us to move the check away from any devirtualized calls (a pass can move the intrinsic call to the same basic block as its users, if possible). So I would propose leaving the code as is and we can probably introduce a new intrinsic like this when we get around to doing devirtualization.
http://reviews.llvm.org/D7424
EMAIL PREFERENCES
http://reviews.llvm.org/settings/panel/emailpreferences/
More information about the cfe-commits
mailing list