[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