├── gold └── ld ├── .gitignore ├── cfi_cast_strict.cpp ├── cfi_derived_cast.cpp ├── cfi_vcall.cpp ├── cfi_unrelated_cast.cpp ├── LICENSE ├── cfi_nvcall.cpp ├── Makefile ├── cfi_icall.c └── README.md /gold/ld: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | gold "$@" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cfi_icall 2 | no_cfi_icall 3 | cfi_vcall 4 | no_cfi_vcall 5 | cfi_nvcall 6 | no_cfi_nvcall 7 | cfi_unrelated_cast 8 | no_cfi_unrelated_cast 9 | cfi_cast_strict 10 | cfi_derived_cast 11 | no_cfi_cast_strict 12 | no_cfi_derived_cast 13 | -------------------------------------------------------------------------------- /cfi_cast_strict.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct Base { 4 | Base() {} 5 | virtual ~Base() {} 6 | virtual void func() { 7 | std::cout << "Base: " << __FUNCTION__ << "\n"; 8 | } 9 | }; 10 | 11 | struct Derived : Base { 12 | }; 13 | 14 | int main(int argc, const char *argv[]) { 15 | 16 | Base b; 17 | 18 | (void)(argc); 19 | (void)(argv); 20 | 21 | // this is undefined behavior, but will generally work 22 | Derived &d = static_cast(b); 23 | 24 | d.func(); 25 | 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /cfi_derived_cast.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct Base { 4 | Base(const std::string &s) : name(s) {} 5 | virtual ~Base() {} 6 | 7 | std::string name; 8 | }; 9 | 10 | struct Derived : Base { 11 | Derived(const std::string &s) : Base(s) {} 12 | virtual ~Derived() {} 13 | 14 | const unsigned long variable = 0x12345678; 15 | 16 | void printName() { 17 | std::cout << "I am: " << name << ", my member variable is: " << std::hex << variable << std::endl; 18 | } 19 | }; 20 | 21 | int main(int argc, const char* argv[]) { 22 | 23 | Base B("base class"); 24 | Derived D("derived class"); 25 | 26 | (void)(argc); 27 | (void)(argv); 28 | 29 | D.printName(); 30 | 31 | // this is illegal 32 | Derived &dptr = static_cast(B); 33 | 34 | dptr.printName(); 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /cfi_vcall.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct Base { 4 | Base() {} 5 | virtual ~Base() {} 6 | virtual void printMe() { 7 | std::cout << "Base::printMe\n"; 8 | } 9 | }; 10 | 11 | struct Derived : Base { 12 | Derived() {} 13 | virtual ~Derived() {} 14 | 15 | virtual void printMe() { 16 | std::cout << "Derived::printMe\n"; 17 | } 18 | }; 19 | 20 | // imagine this is an attacker-created structure 21 | // in memory 22 | struct Evil { 23 | Evil() {} 24 | virtual ~Evil() {} 25 | 26 | virtual void makeAdmin() { 27 | std::cout << "CFI Prevents this control flow\n"; 28 | std::cout << "Evil::makeAdmin\n"; 29 | } 30 | }; 31 | 32 | int main(int argc, const char *argv[]) { 33 | 34 | Evil *eptr = new Evil(); 35 | Derived* dptr = new Derived(); 36 | 37 | (void)(argc); 38 | (void)(argv); 39 | 40 | dptr->printMe(); 41 | 42 | // imagine a type confusion vulnerability 43 | // that does something similar 44 | dptr = reinterpret_cast(eptr); 45 | dptr->printMe(); 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /cfi_unrelated_cast.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct Foo { 4 | Foo(const std::string &s): command(s) {} 5 | virtual ~Foo() {} 6 | 7 | void fooStuff() { 8 | std::cout << "I am in " << __FUNCTION__ << "\n"; 9 | std::cout << "And I would execute: " << command << "\n"; 10 | } 11 | 12 | std::string command; 13 | }; 14 | 15 | struct Bar { 16 | Bar(const std::string &s): name(s) {} 17 | virtual ~Bar() {} 18 | 19 | void barStuff() { 20 | std::cout << "I am in " << __FUNCTION__ << "\n"; 21 | std::cout << "And I am called: " << name << "\n"; 22 | } 23 | 24 | std::string name; 25 | }; 26 | 27 | 28 | enum class WhichObject { 29 | FooObject, 30 | BarObject 31 | }; 32 | 33 | 34 | static void *allocator(WhichObject w, const std::string& arg) { 35 | switch(w) { 36 | case WhichObject::FooObject: 37 | return new Foo(arg); 38 | case WhichObject::BarObject: 39 | return new Bar(arg); 40 | } 41 | } 42 | 43 | int main(int argc, const char *argv[]) { 44 | 45 | void *ptr = nullptr; 46 | (void)(argc); 47 | (void)(argv); 48 | 49 | // Assume an attacker can create Bar objects 50 | // with arbitrary arguments. 51 | ptr = allocator(WhichObject::BarObject, "system(\"/bin/sh\")"); 52 | 53 | // .. and there is a flaw to re-cast Bar objects 54 | // as Foo objects 55 | Foo *fooptr = static_cast(ptr); 56 | fooptr->fooStuff(); 57 | 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Trail of Bits 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal with 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimers. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimers in the 15 | documentation and/or other materials provided with the distribution. 16 | 17 | * Neither the names of Trail of Bits, nor the names of contributors 18 | may be used to endorse or promote products derived from this Software 19 | without specific prior written permission. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 23 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE 27 | SOFTWARE. 28 | -------------------------------------------------------------------------------- /cfi_nvcall.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct Account { 5 | Account(const std::string &s) : name(s) {} 6 | virtual ~Account() {} 7 | void showName() { 8 | std::cout << "Account name is: " << name << std::endl; 9 | } 10 | void adminStuff() { std::cout << "Not Implemented" << std::endl; } 11 | std::string name; 12 | }; 13 | 14 | struct UserAccount : Account { 15 | UserAccount(const std::string &s) : Account(s) {} 16 | virtual ~UserAccount() {} 17 | void adminStuff() { 18 | std::cout << "Admin Work not permitted for a user account!" << std::endl; 19 | } 20 | }; 21 | 22 | struct AdminAccount : Account { 23 | AdminAccount(const std::string &s) : Account(s) {} 24 | virtual ~AdminAccount() {} 25 | void adminStuff() { 26 | std::cout << "Would do admin work in context of: " << this->name << std::endl; 27 | } 28 | }; 29 | 30 | int main(int argc, const char *argv[]) { 31 | 32 | UserAccount* user = new UserAccount("user"); 33 | AdminAccount* admin = new AdminAccount("admin"); 34 | 35 | (void)(argc); 36 | (void)(argv); 37 | 38 | std::cout << "Admin check: " << std::endl; 39 | admin->showName(); 40 | admin->adminStuff(); 41 | 42 | std::cout << "User check: " << std::endl; 43 | user->showName(); 44 | user->adminStuff(); 45 | 46 | // imagine this change happens via memory corruption, 47 | // type confusion, deserialization vulnerability, 48 | // or a similar flaw 49 | Account *account = static_cast(user); 50 | AdminAccount *admin_it = static_cast(account); 51 | 52 | admin_it->showName(); 53 | std::cout << "CFI Should prevent the actions below:" << std::endl; 54 | admin_it->adminStuff(); 55 | 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXX = clang++-3.9 2 | CC = clang-3.9 3 | GOLD = $(shell pwd)/ld 4 | CFLAGS = -B${GOLD} -Weverything -Werror -pedantic -std=c99 -O0 -fvisibility=hidden -flto -fno-sanitize-trap=all 5 | CXXFLAGS = -B${GOLD} -Weverything -Werror -pedantic -Wno-c++98-compat -Wno-weak-vtables -std=c++11 -O0 -fvisibility=hidden -flto -fno-sanitize-trap=all 6 | 7 | CFI_TARGETS = cfi_icall cfi_vcall cfi_nvcall cfi_unrelated_cast cfi_derived_cast cfi_cast_strict 8 | NO_CFI_TARGETS = $(addprefix no_, $(CFI_TARGETS)) 9 | 10 | TARGETS = $(CFI_TARGETS) $(NO_CFI_TARGETS) 11 | 12 | all: $(TARGETS) 13 | 14 | cfi_icall: cfi_icall.c 15 | @echo Compiling $< to $@ 16 | @$(CC) $(CFLAGS) -fsanitize=cfi-icall -o $@ $< 17 | 18 | no_cfi_icall: cfi_icall.c 19 | @echo Compiling $< to $@ 20 | @$(CC) $(CFLAGS) -o $@ $< 21 | 22 | cfi_vcall: cfi_vcall.cpp 23 | @echo Compiling $< to $@ 24 | @$(CXX) $(CXXFLAGS) -fsanitize=cfi-vcall -o $@ $< 25 | 26 | no_cfi_vcall: cfi_vcall.cpp 27 | @echo Compiling $< to $@ 28 | @$(CXX) $(CXXFLAGS) -o $@ $< 29 | 30 | cfi_nvcall: cfi_nvcall.cpp 31 | @echo Compiling $< to $@ 32 | @$(CXX) $(CXXFLAGS) -fsanitize=cfi-nvcall -o $@ $< 33 | 34 | no_cfi_nvcall: cfi_nvcall.cpp 35 | @echo Compiling $< to $@ 36 | @$(CXX) $(CXXFLAGS) -o $@ $< 37 | 38 | cfi_unrelated_cast: cfi_unrelated_cast.cpp 39 | @echo Compiling $< to $@ 40 | @$(CXX) $(CXXFLAGS) -fsanitize=cfi-unrelated-cast -o $@ $< 41 | 42 | no_cfi_unrelated_cast: cfi_unrelated_cast.cpp 43 | @echo Compiling $< to $@ 44 | @$(CXX) $(CXXFLAGS) -o $@ $< 45 | 46 | cfi_derived_cast: cfi_derived_cast.cpp 47 | @echo Compiling $< to $@ 48 | @$(CXX) $(CXXFLAGS) -fsanitize=cfi-derived-cast -o $@ $< 49 | 50 | no_cfi_derived_cast: cfi_derived_cast.cpp 51 | @echo Compiling $< to $@ 52 | @$(CXX) $(CXXFLAGS) -o $@ $< 53 | 54 | cfi_cast_strict: cfi_cast_strict.cpp 55 | @echo Compiling $< to $@ 56 | @$(CXX) $(CXXFLAGS) -fsanitize=cfi-derived-cast -fsanitize=cfi-cast-strict -o $@ $< 57 | 58 | no_cfi_cast_strict: cfi_cast_strict.cpp 59 | @echo Compiling $< to $@ 60 | @# still use cfi-derived-cast, just not the strict version to 61 | @# show the strict version behavior 62 | @$(CXX) $(CXXFLAGS) -fsanitize=cfi-derived-cast -o $@ $< 63 | 64 | clean: 65 | rm -f $(TARGETS) 66 | 67 | .PHONY: clean all 68 | -------------------------------------------------------------------------------- /cfi_icall.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef int (*int_arg_fn)(int); 6 | typedef int (*float_arg_fn)(float); 7 | 8 | static int int_arg(int arg) { 9 | printf("In %s: (%d)\n", __FUNCTION__, arg); 10 | return 0; 11 | } 12 | 13 | static int float_arg(float arg) { 14 | printf("CFI should protect transfer to here\n"); 15 | printf("In %s: (%f)\n", __FUNCTION__, (double)arg); 16 | return 0; 17 | } 18 | 19 | static int bad_int_arg(int arg) { 20 | printf("CFI will not protect transfer to here\n"); 21 | printf("In %s: (%d)\n", __FUNCTION__, arg); 22 | return 0; 23 | } 24 | 25 | static int not_entry_point(int arg) { 26 | // nop sled for x86 / x86-64 27 | // these instructions act as a buffer 28 | // for an indirect control flow transfer to skip 29 | // a valid function entry point, but continue 30 | // to execute normal code 31 | __asm__ volatile ( 32 | "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" 33 | "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" 34 | "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" 35 | "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" 36 | "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" 37 | "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" 38 | "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" 39 | "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" 40 | ); 41 | printf("CFI ensures control flow only transfers to potentially valid destinations\n"); 42 | printf("In %s: (%d)\n", __FUNCTION__, arg); 43 | // need to exit or the program will segfault anyway, 44 | // since the indirect call skipped the function preamble 45 | exit(arg); 46 | } 47 | 48 | struct foo { 49 | int_arg_fn int_funcs[1]; 50 | int_arg_fn bad_int_funcs[1]; 51 | float_arg_fn float_funcs[1]; 52 | int_arg_fn not_entries[1]; 53 | }; 54 | 55 | // the struct aligns the function pointer arrays 56 | // so indexing past the end will reliably 57 | // call working function pointers 58 | static struct foo f = { 59 | .int_funcs = {int_arg}, 60 | .bad_int_funcs = {bad_int_arg}, 61 | .float_funcs = {float_arg}, 62 | .not_entries = {(int_arg_fn)((uintptr_t)(not_entry_point)+0x20)} 63 | }; 64 | 65 | int main(int argc, const char *argv[]) { 66 | 67 | if(argc != 2) { 68 | printf("Usage: %s