├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── evaluation └── lmbench │ └── CONFIG.syzkaller ├── passes ├── CMakeLists.txt ├── ContainerOfSanitizer │ ├── CMakeLists.txt │ └── ContainerOfSanitizer.cpp ├── DumpContainerOf │ ├── CMakeLists.txt │ └── DumpContainerOf.cpp ├── DumpTypes │ ├── CMakeLists.txt │ └── DumpTypes.cpp ├── SimpleDataflowChecker │ ├── CMakeLists.txt │ ├── DataflowRule.cpp │ ├── DataflowSanitizer.cpp │ └── SimpleDataflowChecker.cpp └── include │ ├── Colors │ └── colors.h │ ├── INIReader │ └── INIReader.h │ ├── SimpleDataflowChecker │ ├── Dataflow.hpp │ ├── DataflowRule.hpp │ ├── DataflowSanitizer.hpp │ ├── Helpers.hpp │ └── SimpleDataflowChecker.hpp │ └── TypeUtils │ └── TypeUtils.hpp ├── patches ├── lmbench │ ├── 0001-Link-against-tirpc.patch │ └── 0002-Fix-rpc-include-error.patch └── syzkaller │ └── 0001-syzkaller-uncontained-ignore-non-uncontained-reports.patch ├── runtime ├── CMakeLists.txt ├── uncontained_kasan.c ├── vanilla_container_of.h └── vanilla_list.h ├── scripts ├── compile.sh ├── evaluation │ ├── lmbench.py │ └── syzkaller-bench.py ├── prepare-evaluation.sh ├── run-fuzzing-performance-evaluation.sh ├── run-lmbench-performance-evaluation.sh ├── run-static-analyzer.sh └── run.sh ├── tests ├── config.yaml ├── test.sh ├── test │ ├── .gitignore │ ├── test.c │ └── test1.c ├── testDF.sh └── testDF │ ├── .gitignore │ ├── container_of.h │ ├── kobj.h │ ├── list.h │ ├── test_hit1.c │ ├── test_hit10.c │ ├── test_hit2.c │ ├── test_hit3.c │ ├── test_hit4.c │ ├── test_hit5.c │ ├── test_hit6.c │ ├── test_hit7.c │ ├── test_hit8.c │ ├── test_hit9.c │ ├── test_hit_backwards_contained01.c │ ├── test_hit_backwards_contained02.c │ ├── test_hit_kobj01.c │ ├── test_hit_kobj02.c │ ├── test_hit_kobj03.c │ ├── test_hit_list_entry_correlation01.c │ ├── test_hit_list_entry_correlation02.c │ ├── test_hit_list_entry_correlation03.c │ ├── test_hit_list_entry_correlation04.c │ ├── test_hit_list_entry_null.c │ ├── test_hit_list_entry_null.yaml │ ├── test_nohit1.c │ ├── test_nohit2.c │ ├── test_nohit3.c │ ├── test_nohit_backwards_contained01.c │ ├── test_nohit_backwards_contained02.c │ ├── test_nohit_kobj01.c │ ├── test_nohit_kobj03.c │ ├── test_nohit_kobj04.c │ ├── test_nohit_list_entry1.c │ ├── test_nohit_list_entry2.c │ ├── test_nohit_list_entry3.c │ ├── test_nohit_list_entry4.c │ ├── test_nohit_list_entry4.yaml │ ├── test_nohit_list_entry_correlation01.c │ ├── test_nohit_list_entry_correlation02.c │ ├── test_nohit_list_entry_correlation03.c │ ├── test_nohit_list_entry_correlation04.c │ ├── test_nohit_list_entry_null.c │ ├── test_nohit_list_entry_null.yaml │ ├── test_nohit_list_entry_null2.c │ └── test_nohit_list_entry_null2.yaml └── vscode-extension └── dataflowcheckerviewer ├── .eslintrc.json ├── .gitignore ├── .vscodeignore ├── CHANGELOG.md ├── README.md ├── assets ├── dark │ ├── dataflow.svg │ └── show.svg └── light │ ├── dataflow.svg │ └── show.svg ├── dataflowviewer-0.0.1.vsix ├── package-lock.json ├── package.json ├── src ├── extension.ts ├── items │ ├── flow_item.ts │ ├── report_item.ts │ ├── rule_item.ts │ └── source_item.ts ├── test │ ├── runTest.ts │ └── suite │ │ ├── extension.test.ts │ │ └── index.ts ├── tree_types.ts └── tree_view.ts ├── tsconfig.json └── vsc-extension-quickstart.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build 3 | .cache 4 | evaluation/lmbench/lmbench 5 | evaluation/lmbench/results 6 | evaluation/syzkaller/results 7 | venv 8 | 9 | 10 | # vim 11 | # Swap 12 | [._]*.s[a-v][a-z] 13 | !*.svg # comment out if you don't need vector files 14 | [._]*.sw[a-p] 15 | [._]s[a-rt-v][a-z] 16 | [._]ss[a-gi-z] 17 | [._]sw[a-p] 18 | 19 | # Session 20 | Session.vim 21 | Sessionx.vim 22 | 23 | # Temporary 24 | .netrwhist 25 | *~ 26 | # Auto-generated tag files 27 | tags 28 | # Persistent undo 29 | [._]*.un~ 30 | 31 | # c++ 32 | # Prerequisites 33 | *.d 34 | 35 | # Compiled Object files 36 | *.slo 37 | *.lo 38 | *.o 39 | *.obj 40 | 41 | # Precompiled Headers 42 | *.gch 43 | *.pch 44 | 45 | # Compiled Dynamic libraries 46 | *.so 47 | *.dylib 48 | *.dll 49 | 50 | # Fortran module files 51 | *.mod 52 | *.smod 53 | 54 | # Compiled Static libraries 55 | *.lai 56 | *.la 57 | *.a 58 | *.lib 59 | 60 | # Executables 61 | *.exe 62 | *.out 63 | *.app 64 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "llvm-project"] 2 | path = llvm-project 3 | url = https://github.com/vusec/uncontained-llvm-project.git 4 | branch = uncontained-llvm-12 5 | [submodule "linux"] 6 | path = linux 7 | url = https://github.com/vusec/uncontained-linux.git 8 | branch = uncontained 9 | [submodule "kernel-tools"] 10 | path = kernel-tools 11 | url = https://github.com/Jakob-Koschel/kernel-tools.git 12 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.3) 2 | 3 | if(NOT DEFINED ENV{LLVMPREFIX}) 4 | message(FATAL_ERROR "$LLVMPREFIX is not defined") 5 | else () 6 | set(ENV{LLVM_DIR} $ENV{LLVMPREFIX}/lib/cmake/llvm) 7 | endif() 8 | 9 | SET (CMAKE_C_COMPILER $ENV{LLVMPREFIX}/bin/clang) 10 | SET (CMAKE_CXX_COMPILER $ENV{LLVMPREFIX}/bin/clang++) 11 | 12 | project(uncontained) 13 | 14 | set(CMAKE_CXX_STANDARD 11) 15 | set(CMAKE_BUILD_TYPE RelWithDebInfo) 16 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 17 | 18 | add_subdirectory(passes) 19 | add_subdirectory(runtime) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | -------------------------------------------------------------------------------- /evaluation/lmbench/CONFIG.syzkaller: -------------------------------------------------------------------------------- 1 | DISKS="" 2 | DISK_DESC="" 3 | OUTPUT=/dev/tty 4 | ENOUGH=1000000 5 | FASTMEM="NO" 6 | FILE=/var/tmp/lmbench/XXX 7 | FSDIR=/var/tmp/lmbench 8 | INFO=INFO.syzkaller 9 | LINE_SIZE=128 10 | LOOP_O=0.00000192 11 | MAIL=no 12 | TOTAL_MEM=6737 13 | MB=4715 14 | MHZ="4931 MHz, 0.2028 nanosec clock" 15 | MOTHERBOARD="" 16 | NETWORKS="" 17 | OS="x86_64-linux-gnu" 18 | PROCESSORS="1" 19 | REMOTE="" 20 | SLOWFS="NO" 21 | SYNC_MAX="1" 22 | LMBENCH_SCHED="DEFAULT" 23 | TIMING_O=0 24 | RSH=rsh 25 | RCP=rcp 26 | VERSION=3.0-20230127 27 | BENCHMARK_HARDWARE=NO 28 | BENCHMARK_OS=YES 29 | BENCHMARK_SYSCALL= 30 | BENCHMARK_SELECT= 31 | BENCHMARK_SIG= 32 | BENCHMARK_PROC= 33 | BENCHMARK_CTX= 34 | BENCHMARK_PAGEFAULT= 35 | BENCHMARK_FILE= 36 | BENCHMARK_MMAP= 37 | BENCHMARK_PIPE= 38 | BENCHMARK_UNIX= 39 | BENCHMARK_UDP= 40 | BENCHMARK_TCP= 41 | BENCHMARK_CONNECT= 42 | BENCHMARK_RPC= 43 | BENCHMARK_HTTP= 44 | BENCHMARK_BCOPY= 45 | BENCHMARK_MEM= 46 | BENCHMARK_OPS= 47 | -------------------------------------------------------------------------------- /passes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.3) 2 | project(uncontained-passes) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | 6 | find_package(LLVM REQUIRED CONFIG) 7 | add_definitions(${LLVM_DEFINITIONS}) 8 | include_directories(${LLVM_INCLUDE_DIRS}) 9 | link_directories(${LLVM_LIBRARY_DIRS}) 10 | list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") 11 | include(AddLLVM) 12 | 13 | if (${LLVM_VERSION_MAJOR} VERSION_GREATER_EQUAL 10) 14 | set(CMAKE_CXX_STANDARD 14) 15 | endif () 16 | message(STATUS "Using LLVM version ${LLVM_PACKAGE_VERSION}") 17 | 18 | add_subdirectory(DumpContainerOf) 19 | add_subdirectory(DumpTypes) 20 | add_subdirectory(ContainerOfSanitizer) 21 | add_subdirectory(SimpleDataflowChecker) 22 | -------------------------------------------------------------------------------- /passes/ContainerOfSanitizer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_llvm_library(LLVMContainerOfSanitizerPass MODULE 2 | ContainerOfSanitizer.cpp 3 | ) 4 | 5 | include_directories(../include) 6 | -------------------------------------------------------------------------------- /passes/DumpContainerOf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_llvm_library(LLVMDumpContainerOfPass MODULE 2 | DumpContainerOf.cpp 3 | ) 4 | 5 | include_directories(../include) 6 | -------------------------------------------------------------------------------- /passes/DumpContainerOf/DumpContainerOf.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // FILE: 3 | // DumpContainerOf.cpp 4 | // 5 | // DESCRIPTION: 6 | // Dump container_of macro invocations to collect different informations 7 | // 8 | //============================================================================= 9 | #include "llvm/IR/LegacyPassManager.h" 10 | #include "llvm/Passes/PassBuilder.h" 11 | #include "llvm/Passes/PassPlugin.h" 12 | #include "llvm/Support/raw_ostream.h" 13 | #include "llvm/Support/CommandLine.h" 14 | 15 | #include 16 | #include "llvm/IR/InstVisitor.h" 17 | 18 | #include "llvm/Transforms/IPO/PassManagerBuilder.h" // RegisterStandardPasses 19 | #include // SplitBlock 20 | 21 | #include "TypeUtils/TypeUtils.hpp" 22 | 23 | #include 24 | #include 25 | 26 | extern "C" { 27 | #include 28 | #include 29 | #include // for flock() 30 | } 31 | 32 | using namespace llvm; 33 | 34 | #define oprint(s) (outs() << s << "\n") 35 | 36 | #define EDGES_LOGFILE "container_of_edges.txt" 37 | #define NODES_LOGFILE "container_of_nodes.txt" 38 | 39 | #define DUMP_DEBUG 1 40 | 41 | //----------------------------------------------------------------------------- 42 | // DumpContainerOf implementation 43 | //----------------------------------------------------------------------------- 44 | // No need to expose the internals of the pass to the outside world - keep 45 | // everything in an anonymous namespace. 46 | namespace { 47 | 48 | class DumpContainerOf { 49 | Module *Mod; 50 | LLVMContext *Ctx; 51 | 52 | bool init(Module &M); 53 | bool visitor(Function &F); 54 | void dumpToFile(const char* filename, std::string& s); 55 | 56 | public: 57 | bool runImpl(Module &M); 58 | }; 59 | 60 | bool DumpContainerOf::init(Module &M) { 61 | Mod = &M; 62 | Ctx = &M.getContext(); 63 | 64 | return true; 65 | } 66 | 67 | void DumpContainerOf::dumpToFile(const char* filename, std::string& s) { 68 | // appending to a file is atomic, so should be safe but just to be sure 69 | #define LOCKNAME "/tmp/uncontained_lock" 70 | int fd = open(LOCKNAME, O_RDWR | O_CREAT, 0664); // open or create lockfile 71 | int rc = flock(fd, LOCK_EX); // grab exclusive lock 72 | 73 | std::ofstream log(filename, std::ofstream::app); 74 | log << s; 75 | 76 | // unlock 77 | flock(fd, LOCK_UN); 78 | close(fd); 79 | } 80 | 81 | static std::string getDebugLocation(Instruction &I) { 82 | if (DILocation *Loc = I.getDebugLoc()) { 83 | unsigned Line = Loc->getLine(); 84 | unsigned Col = Loc->getColumn(); 85 | StringRef File = Loc->getFilename(); 86 | DILocation *InlineLoc = Loc->getInlinedAt(); 87 | // not worth 88 | if (Line == 0 && Col == 0) return ""; 89 | if (!InlineLoc) 90 | return "file: " + File.str() + ", line: " + std::to_string(Line) + ", col:" + std::to_string(Col); 91 | else { 92 | unsigned InLine = InlineLoc->getLine(); 93 | unsigned InCol = InlineLoc->getColumn(); 94 | StringRef InFile = InlineLoc->getFilename(); 95 | return "file: " + File.str() + ", line: " + std::to_string(Line) + ", col:" + std::to_string(Col) + 96 | ", inlined at: " + InFile.str() + ", line: " + std::to_string(InLine) + ", col:" + std::to_string(InCol); 97 | } 98 | } else { 99 | // No location metadata available 100 | return ""; 101 | } 102 | } 103 | 104 | bool DumpContainerOf::visitor(Function &F) { 105 | // assume container_of stores information in __container_of_ptr_in, __container_of_type_in, __container_of_type_out, __container_of_ptr_out, __container_of_ptr_diff 106 | // strictly in this order 107 | Value* ptrCasted = nullptr; 108 | Type* srcType = nullptr; 109 | Type* dstType = nullptr; 110 | Value* resCasted = nullptr; 111 | Instruction* resInstr = nullptr; 112 | int64_t ptrDiff = -1; 113 | bool ptrDiffFound = false; 114 | DataLayout* DL = new DataLayout(F.getParent()); 115 | for (auto &BB: F) { 116 | for(auto &I: BB) { 117 | if (StoreInst* SI = dyn_cast(&I)) { 118 | // get the pointer where we are storing 119 | Value* storeTarget = SI->getPointerOperand(); 120 | 121 | // if not a global it cannot be interesting 122 | if (!isa(storeTarget)) continue; 123 | 124 | // get the name of the variable where we are storing 125 | std::string ptrName = storeTarget->getName().str(); 126 | 127 | // try to get the ptr we are casting 128 | if(!ptrCasted && (ptrName.find("__container_of_ptr_in") != std::string::npos)) { 129 | Value* valueStored = SI->getOperand(0); 130 | 131 | // get the ptr we are casting 132 | if(PtrToIntInst* PI = dyn_cast(valueStored)) { 133 | ptrCasted = PI->getOperand(0); 134 | } else if (PtrToIntOperator* PI = dyn_cast(valueStored)) { 135 | ptrCasted = PI->getOperand(0); 136 | } 137 | // try to get the source type we are casting 138 | } else if (ptrCasted && (ptrName.find("__container_of_type_in") != std::string::npos)) { 139 | Value* valueStored = SI->getOperand(0); 140 | 141 | // get the type from the ptrtoint instruction 142 | if(PtrToIntInst* PI = dyn_cast(valueStored)) { 143 | srcType = PI->getOperand(0)->getType()->getPointerElementType()->getPointerElementType(); 144 | } else if (PtrToIntOperator* PI = dyn_cast(valueStored)) { 145 | srcType = PI->getOperand(0)->getType()->getPointerElementType()->getPointerElementType(); 146 | } 147 | // try to get the destination type we are casting 148 | } else if (ptrCasted && srcType && (ptrName.find("__container_of_type_out") != std::string::npos)) { 149 | Value* valueStored = SI->getOperand(0); 150 | 151 | // get the type from the ptrtoint instruction 152 | if(PtrToIntInst* PI = dyn_cast(valueStored)) { 153 | dstType = PI->getOperand(0)->getType()->getPointerElementType()->getPointerElementType(); 154 | } else if (PtrToIntOperator* PI = dyn_cast(valueStored)) { 155 | dstType = PI->getOperand(0)->getType()->getPointerElementType()->getPointerElementType(); 156 | } 157 | // try to get the result of the container_of 158 | } else if (ptrCasted && srcType && dstType && (ptrName.find("__container_of_ptr_out") != std::string::npos)) { 159 | Value* valueStored = SI->getOperand(0); 160 | 161 | // get the result of the container_of 162 | if(PtrToIntInst* PI = dyn_cast(valueStored)) { 163 | resCasted = PI->getOperand(0); 164 | resInstr = SI; 165 | } else if (PtrToIntOperator* PI = dyn_cast(valueStored)) { 166 | resCasted = PI->getOperand(0); 167 | resInstr = SI; 168 | } 169 | } else if (ptrCasted && srcType && dstType && resCasted && (ptrName.find("__container_of_ptr_diff") != std::string::npos)) { 170 | Value* valueStored = SI->getOperand(0); 171 | if (ConstantInt* CI = dyn_cast(valueStored)) { 172 | ptrDiffFound = true; 173 | ptrDiff = CI->getZExtValue(); 174 | } else { 175 | // in some obscure cases, the container_of offset may not be a constant 176 | // see: https://elixir.bootlin.com/linux/v5.17.1/source/kernel/pid.c#L404 177 | ptrDiffFound = true; 178 | ptrDiff = -1; 179 | } 180 | } 181 | 182 | // if we found everything, dump and restart searching 183 | if (ptrCasted && srcType && dstType && resCasted && ptrDiffFound) { 184 | 185 | // compute hashes 186 | size_t src_hash = TypeToNameHash(srcType); 187 | size_t dst_hash = TypeToNameHash(dstType); 188 | { 189 | std::string type_str; 190 | raw_string_ostream rso(type_str); 191 | // dump the edge 192 | rso << format_hex(src_hash, 18, /*upper=*/ false); 193 | rso << " -> "; 194 | rso << format_hex(dst_hash, 18, /*upper=*/ false); 195 | if(DUMP_DEBUG) { 196 | rso << " -> "; 197 | rso << getDebugLocation(*resInstr); 198 | rso << " -> "; 199 | rso << ptrDiff; 200 | rso << ", "; 201 | rso << DL->getTypeAllocSize(dstType); 202 | } 203 | rso << "\n"; 204 | 205 | // std::cout << type_str; 206 | dumpToFile(EDGES_LOGFILE, rso.str()); 207 | } 208 | { 209 | std::string type_str; 210 | raw_string_ostream rso(type_str); 211 | // dump the type info 212 | rso << format_hex(src_hash, 18, /*upper=*/ false); 213 | rso << ": "; 214 | srcType->print(rso); 215 | rso << "\n"; 216 | rso << format_hex(dst_hash, 18, /*upper=*/ false); 217 | rso << ": "; 218 | dstType->print(rso); 219 | rso << "\n"; 220 | 221 | dumpToFile(NODES_LOGFILE, rso.str()); 222 | } 223 | 224 | // reset all 225 | ptrCasted = nullptr; 226 | srcType = nullptr; 227 | dstType = nullptr; 228 | resCasted = nullptr; 229 | ptrDiffFound = false; 230 | ptrDiff = -1; 231 | } 232 | } 233 | } 234 | } 235 | return true; 236 | } 237 | 238 | bool DumpContainerOf::runImpl(Module &M) { 239 | bool Changed = false; 240 | for (Function &F : M) 241 | Changed |= visitor(F); 242 | dbgs() << "dumped container_of\n"; 243 | return Changed; 244 | } 245 | 246 | // New PM implementation 247 | struct DumpContainerOfPass : PassInfoMixin { 248 | PreservedAnalyses run(Module &M, ModuleAnalysisManager &) { 249 | bool Changed = DumpContainerOf().runImpl(M); 250 | return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all(); 251 | } 252 | }; 253 | 254 | // Legacy PM implementation 255 | struct LegacyDumpContainerOfPass : public ModulePass { 256 | static char ID; 257 | LegacyDumpContainerOfPass() : ModulePass(ID) {} 258 | // Main entry point - the name conveys what unit of IR this is to be run on. 259 | bool runOnModule(Module &M) override { 260 | return DumpContainerOf().runImpl(M); 261 | } 262 | }; 263 | } // namespace 264 | 265 | //----------------------------------------------------------------------------- 266 | // New PM Registration 267 | //----------------------------------------------------------------------------- 268 | llvm::PassPluginLibraryInfo getDumpContainerOfPassPluginInfo() { 269 | return {LLVM_PLUGIN_API_VERSION, "DumpContainerOf", LLVM_VERSION_STRING, 270 | [](PassBuilder &PB) { 271 | PB.registerOptimizerLastEPCallback( 272 | [](llvm::ModulePassManager &PM, 273 | llvm::PassBuilder::OptimizationLevel Level) { 274 | PM.addPass(DumpContainerOfPass()); 275 | }); 276 | PB.registerPipelineParsingCallback( 277 | [](StringRef Name, ModulePassManager &MPM, 278 | ArrayRef) { 279 | if (Name == "dump-container-of") { 280 | MPM.addPass(DumpContainerOfPass()); 281 | return true; 282 | } 283 | return false; 284 | }); 285 | }}; 286 | } 287 | 288 | extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo 289 | llvmGetPassPluginInfo() { 290 | return getDumpContainerOfPassPluginInfo(); 291 | } 292 | 293 | 294 | //----------------------------------------------------------------------------- 295 | // Legacy PM Registration 296 | //----------------------------------------------------------------------------- 297 | char LegacyDumpContainerOfPass::ID = 0; 298 | 299 | static RegisterPass 300 | X("dump-container-of", "DumpContainerOf Pass", 301 | false, // This pass does modify the CFG => false 302 | false // This pass is not a pure analysis pass => false 303 | ); 304 | 305 | static llvm::RegisterStandardPasses RegisterDumpContainerOfLTOThinPass( 306 | llvm::PassManagerBuilder::EP_OptimizerLast, 307 | [](const llvm::PassManagerBuilder &Builder, 308 | llvm::legacy::PassManagerBase &PM) { PM.add(new LegacyDumpContainerOfPass()); }); 309 | 310 | static llvm::RegisterStandardPasses RegisterDumpContainerOfLTOPass( 311 | llvm::PassManagerBuilder::EP_FullLinkTimeOptimizationEarly, 312 | [](const llvm::PassManagerBuilder &Builder, 313 | llvm::legacy::PassManagerBase &PM) { PM.add(new LegacyDumpContainerOfPass()); }); 314 | -------------------------------------------------------------------------------- /passes/DumpTypes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_llvm_library(LLVMDumpTypesPass MODULE 2 | DumpTypes.cpp 3 | ) 4 | 5 | include_directories(../include) 6 | -------------------------------------------------------------------------------- /passes/DumpTypes/DumpTypes.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // FILE: 3 | // DumpTypes.cpp 4 | // 5 | // DESCRIPTION: 6 | // Dump container_of macro invocations to collect different informations 7 | // 8 | //============================================================================= 9 | #include "llvm/IR/LegacyPassManager.h" 10 | #include "llvm/Passes/PassBuilder.h" 11 | #include "llvm/Passes/PassPlugin.h" 12 | #include "llvm/Support/raw_ostream.h" 13 | #include "llvm/Support/CommandLine.h" 14 | 15 | #include 16 | #include "llvm/IR/InstVisitor.h" 17 | 18 | #include "llvm/Transforms/IPO/PassManagerBuilder.h" // RegisterStandardPasses 19 | #include // SplitBlock 20 | 21 | #include "TypeUtils/TypeUtils.hpp" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | extern "C" { 28 | #include 29 | #include 30 | #include // for flock() 31 | } 32 | 33 | using namespace llvm; 34 | 35 | #define oprint(s) (outs() << s << "\n") 36 | 37 | #define LOGFILE "all_types_log.txt" 38 | 39 | //----------------------------------------------------------------------------- 40 | // DumpTypes implementation 41 | //----------------------------------------------------------------------------- 42 | // No need to expose the internals of the pass to the outside world - keep 43 | // everything in an anonymous namespace. 44 | namespace { 45 | 46 | class DumpTypes { 47 | Module *Mod; 48 | LLVMContext *Ctx; 49 | 50 | bool init(Module &M); 51 | bool visitor(Function &F); 52 | void dumpToFile(const char* filename, std::string& s); 53 | 54 | public: 55 | bool runImpl(Module &M); 56 | }; 57 | 58 | bool DumpTypes::init(Module &M) { 59 | Mod = &M; 60 | Ctx = &M.getContext(); 61 | 62 | return true; 63 | } 64 | 65 | void DumpTypes::dumpToFile(const char* filename, std::string& s) { 66 | // appending to a file is atomic, so should be safe but just to be sure 67 | #define LOCKNAME "/tmp/uncontained_lock" 68 | int fd = open(LOCKNAME, O_RDWR | O_CREAT, 0664); // open or create lockfile 69 | int rc = flock(fd, LOCK_EX); // grab exclusive lock 70 | 71 | std::ofstream log(LOGFILE, std::ofstream::app); 72 | log << s; 73 | 74 | // unlock 75 | flock(fd, LOCK_UN); 76 | close(fd); 77 | } 78 | 79 | bool DumpTypes::visitor(Function &F) { 80 | return false; 81 | } 82 | 83 | bool DumpTypes::runImpl(Module &M) { 84 | 85 | // keep track of all the uncontained types, i.e. the types that are never contained 86 | // into any other type 87 | std::set uncontainedTypes; 88 | 89 | // add each type to the list of potential uncontained types 90 | for (StructType* ST: M.getIdentifiedStructTypes()) { 91 | uncontainedTypes.insert(TypeToNameHash(ST)); 92 | } 93 | 94 | // for each type, remove all the types it contains from the set 95 | for (StructType* ST: M.getIdentifiedStructTypes()) { 96 | for (unsigned i = 0, e = ST->getNumElements(); i != e; ++i) 97 | { 98 | Type* T = ST->getElementType(i); 99 | if (isa(T)) 100 | uncontainedTypes.erase(TypeToNameHash(T)); 101 | } 102 | } 103 | 104 | // collect all type hashes 105 | for (StructType* ST: M.getIdentifiedStructTypes()) { 106 | std::string type_str; 107 | raw_string_ostream rso(type_str); 108 | 109 | size_t name_hash = TypeToNameHash(ST); 110 | rso << format_hex(name_hash, 18, /*upper=*/ false); 111 | rso << " -> "; 112 | ST->print(rso); 113 | 114 | // mark the uncontained types 115 | if (uncontainedTypes.find(name_hash) != uncontainedTypes.end()) 116 | rso << " UNCONTAINED"; 117 | rso << "\n"; 118 | 119 | // std::cout << type_str; 120 | dumpToFile(LOGFILE, rso.str()); 121 | } 122 | return false; 123 | } 124 | 125 | // New PM implementation 126 | struct DumpTypesPass : PassInfoMixin { 127 | PreservedAnalyses run(Module &M, ModuleAnalysisManager &) { 128 | bool Changed = DumpTypes().runImpl(M); 129 | return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all(); 130 | } 131 | }; 132 | 133 | // Legacy PM implementation 134 | struct LegacyDumpTypesPass : public ModulePass { 135 | static char ID; 136 | LegacyDumpTypesPass() : ModulePass(ID) {} 137 | // Main entry point - the name conveys what unit of IR this is to be run on. 138 | bool runOnModule(Module &M) override { 139 | return DumpTypes().runImpl(M); 140 | } 141 | }; 142 | } // namespace 143 | 144 | //----------------------------------------------------------------------------- 145 | // New PM Registration 146 | //----------------------------------------------------------------------------- 147 | llvm::PassPluginLibraryInfo getDumpTypesPassPluginInfo() { 148 | return {LLVM_PLUGIN_API_VERSION, "DumpTypes", LLVM_VERSION_STRING, 149 | [](PassBuilder &PB) { 150 | PB.registerOptimizerLastEPCallback( 151 | [](llvm::ModulePassManager &PM, 152 | llvm::PassBuilder::OptimizationLevel Level) { 153 | PM.addPass(DumpTypesPass()); 154 | }); 155 | PB.registerPipelineParsingCallback( 156 | [](StringRef Name, ModulePassManager &MPM, 157 | ArrayRef) { 158 | if (Name == "dump-types") { 159 | MPM.addPass(DumpTypesPass()); 160 | return true; 161 | } 162 | return false; 163 | }); 164 | }}; 165 | } 166 | 167 | extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo 168 | llvmGetPassPluginInfo() { 169 | return getDumpTypesPassPluginInfo(); 170 | } 171 | 172 | 173 | //----------------------------------------------------------------------------- 174 | // Legacy PM Registration 175 | //----------------------------------------------------------------------------- 176 | char LegacyDumpTypesPass::ID = 0; 177 | 178 | static RegisterPass 179 | X("dump-container-of", "DumpTypes Pass", 180 | false, // This pass does modify the CFG => false 181 | false // This pass is not a pure analysis pass => false 182 | ); 183 | 184 | static llvm::RegisterStandardPasses RegisterDumpTypesLTOThinPass( 185 | llvm::PassManagerBuilder::EP_OptimizerLast, 186 | [](const llvm::PassManagerBuilder &Builder, 187 | llvm::legacy::PassManagerBase &PM) { PM.add(new LegacyDumpTypesPass()); }); 188 | 189 | static llvm::RegisterStandardPasses RegisterDumpTypesLTOPass( 190 | llvm::PassManagerBuilder::EP_FullLinkTimeOptimizationLast, 191 | [](const llvm::PassManagerBuilder &Builder, 192 | llvm::legacy::PassManagerBase &PM) { PM.add(new LegacyDumpTypesPass()); }); 193 | -------------------------------------------------------------------------------- /passes/SimpleDataflowChecker/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_llvm_library(LLVMSimpleDataflowCheckerPass MODULE 2 | SimpleDataflowChecker.cpp 3 | DataflowRule.cpp 4 | DataflowSanitizer.cpp 5 | ) 6 | 7 | include_directories(../include) 8 | 9 | include(FetchContent) 10 | FetchContent_Declare( 11 | yaml-cpp 12 | GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git 13 | GIT_TAG 0579ae3d976091d7d664aa9d2527e0d0cff25763 14 | ) 15 | FetchContent_MakeAvailable(yaml-cpp) 16 | 17 | target_compile_options(yaml-cpp PRIVATE -Wno-shadow) 18 | set_property(TARGET yaml-cpp PROPERTY POSITION_INDEPENDENT_CODE ON) 19 | include_directories(${yaml-cpp_SOURCE_DIR}/include) 20 | 21 | target_link_libraries(LLVMSimpleDataflowCheckerPass PRIVATE yaml-cpp) 22 | -------------------------------------------------------------------------------- /passes/SimpleDataflowChecker/DataflowSanitizer.cpp: -------------------------------------------------------------------------------- 1 | #include "SimpleDataflowChecker/DataflowSanitizer.hpp" 2 | 3 | raw_ostream& operator<<(raw_ostream& os, const DataflowSanitizer& d) 4 | { 5 | d.print(os); 6 | return os; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /passes/include/Colors/colors.h: -------------------------------------------------------------------------------- 1 | #ifndef _COLORS_ 2 | #define _COLORS_ 3 | 4 | /* FOREGROUND */ 5 | #define RST "\x1B[0m" 6 | #define KRED "\x1B[31m" 7 | #define KGRN "\x1B[32m" 8 | #define KYEL "\x1B[33m" 9 | #define KBLU "\x1B[34m" 10 | #define KMAG "\x1B[35m" 11 | #define KCYN "\x1B[36m" 12 | #define KWHT "\x1B[37m" 13 | #define KBLD "\x1B[1m" 14 | #define KUND "\x1B[4m" 15 | 16 | #define RED(x) KRED x RST 17 | #define GREEN(x) KGRN x RST 18 | #define YELLOW(x) KYEL x RST 19 | #define BLUE(x) KBLU x RST 20 | #define MAGENTA(x) KMAG x RST 21 | #define CYAN(x) KCYN x RST 22 | #define WHITE(x) KWHT x RST 23 | 24 | #define SRED(x) KRED << x << RST 25 | #define SGREEN(x) KGRN << x << RST 26 | #define SYELLOW(x) KYEL << x << RST 27 | #define SBLUE(x) KBLU << x << RST 28 | #define SMAGENTA(x) KMAG << x << RST 29 | #define SCYAN(x) KCYN << x << RST 30 | #define SWHITE(x) KWHT << x << RST 31 | 32 | #define BOLD(x) KBLD x RST 33 | #define UNDERLINE(x) KUND x RST 34 | 35 | #define SBOLD(x) KBLD << x << RST 36 | #define SUNDERLINE(x) KUND << x << RST 37 | 38 | #define PBOLD(x) KBLD + x + RST 39 | #define PUNDERLINE(x) KUND + x + RST 40 | 41 | 42 | #endif /* _COLORS_ */ -------------------------------------------------------------------------------- /passes/include/SimpleDataflowChecker/Dataflow.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _Dataflow_ 2 | #define _Dataflow_ 3 | 4 | #include "llvm/IR/Module.h" 5 | #include "llvm/IR/Instructions.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace llvm; 13 | 14 | struct Offset { 15 | const bool is_valid; 16 | const long offset; 17 | 18 | Offset(const long _offset, const bool _is_valid) : 19 | offset(_offset), is_valid(_is_valid) {} 20 | 21 | Offset apply(Value *op) const { 22 | // if the offset is not valid, return another invalid one 23 | if (!is_valid) return Offset(0, false); 24 | 25 | // Apply the GEP operation 26 | if (GetElementPtrInst* GEP = dyn_cast(op)) { 27 | Module* Mod = GEP->getModule(); 28 | LLVMContext* Ctx = &Mod->getContext(); 29 | static thread_local DataLayout* DL = new DataLayout(Mod); 30 | APInt GEPOffset = APInt(sizeof(unsigned long)*8, 0); 31 | 32 | // track the offset only if constant GEP 33 | if(GEP->accumulateConstantOffset(*DL, GEPOffset)) { 34 | return Offset(offset + GEPOffset.getSExtValue(), true); 35 | // otherwise invalidate the offset 36 | } else { 37 | return Offset(0, false); 38 | } 39 | // If it is a load, then we loose the offset of the loaded value 40 | } else if (LoadInst *LI = dyn_cast(op)) { 41 | return Offset(0, false); 42 | } 43 | return Offset(offset, true); 44 | } 45 | }; 46 | 47 | 48 | // Linked list to represent dataflows 49 | struct Dataflow { 50 | const std::shared_ptr next; 51 | const Value* flow; 52 | const Offset offset; 53 | 54 | Dataflow(const Value* _flow, 55 | const Offset& _offset, 56 | const std::shared_ptr& _next) : 57 | next(_next), flow(_flow), offset(_offset) {} 58 | 59 | // return a new dataflow passing trough the user 60 | static std::shared_ptr pass_through(std::shared_ptr &dataflow, User* user) { 61 | // Compute the new offset at the instruction execution 62 | Offset new_offset = dataflow->offset.apply(user); 63 | 64 | return std::make_shared(user, new_offset, dataflow); 65 | } 66 | }; 67 | 68 | struct DataflowBackwards : public Dataflow { 69 | DataflowBackwards(const Value* _flow, 70 | const Offset& _offset, 71 | const std::shared_ptr& _next) : Dataflow(_flow, _offset, _next) {} 72 | 73 | // return a new dataflow passing trough the user 74 | static std::shared_ptr pass_through(std::shared_ptr &dataflow, const Value* val) { 75 | // Compute the new offset at the instruction execution 76 | // Offset new_offset = dataflow->offset.apply(user); 77 | Offset new_offset = dataflow->offset; 78 | 79 | return std::make_shared(val, new_offset, dataflow); 80 | } 81 | }; 82 | 83 | #endif /* _Dataflow_ */ 84 | -------------------------------------------------------------------------------- /passes/include/SimpleDataflowChecker/DataflowRule.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _DataflowRule_ 2 | #define _DataflowRule_ 3 | 4 | #include "llvm/IR/Module.h" 5 | #include "llvm/IR/Operator.h" 6 | #include "llvm/IR/Dominators.h" 7 | #include "llvm/Support/CommandLine.h" 8 | #include "llvm/Analysis/CFG.h" 9 | #include "llvm/ADT/Optional.h" 10 | 11 | #include "yaml-cpp/yaml.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "SimpleDataflowChecker/Helpers.hpp" 19 | #include "SimpleDataflowChecker/DataflowSanitizer.hpp" 20 | 21 | #include "TypeUtils/TypeUtils.hpp" 22 | 23 | using namespace llvm; 24 | 25 | struct DataflowSource { 26 | const Value *source; 27 | const Instruction *instruction; 28 | 29 | virtual ~DataflowSource() = default; 30 | virtual void write(raw_ostream&) const; 31 | DataflowSource(const Value *source, const Instruction *instruction) : source(source), instruction(instruction) {}; 32 | }; 33 | 34 | struct BackwardsContainedDataflowSource : public DataflowSource { 35 | Type *type; 36 | void write(raw_ostream&) const; 37 | BackwardsContainedDataflowSource(const Value *source, const Instruction *instruction, Type* type) : 38 | DataflowSource(source, instruction), type(type) {}; 39 | }; 40 | 41 | struct KObjSourcesDataflowSource : public DataflowSource { 42 | Type *containerType; 43 | const CallBase *callBase; 44 | KObjSourcesDataflowSource(const Value *source, const Instruction *instruction, Type* containerType, const CallBase *callBase) : 45 | DataflowSource(source, instruction), containerType(containerType), callBase(callBase) {}; 46 | }; 47 | 48 | struct KObjDataflowSource : public DataflowSource { 49 | Type *containerType; 50 | const CallBase *callBase; 51 | void write(raw_ostream&) const; 52 | KObjDataflowSource(const Value *source, const Instruction *instruction, Type* containerType, const CallBase *callBase) : DataflowSource(source, instruction), containerType(containerType), callBase(callBase) {}; 53 | }; 54 | 55 | struct CompareSource { 56 | bool operator() (DataflowSource *left, DataflowSource *right) { 57 | KObjDataflowSource *kleft = dynamic_cast(left); 58 | KObjDataflowSource *kright = dynamic_cast(right); 59 | if (left->source == right->source && 60 | kleft != nullptr && kright != nullptr) { 61 | return kleft->callBase < kright->callBase; 62 | } 63 | return left->source < right->source; 64 | } 65 | }; 66 | 67 | struct DataflowSink { 68 | const Value *sink; 69 | bool stopFlow; 70 | std::string message; 71 | 72 | virtual void write(raw_ostream&) const; 73 | DataflowSink(const Value *sink, bool stopFlow=false, const char* message=""): sink(sink), stopFlow(stopFlow), message(message) {}; 74 | }; 75 | 76 | struct BackwardsContainedDataflowSink : public DataflowSink { 77 | Type *sinkType; 78 | void write(raw_ostream&) const; 79 | BackwardsContainedDataflowSink(const Value *sink, Type *sinkType, bool stopFlow=false, const char* message=""): 80 | DataflowSink(sink, stopFlow, message), sinkType(sinkType) {}; 81 | }; 82 | 83 | struct ContainerOf { 84 | Type *inType; 85 | Type *resType; 86 | Value *inPtr; 87 | }; 88 | 89 | std::map buildContainerOfMap(Module &M); 90 | std::map buildListMap(Module &M); 91 | 92 | class DataflowRule { 93 | public: 94 | std::string name; 95 | std::set sources; 96 | std::set sinks; 97 | std::set ignores; 98 | bool any_sink=false; 99 | 100 | // TODO: this is orrible, implement the same parsing as we do for sanitizers 101 | bool cmp_null=false; 102 | 103 | bool sanitize_reachable = false; 104 | bool sanitize_implicit_flow = false; 105 | // true if the rule should apply all the sanitizers also to the source of the 106 | // dataflow, and not only to the values in the flow 107 | // e.g., 108 | // source: list_entry() 109 | // sanitizer: list_empty(), list_is_head() 110 | // if(list_empty()) list_entry() 111 | // would sanitize only if sanitize_source = true 112 | // 113 | // entry = list_entry(); if(list_is_head(entry)) ... 114 | // would sanitize also with sanitize_source = false 115 | bool sanitize_source = false; 116 | // true if sanitize_source==true or if any sanitizer has sanitize_source==true 117 | bool may_sanitize_source = false; 118 | bool backward_flow = false; 119 | 120 | std::map container_of_map; 121 | 122 | // The list of sanitizers to apply to this rule 123 | std::set> sanitizers; 124 | 125 | DataflowRule() { 126 | // empty constructor for subclasses 127 | } 128 | 129 | DataflowRule(Module& M, YAML::Node& config); 130 | 131 | void dump(); 132 | virtual bool isSanitized(const Value* V, std::set& sanitizerInstructions); 133 | virtual DataflowSink *isSink(Value* V, DataflowSource *source); 134 | virtual void reportFlow(DataflowSource *dataflowSource, DataflowSink *dataflowSink, std::shared_ptr Dataflow); 135 | virtual void dumpReport(std::string& filename, DataflowSource *dataflowSource, 136 | DataflowSink *dataflowSink, std::shared_ptr Dataflow); 137 | 138 | bool isIgnore(Value *V); 139 | bool isSanitizer(Value* V, const Offset& offset); 140 | void gatherAllSanitizers(const Function* F, std::set &out, bool target_source=false); 141 | 142 | 143 | protected: 144 | void initialize(Module& M, YAML::Node& config); 145 | 146 | virtual std::set getSources(Module& M, std::string&& _source); 147 | std::set getSinks(Module& M, std::string&& _sink); 148 | std::set getIgnores(Module &M, std::list&& ignores); 149 | unsigned getID(std::string&& valueName); 150 | std::set> getSanitizers(Module& M, YAML::Node&& config_sanitizers); 151 | void parseOptions(YAML::Node&& config_options); 152 | }; 153 | 154 | class BackwardsContainedDataflowRule : public DataflowRule { 155 | public: 156 | BackwardsContainedDataflowRule(Module& M, YAML::Node& config) : DataflowRule() { 157 | backward_flow = true; 158 | initialize(M, config); 159 | }; 160 | 161 | DataflowSink *isSink(Value* V, DataflowSource *source); 162 | 163 | protected: 164 | std::set getSources(Module& M, std::string&& _source); 165 | }; 166 | 167 | class KObjSourcesDataflowRule : public DataflowRule { 168 | public: 169 | KObjSourcesDataflowRule(Module& M); 170 | 171 | DataflowSink *isSink(Value* V, DataflowSource *source); 172 | 173 | protected: 174 | void getSources(Module &M); 175 | }; 176 | 177 | class KObjDataflowRule : public DataflowRule { 178 | public: 179 | KObjDataflowRule(Module& M, YAML::Node& config) : DataflowRule() { initialize(M, config); }; 180 | 181 | bool isSanitized(const Value* V, std::set& sanitizerInstructions); 182 | DataflowSink *isSink(Value* V, DataflowSource *source); 183 | 184 | protected: 185 | std::set getSources(Module& M, std::string&& _source); 186 | }; 187 | 188 | class ListEntryCorrelationSourcesDataflowRule : public DataflowRule { 189 | public: 190 | ListEntryCorrelationSourcesDataflowRule(Module& M); 191 | 192 | DataflowSink *isSink(Value* V, DataflowSource *source); 193 | 194 | protected: 195 | void getSources(Module &M); 196 | }; 197 | 198 | class ListEntryCorrelationDataflowRule : public DataflowRule { 199 | public: 200 | ListEntryCorrelationDataflowRule(Module& M, YAML::Node& config); 201 | 202 | std::map list_map; 203 | std::map list_add_map; 204 | 205 | DataflowSink *isSink(Value* V, DataflowSource *source); 206 | 207 | protected: 208 | std::set getSources(Module& M, std::string&& _source); 209 | 210 | void buildListAddMap(Module& M); 211 | }; 212 | 213 | #endif /* _DataflowRule_ */ 214 | -------------------------------------------------------------------------------- /passes/include/SimpleDataflowChecker/DataflowSanitizer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _DataflowSanitizer_ 2 | #define _DataflowSanitizer_ 3 | 4 | #include "llvm/IR/Module.h" 5 | #include "llvm/IR/Instructions.h" 6 | 7 | #include "SimpleDataflowChecker/Helpers.hpp" 8 | #include "SimpleDataflowChecker/Dataflow.hpp" 9 | 10 | using namespace llvm; 11 | 12 | class DataflowSanitizer { 13 | public: 14 | // true if the sanitizer applies also to the source of the dataflow, and not 15 | // only to the values in the flow 16 | // e.g., 17 | // source: list_entry() 18 | // sanitizer: list_empty(), list_is_head() 19 | // if(list_empty()) list_entry() 20 | // would sanitize only if sanitize_source = true 21 | // 22 | // entry = list_entry(); if(list_is_head(entry)) ... 23 | // would sanitize also with sanitize_source = false 24 | bool sanitize_source = false; 25 | 26 | virtual bool match(const Value* V, const Offset& offset) = 0; 27 | virtual void print(raw_ostream&) const = 0; 28 | virtual ~DataflowSanitizer() {}; 29 | friend raw_ostream& operator<<(raw_ostream& os, const DataflowSanitizer& d); 30 | }; 31 | 32 | // Sanitizer: specific instruction that operate on a particular offset 33 | // e.g, loading ptr->type will probably check the type 34 | class DataflowInstructionSanitizer : public DataflowSanitizer { 35 | public: 36 | const long match_offset; 37 | // wether or not the offset should be checked for this sanitizer 38 | const long use_match_offset; 39 | const unsigned match_id; 40 | 41 | const long match_operand; 42 | // wether or not the operands should be checked for this sanitizer 43 | const long use_match_operand; 44 | 45 | DataflowInstructionSanitizer(long offset, bool use_offset, long operand, bool use_operand, unsigned id) : 46 | match_offset(offset), use_match_offset(use_offset), 47 | match_operand(operand), use_match_operand(use_operand), match_id(id) {}; 48 | 49 | bool match(const Value* _V, const Offset& offset) { 50 | // bail out if the wrong type of instruction 51 | if (getValueID(_V) != match_id) 52 | return false; 53 | 54 | // check the offset 55 | bool offset_matches = true; 56 | if (use_match_offset) 57 | offset_matches = offset.is_valid && offset.offset == match_offset; 58 | 59 | // check the operand 60 | bool operand_matches = true; 61 | if (use_match_operand) 62 | operand_matches = searchOperandInInstruction(_V, match_operand); 63 | 64 | return offset_matches && operand_matches; 65 | } 66 | 67 | void print(raw_ostream& os) const 68 | { 69 | os << "DataflowInstructionSanitizer: {id: " << match_id; 70 | if (use_match_offset) 71 | os << ", offset: " << match_offset; 72 | if (use_match_operand) 73 | os << ", operand: " << match_operand; 74 | os << "}"; 75 | } 76 | }; 77 | 78 | class DataflowFunctionCallSanitizer : public DataflowSanitizer { 79 | public: 80 | Function* match_function; 81 | 82 | DataflowFunctionCallSanitizer(Module &M, std::string& function_name) { 83 | match_function = M.getFunction(function_name); 84 | if (!match_function) 85 | warning("No function found for sanitizer: " + function_name); 86 | }; 87 | 88 | bool match(const Value* V, const Offset& _offset) { 89 | if (const CallBase *CB = dyn_cast(V)) { 90 | // Only if they represent direct calls to functions 91 | if (CB->isInlineAsm()) return false; 92 | Function *Called = dyn_cast(CB->getCalledOperand()->stripPointerCasts()); 93 | if (!Called || Called->isDeclaration() || Called->isIntrinsic()) return false; 94 | 95 | return Called == match_function; 96 | } 97 | return false; 98 | } 99 | 100 | void print(raw_ostream& os) const 101 | { 102 | if (match_function) 103 | os << "DataflowFunctionCallSanitizer: " << match_function->getName().str(); 104 | else 105 | os << "DataflowFunctionCallSanitizer: NULL"; } 106 | }; 107 | 108 | #endif /* _DataflowSanitizer_ */ 109 | -------------------------------------------------------------------------------- /passes/include/SimpleDataflowChecker/Helpers.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _Helpers_ 2 | #define _Helpers_ 3 | 4 | #include "llvm/IR/Module.h" 5 | #include "llvm/IR/Instructions.h" 6 | #include "llvm/IR/DebugInfoMetadata.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "Colors/colors.h" 19 | 20 | #define oprint(s) (outs() << s << "\n") 21 | #define eprint(s) (errs() << s << "\n") 22 | #define warning(s) (errs() << "[" YELLOW("WARNING") "] " << s << "\n") 23 | 24 | using namespace llvm; 25 | 26 | // This is an hack: artificially modify the LLVM ID of conditional branches to recognize them 27 | const unsigned CC_BRANCH_ID = 0x0CC00000uL; 28 | 29 | // Return all the calls to the function F 30 | static std::set getCallsTo(const Function* F) { 31 | std::set calls; 32 | static thread_local std::list toVisit; 33 | static thread_local std::set visited; 34 | toVisit.clear(); 35 | visited.clear(); 36 | if (F) { 37 | for(auto user: F->users()) { 38 | toVisit.push_back(user); 39 | } 40 | while (!toVisit.empty()) { 41 | // Always pop from the front 42 | const Value* curr = toVisit.front(); 43 | toVisit.pop_front(); 44 | 45 | // skip if circular visit 46 | if (visited.find(curr) != visited.end()) continue; 47 | visited.insert(curr); 48 | 49 | // If we found a callbase then add it to the result 50 | if (const CallBase* CB = dyn_cast(curr)) { 51 | if (CB->getCalledOperand()->stripPointerCasts() == F) 52 | calls.insert(CB); 53 | } else { 54 | for (auto user : curr->users()) { 55 | toVisit.push_back(user); 56 | } 57 | } 58 | } 59 | } 60 | return calls; 61 | } 62 | 63 | static std::string getDebugLocation(const Instruction &I, bool short_print=false, std::string *inlinedShort = nullptr) { 64 | if (DILocation *Loc = I.getDebugLoc()) { 65 | unsigned Line = Loc->getLine(); 66 | StringRef File = Loc->getFilename(); 67 | DILocation *InlineLoc = Loc->getInlinedAt(); 68 | if (!InlineLoc) 69 | return File.str() + ":" + std::to_string(Line); 70 | else { 71 | unsigned InLine = InlineLoc->getLine(); 72 | StringRef InFile = InlineLoc->getFilename(); 73 | if (short_print && inlinedShort) { 74 | *inlinedShort = InFile.str() + ":" + std::to_string(InLine); 75 | return File.str() + ":" + std::to_string(Line); 76 | } 77 | return File.str() + ":" + std::to_string(Line) + 78 | ", inlined at: " + InFile.str() + ":" + std::to_string(InLine); 79 | } 80 | } else { 81 | // No location metadata available 82 | return ""; 83 | } 84 | } 85 | 86 | static std::string getDebugLocation(const Function& F) { 87 | if (DISubprogram *SP = F.getSubprogram()) { 88 | unsigned Line = SP->getLine(); 89 | StringRef File = SP->getFilename(); 90 | return File.str() + ":" + std::to_string(Line); 91 | } else { 92 | // No location metadata available 93 | return ""; 94 | } 95 | } 96 | 97 | static std::string instructionToLocation(const Instruction* I, bool colors=true) { 98 | if (I) { 99 | if (colors) 100 | return PBOLD(I->getFunction()->getName().str() + "()") + " in " + getDebugLocation(*I); 101 | else 102 | return I->getFunction()->getName().str() + "()" + " in " + getDebugLocation(*I); 103 | } 104 | return ""; 105 | } 106 | 107 | static std::string functionToLocation(const Function* F, bool colors=true) { 108 | if (F) { 109 | if (colors) 110 | return PBOLD(F->getName().str() + "()") + " in " + getDebugLocation(*F); 111 | else 112 | return F->getName().str() + "()" + " in " + getDebugLocation(*F); 113 | } 114 | return ""; 115 | } 116 | 117 | static std::string instructionToYAMLLocation(const Instruction* I) { 118 | if (I) { 119 | std::string inlined; 120 | std::string debugLoc = getDebugLocation(*I, true, &inlined); 121 | if (!inlined.empty()) 122 | return "{func: \"" + I->getFunction()->getName().str() + "()\", file: \"" + debugLoc + "\", inlined_at: \"" + inlined + "\"}"; 123 | return "{func: \"" + I->getFunction()->getName().str() + "()\", file: \"" + debugLoc + "\"}"; 124 | } 125 | return "{}"; 126 | } 127 | 128 | static std::string functionToYAMLLocation(const Function* F) { 129 | if (F) { 130 | return "{func: \"" + F->getName().str() + "()\", file: \"" + getDebugLocation(*F) + "\"}"; 131 | } 132 | return "{}"; 133 | } 134 | 135 | // Print the instruction location to yaml 136 | static std::string valueToYAMLLocation(const Value* V) { 137 | if(const Instruction *I = dyn_cast(V)) 138 | return instructionToYAMLLocation(I); 139 | else if (const Function *F = dyn_cast(V)) 140 | return functionToYAMLLocation(F); 141 | else if (const Argument *A = dyn_cast(V)) 142 | return functionToYAMLLocation(A->getParent()); 143 | std::string str; 144 | llvm::raw_string_ostream rso(str); 145 | rso << "{raw: \""; 146 | V->print(rso); 147 | rso << "}\""; 148 | return str; 149 | } 150 | 151 | // Return the ID of LLVM values differentiating branches and conditional branches 152 | static unsigned getValueID(const Value* V) { 153 | if (const BranchInst *BI = dyn_cast(V)) { 154 | if (BI->isConditional()) return V->getValueID() + CC_BRANCH_ID; 155 | } 156 | return V->getValueID(); 157 | } 158 | 159 | static void appendToFile(const char* filename, std::string& s) { 160 | // appending to a file is atomic, so should be safe but just to be sure 161 | #define LOCKNAME "/tmp/uncontained_lock" 162 | int fd = open(LOCKNAME, O_RDWR | O_CREAT, 0664); // open or create lockfile 163 | int rc = flock(fd, LOCK_EX); // grab exclusive lock 164 | 165 | std::ofstream log(filename, std::ofstream::app); 166 | log << s; 167 | 168 | // unlock 169 | flock(fd, LOCK_UN); 170 | close(fd); 171 | } 172 | 173 | static bool searchOperandInInstruction(const Value* V, const long match_operand) { 174 | if (const Instruction* I = dyn_cast(V)) { 175 | for (Value* operand: I->operands()) { 176 | if (ConstantInt *C = dyn_cast(operand)) { 177 | if (C->getSExtValue() == match_operand) return true; 178 | } else if (isa(operand)) { 179 | // represent the NULL pointer with 0 180 | if (match_operand == 0) return true; 181 | } 182 | } 183 | } 184 | return false; 185 | } 186 | 187 | #endif /* _Helpers_ */ 188 | -------------------------------------------------------------------------------- /passes/include/SimpleDataflowChecker/SimpleDataflowChecker.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _DataflowChecker_ 2 | #define _DataflowChecker_ 3 | 4 | #include "llvm/IR/Module.h" 5 | 6 | #include "yaml-cpp/yaml.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "SimpleDataflowChecker/DataflowRule.hpp" 14 | 15 | using namespace llvm; 16 | 17 | // Allow using cl arguments also in other files; 18 | extern cl::opt PrintInstructions; 19 | extern cl::opt CompactPrint; 20 | 21 | class SimpleDataflowChecker { 22 | bool init(Module &M); 23 | 24 | void addStatistics(DataflowSink *sink); 25 | void printStatistics(void); 26 | 27 | void parseRule(Module& M, YAML::Node& config_rule, std::set& rules); 28 | void checkRule(DataflowRule& rule, Module& M); 29 | bool isSourceSanitized(const Value* source, DataflowRule& rule); 30 | std::set getSanitizers(const Value* source, DataflowRule& rule); 31 | 32 | std::map stats_map; 33 | std::mutex map_mutex; 34 | 35 | public: 36 | bool runImpl(Module &M); 37 | 38 | void searchFlows(DataflowSource *source, DataflowRule& rule, std::set &sanitizerInstructions); 39 | }; 40 | 41 | #endif /* _DataflowChecker_ */ 42 | -------------------------------------------------------------------------------- /passes/include/TypeUtils/TypeUtils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _TypeUtils_ 2 | #define _TypeUtils_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | static std::vector string_split(const std::string &s, char delim) { 14 | std::stringstream ss(s); 15 | std::string item; 16 | std::vector elems; 17 | while (std::getline(ss, item, delim)) { 18 | // elems.push_back(item); 19 | elems.push_back(std::move(item)); 20 | } 21 | return elems; 22 | } 23 | 24 | static std::string GetTypeName(llvm::Type* T) { 25 | switch (T->getTypeID()) 26 | { 27 | case llvm::Type::IntegerTyID: 28 | return "i" + std::to_string(llvm::cast(T)->getBitWidth()); 29 | 30 | case llvm::Type::VoidTyID: 31 | return "v"; 32 | case llvm::Type::FloatTyID: 33 | return "f"; 34 | case llvm::Type::DoubleTyID: 35 | return "d"; 36 | case llvm::Type::X86_FP80TyID: 37 | return "f80"; 38 | case llvm::Type::FP128TyID: 39 | return "f128"; 40 | case llvm::Type::PPC_FP128TyID: 41 | return "f2x64"; 42 | 43 | case llvm::Type::PointerTyID: 44 | { 45 | return GetTypeName(llvm::cast(T)->getPointerElementType()) + "*"; 46 | } 47 | 48 | case llvm::Type::StructTyID: 49 | { 50 | // get the actual Struct name but strip away llvm suffixes 51 | auto ST = llvm::cast(T); 52 | 53 | // if the structure has no name, or is an anonymous structure, visit the fields 54 | if (!ST->hasName() || ST->getName().str() == "" || ST->getName().contains(".anon.")) { 55 | std::string struct_repr = "struct("; 56 | // insert each type recursively in the struct 57 | for (unsigned i = 0, e = ST->getNumElements(); i != e; ++i) 58 | { 59 | // NOTICE: 60 | // The recursive call here should be guaranteed to terminate, as 61 | // there should not be pointers to the same anon struct (otherwise 62 | // it could not be anonymous), thus anon types should have no 63 | // loops 64 | struct_repr += GetTypeName(ST->getElementType(i)); 65 | if (i < e-1) struct_repr += ", "; 66 | } 67 | 68 | struct_repr += ")"; 69 | return struct_repr; 70 | } 71 | 72 | std::string struct_name = string_split(ST->getName().str(), '.').at(1); 73 | 74 | return struct_name; 75 | } 76 | 77 | case llvm::Type::FunctionTyID: 78 | { 79 | auto FT = llvm::cast(T); 80 | 81 | std::string func_repr = "func("; 82 | func_repr += GetTypeName(FT->getReturnType()); 83 | func_repr += ","; 84 | 85 | for (unsigned i = 0, e = FT->getNumParams(); i != e; ++i) 86 | { 87 | func_repr += GetTypeName(FT->getParamType(i)); 88 | if (i < e-1) func_repr += ", "; 89 | } 90 | 91 | if (FT->isVarArg()) func_repr += ", ...)"; 92 | else func_repr += ")"; 93 | 94 | return func_repr; 95 | } 96 | 97 | case llvm::Type::ArrayTyID: 98 | { 99 | auto AT = llvm::cast(T); 100 | return "arr(" + std::to_string(AT->getNumElements()) + ", " + GetTypeName(AT->getElementType()) + ")"; 101 | } 102 | 103 | case llvm::Type::FixedVectorTyID: 104 | { 105 | auto FVT = llvm::cast(T); 106 | return "fvec(" + std::to_string(FVT->getNumElements()) + ", " + GetTypeName(FVT->getElementType()) + ")"; 107 | } 108 | 109 | default: 110 | { 111 | llvm::errs() << "UNABLE TO GENERATE NAME:"; 112 | T->print(llvm::errs()); 113 | return "unknown"; 114 | } 115 | } 116 | } 117 | 118 | // Return a string representation of the type T, that represents its structure 119 | static std::string __TypeToString(llvm::Type* T, int depth) { 120 | 121 | switch (T->getTypeID()) 122 | { 123 | case llvm::Type::IntegerTyID: 124 | return "i" + std::to_string(llvm::cast(T)->getBitWidth()); 125 | 126 | case llvm::Type::VoidTyID: 127 | return "v"; 128 | case llvm::Type::FloatTyID: 129 | return "f"; 130 | case llvm::Type::DoubleTyID: 131 | return "d"; 132 | case llvm::Type::X86_FP80TyID: 133 | return "f80"; 134 | case llvm::Type::FP128TyID: 135 | return "f128"; 136 | case llvm::Type::PPC_FP128TyID: 137 | return "f2x64"; 138 | 139 | case llvm::Type::PointerTyID: 140 | { 141 | // if the pointer points to a struct we do not explore the structure to avoid recursive type visits 142 | // we originally solved the problem giving IDs to structs based on the StructType, but this does not work 143 | // when a struct has two variations of the same type, and a topologically equivalent struct has only one variation 144 | // e.g. 145 | // struct s1.0 {int a; struct p1.0* p; struct p1.0* p} 146 | // struct s1.1 {int a; struct p1.1* p; struct p1.2* p} 147 | // with p1.0, p1.1 and p1.1 being structurally equivalent. 148 | // While these two structs should be equivalent, they would be evaluated as different 149 | if (llvm::isa(llvm::cast(T)->getPointerElementType())) { 150 | return "p(struct)"; 151 | } 152 | return "p(" + __TypeToString(llvm::cast(T)->getPointerElementType(), depth+1) + ")"; 153 | } 154 | 155 | case llvm::Type::StructTyID: 156 | { 157 | auto ST = llvm::cast(T); 158 | 159 | std::string struct_repr = "struct("; 160 | 161 | // insert each type recursively in the struct 162 | for (unsigned i = 0, e = ST->getNumElements(); i != e; ++i) 163 | { 164 | struct_repr += __TypeToString(ST->getElementType(i), depth+1); 165 | if (i < e-1) struct_repr += ", "; 166 | } 167 | 168 | struct_repr += ")"; 169 | 170 | return struct_repr; 171 | } 172 | 173 | case llvm::Type::FunctionTyID: 174 | { 175 | auto FT = llvm::cast(T); 176 | 177 | std::string func_repr = "func("; 178 | func_repr += __TypeToString(FT->getReturnType(), depth+1); 179 | func_repr += ","; 180 | 181 | for (unsigned i = 0, e = FT->getNumParams(); i != e; ++i) 182 | { 183 | func_repr += __TypeToString(FT->getParamType(i), depth+1); 184 | if (i < e-1) func_repr += ", "; 185 | } 186 | 187 | if (FT->isVarArg()) func_repr += ", ...)"; 188 | else func_repr += ")"; 189 | 190 | return func_repr; 191 | } 192 | 193 | case llvm::Type::ArrayTyID: 194 | { 195 | auto AT = llvm::cast(T); 196 | return "arr(" + std::to_string(AT->getNumElements()) + ", " + __TypeToString(AT->getElementType(), depth+1) + ")"; 197 | } 198 | 199 | default: 200 | { 201 | llvm::errs() << "UNABLE TO DUMP:"; 202 | T->print(llvm::errs()); 203 | return "unknown"; 204 | } 205 | } 206 | } 207 | 208 | static std::string TypeToString(llvm::Type* T) { 209 | return __TypeToString(T, 0); 210 | } 211 | 212 | static size_t TypeToNameHash(llvm::Type* T) { 213 | static thread_local std::map HashCache; 214 | if (HashCache.find(T) != HashCache.end()) { 215 | return HashCache[T]; 216 | } 217 | size_t type_hash = std::hash{}(GetTypeName(T)); 218 | HashCache[T] = type_hash; 219 | return type_hash; 220 | } 221 | 222 | static size_t TypeToTopologyHash(llvm::Type* T) { 223 | static thread_local std::map HashCache; 224 | if (HashCache.find(T) != HashCache.end()) { 225 | return HashCache[T]; 226 | } 227 | // yes this is super lazy 228 | size_t type_hash = std::hash{}(__TypeToString(T, 0)); 229 | HashCache[T] = type_hash; 230 | return type_hash; 231 | } 232 | 233 | #endif /* _TypeUtils_ */ 234 | -------------------------------------------------------------------------------- /patches/lmbench/0001-Link-against-tirpc.patch: -------------------------------------------------------------------------------- 1 | From 48a48b4cbb22f5256d8d717a5fa1cc215f9622cd Mon Sep 17 00:00:00 2001 2 | From: Andrew Fasano 3 | Date: Mon, 3 Apr 2023 12:28:39 -0400 4 | Subject: [PATCH 1/2] Link against tirpc 5 | 6 | https://github.com/intel/lmbench/issues/21 7 | --- 8 | scripts/build | 2 +- 9 | 1 file changed, 1 insertion(+), 1 deletion(-) 10 | 11 | diff --git a/scripts/build b/scripts/build 12 | index 16a6600..561cdb3 100755 13 | --- a/scripts/build 14 | +++ b/scripts/build 15 | @@ -18,7 +18,7 @@ done 16 | 17 | trap 'rm -f ${BASE}$$.s ${BASE}$$.c ${BASE}$$.o ${BASE}$$; exit 1' 1 2 15 18 | 19 | -LDLIBS=-lm 20 | +LDLIBS="-lm -ltirpc" 21 | 22 | # check for HP-UX's ANSI compiler 23 | echo "main(int ac, char *av[]) { int i; }" > ${BASE}$$.c 24 | -- 25 | 2.34.1 26 | 27 | -------------------------------------------------------------------------------- /patches/lmbench/0002-Fix-rpc-include-error.patch: -------------------------------------------------------------------------------- 1 | From 4a1c2a7616d25b6ecd47594ff98a571dfbb9eb4e Mon Sep 17 00:00:00 2001 2 | From: Andrew Fasano 3 | Date: Mon, 3 Apr 2023 12:27:48 -0400 4 | Subject: [PATCH 2/2] Fix rpc include error 5 | 6 | https://github.com/intel/lmbench/issues/16 7 | --- 8 | src/Makefile | 2 ++ 9 | 1 file changed, 2 insertions(+) 10 | 11 | diff --git a/src/Makefile b/src/Makefile 12 | index 2555014..dcf3226 100644 13 | --- a/src/Makefile 14 | +++ b/src/Makefile 15 | @@ -58,6 +58,8 @@ SAMPLES=lmbench/Results/aix/rs6000 lmbench/Results/hpux/snake \ 16 | lmbench/Results/irix/indigo2 lmbench/Results/linux/pentium \ 17 | lmbench/Results/osf1/alpha lmbench/Results/solaris/ss20* 18 | 19 | +CPPFLAGS:=$(CPPFLAGS) -I /usr/include/tirpc/ 20 | + 21 | COMPILE=$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) 22 | 23 | INCS = bench.h lib_mem.h lib_tcp.h lib_udp.h stats.h timing.h 24 | -- 25 | 2.34.1 26 | 27 | -------------------------------------------------------------------------------- /patches/syzkaller/0001-syzkaller-uncontained-ignore-non-uncontained-reports.patch: -------------------------------------------------------------------------------- 1 | From 6dfaf8538b07efd8d26b1decb3d4119873b8aabc Mon Sep 17 00:00:00 2001 2 | From: Jakob Koschel 3 | Date: Tue, 28 Feb 2023 16:18:38 +0100 4 | Subject: [PATCH 2/2] uncontained: ignore non uncontained reports 5 | 6 | --- 7 | syz-manager/html.go | 17 +++++++++++++++++ 8 | 1 file changed, 17 insertions(+) 9 | 10 | diff --git a/syz-manager/html.go b/syz-manager/html.go 11 | index f9549637a..4830a509c 100644 12 | --- a/syz-manager/html.go 13 | +++ b/syz-manager/html.go 14 | @@ -550,6 +550,9 @@ func readCrash(workdir, dir string, repros map[string]bool, start time.Time, ful 15 | return nil 16 | } 17 | desc := string(trimNewLines(descBytes)) 18 | + if !strings.HasPrefix(desc, "KASAN") { 19 | + return nil 20 | + } 21 | stat, err := descFile.Stat() 22 | if err != nil { 23 | return nil 24 | @@ -575,6 +578,20 @@ func readCrash(workdir, dir string, repros map[string]bool, start time.Time, ful 25 | } 26 | } else if strings.HasPrefix(f, "report") { 27 | reports[f] = true 28 | + 29 | + reportFile, err := os.Open(filepath.Join(crashdir, dir, f)) 30 | + if err != nil { 31 | + return nil 32 | + } 33 | + defer reportFile.Close() 34 | + reportBytes, err := ioutil.ReadAll(reportFile) 35 | + if err != nil || len(reportBytes) == 0 { 36 | + return nil 37 | + } 38 | + report := string(trimNewLines(reportBytes)) 39 | + if !strings.Contains(report, "[UNCONTAINED]") { 40 | + return nil 41 | + } 42 | } else if f == "repro.prog" { 43 | hasRepro = true 44 | } else if f == "repro.cprog" { 45 | -- 46 | 2.34.1 47 | 48 | -------------------------------------------------------------------------------- /runtime/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.3) 2 | project(uncontained-runtime) 3 | 4 | set(CMAKE_CXX_STANDARD 11) 5 | 6 | if(DEFINED ENV{ENABLE_KASAN}) # DEFINED ENABLE_KASAN 7 | 8 | if(NOT DEFINED ENV{KERNEL}) 9 | message(FATAL_ERROR "$KERNEL is not defined") 10 | endif() 11 | 12 | SET (OLD_PWD $ENV{PWD}) # just make sure that PWD is reset correctly 13 | set(ENV{PWD} "$ENV{ROOT}") 14 | execute_process(COMMAND task kernel:make -- print-NOSTDINC_FLAGS 15 | WORKING_DIRECTORY $ENV{ROOT} 16 | OUTPUT_VARIABLE NOSTDINC_FLAGS 17 | OUTPUT_STRIP_TRAILING_WHITESPACE) 18 | execute_process(COMMAND task kernel:make -- print-LINUXINCLUDE 19 | WORKING_DIRECTORY $ENV{ROOT} 20 | OUTPUT_VARIABLE LINUXINCLUDE 21 | OUTPUT_STRIP_TRAILING_WHITESPACE) 22 | execute_process(COMMAND task kernel:make -- print-KBUILD_CFLAGS 23 | WORKING_DIRECTORY $ENV{ROOT} 24 | OUTPUT_VARIABLE KBUILD_CFLAGS 25 | OUTPUT_STRIP_TRAILING_WHITESPACE) 26 | set(ENV{PWD} ${OLD_PWD}) 27 | 28 | string (REPLACE ./ $ENV{KERNEL}/ LINUXINCLUDE ${LINUXINCLUDE}) 29 | 30 | string (CONCAT CMAKE_C_FLAGS 31 | "-D__KERNEL__ -Qunused-arguments -fmacro-prefix-map=./= " 32 | "${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CFLAGS} " 33 | "-fno-function-sections -fno-data-sections") 34 | 35 | include_directories($ENV{KERNEL}/arch/x86/include) 36 | include_directories($ENV{KERNEL}/arch/x86/include/generated) 37 | include_directories($ENV{KERNEL}/include) 38 | include_directories($ENV{KERNEL}/arch/x86/include/uapi) 39 | include_directories($ENV{KERNEL}/arch/x86/include/uapi/generated) 40 | include_directories($ENV{KERNEL}/include/uapi) 41 | include_directories($ENV{KERNEL}/include/generated/uapi) 42 | 43 | # look for kasan.h 44 | include_directories($ENV{KERNEL}/mm/kasan) 45 | # look for percpu-internal.h 46 | include_directories($ENV{KERNEL}/mm/) 47 | 48 | if(DEFINED ENV{DISABLE_PRINTING}) 49 | add_definitions(-DDISABLE_PRINTING) 50 | endif() 51 | 52 | set(SOURCES 53 | uncontained_kasan.c 54 | ) 55 | 56 | include_directories(./include) 57 | 58 | # enable_language(ASM) 59 | 60 | add_library(uncontained STATIC 61 | ${SOURCES} 62 | ) 63 | 64 | endif() # DEFINED ENABLE_KASAN 65 | -------------------------------------------------------------------------------- /runtime/uncontained_kasan.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "vanilla_list.h" 12 | #include "percpu-internal.h" 13 | 14 | // set to true when kasan has initialized global poisons 15 | extern bool __asan_globals_ready; 16 | 17 | // check if the address is reserved by the boot memory allcator (memblock) 18 | // since kasan does not cover those 19 | static bool is_boot_memory(char* addr) { 20 | phys_addr_t phys_addr; 21 | 22 | // if addr is not in the physmap, it cannot be in boot memory 23 | if (((unsigned long)addr < PAGE_OFFSET || (unsigned long)addr >= (PAGE_OFFSET + get_max_mapped()))) 24 | return false; 25 | 26 | phys_addr = (unsigned long)addr - PAGE_OFFSET; 27 | return memblock_is_reserved(phys_addr); 28 | } 29 | 30 | // return true if the address is allocated in the percpu memory region, that has 31 | // no fine-grained KASAN redzones 32 | static bool is_percpu_memory(unsigned long addr) { 33 | int slot; 34 | struct pcpu_chunk *chunk; 35 | 36 | if (is_kernel_percpu_address(addr) || is_module_percpu_address(addr)) 37 | return true; 38 | 39 | for (slot = 0; slot < pcpu_nr_slots; slot++) { 40 | list_for_each_entry(chunk, &pcpu_chunk_lists[slot], list) { 41 | if (addr >= (unsigned long)chunk->base_addr && addr < ((unsigned long)chunk->base_addr + chunk->nr_pages*PAGE_SIZE)) 42 | return true; 43 | } 44 | } 45 | return false; 46 | } 47 | 48 | bool __used kasan_memory_is_accessible(char* addr) 49 | { 50 | s8 shadow_value = *(s8 *)kasan_mem_to_shadow((void*)addr); 51 | 52 | if (unlikely(shadow_value)) { 53 | s8 last_accessible_byte = ((unsigned long)addr) & KASAN_GRANULE_MASK; 54 | return unlikely(last_accessible_byte < shadow_value); 55 | } 56 | 57 | return true; 58 | } 59 | 60 | static noinline void uncontained_report(char* addr, size_t size, unsigned long ip) { 61 | pr_err("[UNCONTAINED] Unexpected type at address %px\n", addr); 62 | pr_err("[UNCONTAINED] %px[0x0-1]: %s\n", addr, !kasan_memory_is_accessible(addr - 1)? "OK poison" : "KO valid"); 63 | pr_err("[UNCONTAINED] %px[0x0]: %s\n", addr, kasan_memory_is_accessible(addr)? "OK valid" : "KO poison"); 64 | pr_err("[UNCONTAINED] %px[0x%lx-1]: %s\n", addr, size, kasan_memory_is_accessible(addr + size - 1)? "OK valid" : "KO poison"); 65 | pr_err("[UNCONTAINED] %px[0x%lx]: %s\n", addr, size, !kasan_memory_is_accessible(addr + size)? "OK poison" : "KO valid"); 66 | #ifndef DISABLE_PRINTING 67 | kasan_report((unsigned long) addr, 1, false, ip); 68 | #endif 69 | } 70 | 71 | // Sanitizer check for container_of to check that addr 72 | // has the right size and position in the redzone 73 | // [--------object--------][--------redzone--------] 74 | // ^ ^^ 75 | // | || 76 | // addr addr+size-1- - redzone_start 77 | // =====must be valid===== === must be poisoned==== 78 | // orig_addr holds the original pointer we are checking to take into account edge 79 | // cases as ERR values or NULL 80 | // returns true if the type is valid 81 | bool __used uncontained_type_check(char* orig_addr, char* addr, unsigned long size) { 82 | 83 | // bail out if we are during boot and kasan is still not ready 84 | if (unlikely(!__asan_globals_ready)) { 85 | return true; 86 | } 87 | 88 | // bail out if `addr` == NULL or IS_ERR(addr), since we may be in a path not strictly coming 89 | // from a container_of, but a forwarded use that may depend on something else 90 | // e.g., return phi(container_of: found_bb, NULL or -EINVAL: not_found_bb) 91 | if (unlikely(IS_ERR_OR_NULL((void*) orig_addr) || (unsigned long) orig_addr < 0x1000)) { 92 | return true; 93 | } 94 | 95 | // bail out if the object is in boot memory 96 | if (unlikely(is_boot_memory(orig_addr))) { 97 | return true; 98 | } 99 | 100 | // report if the address may overflow 101 | if (unlikely(addr + size < addr)) { 102 | return false; 103 | } 104 | 105 | // report if the address has no kasan mapping 106 | if (unlikely((void *)addr < 107 | kasan_shadow_to_mem((void *)KASAN_SHADOW_START))) { 108 | return false; 109 | } 110 | 111 | if (unlikely( 112 | // check for the valid start 113 | !kasan_memory_is_accessible(addr) || 114 | // check for the last byte valid 115 | !kasan_memory_is_accessible(addr + size - 1) || 116 | // check for the first byte of redzone 117 | kasan_memory_is_accessible(addr + size) 118 | )) { 119 | // actually report only if it is not percpu allocated memory 120 | if (!is_percpu_memory((unsigned long) addr)) 121 | return false; 122 | } 123 | 124 | // if it is not a global object, then check the left redzone 125 | if (unlikely( 126 | // skip page aligned addresses as it may be the first heap object of a cache 127 | ((unsigned long)addr & (PAGE_SIZE-1)) != 0 && 128 | // not in kernel data 129 | !is_kernel((unsigned long)orig_addr) && 130 | // check for the first left byte of redzone 131 | kasan_memory_is_accessible(addr - 1) 132 | )) { 133 | // actually report only if it is not percpu allocated memory and not module data 134 | if (!is_percpu_memory((unsigned long) orig_addr) && !is_module_address((unsigned long)orig_addr)) 135 | return false; 136 | } 137 | // the type is valid 138 | return true; 139 | } 140 | 141 | // returns uncontained_report, or true in case we are not supposed to check 142 | bool __used uncontained_type_maybe_check(char* orig_addr, char* addr, unsigned long size, bool should_check) { 143 | if (should_check) { 144 | return uncontained_type_check(orig_addr, addr, size); 145 | } else { 146 | return true; 147 | } 148 | } 149 | 150 | void __used uncontained_type_maybe_report(char* addr, unsigned long size, bool is_safe) { 151 | if (!is_safe) { 152 | uncontained_report(addr, size, _RET_IP_); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /runtime/vanilla_container_of.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 */ 2 | #ifndef _LINUX_CONTAINER_OF_H 3 | #define _LINUX_CONTAINER_OF_H 4 | 5 | #include 6 | #include 7 | 8 | #define typeof_member(T, m) typeof(((T*)0)->m) 9 | 10 | /** 11 | * container_of - cast a member of a structure out to the containing structure 12 | * @ptr: the pointer to the member. 13 | * @type: the type of the container struct this is embedded in. 14 | * @member: the name of the member within the struct. 15 | * 16 | */ 17 | #define container_of(ptr, type, member) ({ \ 18 | void *__mptr = (void *)(ptr); \ 19 | static_assert(__same_type(*(ptr), ((type *)0)->member) || \ 20 | __same_type(*(ptr), void), \ 21 | "pointer type mismatch in container_of()"); \ 22 | ((type *)(__mptr - offsetof(type, member))); }) 23 | 24 | /** 25 | * container_of_safe - cast a member of a structure out to the containing structure 26 | * @ptr: the pointer to the member. 27 | * @type: the type of the container struct this is embedded in. 28 | * @member: the name of the member within the struct. 29 | * 30 | * If IS_ERR_OR_NULL(ptr), ptr is returned unchanged. 31 | */ 32 | #define container_of_safe(ptr, type, member) ({ \ 33 | void *__mptr = (void *)(ptr); \ 34 | static_assert(__same_type(*(ptr), ((type *)0)->member) || \ 35 | __same_type(*(ptr), void), \ 36 | "pointer type mismatch in container_of_safe()"); \ 37 | IS_ERR_OR_NULL(__mptr) ? ERR_CAST(__mptr) : \ 38 | ((type *)(__mptr - offsetof(type, member))); }) 39 | 40 | #endif /* _LINUX_CONTAINER_OF_H */ 41 | -------------------------------------------------------------------------------- /scripts/compile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | SCRIPTS_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 6 | UNCONTAINED_PATH=$(realpath $SCRIPTS_PATH/..) 7 | KERNEL_TOOLS_PATH=$UNCONTAINED_PATH/kernel-tools 8 | KERNEL_PATH=$UNCONTAINED_PATH/linux 9 | 10 | cd $KERNEL_TOOLS_PATH 11 | 12 | read -p "[INFO] IMPORTANT! Before you continue make sure you added the .env file 13 | with the correct paths. 14 | When you're done press [ENTER] to continue 15 | " 16 | 17 | echo "[INFO] ensure that kernel-tools submodules are updated" 18 | git submodule update --init 19 | 20 | read -p "Do you want to create the syzkaller image? (y/N) " answer 21 | 22 | if [[ $answer =~ ^[Yy]$ ]]; then 23 | task syzkaller:create-image 24 | fi 25 | 26 | read -p "Do you want to compile LLVM, the kernel and syzkaller? (Y/n) " answer 27 | 28 | if [[ $answer =~ ^[Yy]$ ]] || [[ -z $answer ]]; then 29 | echo "[INFO] Build uncontained-llvm-project" 30 | task llvm:config llvm:build 31 | 32 | echo "[INFO] Ensure you are on the right kernel commit" 33 | (cd $KERNEL_PATH && git checkout uncontained-evaluation) 34 | 35 | echo "[INFO] Overwriting .config" 36 | cp $KERNEL_PATH/syzbot.config $KERNEL_PATH/.config 37 | 38 | echo "[INFO] Build the kernel once (required to build the runtime library)" 39 | task kernel:bzImage 40 | 41 | echo "[INFO] Force rebuilding the passes and runtime library" 42 | rm -rf $UNCONTAINED_PATH/build 43 | task build 44 | 45 | echo "[INFO] Compile the kernel with the sanitizer" 46 | task passes:run -- lto:ContainerOfSanitizer 47 | 48 | echo "[INFO] Apply syzkaller patch if necessary" 49 | set +e 50 | (cd $KERNEL_TOOLS_PATH/syzkaller && git am $UNCONTAINED_PATH/patches/syzkaller/0001-syzkaller-uncontained-ignore-non-uncontained-reports.patch) 51 | 52 | if [ $? -ne 0 ]; then 53 | echo "[WARNING] Applying syzkaller patch failed, hopefully already applied!" 54 | (cd $KERNEL_TOOLS_PATH/syzkaller && git am --abort) 55 | fi 56 | set -e 57 | 58 | echo "[INFO] Compile syzkaller and generate config" 59 | task syzkaller:build syzkaller:config 60 | fi 61 | -------------------------------------------------------------------------------- /scripts/evaluation/lmbench.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from glob import glob 3 | import sys 4 | import pandas as pd 5 | from scipy import stats 6 | from collections import OrderedDict 7 | import argparse 8 | 9 | SIMPLE_LAT_SPLIT_INDEX = 0 10 | 11 | LMBENCH_DIR = "/var/tmp/lmbench" 12 | 13 | simple_lat_benchs = ["Simple syscall", "Simple read", "Simple write", "Simple stat", "Simple fstat", 14 | "Simple open/close", "Select on 10 fd's", "Select on 100 fd's", "Select on 250 fd's", 15 | "Select on 500 fd's", "Select on 10 tcp fd's", "Select on 100 tcp fd's", 16 | "Select on 250 tcp fd's", "Select on 500 tcp fd's", "Signal handler installation", 17 | "Signal handler overhead", "Pipe latency", "AF_UNIX sock stream latency", 18 | "Process fork+exit", "Process fork+execve", "Process fork+/bin/sh -c", 19 | f"Pagefaults on {LMBENCH_DIR}/XXX", "UDP latency using localhost", 20 | "TCP latency using localhost", "TCP/IP connection cost to localhost"] 21 | 22 | 23 | def parse_lmbench_simple_lat(lmbench_result): 24 | result = {} 25 | for l in lmbench_result.splitlines(): 26 | s = l.split(": ") 27 | key = s[0] 28 | if key not in simple_lat_benchs: 29 | continue 30 | val = float(s[1].split()[0]) # discard microseconds part 31 | result[key] = val 32 | return result 33 | 34 | 35 | def parse_lmbench_file(f): 36 | results = {} 37 | print('f: ', f) 38 | with open(f, "rb") as fh: 39 | data = fh.read().decode("utf-8", errors="ignore") 40 | r = parse_lmbench_simple_lat(data) 41 | results["lat"] = r 42 | return results 43 | 44 | 45 | def gen_table(data): 46 | lat_data = {k: data[k]["lat"] for k in data} 47 | df = pd.DataFrame(data=lat_data) 48 | 49 | transp = df.T.reset_index() 50 | df = transp.groupby('benchmark')[simple_lat_benchs].median().T 51 | 52 | df['uncontained overhead'] = df['uncontained']/df['baseline'] 53 | df['kasan overhead'] = df['kasan']/df['baseline'] 54 | # df['uncontained_kasan overhead'] = df['uncontained_kasan']/df['baseline'] 55 | # df['kasan_no_checks overhead'] = df['kasan_no_checks']/df['baseline'] 56 | 57 | df.loc['geomean'] = { 58 | 'baseline': '-', 59 | 'uncontained': '-', 60 | 'kasan': '-', 61 | # 'uncontained_kasan': '-', 62 | # 'kasan_no_checks': '-', 63 | 'uncontained overhead': stats.gmean(df['uncontained overhead'].astype(float)), 64 | 'kasan overhead': stats.gmean(df['kasan overhead'].astype(float)), 65 | # 'uncontained_kasan overhead': stats.gmean(df['uncontained_kasan overhead'].astype(float)), 66 | # 'kasan_no_checks overhead': stats.gmean(df['kasan_no_checks overhead'].astype(float)), 67 | } 68 | 69 | pd.options.display.float_format = '{:.2f}'.format 70 | print(df) 71 | 72 | 73 | def start(prefix=''): 74 | results = {} 75 | for f in glob(f"{prefix}*.*"): 76 | r = parse_lmbench_file(f) 77 | benchmark = 'unknown' 78 | 79 | if f[len(prefix):].startswith("baseline."): 80 | benchmark = 'baseline' 81 | elif f[len(prefix):].startswith("uncontained."): 82 | benchmark = 'uncontained' 83 | elif f[len(prefix):].startswith("kasan."): 84 | benchmark = 'kasan' 85 | elif f[len(prefix):].startswith("uncontained_kasan."): 86 | benchmark = 'uncontained_kasan' 87 | elif f[len(prefix):].startswith("kasan_no_checks."): 88 | benchmark = 'kasan_no_checks' 89 | key = f[len(prefix):] 90 | r['lat']['benchmark'] = benchmark 91 | 92 | results[key] = r 93 | 94 | results = OrderedDict(sorted(results.items())) 95 | gen_table(results) 96 | 97 | 98 | if __name__ == "__main__": 99 | parser = argparse.ArgumentParser(description='parse lmbench results and compute results') 100 | parser.add_argument('--prefix', dest='prefix', default='', type=str) 101 | args = parser.parse_args() 102 | 103 | start(prefix=args.prefix) 104 | -------------------------------------------------------------------------------- /scripts/evaluation/syzkaller-bench.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from glob import glob 3 | import json 4 | import pandas as pd 5 | from scipy import stats 6 | from collections import OrderedDict 7 | import argparse 8 | 9 | def parse_syzkaller_bench_file(file): 10 | with open(file, 'r') as f: 11 | data = list(f.read()) 12 | closing_indices = [] 13 | for i, c in enumerate(data): 14 | if c == '}': 15 | closing_indices.append(i) 16 | closing_indices.pop() 17 | for closing_idx in reversed(closing_indices): 18 | data.insert(closing_idx+1, ',') 19 | data.insert(0, '[') 20 | data.append(']') 21 | s = "".join(data) 22 | return json.loads(s) 23 | 24 | def stats_after_seconds(syzkaller_bench, time): 25 | for b in syzkaller_bench: 26 | if b['fuzzing'] >= time: 27 | return b 28 | raise Exception("not found!") 29 | 30 | def gen_table(data): 31 | df = pd.DataFrame(data=data) 32 | 33 | transp = df.T.reset_index() 34 | columns = [col for col in transp.columns if col != 'benchmark' and col != 'index'] 35 | df = transp.groupby('benchmark')[columns].median().T 36 | df = df.loc[(df != 0).all(axis=1), :] 37 | 38 | df['uncontained overhead'] = df['uncontained']/df['baseline'] 39 | df['kasan overhead'] = df['kasan']/df['baseline'] 40 | 41 | pd.options.display.float_format = '{:.2f}'.format 42 | print(df) 43 | 44 | 45 | def start(prefix=''): 46 | results = {} 47 | for f in glob(f"{prefix}*"): 48 | r = parse_syzkaller_bench_file(f) 49 | 50 | obj_after = stats_after_seconds(r, 60 * 60) 51 | benchmark = 'unknown' 52 | 53 | if f[len(prefix):].startswith("baseline"): 54 | benchmark = 'baseline' 55 | elif f[len(prefix):].startswith("uncontained"): 56 | benchmark = 'uncontained' 57 | elif f[len(prefix):].startswith("kasan"): 58 | benchmark = 'kasan' 59 | key = f[len(prefix):] 60 | obj_after['benchmark'] = benchmark 61 | 62 | results[key] = obj_after 63 | 64 | results = OrderedDict(sorted(results.items())) 65 | gen_table(results) 66 | 67 | 68 | if __name__ == "__main__": 69 | parser = argparse.ArgumentParser(description='parse lmbench results and compute results') 70 | parser.add_argument('--prefix', dest='prefix', default='', type=str) 71 | args = parser.parse_args() 72 | 73 | start(prefix=args.prefix) 74 | -------------------------------------------------------------------------------- /scripts/prepare-evaluation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | SCRIPTS_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 6 | UNCONTAINED_PATH=$(realpath $SCRIPTS_PATH/..) 7 | KERNEL_TOOLS_PATH=$UNCONTAINED_PATH/kernel-tools 8 | KERNEL_PATH=$UNCONTAINED_PATH/linux 9 | 10 | prepare_checks() { 11 | read -p "[INFO] IMPORTANT! Before you continue make sure you added the .env file 12 | with the correct paths. 13 | When you're done press [ENTER] to continue 14 | " 15 | 16 | read -p "Do you want to build all the dependencies (running compile.sh)? (y/N) " answer 17 | 18 | if [[ $answer =~ ^[Yy]$ ]]; then 19 | $SCRIPTS_PATH/compile.sh 20 | fi 21 | 22 | read -p "Do you want to kill any dangling qemu-system-x86_64 processes? (y/N) " answer 23 | 24 | if [[ $answer =~ ^[Yy]$ ]]; then 25 | killall qemu-system-x86_64 || true 26 | fi 27 | } 28 | 29 | prepare_linux() { 30 | LINUX_INSTANCE=$1 31 | 32 | cd $KERNEL_TOOLS_PATH 33 | 34 | case $LINUX_INSTANCE in 35 | "baseline") 36 | INSTANCE_NAME="BASELINE" 37 | BRANCH_NAME="uncontained-baseline" 38 | ;; 39 | "kasan") 40 | INSTANCE_NAME="KASAN" 41 | BRANCH_NAME="uncontained-evaluation-kasan" 42 | ;; 43 | "uncontained") 44 | INSTANCE_NAME="UNCONTAINED" 45 | BRANCH_NAME="uncontained-evaluation" 46 | 47 | ;; 48 | *) 49 | echo "[INFO] invalid instance specified" 50 | exit 1 51 | ;; 52 | esac 53 | 54 | echo "[INFO] [PREPARE $INSTANCE_NAME]" 55 | 56 | echo "[INFO] Overwriting .config" 57 | (cd $KERNEL_PATH && git checkout uncontained) 58 | cp $KERNEL_PATH/syzbot.config $KERNEL_PATH/.config 59 | 60 | if [[ "$INSTANCE_NAME" == "BASELINE" ]]; then 61 | (cd $KERNEL_PATH && scripts/config --disable CONFIG_KASAN) 62 | (cd $KERNEL_PATH && scripts/config --disable CONFIG_KCSAN) 63 | fi 64 | 65 | echo "[INFO] switch linux to $BRANCH_NAME branch" 66 | (cd $KERNEL_PATH && git checkout $BRANCH_NAME) 67 | 68 | echo "[INFO] building kernel..." 69 | task kernel:bzImage 70 | if [[ "$INSTANCE_NAME" == "UNCONTAINED" ]]; then 71 | echo "[INFO] wiping build folder to remove runtime component" 72 | rm -rf $UNCONTAINED_PATH/build 73 | DISABLE_PRINTING=1 task build 74 | task passes:run -- lto:ContainerOfSanitizer 75 | fi 76 | } 77 | -------------------------------------------------------------------------------- /scripts/run-fuzzing-performance-evaluation.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | SCRIPTS_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 6 | UNCONTAINED_PATH=$(realpath $SCRIPTS_PATH/..) 7 | KERNEL_TOOLS_PATH=$UNCONTAINED_PATH/kernel-tools 8 | KERNEL_PATH=$UNCONTAINED_PATH/linux 9 | SYZKALLER_RESULTS_PATH=$UNCONTAINED_PATH/evaluation/syzkaller/results 10 | 11 | SYZKALLER_NR_RUNS=10 12 | # 1 hour (and two minutes to be safe) 13 | FUZZING_TIME=3720 14 | 15 | source $SCRIPTS_PATH/prepare-evaluation.sh 16 | 17 | prepare_syzkaller_eval() { 18 | echo "[WARNING!!!] This script will delete the syzkaller-workdir (containing previous fuzzing results) please backup before continuing if needed." 19 | read -p "Do you want to continue? (y/N) " answer 20 | if [[ ! $answer =~ ^[Yy]$ ]]; then 21 | exit 0 22 | fi 23 | } 24 | 25 | run_syzkaller_eval() { 26 | LINUX_INSTANCE=$1 27 | cd $KERNEL_TOOLS_PATH 28 | 29 | mkdir -p $SYZKALLER_RESULTS_PATH 30 | for ((i=0; i/dev/null 2>&1 ; pwd -P )" 6 | UNCONTAINED_PATH=$(realpath $SCRIPTS_PATH/..) 7 | KERNEL_TOOLS_PATH=$UNCONTAINED_PATH/kernel-tools 8 | KERNEL_PATH=$UNCONTAINED_PATH/linux 9 | LMBENCH_PATH=$UNCONTAINED_PATH/evaluation/lmbench 10 | LMBENCH_RESULTS_PATH=$LMBENCH_PATH/results 11 | 12 | LMBENCH_NR_RUNS=10 13 | 14 | source $SCRIPTS_PATH/prepare-evaluation.sh 15 | 16 | build_lmbench() { 17 | cd $LMBENCH_PATH 18 | 19 | if [ ! -e lmbench ] 20 | then 21 | echo "[INFO] download LMBench" 22 | git clone https://github.com/intel/lmbench.git 23 | cd lmbench 24 | git checkout 701c6c35b0270d4634fb1dc5272721340322b8ed 25 | git am $SCRIPTS_PATH/../patches/lmbench/0001-Link-against-tirpc.patch 26 | git am $SCRIPTS_PATH/../patches/lmbench/0002-Fix-rpc-include-error.patch 27 | fi 28 | 29 | echo "[INFO] build LMBench" 30 | cd lmbench 31 | make 32 | 33 | echo "[INFO] copy CONFIG.syzkaller into LMBench" 34 | cp $LMBENCH_PATH/CONFIG.syzkaller $LMBENCH_PATH/lmbench/bin/x86_64-linux-gnu/ 35 | } 36 | 37 | run_lmbench () { 38 | LINUX_INSTANCE=$1 39 | 40 | cd $KERNEL_TOOLS_PATH 41 | 42 | echo "[INFO] launch VM" 43 | script -c 'task qemu:syzkaller' -q -a /dev/null & 44 | QEMU_PID=$! 45 | 46 | for i in {1..3} 47 | do 48 | echo "[INFO] try to connect to VM and copy in LMBench" 49 | sleep 60 50 | set +e 51 | task syzkaller:scp -- $LMBENCH_PATH/lmbench root@localhost: 52 | if [ $? -eq 0 ]; then 53 | # succeeded 54 | break 55 | fi 56 | set -e 57 | done 58 | 59 | echo "[INFO] LMBench copying done, install dependencies" 60 | task syzkaller:ssh -- -o StrictHostKeyChecking=no 'sudo apt update' 61 | task syzkaller:ssh -- -o StrictHostKeyChecking=no 'sudo apt install -y make' 62 | 63 | echo "[INFO] run LMBench" 64 | for ((i=0; i/dev/null 2>&1 ; pwd -P )" 6 | UNCONTAINED_PATH=$(realpath $SCRIPTS_PATH/..) 7 | KERNEL_TOOLS_PATH=$UNCONTAINED_PATH/kernel-tools 8 | KERNEL_PATH=$UNCONTAINED_PATH/linux 9 | STATIC_ANALYZERS_RESULTS_PATH=$UNCONTAINED_PATH/evaluation/static-analyzer-results 10 | 11 | # we don't want to compile the runtime and not have KASAN/KCOV enabled 12 | export unset ENABLE_KASAN 13 | export unset ENABLE_SYZKALLER 14 | 15 | cd $KERNEL_TOOLS_PATH 16 | 17 | # go to a branch that is guaranteed to have the `syzbot-nosan.config`. 18 | (cd $KERNEL_PATH && git checkout uncontained-static-analysis-tainted) 19 | 20 | echo "[INFO] Overwriting .config (disabling sanitizers)" 21 | cp $KERNEL_PATH/syzbot-nosan.config $KERNEL_PATH/.config 22 | task kernel:bzImage 23 | 24 | echo "[INFO] wiping build folder to remove runtime component" 25 | rm -rf $UNCONTAINED_PATH/build 26 | task build 27 | 28 | mkdir -p $STATIC_ANALYZERS_RESULTS_PATH 29 | 30 | echo "[INFO] running coccinelle script for Pattern 4 (Past-the-end Iterator)" 31 | # Pattern 4 (Past-the-end Iterator) 32 | (cd $KERNEL_PATH && git checkout uncontained-static-analysis-tainted) 33 | (cd $KERNEL_PATH && make coccicheck COCCI=scripts/coccinelle/iterators/use_after_iter.cocci > $STATIC_ANALYZERS_RESULTS_PATH/reports-use_after_iter.txt) 34 | sed -i '1,10d' $STATIC_ANALYZERS_RESULTS_PATH/reports-use_after_iter.txt 35 | cat $STATIC_ANALYZERS_RESULTS_PATH/reports-use_after_iter.txt | sort -u > $STATIC_ANALYZERS_RESULTS_PATH/reports-use_after_iter-filtered.txt 36 | mv $STATIC_ANALYZERS_RESULTS_PATH/reports-use_after_iter-filtered.txt $STATIC_ANALYZERS_RESULTS_PATH/reports-use_after_iter.txt 37 | 38 | echo "[INFO] running LLVM pass for Pattern 1 (Statically Incompatible Containers) & Pattern 2 (Empty-list Confusion) & Pattern 5 (Containers with Contracts)" 39 | # Pattern 1 (Statically Incompatible Containers) & Pattern 2 (Empty-list Confusion) & Pattern 5 (Containers with Contracts) 40 | (cd $KERNEL_PATH && git checkout uncontained-static-analysis-tainted) 41 | task passes:run -- lto:SimpleDataflowChecker 42 | cp $KERNEL_PATH/reports.yaml $STATIC_ANALYZERS_RESULTS_PATH/reports-tainted.yaml 43 | 44 | echo "[INFO] running LLVM pass for Pattern 3 (Mismatch on Data Structure Operators)" 45 | # Pattern 3 (Mismatch on Data Structure Operators) 46 | (cd $KERNEL_PATH && git checkout uncontained-static-analysis-list-correlation) 47 | task passes:run -- lto:SimpleDataflowChecker 48 | cp $KERNEL_PATH/reports.yaml $STATIC_ANALYZERS_RESULTS_PATH/reports-list_entry_correlation.yaml 49 | 50 | echo "[INFO] combining reports YAML files into single file" 51 | rm $KERNEL_PATH/reports.yaml 52 | cp $STATIC_ANALYZERS_RESULTS_PATH/reports-tainted.yaml $KERNEL_PATH/reports.yaml 53 | tail -n +3 $STATIC_ANALYZERS_RESULTS_PATH/reports-list_entry_correlation.yaml >> $KERNEL_PATH/reports.yaml 54 | 55 | echo "[INFO] final reporst YAML is stored at $KERNEL_PATH/reports.yaml" 56 | echo "[INFO] you can now look at it directly or load it into the vscode extension" 57 | echo "[INFO] see the README.md in vscode-extension for more details" 58 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | SCRIPTS_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 6 | UNCONTAINED_PATH=$(realpath $SCRIPTS_PATH/..) 7 | KERNEL_TOOLS_PATH=$UNCONTAINED_PATH/kernel-tools 8 | KERNEL_PATH=$UNCONTAINED_PATH/linux 9 | 10 | cd $KERNEL_TOOLS_PATH 11 | 12 | echo "[INFO] Start syzkaller to do fuzzing (run for at least 24 hours)" 13 | task syzkaller:run 14 | -------------------------------------------------------------------------------- /tests/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | - name: inet_lookup 4 | source: "__inet_lookup_established" 5 | sink: ANY 6 | # ignore all the flows inside the "ignore" functions 7 | ignore: 8 | - kmem_cache_free 9 | # ignore all the flows dominated by the sanitizers 10 | sanitizers: 11 | - { instruction: "load", offset: 18 } # access to skc_state 12 | - { function_call: "sk_fullsock" } 13 | - name: test_rule 14 | source: __test_source 15 | sink: __test_sink 16 | sanitizers: 17 | - { function_call: "__test_sanitizer" } 18 | - name: list_entry 19 | source: "__uncontained_list_entry_source" 20 | sink: ANY 21 | # ignore all the flows dominated by the sanitizers 22 | sanitizers: 23 | - { function_call: "list_empty" } 24 | - { function_call: "list_is_head" } 25 | - { function_call: "__uncontained_list_entry_is_head" } 26 | options: 27 | # do not need dominance for sanitizers, just reachability 28 | sanitize_reachable: true 29 | # filter out if the source is actually generated behind any sanitizer 30 | sanitize_source: true 31 | - name: backwards_contained 32 | source: "backwards_contained" 33 | sink: ANY 34 | - name: kobj 35 | source: "kobj" 36 | sink: ANY 37 | - name: list_entry_correlation 38 | source: "list_entry_correlation" 39 | sink: ANY 40 | sanitizers: 41 | - { function_call: "kfree" } 42 | - { function_call: "__free_pages" } 43 | - { function_call: "__list_add" } 44 | -------------------------------------------------------------------------------- /tests/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -u 5 | # set -x 6 | 7 | TESTS_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 8 | BUILD_PATH="$TESTS_PATH/../build" 9 | 10 | LLVM_DIR=${LLVM_DIR:-'/home/ubuntu/uncontained-llvm-project/build'} 11 | export PATH="$LLVM_DIR/bin:$PATH" 12 | 13 | # clang -O3 -emit-llvm -c $TESTS_PATH/test/test.c -o $TESTS_PATH/test/test.bc 14 | # llvm-dis $TESTS_PATH/test/test.bc 15 | # llvm-link -o $TESTS_PATH/test/test.linked.bc $TESTS_PATH/test/test.bc 16 | # opt -load=$BUILD_PATH/passes/DumpContainerOf/LLVMDumpContainerOfPass.so -dump-container-of $TESTS_PATH/test/test.linked.bc -o $TESTS_PATH/test/test.out.bc 17 | # clang $TESTS_PATH/test/test.out.bc -o $TESTS_PATH/test.out 18 | 19 | # this run at compilation time for each module 20 | # clang -O3 -flto -Xclang -load -Xclang $BUILD_PATH/passes/DumpContainerOf/LLVMDumpContainerOfPass.so $TESTS_PATH/test/test.c $TESTS_PATH/test/test1.c -o $TESTS_PATH/test.out 21 | 22 | # this run at lto time 23 | rm container_of_log.txt types_log.txt || true 24 | # clang -flegacy-pass-manager -O2 -fuse-ld=lld -flto -Wl,-mllvm=-load=$BUILD_PATH/passes/DumpContainerOf/LLVMDumpContainerOfPass.so $TESTS_PATH/test/test.c $TESTS_PATH/test/test1.c -o $TESTS_PATH/test/test.out 25 | # clang -flegacy-pass-manager -O2 -fuse-ld=lld -flto -Wl,-mllvm=-load=$BUILD_PATH/passes/DumpTypes/LLVMDumpTypesPass.so $TESTS_PATH/test/test.c $TESTS_PATH/test/test1.c -o $TESTS_PATH/test/test.out 26 | # -Wl,--plugin-opt=save-temps 27 | # -mllvm -print-changed -mllvm -filter-print-funcs=main 28 | clang -flegacy-pass-manager -O2 -g -fsanitize=address -mllvm -asan-recover=1 -mllvm -asan-instrument-reads=0 -mllvm -asan-instrument-writes=0 -mllvm -asan-instrument-atomics=0 -mllvm -asan-instrument-byval=0 -fuse-ld=lld -flto -Wl,--plugin-opt=-lto-embed-bitcode=optimized $TESTS_PATH/test/test.c $TESTS_PATH/test/test1.c -o $TESTS_PATH/test/test.out 29 | objcopy $TESTS_PATH/test/test.out --dump-section .llvmbc=$TESTS_PATH/test/test.bc 30 | llvm-dis $TESTS_PATH/test/test.bc -o $TESTS_PATH/test/test.orig.ll 31 | clang -flegacy-pass-manager -O2 -g -fsanitize=address -mllvm -asan-recover=1 -mllvm -asan-instrument-reads=0 -mllvm -asan-instrument-writes=0 -mllvm -asan-instrument-atomics=0 -mllvm -asan-instrument-byval=0 -fuse-ld=lld -flto -Wl,-mllvm=-load=$BUILD_PATH/passes/ContainerOfSanitizer/LLVMContainerOfSanitizerPass.so -Wl,--plugin-opt=-lto-embed-bitcode=optimized $TESTS_PATH/test/test.c $TESTS_PATH/test/test1.c -o $TESTS_PATH/test/test.out 32 | objcopy $TESTS_PATH/test/test.out --dump-section .llvmbc=$TESTS_PATH/test/test.bc 33 | llvm-dis $TESTS_PATH/test/test.bc 34 | -------------------------------------------------------------------------------- /tests/test/.gitignore: -------------------------------------------------------------------------------- 1 | test.out 2 | *.bc 3 | *.ll -------------------------------------------------------------------------------- /tests/test/test1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* Are two types/vars the same type (ignoring qualifiers)? */ 5 | #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b)) 6 | #define static_assert _Static_assert 7 | 8 | #define STRINGIFY(a) #a 9 | #define SSTRINGIFY(x) STRINGIFY(x) 10 | 11 | // _Pragma( SSTRINGIFY( message (SSTRINGIFY(typeof(*(ptr))) " -> " SSTRINGIFY(type))) ) 12 | 13 | __attribute__((no_sanitize_address)) static volatile unsigned long __container_of_type_in; 14 | __attribute__((no_sanitize_address)) static volatile unsigned long __container_of_type_out; 15 | __attribute__((no_sanitize_address)) static volatile unsigned long __container_of_ptr_in; 16 | __attribute__((no_sanitize_address)) static volatile unsigned long __container_of_ptr_out; 17 | __attribute__((no_sanitize_address)) static volatile unsigned long __container_of_ptr_diff; 18 | 19 | #define container_of(ptr, type, member) ({ \ 20 | typeof(ptr) __tmp_type_in; \ 21 | type* __tmp_ptr_out = __container_of(ptr, type, member); \ 22 | __container_of_ptr_in = (unsigned long)ptr; \ 23 | __container_of_type_in = (unsigned long)&__tmp_type_in; \ 24 | __container_of_type_out = (unsigned long)&__tmp_ptr_out; \ 25 | __container_of_ptr_out = (unsigned long) __tmp_ptr_out; \ 26 | __container_of_ptr_diff = (unsigned long) offsetof(type, member); \ 27 | (type*)__container_of_ptr_out;}) 28 | 29 | #define __container_of(ptr, type, member) ({ \ 30 | void *__mptr = (void *)(ptr); \ 31 | static_assert(__same_type(*(ptr), ((type *)0)->member) || \ 32 | __same_type(*(ptr), void), \ 33 | "pointer type mismatch in container_of()"); \ 34 | ((type *)(__mptr - offsetof(type, member))); }) 35 | 36 | 37 | struct S1 { 38 | int mem1; 39 | char mem2; 40 | }; 41 | 42 | struct S21; 43 | struct S21_operations { 44 | 45 | int (*func1)(struct S21 *); 46 | int (*func2)(struct S21 *); 47 | int (*func3)(struct S21 *); 48 | }; 49 | 50 | struct S21 { 51 | 52 | long mem1; 53 | short mem2; 54 | struct S1 inner; 55 | long mem3; 56 | struct S21_operations *ops; 57 | }; 58 | 59 | typedef struct member_s { 60 | unsigned x; 61 | unsigned y; 62 | struct member_s* m; 63 | } member_t; 64 | 65 | typedef struct { 66 | unsigned z; 67 | member_t m; 68 | } container_t; 69 | 70 | struct S21 s21; 71 | 72 | struct OP_test; 73 | 74 | void foo1(struct S1* s1); 75 | 76 | 77 | int main1(int argc, char*argv[]) { 78 | 79 | struct S1* s1 = &s21.inner; 80 | member_t mem; 81 | container_t cont1; 82 | printf("%lx\n", (unsigned long)&mem); 83 | 84 | // member_t *m_ptr = (member_t*)main1; 85 | member_t *m_ptr = (member_t*)&cont1.m; 86 | struct OP_test *op_ptr = (struct OP_test*)foo1; 87 | printf("%lx\n", (unsigned long)&container_of(m_ptr, container_t, m)->z); 88 | printf("%lx\n", (unsigned long)&op_ptr); 89 | foo1(&s21.inner); 90 | return 0; 91 | } 92 | 93 | void foo1(struct S1* s1) { 94 | printf("%lx\n", container_of(s1, struct S21, inner)->mem3); 95 | // printf("%lx\n", (container_of(s1, struct S2, inner)->mem2)); 96 | } -------------------------------------------------------------------------------- /tests/testDF.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -u 5 | # set -x 6 | 7 | TESTS_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" 8 | BUILD_PATH="$TESTS_PATH/../build" 9 | 10 | LLVM_DIR=${LLVM_DIR:-'/home/ubuntu/uncontained-llvm-project/build'} 11 | export PATH="$LLVM_DIR/bin:$PATH" 12 | 13 | arrFailures=() 14 | 15 | function check_hit() { 16 | filename=$1 17 | config="$TESTS_PATH/config.yaml" 18 | # support ad-hoc configs 19 | if [ -f ${filename%.c}.yaml ]; then 20 | config=${filename%.c}.yaml 21 | fi 22 | # this runs at lto time 23 | clang -flegacy-pass-manager -O1 -g -fuse-ld=lld -flto -Wl,--plugin-opt=-lto-embed-bitcode=optimized ${filename} -o ${filename%.c}.out 24 | objcopy ${filename%.c}.out --dump-section .llvmbc=${filename%.c}.bc 25 | llvm-dis ${filename%.c}.bc 26 | echo "---> ${filename}" 27 | clang -flegacy-pass-manager -O1 -g -fuse-ld=lld -flto -Wl,-mllvm=-load=$BUILD_PATH/passes/SimpleDataflowChecker/LLVMSimpleDataflowCheckerPass.so -Wl,-mllvm=-config=$config -Wl,-mllvm=-dump-reports="" -Wl,-mllvm=-print-instructions=1 -Wl,--plugin-opt=-lto-embed-bitcode=optimized ${filename} -o ${filename%.c}.out | tee out.txt 28 | grep -q "DATAFLOW RULE TRIGGER" out.txt || { echo "ERROR: RULE DID NOT TRIGGER" && arrFailures[${#arrFailures[@]}]=${filename}; } 29 | rm out.txt 30 | } 31 | 32 | function check_nohit() { 33 | filename=$1 34 | config="$TESTS_PATH/config.yaml" 35 | # support ad-hoc configs 36 | if [ -f ${filename%.c}.yaml ]; then 37 | config=${filename%.c}.yaml 38 | fi 39 | # this runs at lto time 40 | clang -flegacy-pass-manager -O1 -g -fuse-ld=lld -flto -Wl,--plugin-opt=-lto-embed-bitcode=optimized ${filename} -o ${filename%.c}.out 41 | objcopy ${filename%.c}.out --dump-section .llvmbc=${filename%.c}.bc 42 | llvm-dis ${filename%.c}.bc 43 | echo "---> ${filename}" 44 | clang -flegacy-pass-manager -O1 -g -fuse-ld=lld -flto -Wl,-mllvm=-load=$BUILD_PATH/passes/SimpleDataflowChecker/LLVMSimpleDataflowCheckerPass.so -Wl,-mllvm=-config=$config -Wl,-mllvm=-dump-reports="" -Wl,-mllvm=-print-instructions=1 -Wl,--plugin-opt=-lto-embed-bitcode=optimized ${filename} -o ${filename%.c}.out | tee out.txt 45 | { grep -q "DATAFLOW RULE TRIGGER" out.txt && echo "ERROR: RULE DID TRIGGER WHEN SHOULDN'T" && arrFailures[${#arrFailures[@]}]=${filename}; } || true 46 | rm out.txt 47 | } 48 | if [[ "${1-}" == *"_hit"* ]]; then 49 | check_hit $1 50 | elif [[ "${1-}" == *"_nohit"* ]]; then 51 | check_nohit $1 52 | else 53 | for filename in $TESTS_PATH/testDF/test_hit*.c; do 54 | [ -e "$filename" ] || continue 55 | check_hit $filename 56 | done 57 | 58 | for filename in $TESTS_PATH/testDF/test_nohit*.c; do 59 | [ -e "$filename" ] || continue 60 | check_nohit $filename 61 | done 62 | fi 63 | 64 | if [ ${#arrFailures[@]} -eq 0 ]; then 65 | echo "[OK]" 66 | exit 0 67 | fi 68 | 69 | echo "[FAILURES]" 70 | for value in "${arrFailures[@]}" 71 | do 72 | echo $value 73 | done 74 | exit 1 75 | -------------------------------------------------------------------------------- /tests/testDF/.gitignore: -------------------------------------------------------------------------------- 1 | test.out 2 | *.bc 3 | *.ll -------------------------------------------------------------------------------- /tests/testDF/container_of.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONTAINER_OF_ 2 | #define _CONTAINER_OF_ 3 | 4 | #define __uncontained_container_of(ptr, type, member) ({ \ 5 | void *__mptr = (void *)(ptr); \ 6 | ((type *)(__mptr - offsetof(type, member))); }) 7 | 8 | static volatile unsigned long __container_of_flow_ptr_in; 9 | static volatile unsigned long __container_of_flow_type_in; 10 | static volatile unsigned long __container_of_flow_type_out; 11 | 12 | #define container_of(ptr, type, member) ({ \ 13 | typeof(ptr) __tmp_type_in; \ 14 | type* __tmp_ptr_out; \ 15 | __container_of_flow_ptr_in = (unsigned long)ptr; \ 16 | __container_of_flow_type_in = (unsigned long)&__tmp_type_in; \ 17 | __tmp_ptr_out = __uncontained_container_of(ptr, type, member); \ 18 | __container_of_flow_type_out = (unsigned long)&__tmp_ptr_out; \ 19 | (type*)__tmp_ptr_out; }) 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /tests/testDF/kobj.h: -------------------------------------------------------------------------------- 1 | #ifndef _KOBJ_ 2 | #define _KOBJ_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct kobject { 9 | uint64_t other; 10 | }; 11 | 12 | struct other_container { 13 | struct kobject kobj; 14 | }; 15 | 16 | struct cpufreq_policy { 17 | uint64_t other; 18 | struct kobject kobj; 19 | }; 20 | 21 | struct outer_container { 22 | uint64_t other; 23 | struct cpufreq_policy policy; 24 | }; 25 | 26 | struct outer_container2 { 27 | uint64_t other; 28 | struct other_container container; 29 | }; 30 | 31 | struct attribute { 32 | const char *name; 33 | }; 34 | 35 | struct sysfs_ops { 36 | ssize_t (*show)(struct kobject *, struct attribute *, char *); 37 | ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t); 38 | }; 39 | 40 | struct kobj_type { 41 | uint64_t first; 42 | const struct sysfs_ops *sysfs_ops; 43 | }; 44 | 45 | #endif 46 | 47 | -------------------------------------------------------------------------------- /tests/testDF/list.h: -------------------------------------------------------------------------------- 1 | #ifndef _LIST_ 2 | #define _LIST_ 3 | 4 | #include "container_of.h" 5 | 6 | struct list_head { 7 | struct list_head *next, *prev; 8 | }; 9 | 10 | static inline void __list_add(struct list_head *newh, 11 | struct list_head *prev, 12 | struct list_head *next) 13 | { 14 | next->prev = newh; 15 | newh->next = next; 16 | newh->prev = prev; 17 | prev->next = newh; 18 | } 19 | 20 | static volatile unsigned long __list_entry_flow_ptr_in; 21 | static volatile unsigned long __list_entry_flow_type_out; 22 | 23 | static void __attribute_noinline__ list_add(struct list_head *newh, struct list_head *head) 24 | { 25 | __list_add(newh, head, head->next); 26 | } 27 | 28 | #define list_entry(ptr, type, member) ({ \ 29 | type* __tmp_ptr_out; \ 30 | __list_entry_flow_ptr_in = (unsigned long)ptr; \ 31 | __tmp_ptr_out = container_of(ptr, type, member); \ 32 | __list_entry_flow_type_out = (unsigned long)&__tmp_ptr_out; \ 33 | (type*)__tmp_ptr_out; }) 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /tests/testDF/test_hit1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int dummy = 0; 6 | int* dummy_addr = &dummy; 7 | int* volatile out; 8 | 9 | int* __attribute_noinline__ __inet_lookup_established() { 10 | int* volatile res = dummy_addr; 11 | return res; 12 | } 13 | 14 | int* __attribute_noinline__ func4(void* ptr) { 15 | return ptr; 16 | } 17 | 18 | int* __attribute_noinline__ func3(void* ptr) { 19 | return func4(ptr); 20 | } 21 | 22 | int* __attribute_noinline__ func2(void* ptr) { 23 | return func3(ptr); 24 | } 25 | 26 | int* __attribute_noinline__ func1(void* ptr) { 27 | return func2(ptr); 28 | } 29 | 30 | int main(int argc, char*argv[]) { 31 | int* ptr = __inet_lookup_established(); 32 | out = func1(ptr); 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /tests/testDF/test_hit10.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int dummy = 0; 6 | int* dummy_addr = &dummy; 7 | int* volatile out; 8 | 9 | static int* __attribute_noinline__ __test_source(void* ptr) { 10 | return ptr; 11 | } 12 | 13 | static void __attribute_noinline__ __test_sink(void* ptr) { 14 | out = ptr; 15 | } 16 | 17 | int* __attribute_noinline__ func4(void* ptr) { 18 | out = ptr; 19 | return ptr; 20 | } 21 | 22 | int* __attribute_noinline__ func3(void* ptr) { 23 | return func4(ptr); 24 | } 25 | 26 | int* __attribute_noinline__ func2(void* ptr) { 27 | return func3(ptr); 28 | } 29 | 30 | int* __attribute_noinline__ func1(void* ptr) { 31 | return func2(ptr); 32 | } 33 | 34 | int main(int argc, char*argv[]) { 35 | int* ptr = __test_source(dummy_addr); 36 | __test_sink(ptr); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /tests/testDF/test_hit2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int dummy = 0; 6 | int* dummy_addr = &dummy; 7 | int* volatile out; 8 | 9 | int* __attribute_noinline__ __inet_lookup_established() { 10 | int* volatile res = dummy_addr; 11 | return res; 12 | } 13 | 14 | int* __attribute_noinline__ func4(void* ptr) { 15 | out = ptr; 16 | return ptr; 17 | } 18 | 19 | int* __attribute_noinline__ func3(void* ptr) { 20 | return func4(ptr); 21 | } 22 | 23 | int* __attribute_noinline__ func2(void* ptr) { 24 | return func3(ptr); 25 | } 26 | 27 | int* __attribute_noinline__ func1(void* ptr) { 28 | return func2(ptr); 29 | } 30 | 31 | int main(int argc, char*argv[]) { 32 | int* ptr = __inet_lookup_established(); 33 | func1(ptr); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /tests/testDF/test_hit3.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int dummy = 0; 6 | int* dummy_addr = &dummy; 7 | int* volatile out; 8 | 9 | int* __attribute_noinline__ __inet_lookup_established() { 10 | int* volatile res = dummy_addr; 11 | return res; 12 | } 13 | 14 | int* __attribute_noinline__ func4() { 15 | int* ptr = __inet_lookup_established(); 16 | return ptr; 17 | } 18 | 19 | int* __attribute_noinline__ func3() { 20 | return func4(); 21 | } 22 | 23 | int* __attribute_noinline__ func2() { 24 | return func3(); 25 | } 26 | 27 | int* __attribute_noinline__ func1() { 28 | return func2(); 29 | } 30 | 31 | int main(int argc, char*argv[]) { 32 | out = func1(); 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /tests/testDF/test_hit4.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int dummy = 0; 6 | int* dummy_addr = &dummy; 7 | int* volatile out; 8 | 9 | int* __attribute_noinline__ __inet_lookup_established() { 10 | int* volatile res = dummy_addr; 11 | return res; 12 | } 13 | 14 | int* __attribute_noinline__ func4() { 15 | int* ptr = __inet_lookup_established(); 16 | return ptr; 17 | } 18 | 19 | int* __attribute_noinline__ func3() { 20 | return func4(); 21 | } 22 | 23 | int* __attribute_noinline__ func2() { 24 | return func3(); 25 | } 26 | 27 | int* __attribute_noinline__ func1() { 28 | return func2(); 29 | } 30 | 31 | int* __attribute_noinline__ func7(void* ptr) { 32 | out = ptr; 33 | return ptr; 34 | } 35 | 36 | int* __attribute_noinline__ func6(void* ptr) { 37 | func7(ptr); 38 | return ptr; 39 | } 40 | 41 | int* __attribute_noinline__ func5(void* ptr) { 42 | func6(ptr); 43 | return ptr; 44 | } 45 | 46 | int main(int argc, char*argv[]) { 47 | int* ptr = func1(); 48 | func5(ptr); 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /tests/testDF/test_hit5.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int dummy = 0; 6 | int* dummy_addr = &dummy; 7 | int* volatile out; 8 | 9 | int* __attribute_noinline__ __inet_lookup_established() { 10 | int* volatile res = dummy_addr; 11 | return res; 12 | } 13 | 14 | int* __attribute_noinline__ func4() { 15 | int* ptr = __inet_lookup_established(); 16 | out = ptr; 17 | return ptr; 18 | } 19 | 20 | int* __attribute_noinline__ func3() { 21 | return func4(); 22 | } 23 | 24 | int* __attribute_noinline__ func2() { 25 | return func3(); 26 | } 27 | 28 | int* __attribute_noinline__ func1() { 29 | return func2(); 30 | } 31 | 32 | int main(int argc, char*argv[]) { 33 | out = func1(); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /tests/testDF/test_hit6.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int dummy = 0; 6 | int* dummy_addr = &dummy; 7 | int* volatile out; 8 | 9 | void __attribute_noinline__ __test_sink(void* ptr) { 10 | out = ptr; 11 | } 12 | 13 | int* __attribute_noinline__ __test_source() { 14 | int* volatile res = dummy_addr; 15 | return res; 16 | } 17 | 18 | int* __attribute_noinline__ func4(void* ptr) { 19 | return ptr; 20 | } 21 | 22 | int* __attribute_noinline__ func3(void* ptr) { 23 | return func4(ptr); 24 | } 25 | 26 | int* __attribute_noinline__ func2(void* ptr) { 27 | return func3(ptr); 28 | } 29 | 30 | int* __attribute_noinline__ func1(void* ptr) { 31 | return func2(ptr); 32 | } 33 | 34 | int main(int argc, char*argv[]) { 35 | int* ptr = __test_source(); 36 | __test_sink(func1(ptr)); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /tests/testDF/test_hit7.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int dummy = 0; 6 | int* dummy_addr = &dummy; 7 | int* volatile out; 8 | 9 | int __attribute_noinline__ __test_sanitizer(void* ptr) { 10 | return *(int*)ptr; 11 | } 12 | 13 | void __attribute_noinline__ __test_sink(void* ptr) { 14 | out = ptr; 15 | } 16 | 17 | int* __attribute_noinline__ __test_source() { 18 | int* volatile res = dummy_addr; 19 | return res; 20 | } 21 | 22 | int* __attribute_noinline__ func4(void* ptr) { 23 | return ptr; 24 | } 25 | 26 | int* __attribute_noinline__ func3(void* ptr) { 27 | return func4(ptr); 28 | } 29 | 30 | int* __attribute_noinline__ func2(void* ptr) { 31 | return func3(ptr); 32 | } 33 | 34 | int* __attribute_noinline__ func1(void* ptr) { 35 | return func2(ptr); 36 | } 37 | 38 | int main(int argc, char*argv[]) { 39 | int* ptr = __test_source(); 40 | __test_sink(func1(ptr)); 41 | if (__test_sanitizer(ptr)) 42 | __test_sink(func1(ptr)); 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /tests/testDF/test_hit8.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int dummy = 0; 6 | int* dummy_addr = &dummy; 7 | int* volatile out; 8 | 9 | int __attribute_noinline__ __test_sanitizer(void* ptr) { 10 | return *(int*)ptr; 11 | } 12 | 13 | int __attribute_noinline__ wrapper_sanitizer(void* ptr) { 14 | return __test_sanitizer(ptr); 15 | } 16 | 17 | void __attribute_noinline__ __test_sink(void* ptr) { 18 | out = ptr; 19 | } 20 | 21 | int* __attribute_noinline__ __test_source() { 22 | int* volatile res = dummy_addr; 23 | return res; 24 | } 25 | 26 | int* __attribute_noinline__ func4(void* ptr) { 27 | return ptr; 28 | } 29 | 30 | int* __attribute_noinline__ func3(void* ptr) { 31 | return func4(ptr); 32 | } 33 | 34 | int* __attribute_noinline__ func2(void* ptr) { 35 | return func3(ptr); 36 | } 37 | 38 | int* __attribute_noinline__ func1(void* ptr) { 39 | return func2(ptr); 40 | } 41 | 42 | int main(int argc, char*argv[]) { 43 | int* ptr = __test_source(); 44 | // FIXME: this is a false positive, due to the function 45 | // being wrapped. Do we want to detect this? 46 | if (wrapper_sanitizer(ptr)) 47 | __test_sink(func1(ptr)); 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /tests/testDF/test_hit9.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct sock_common { 6 | /* skc_daddr and skc_rcv_saddr must be grouped on a 8 bytes aligned 7 | * address on 64bit arches : cf INET_MATCH() 8 | */ 9 | struct { 10 | unsigned int skc_daddr; 11 | unsigned int skc_rcv_saddr; 12 | }; 13 | unsigned int skc_hash; 14 | /* skc_dport && skc_num must be grouped as well */ 15 | struct { 16 | unsigned short skc_dport; 17 | unsigned short skc_num; 18 | }; 19 | 20 | unsigned short skc_family; 21 | volatile unsigned char skc_state; 22 | unsigned char skc_reuse:4; 23 | unsigned char skc_reuseport:1; 24 | unsigned char skc_ipv6only:1; 25 | unsigned char skc_net_refcnt:1; 26 | }; 27 | 28 | int dummy = 0; 29 | int* dummy_addr = &dummy; 30 | int* volatile out; 31 | 32 | struct sock_common* __attribute_noinline__ __inet_lookup_established() { 33 | int* volatile res = dummy_addr; 34 | return (struct sock_common*)res; 35 | } 36 | 37 | int* __attribute_noinline__ func4(void* ptr) { 38 | return ptr; 39 | } 40 | 41 | int* __attribute_noinline__ func3(void* ptr) { 42 | return func4(ptr); 43 | } 44 | 45 | int* __attribute_noinline__ func2(void* ptr) { 46 | return func3(ptr); 47 | } 48 | 49 | int* __attribute_noinline__ func1(void* ptr) { 50 | return func2(ptr); 51 | } 52 | 53 | int main(int argc, char*argv[]) { 54 | struct sock_common* ptr = __inet_lookup_established(); 55 | // this should not hit since it is not a load 56 | if (&ptr->skc_state == (volatile unsigned char *)0x1000) 57 | out = func1(ptr); 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /tests/testDF/test_hit_backwards_contained01.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "container_of.h" 7 | 8 | 9 | struct socket { 10 | uint64_t other; 11 | }; 12 | 13 | struct sock { 14 | uint64_t sk_uid; 15 | uint64_t other1; 16 | }; 17 | 18 | struct inode { 19 | uint64_t i_uid; 20 | }; 21 | 22 | struct socket_alloc { 23 | struct inode vfs_inode; 24 | struct socket socket; 25 | }; 26 | 27 | struct tun_file { 28 | struct sock sk; 29 | struct socket socket; 30 | uint64_t other; 31 | }; 32 | 33 | static inline struct inode *SOCK_INODE(struct socket *socket) 34 | { 35 | return &container_of(socket, struct socket_alloc, socket)->vfs_inode; 36 | } 37 | 38 | void sock_init_data(struct socket *sock, struct sock *sk) 39 | { 40 | if (sock) { 41 | sk->sk_uid = SOCK_INODE(sock)->i_uid; 42 | } 43 | } 44 | 45 | volatile struct tun_file tfile_storage; 46 | 47 | static void __attribute_noinline__ tun_chr_open() 48 | { 49 | struct tun_file *tfile = (struct tun_file *)&tfile_storage; 50 | sock_init_data(&tfile->socket, &tfile->sk); 51 | *((uint64_t volatile *)&tfile_storage.other) = 12; 52 | } 53 | 54 | void __attribute_noinline__ func2() { 55 | } 56 | 57 | void __attribute_noinline__ func1() { 58 | func2(); 59 | } 60 | 61 | int main(int argc, char*argv[]) { 62 | tun_chr_open(); 63 | return 0; 64 | } 65 | -------------------------------------------------------------------------------- /tests/testDF/test_hit_backwards_contained02.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "container_of.h" 7 | 8 | 9 | struct socket { 10 | uint64_t other; 11 | }; 12 | 13 | struct sock { 14 | uint64_t sk_uid; 15 | uint64_t other1; 16 | }; 17 | 18 | struct inode { 19 | uint64_t i_uid; 20 | }; 21 | 22 | struct intermediate { 23 | struct socket socket; 24 | }; 25 | 26 | struct socket_alloc { 27 | struct inode vfs_inode; 28 | struct intermediate intermediate; 29 | }; 30 | 31 | struct tun_file { 32 | struct sock sk; 33 | struct socket socket; 34 | uint64_t other; 35 | }; 36 | 37 | static inline struct inode *SOCK_INODE(struct socket *socket) 38 | { 39 | return &container_of(socket, struct socket_alloc, intermediate.socket)->vfs_inode; 40 | } 41 | 42 | void sock_init_data(struct socket *sock, struct sock *sk) 43 | { 44 | if (sock) { 45 | sk->sk_uid = SOCK_INODE(sock)->i_uid; 46 | } 47 | } 48 | 49 | volatile struct tun_file tfile_storage; 50 | 51 | static void __attribute_noinline__ tun_chr_open() 52 | { 53 | struct tun_file *tfile = (struct tun_file *)&tfile_storage; 54 | sock_init_data(&tfile->socket, &tfile->sk); 55 | *((uint64_t volatile *)&tfile_storage.other) = 12; 56 | } 57 | 58 | void __attribute_noinline__ func2() { 59 | } 60 | 61 | void __attribute_noinline__ func1() { 62 | func2(); 63 | } 64 | 65 | int main(int argc, char*argv[]) { 66 | tun_chr_open(); 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /tests/testDF/test_hit_kobj01.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "container_of.h" 7 | #include "kobj.h" 8 | 9 | /* kobj is contained in another type */ 10 | 11 | static ssize_t show(struct kobject *kobj, struct attribute *attr, char *buf) 12 | { 13 | struct other_container *other = container_of(kobj, struct other_container, kobj); 14 | printf("other: %p\n", other); 15 | return 0; 16 | } 17 | 18 | static ssize_t store(struct kobject *kobj, struct attribute *attr, 19 | const char *buf, size_t count) 20 | { 21 | return 0; 22 | } 23 | 24 | static const struct sysfs_ops sysfs_ops = { 25 | .show = show, 26 | .store = store, 27 | }; 28 | 29 | static struct kobj_type ktype_cpufreq = { 30 | .sysfs_ops = &sysfs_ops, 31 | }; 32 | 33 | int kobject_init_and_add(struct kobject *kobj, const struct kobj_type *ktype) { 34 | printf("%p\n", kobj); 35 | printf("%p\n", ktype); 36 | return 0; 37 | } 38 | 39 | struct cpufreq_policy policy_storage; 40 | 41 | int main(int argc, char*argv[]) { 42 | struct cpufreq_policy *policy = &policy_storage; 43 | int ret = kobject_init_and_add(&policy->kobj, &ktype_cpufreq); 44 | return ret; 45 | } 46 | -------------------------------------------------------------------------------- /tests/testDF/test_hit_kobj02.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "container_of.h" 7 | #include "kobj.h" 8 | 9 | /* kobj is not contained at all */ 10 | 11 | static ssize_t show(struct kobject *kobj, struct attribute *attr, char *buf) 12 | { 13 | struct other_container *other = container_of(kobj, struct other_container, kobj); 14 | printf("other: %p\n", other); 15 | return 0; 16 | } 17 | 18 | static ssize_t store(struct kobject *kobj, struct attribute *attr, 19 | const char *buf, size_t count) 20 | { 21 | return 0; 22 | } 23 | 24 | static const struct sysfs_ops sysfs_ops = { 25 | .show = show, 26 | .store = store, 27 | }; 28 | 29 | static struct kobj_type ktype_cpufreq = { 30 | .sysfs_ops = &sysfs_ops, 31 | }; 32 | 33 | int kobject_init_and_add(struct kobject *kobj, const struct kobj_type *ktype) { 34 | printf("%p\n", kobj); 35 | printf("%p\n", ktype); 36 | return 0; 37 | } 38 | 39 | struct kobject global_obj; 40 | 41 | int main(int argc, char*argv[]) { 42 | int ret = kobject_init_and_add(&global_obj, &ktype_cpufreq); 43 | return ret; 44 | } 45 | -------------------------------------------------------------------------------- /tests/testDF/test_hit_kobj03.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "container_of.h" 7 | #include "kobj.h" 8 | 9 | /* the container_of is incorrect since kobj is actuallly within outer_container */ 10 | 11 | static ssize_t show(struct kobject *kobj, struct attribute *attr, char *buf) 12 | { 13 | struct outer_container2 *other = container_of(kobj, struct outer_container2, container.kobj); 14 | printf("other: %p\n", other); 15 | return 0; 16 | } 17 | 18 | static ssize_t store(struct kobject *kobj, struct attribute *attr, 19 | const char *buf, size_t count) 20 | { 21 | return 0; 22 | } 23 | 24 | static const struct sysfs_ops sysfs_ops = { 25 | .show = show, 26 | .store = store, 27 | }; 28 | 29 | static struct kobj_type ktype_cpufreq = { 30 | .sysfs_ops = &sysfs_ops, 31 | }; 32 | 33 | int kobject_init_and_add(struct kobject *kobj, const struct kobj_type *ktype) { 34 | printf("%p\n", kobj); 35 | printf("%p\n", ktype); 36 | return 0; 37 | } 38 | 39 | int __attribute_noinline__ func2(struct cpufreq_policy *policy) { 40 | int ret = kobject_init_and_add(&policy->kobj, &ktype_cpufreq); 41 | return ret; 42 | } 43 | 44 | struct outer_container outer_storage; 45 | 46 | int main(int argc, char*argv[]) { 47 | struct cpufreq_policy *policy = &outer_storage.policy; 48 | return func2(policy); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /tests/testDF/test_hit_list_entry_correlation01.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "list.h" 7 | 8 | /* list_entry is using a different struct type than preceeding list_add */ 9 | 10 | struct test { 11 | uint64_t other; 12 | struct list_head list; 13 | }; 14 | 15 | struct test1 { 16 | uint64_t other1; 17 | uint64_t other2; 18 | struct list_head list; 19 | }; 20 | 21 | struct test test_storage; 22 | 23 | static void __attribute_noinline__ func2(struct list_head *head) 24 | { 25 | struct test1 *test1 = list_entry(head, struct test1, list); 26 | printf("test: %p\n", test1); 27 | } 28 | 29 | static void __attribute_noinline__ func1() 30 | { 31 | struct list_head test_list; 32 | list_add(&test_storage.list, &test_list); 33 | func2(&test_list); 34 | } 35 | 36 | 37 | /* without this list_add does not have any arguments after optimizations :( */ 38 | struct list_head test_list_decoy; 39 | struct test1 test1_decoy_storage; 40 | 41 | int main(int argc, char*argv[]) { 42 | func1(); 43 | 44 | // break optimizations 45 | list_add(&test1_decoy_storage.list, &test_list_decoy); 46 | func2(&test_list_decoy); 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /tests/testDF/test_hit_list_entry_correlation02.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "list.h" 7 | 8 | /* list_add is using a different struct type than preceeding list_add */ 9 | 10 | struct test { 11 | uint64_t other; 12 | struct list_head list; 13 | }; 14 | 15 | struct test1 { 16 | uint64_t other1; 17 | uint64_t other2; 18 | struct list_head list; 19 | }; 20 | 21 | struct test test_storage; 22 | struct test1 test1_storage; 23 | 24 | static void __attribute_noinline__ func2(struct list_head *head) 25 | { 26 | list_add(&test_storage.list, head); 27 | } 28 | 29 | static void __attribute_noinline__ func1() 30 | { 31 | struct list_head test_list; 32 | list_add(&test1_storage.list, &test_list); 33 | func2(&test_list); 34 | } 35 | 36 | 37 | /* without this list_add does not have any arguments after optimizations :( */ 38 | struct list_head test_list_decoy; 39 | struct test test_decoy_storage; 40 | 41 | int main(int argc, char*argv[]) { 42 | func1(); 43 | 44 | // break optimizations 45 | list_add(&test_decoy_storage.list, &test_list_decoy); 46 | func2(&test_list_decoy); 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /tests/testDF/test_hit_list_entry_correlation03.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "list.h" 7 | 8 | /* list_entry is using a different struct type than preceeding list_entry */ 9 | 10 | struct test { 11 | uint64_t other; 12 | struct list_head list; 13 | }; 14 | 15 | struct test1 { 16 | uint64_t other1; 17 | uint64_t other2; 18 | struct list_head list; 19 | }; 20 | 21 | struct test test_storage; 22 | struct test1 test1_storage; 23 | 24 | static void __attribute_noinline__ func3(struct list_head *head) 25 | { 26 | struct test1 *test = list_entry(head, struct test, list); 27 | printf("test: %p\n", test); 28 | } 29 | 30 | static void __attribute_noinline__ func2(struct list_head *head) 31 | { 32 | struct test1 *test1 = list_entry(head, struct test1, list); 33 | printf("test: %p\n", test1); 34 | func3(head); 35 | } 36 | 37 | static void __attribute_noinline__ func1() 38 | { 39 | struct list_head test_list; 40 | list_add(&test1_storage.list, &test_list); 41 | func2(&test_list); 42 | } 43 | 44 | 45 | /* without this list_add does not have any arguments after optimizations :( */ 46 | struct list_head test_list_decoy; 47 | struct test test_decoy_storage; 48 | 49 | int main(int argc, char*argv[]) { 50 | func1(); 51 | 52 | // break optimizations 53 | list_add(&test_decoy_storage.list, &test_list_decoy); 54 | func2(&test_list_decoy); 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /tests/testDF/test_hit_list_entry_correlation04.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "list.h" 7 | 8 | /* list_add is using a different struct type than preceeding list_entry */ 9 | 10 | struct test { 11 | uint64_t other; 12 | struct list_head list; 13 | }; 14 | 15 | struct test1 { 16 | uint64_t other1; 17 | uint64_t other2; 18 | struct list_head list; 19 | }; 20 | 21 | struct test test_storage; 22 | struct test1 test1_storage; 23 | 24 | static void __attribute_noinline__ func3(struct list_head *head) 25 | { 26 | list_add(&test_storage.list, head); 27 | } 28 | 29 | static void __attribute_noinline__ func2(struct list_head *head) 30 | { 31 | struct test1 *test1 = list_entry(head, struct test1, list); 32 | printf("test: %p\n", test1); 33 | func3(head); 34 | } 35 | 36 | static void __attribute_noinline__ func1() 37 | { 38 | struct list_head test_list; 39 | list_add(&test1_storage.list, &test_list); 40 | func2(&test_list); 41 | } 42 | 43 | 44 | /* without this list_add does not have any arguments after optimizations :( */ 45 | struct list_head test_list_decoy; 46 | struct test test_decoy_storage; 47 | 48 | int main(int argc, char*argv[]) { 49 | func1(); 50 | 51 | // break optimizations 52 | list_add(&test_decoy_storage.list, &test_list_decoy); 53 | func2(&test_list_decoy); 54 | return 0; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /tests/testDF/test_hit_list_entry_null.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct list_head { 6 | struct list_head *volatile next, *volatile prev; 7 | }; 8 | 9 | #define container_of(ptr, type, member) ({ \ 10 | void *__mptr = (void *)(ptr); \ 11 | ((type *)(__mptr - offsetof(type, member))); }) 12 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr); 13 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr) { 14 | return ptr; 15 | } 16 | #define list_entry(ptr, type, member)({ \ 17 | type* __tmp_ptr_out = container_of(ptr, type, member); \ 18 | (type*)__uncontained_list_entry_source((void*)__tmp_ptr_out);}) 19 | #define list_next_entry(pos, member) \ 20 | ({ list_entry((pos)->member.next, typeof(*(pos)), member);}) 21 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res); 22 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res) { 23 | return res; 24 | } 25 | #define list_entry_is_head(pos, head, member) \ 26 | ({ __uncontained_list_entry_is_head(&((typeof(pos)) ((void*)pos))->member == (head));}) 27 | 28 | #define list_first_entry(ptr, type, member) \ 29 | ({ list_entry((ptr)->next, type, member);}) 30 | #define list_for_each_entry(pos, head, member) \ 31 | for (pos = list_first_entry(head, typeof(*pos), member); \ 32 | !list_entry_is_head(pos, head, member); \ 33 | pos = list_next_entry(pos, member)) 34 | 35 | #define list_for_each_entry_safe(pos, n, head, member) \ 36 | for (pos = list_first_entry(head, typeof(*pos), member);\ 37 | !list_entry_is_head(pos, head, member); \ 38 | pos = n = list_next_entry(pos, member), n = list_next_entry(n, member)) 39 | 40 | int __attribute_noinline__ list_is_head(const struct list_head *list, const struct list_head *head) 41 | { 42 | return list == head; 43 | } 44 | 45 | #define list_for_each(pos, head) \ 46 | for (pos = (head)->next; \ 47 | !list_is_head(pos, (head)); \ 48 | pos = pos->next) 49 | 50 | #define list_first_entry_or_null(ptr, type, member) ({ \ 51 | struct list_head *head__ = (ptr); \ 52 | struct list_head *pos__ = (head__->next); \ 53 | !list_is_head(pos__, head__) ? list_entry(pos__, type, member) : NULL; \ 54 | }) 55 | 56 | static __attribute_noinline__ int list_empty(const struct list_head *head) 57 | { 58 | return head->next == head; 59 | } 60 | 61 | static inline void __list_del(struct list_head * prev, struct list_head * next) 62 | { 63 | next->prev = prev; 64 | prev->next = next; 65 | } 66 | static inline void __list_del_entry(struct list_head *entry) 67 | { 68 | __list_del(entry->prev, entry->next); 69 | } 70 | 71 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 72 | 73 | #define LIST_HEAD(name) \ 74 | struct list_head name = LIST_HEAD_INIT(name) 75 | 76 | struct S { 77 | struct list_head list; 78 | int i; 79 | }; 80 | 81 | static LIST_HEAD(glob_list); 82 | // fake variable that tracks list size 83 | static volatile int list_size = 1; 84 | static __attribute_noinline__ int func() 85 | { 86 | struct S *_op, *tmp; 87 | 88 | _op = list_first_entry(&glob_list, struct S, list); 89 | printf("%p\n", _op); 90 | if (_op != NULL) 91 | return _op->i; 92 | return 0; 93 | } 94 | 95 | int main(int argc, char*argv[]) { 96 | printf("%d\n", func()); 97 | return 0; 98 | } 99 | -------------------------------------------------------------------------------- /tests/testDF/test_hit_list_entry_null.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | - name: list_entry_null 4 | source: "__uncontained_list_entry_source" 5 | sink: CMPNULL 6 | # ignore all the flows dominated by the sanitizers 7 | sanitizers: 8 | - { function_call: "list_is_last" } 9 | - { function_call: "list_is_first" } 10 | - { function_call: "list_is_head" } 11 | - { function_call: "list_empty" } 12 | - { function_call: "__uncontained_list_entry_is_head" } 13 | - { instruction: "load"} 14 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int dummy = 0; 6 | int* dummy_addr = &dummy; 7 | int* volatile out; 8 | 9 | int* __attribute_noinline__ __inet_lookup_established() { 10 | int* volatile res = dummy_addr; 11 | return res; 12 | } 13 | 14 | int* __attribute_noinline__ func4(void* ptr) { 15 | out = ptr; 16 | return ptr; 17 | } 18 | 19 | int* __attribute_noinline__ func3(void* ptr) { 20 | return func4(ptr); 21 | } 22 | 23 | int* __attribute_noinline__ func2(void* ptr) { 24 | return func3(ptr); 25 | } 26 | 27 | int* __attribute_noinline__ kmem_cache_free(void* ptr) { 28 | return func2(ptr); 29 | } 30 | 31 | int main(int argc, char*argv[]) { 32 | int* ptr = __inet_lookup_established(); 33 | kmem_cache_free(ptr); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int dummy = 0; 6 | int* dummy_addr = &dummy; 7 | int* volatile out; 8 | 9 | int __attribute_noinline__ __test_sanitizer(void* ptr) { 10 | return *(int*)ptr; 11 | } 12 | 13 | void __attribute_noinline__ __test_sink(void* ptr) { 14 | out = ptr; 15 | } 16 | 17 | int* __attribute_noinline__ __test_source() { 18 | int* volatile res = dummy_addr; 19 | return res; 20 | } 21 | 22 | int* __attribute_noinline__ func4(void* ptr) { 23 | return ptr; 24 | } 25 | 26 | int* __attribute_noinline__ func3(void* ptr) { 27 | return func4(ptr); 28 | } 29 | 30 | int* __attribute_noinline__ func2(void* ptr) { 31 | return func3(ptr); 32 | } 33 | 34 | int* __attribute_noinline__ func1(void* ptr) { 35 | return func2(ptr); 36 | } 37 | 38 | int main(int argc, char*argv[]) { 39 | int* ptr = __test_source(); 40 | if (__test_sanitizer(ptr)) 41 | __test_sink(func1(ptr)); 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit3.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct sock_common { 6 | /* skc_daddr and skc_rcv_saddr must be grouped on a 8 bytes aligned 7 | * address on 64bit arches : cf INET_MATCH() 8 | */ 9 | struct { 10 | unsigned int skc_daddr; 11 | unsigned int skc_rcv_saddr; 12 | }; 13 | unsigned int skc_hash; 14 | /* skc_dport && skc_num must be grouped as well */ 15 | struct { 16 | unsigned short skc_dport; 17 | unsigned short skc_num; 18 | }; 19 | 20 | unsigned short skc_family; 21 | volatile unsigned char skc_state; 22 | unsigned char skc_reuse:4; 23 | unsigned char skc_reuseport:1; 24 | unsigned char skc_ipv6only:1; 25 | unsigned char skc_net_refcnt:1; 26 | }; 27 | 28 | int dummy = 0; 29 | int* dummy_addr = &dummy; 30 | int* volatile out; 31 | 32 | struct sock_common* __attribute_noinline__ __inet_lookup_established() { 33 | int* volatile res = dummy_addr; 34 | return (struct sock_common*)res; 35 | } 36 | 37 | int* __attribute_noinline__ func4(void* ptr) { 38 | return ptr; 39 | } 40 | 41 | int* __attribute_noinline__ func3(void* ptr) { 42 | return func4(ptr); 43 | } 44 | 45 | int* __attribute_noinline__ func2(void* ptr) { 46 | return func3(ptr); 47 | } 48 | 49 | int* __attribute_noinline__ func1(void* ptr) { 50 | return func2(ptr); 51 | } 52 | 53 | int main(int argc, char*argv[]) { 54 | struct sock_common* ptr = __inet_lookup_established(); 55 | if (ptr->skc_state == 0) 56 | out = func1(ptr); 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_backwards_contained01.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "container_of.h" 7 | 8 | 9 | struct socket { 10 | uint64_t other; 11 | }; 12 | 13 | struct sock { 14 | uint64_t sk_uid; 15 | uint64_t other1; 16 | }; 17 | 18 | struct inode { 19 | uint64_t i_uid; 20 | }; 21 | 22 | struct socket_alloc { 23 | struct inode vfs_inode; 24 | struct socket socket; 25 | uint64_t other; 26 | }; 27 | 28 | struct tun_file { 29 | struct sock sk; 30 | struct socket socket; 31 | uint64_t other; 32 | }; 33 | 34 | static inline struct inode *SOCK_INODE(struct socket *socket) 35 | { 36 | return &container_of(socket, struct socket_alloc, socket)->vfs_inode; 37 | } 38 | 39 | void sock_init_data(struct socket *sock, struct sock *sk) 40 | { 41 | if (sock) { 42 | sk->sk_uid = SOCK_INODE(sock)->i_uid; 43 | } 44 | } 45 | 46 | volatile struct socket_alloc salloc_storage; 47 | 48 | static void __attribute_noinline__ tun_chr_open() 49 | { 50 | struct socket_alloc *salloc = (struct socket_alloc *)&salloc_storage; 51 | struct sock sk; 52 | sock_init_data(&salloc->socket, &sk); 53 | *((uint64_t volatile *)&salloc_storage.other) = 12; 54 | } 55 | 56 | void __attribute_noinline__ func2() { 57 | } 58 | 59 | void __attribute_noinline__ func1() { 60 | func2(); 61 | } 62 | 63 | int main(int argc, char*argv[]) { 64 | tun_chr_open(); 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_backwards_contained02.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "container_of.h" 7 | 8 | 9 | struct socket { 10 | uint64_t other; 11 | }; 12 | 13 | struct sock { 14 | uint64_t sk_uid; 15 | uint64_t other1; 16 | }; 17 | 18 | struct inode { 19 | uint64_t i_uid; 20 | }; 21 | 22 | struct intermediate { 23 | uint64_t other; 24 | struct socket socket; 25 | }; 26 | 27 | struct socket_alloc { 28 | struct inode vfs_inode; 29 | struct intermediate intermediate; 30 | uint64_t other; 31 | }; 32 | 33 | struct tun_file { 34 | struct sock sk; 35 | struct socket socket; 36 | uint64_t other; 37 | }; 38 | 39 | static inline struct inode *SOCK_INODE(struct socket *socket) 40 | { 41 | return &container_of(socket, struct socket_alloc, intermediate.socket)->vfs_inode; 42 | } 43 | 44 | void sock_init_data(struct socket *sock, struct sock *sk) 45 | { 46 | if (sock) { 47 | sk->sk_uid = SOCK_INODE(sock)->i_uid; 48 | } 49 | } 50 | 51 | volatile struct socket_alloc salloc_storage; 52 | 53 | static void __attribute_noinline__ tun_chr_open(struct intermediate *intermediate) 54 | { 55 | struct sock sk; 56 | sock_init_data(&intermediate->socket, &sk); 57 | *((uint64_t volatile *)&salloc_storage.intermediate.other) = 12; 58 | } 59 | 60 | uint64_t rand_uint64_slow(void) { 61 | uint64_t r = 0; 62 | for (int i=0; i<64; i++) { 63 | r = r*2 + rand()%2; 64 | } 65 | return r; 66 | } 67 | 68 | void __attribute_noinline__ func1() { 69 | tun_chr_open((struct intermediate *)rand_uint64_slow()); 70 | } 71 | 72 | int main(int argc, char*argv[]) { 73 | struct socket_alloc *salloc = (struct socket_alloc *)&salloc_storage; 74 | tun_chr_open(&salloc->intermediate); 75 | *((uint64_t volatile *)&salloc_storage.other) = 12; 76 | 77 | func1(); 78 | return 0; 79 | } 80 | 81 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_kobj01.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "container_of.h" 7 | #include "kobj.h" 8 | 9 | static ssize_t show(struct kobject *kobj, struct attribute *attr, char *buf) 10 | { 11 | struct cpufreq_policy *other = container_of(kobj, struct cpufreq_policy, kobj); 12 | printf("other: %p\n", other); 13 | return 0; 14 | } 15 | 16 | static ssize_t store(struct kobject *kobj, struct attribute *attr, 17 | const char *buf, size_t count) 18 | { 19 | return 0; 20 | } 21 | 22 | static const struct sysfs_ops sysfs_ops = { 23 | .show = show, 24 | .store = store, 25 | }; 26 | 27 | static struct kobj_type ktype_cpufreq = { 28 | .sysfs_ops = &sysfs_ops, 29 | }; 30 | 31 | int kobject_init_and_add(struct kobject *kobj, const struct kobj_type *ktype) { 32 | printf("%p\n", kobj); 33 | printf("%p\n", ktype); 34 | return 0; 35 | } 36 | 37 | struct cpufreq_policy policy_storage; 38 | 39 | int main(int argc, char*argv[]) { 40 | struct cpufreq_policy *policy = &policy_storage; 41 | int ret = kobject_init_and_add(&policy->kobj, &ktype_cpufreq); 42 | return ret; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_kobj03.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "container_of.h" 7 | #include "kobj.h" 8 | 9 | /* the second container_of should not trigger */ 10 | 11 | void __attribute_noinline__ func1(void* ptr) { 12 | struct outer_container *other = container_of(ptr, struct outer_container, policy); 13 | printf("other: %p\n", other); 14 | } 15 | 16 | static ssize_t show(struct kobject *kobj, struct attribute *attr, char *buf) 17 | { 18 | struct cpufreq_policy *other = container_of(kobj, struct cpufreq_policy, kobj); 19 | printf("other: %p\n", other); 20 | func1(other); 21 | return 0; 22 | } 23 | 24 | static ssize_t store(struct kobject *kobj, struct attribute *attr, 25 | const char *buf, size_t count) 26 | { 27 | return 0; 28 | } 29 | 30 | static const struct sysfs_ops sysfs_ops = { 31 | .show = show, 32 | .store = store, 33 | }; 34 | 35 | static struct kobj_type ktype_cpufreq = { 36 | .sysfs_ops = &sysfs_ops, 37 | }; 38 | 39 | int kobject_init_and_add(struct kobject *kobj, const struct kobj_type *ktype) { 40 | printf("%p\n", kobj); 41 | printf("%p\n", ktype); 42 | return 0; 43 | } 44 | 45 | int __attribute_noinline__ func2(struct cpufreq_policy *policy) { 46 | int ret = kobject_init_and_add(&policy->kobj, &ktype_cpufreq); 47 | return ret; 48 | } 49 | 50 | struct outer_container outer_storage; 51 | 52 | int main(int argc, char*argv[]) { 53 | struct cpufreq_policy *policy = &outer_storage.policy; 54 | return func2(policy); 55 | } 56 | 57 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_kobj04.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "container_of.h" 7 | #include "kobj.h" 8 | 9 | /* the container_of is correct and should not trigger */ 10 | 11 | static ssize_t show(struct kobject *kobj, struct attribute *attr, char *buf) 12 | { 13 | struct outer_container *other = container_of(kobj, struct outer_container, policy.kobj); 14 | printf("other: %p\n", other); 15 | return 0; 16 | } 17 | 18 | static ssize_t store(struct kobject *kobj, struct attribute *attr, 19 | const char *buf, size_t count) 20 | { 21 | return 0; 22 | } 23 | 24 | static const struct sysfs_ops sysfs_ops = { 25 | .show = show, 26 | .store = store, 27 | }; 28 | 29 | static struct kobj_type ktype_cpufreq = { 30 | .sysfs_ops = &sysfs_ops, 31 | }; 32 | 33 | int kobject_init_and_add(struct kobject *kobj, const struct kobj_type *ktype) { 34 | printf("%p\n", kobj); 35 | printf("%p\n", ktype); 36 | return 0; 37 | } 38 | 39 | int __attribute_noinline__ func2(struct cpufreq_policy *policy) { 40 | int ret = kobject_init_and_add(&policy->kobj, &ktype_cpufreq); 41 | return ret; 42 | } 43 | 44 | struct outer_container outer_storage; 45 | 46 | int main(int argc, char*argv[]) { 47 | struct cpufreq_policy *policy = &outer_storage.policy; 48 | return func2(policy); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct list_head { 6 | struct list_head *next, *prev; 7 | }; 8 | 9 | #define container_of(ptr, type, member) ({ \ 10 | void *__mptr = (void *)(ptr); \ 11 | ((type *)(__mptr - offsetof(type, member))); }) 12 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr); 13 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr) { 14 | return ptr; 15 | } 16 | #define list_entry(ptr, type, member)({ \ 17 | type* __tmp_ptr_out = container_of(ptr, type, member); \ 18 | (type*)__uncontained_list_entry_source((void*)__tmp_ptr_out);}) 19 | #define list_next_entry(pos, member) \ 20 | ({ list_entry((pos)->member.next, typeof(*(pos)), member);}) 21 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res); 22 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res) { 23 | return res; 24 | } 25 | #define list_entry_is_head(pos, head, member) \ 26 | ({ __uncontained_list_entry_is_head(&((typeof(pos)) ((void*)pos))->member == (head));}) 27 | 28 | #define list_first_entry(ptr, type, member) \ 29 | ({ list_entry((ptr)->next, type, member);}) 30 | #define list_for_each_entry(pos, head, member) \ 31 | for (pos = list_first_entry(head, typeof(*pos), member); \ 32 | !list_entry_is_head(pos, head, member); \ 33 | pos = list_next_entry(pos, member)) 34 | 35 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 36 | 37 | #define LIST_HEAD(name) \ 38 | struct list_head name = LIST_HEAD_INIT(name) 39 | 40 | struct S { 41 | struct list_head list; 42 | int i; 43 | }; 44 | 45 | int dummy = 0; 46 | int* dummy_addr = &dummy; 47 | int* volatile out; 48 | 49 | static LIST_HEAD(glob_list); 50 | static __attribute_noinline__ int func(void *op) 51 | { 52 | struct S *_op; 53 | 54 | list_for_each_entry(_op, &glob_list, list) { 55 | if (op == _op) 56 | return _op->i; 57 | } 58 | return 0; 59 | } 60 | 61 | int main(int argc, char*argv[]) { 62 | int* ptr = dummy_addr; 63 | return func(ptr); 64 | } 65 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct list_head { 6 | struct list_head *next, *prev; 7 | }; 8 | 9 | #define container_of(ptr, type, member) ({ \ 10 | void *__mptr = (void *)(ptr); \ 11 | ((type *)(__mptr - offsetof(type, member))); }) 12 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr); 13 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr) { 14 | return ptr; 15 | } 16 | #define list_entry(ptr, type, member)({ \ 17 | type* __tmp_ptr_out = container_of(ptr, type, member); \ 18 | (type*)__uncontained_list_entry_source((void*)__tmp_ptr_out);}) 19 | #define list_next_entry(pos, member) \ 20 | ({ list_entry((pos)->member.next, typeof(*(pos)), member);}) 21 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res); 22 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res) { 23 | return res; 24 | } 25 | #define list_entry_is_head(pos, head, member) \ 26 | ({ __uncontained_list_entry_is_head(&((typeof(pos)) ((void*)pos))->member == (head));}) 27 | 28 | #define list_first_entry(ptr, type, member) \ 29 | ({ list_entry((ptr)->next, type, member);}) 30 | #define list_for_each_entry(pos, head, member) \ 31 | for (pos = list_first_entry(head, typeof(*pos), member); \ 32 | !list_entry_is_head(pos, head, member); \ 33 | pos = list_next_entry(pos, member)) 34 | 35 | #define list_for_each_entry_safe(pos, n, head, member) \ 36 | for (pos = list_first_entry(head, typeof(*pos), member);\ 37 | !list_entry_is_head(pos, head, member); \ 38 | pos = n = list_next_entry(pos, member), n = list_next_entry(n, member)) 39 | 40 | static __attribute_noinline__ int list_is_head(const struct list_head *list, const struct list_head *head) 41 | { 42 | return list == head; 43 | } 44 | 45 | #define list_for_each(pos, head) \ 46 | for (pos = (head)->next; \ 47 | !list_is_head(pos, (head)); \ 48 | pos = pos->next) 49 | 50 | static __attribute_noinline__ int list_empty(const struct list_head *head) 51 | { 52 | return head->next == head; 53 | } 54 | 55 | static inline void __list_del(struct list_head * prev, struct list_head * next) 56 | { 57 | next->prev = prev; 58 | prev->next = next; 59 | } 60 | static inline void __list_del_entry(struct list_head *entry) 61 | { 62 | __list_del(entry->prev, entry->next); 63 | } 64 | 65 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 66 | 67 | #define LIST_HEAD(name) \ 68 | struct list_head name = LIST_HEAD_INIT(name) 69 | 70 | struct S { 71 | struct list_head list; 72 | int i; 73 | }; 74 | 75 | static LIST_HEAD(glob_list); 76 | static __attribute_noinline__ int func() 77 | { 78 | struct S *_op, *tmp; 79 | 80 | _op = list_entry(glob_list.next, struct S, list); 81 | return _op->i; 82 | } 83 | 84 | int main(int argc, char*argv[]) { 85 | if (!list_empty(&glob_list)) 86 | return func(); 87 | else 88 | return 0; 89 | } 90 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry3.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct list_head { 6 | struct list_head *next, *prev; 7 | }; 8 | 9 | #define container_of(ptr, type, member) ({ \ 10 | void *__mptr = (void *)(ptr); \ 11 | ((type *)(__mptr - offsetof(type, member))); }) 12 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr); 13 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr) { 14 | return ptr; 15 | } 16 | #define list_entry(ptr, type, member)({ \ 17 | type* __tmp_ptr_out = container_of(ptr, type, member); \ 18 | (type*)__uncontained_list_entry_source((void*)__tmp_ptr_out);}) 19 | #define list_next_entry(pos, member) \ 20 | ({ list_entry((pos)->member.next, typeof(*(pos)), member);}) 21 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res); 22 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res) { 23 | return res; 24 | } 25 | #define list_entry_is_head(pos, head, member) \ 26 | ({ __uncontained_list_entry_is_head(&((typeof(pos)) ((void*)pos))->member == (head));}) 27 | 28 | #define list_first_entry(ptr, type, member) \ 29 | ({ list_entry((ptr)->next, type, member);}) 30 | #define list_for_each_entry(pos, head, member) \ 31 | for (pos = list_first_entry(head, typeof(*pos), member); \ 32 | !list_entry_is_head(pos, head, member); \ 33 | pos = list_next_entry(pos, member)) 34 | 35 | static __attribute_noinline__ int list_empty(const struct list_head *head) 36 | { 37 | return head->next == head; 38 | } 39 | 40 | static inline void __list_del(struct list_head * prev, struct list_head * next) 41 | { 42 | next->prev = prev; 43 | prev->next = next; 44 | } 45 | static inline void __list_del_entry(struct list_head *entry) 46 | { 47 | __list_del(entry->prev, entry->next); 48 | } 49 | 50 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 51 | 52 | #define LIST_HEAD(name) \ 53 | struct list_head name = LIST_HEAD_INIT(name) 54 | 55 | struct S { 56 | struct list_head list; 57 | int i; 58 | }; 59 | 60 | int dummy = 0; 61 | int* dummy_addr = &dummy; 62 | int* volatile out; 63 | 64 | static LIST_HEAD(glob_list); 65 | static __attribute_noinline__ int func(void *op) 66 | { 67 | struct S *_op; 68 | 69 | while (!list_empty(&glob_list)) { 70 | struct S *_op = list_first_entry(&glob_list, struct S, list); 71 | if (op == _op) 72 | return _op->i; 73 | __list_del_entry(&_op->list); 74 | } 75 | return 0; 76 | } 77 | 78 | int main(int argc, char*argv[]) { 79 | int* ptr = dummy_addr; 80 | return func(ptr); 81 | } 82 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry4.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct list_head { 6 | struct list_head *next, *prev; 7 | }; 8 | 9 | #define container_of(ptr, type, member) ({ \ 10 | void *__mptr = (void *)(ptr); \ 11 | ((type *)(__mptr - offsetof(type, member))); }) 12 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr); 13 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr) { 14 | return ptr; 15 | } 16 | #define list_entry(ptr, type, member)({ \ 17 | type* __tmp_ptr_out = container_of(ptr, type, member); \ 18 | (type*)__uncontained_list_entry_source((void*)__tmp_ptr_out);}) 19 | #define list_next_entry(pos, member) \ 20 | ({ list_entry((pos)->member.next, typeof(*(pos)), member);}) 21 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res); 22 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res) { 23 | return res; 24 | } 25 | #define list_entry_is_head(pos, head, member) \ 26 | ({ __uncontained_list_entry_is_head(&((typeof(pos)) ((void*)pos))->member == (head));}) 27 | 28 | #define list_first_entry(ptr, type, member) \ 29 | ({ list_entry((ptr)->next, type, member);}) 30 | #define list_for_each_entry(pos, head, member) \ 31 | for (pos = list_first_entry(head, typeof(*pos), member); \ 32 | !list_entry_is_head(pos, head, member); \ 33 | pos = list_next_entry(pos, member)) 34 | 35 | #define list_for_each_entry_safe(pos, n, head, member) \ 36 | for (pos = list_first_entry(head, typeof(*pos), member);\ 37 | !list_entry_is_head(pos, head, member); \ 38 | pos = n = list_next_entry(pos, member), n = list_next_entry(n, member)) 39 | 40 | static __attribute_noinline__ int list_is_head(const struct list_head *list, const struct list_head *head) 41 | { 42 | return list == head; 43 | } 44 | 45 | #define list_for_each(pos, head) \ 46 | for (pos = (head)->next; \ 47 | !list_is_head(pos, (head)); \ 48 | pos = pos->next) 49 | 50 | static __attribute_noinline__ int list_empty(const struct list_head *head) 51 | { 52 | return head->next == head; 53 | } 54 | 55 | static inline void __list_del(struct list_head * prev, struct list_head * next) 56 | { 57 | next->prev = prev; 58 | prev->next = next; 59 | } 60 | static inline void __list_del_entry(struct list_head *entry) 61 | { 62 | __list_del(entry->prev, entry->next); 63 | } 64 | 65 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 66 | 67 | #define LIST_HEAD(name) \ 68 | struct list_head name = LIST_HEAD_INIT(name) 69 | 70 | struct S { 71 | struct list_head list; 72 | int i; 73 | }; 74 | 75 | static LIST_HEAD(glob_list); 76 | // fake variable that tracks list size 77 | static volatile int list_size = 1; 78 | static __attribute_noinline__ int func() 79 | { 80 | struct S *_op, *tmp; 81 | 82 | _op = list_entry(glob_list.next, struct S, list); 83 | return _op->i; 84 | } 85 | 86 | int main(int argc, char*argv[]) { 87 | if (list_size) 88 | return func(); 89 | else 90 | return 0; 91 | } 92 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry4.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | - name: list_entry_strict 4 | source: "__uncontained_list_entry_source" 5 | sink: ANY 6 | # ignore all the flows dominated by the sanitizers 7 | sanitizers: 8 | - { function_call: "list_empty" } 9 | - { function_call: "list_is_head" } 10 | - { function_call: "__uncontained_list_entry_is_head" } 11 | - { instruction: "cbr"} 12 | options: 13 | # do not need dominance for sanitizers, just reachability 14 | sanitize_reachable: true 15 | # filter out if the source is actually generated behind any sanitizer 16 | sanitize_source: true 17 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry_correlation01.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "list.h" 7 | 8 | /* list_entry is using the same struct type than preceeding list_add */ 9 | 10 | struct test { 11 | uint64_t other; 12 | struct list_head list; 13 | }; 14 | 15 | struct test1 { 16 | uint64_t other1; 17 | uint64_t other2; 18 | struct list_head list; 19 | }; 20 | 21 | struct test test_storage; 22 | 23 | static void __attribute_noinline__ func2(struct list_head *head) 24 | { 25 | struct test *test = list_entry(head, struct test, list); 26 | printf("test: %p\n", test); 27 | } 28 | 29 | static void __attribute_noinline__ func1() 30 | { 31 | struct list_head test_list; 32 | list_add(&test_storage.list, &test_list); 33 | func2(&test_list); 34 | } 35 | 36 | 37 | /* without this list_add does not have any arguments after optimizations :( */ 38 | struct list_head test_list_decoy; 39 | struct test test_decoy_storage; 40 | 41 | int main(int argc, char*argv[]) { 42 | func1(); 43 | 44 | // break optimizations 45 | list_add(&test_decoy_storage.list, &test_list_decoy); 46 | func2(&test_list_decoy); 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry_correlation02.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "list.h" 7 | 8 | /* list_add is using the same struct type than preceeding list_add */ 9 | 10 | struct test { 11 | uint64_t other; 12 | struct list_head list; 13 | }; 14 | 15 | struct test1 { 16 | uint64_t other1; 17 | uint64_t other2; 18 | struct list_head list; 19 | }; 20 | 21 | struct test test_storage; 22 | struct test1 test1_storage; 23 | 24 | static void __attribute_noinline__ func2(struct list_head *head) 25 | { 26 | list_add(&test_storage.list, head); 27 | } 28 | 29 | static void __attribute_noinline__ func1() 30 | { 31 | struct list_head test_list; 32 | list_add(&test_storage.list, &test_list); 33 | func2(&test_list); 34 | } 35 | 36 | 37 | /* without this list_add does not have any arguments after optimizations :( */ 38 | struct list_head test_list_decoy; 39 | struct test test_decoy_storage; 40 | 41 | int main(int argc, char*argv[]) { 42 | func1(); 43 | 44 | // break optimizations 45 | list_add(&test_decoy_storage.list, &test_list_decoy); 46 | func2(&test_list_decoy); 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry_correlation03.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "list.h" 7 | 8 | /* list_entry is using the same struct type than preceeding list_entry */ 9 | 10 | struct test { 11 | uint64_t other; 12 | struct list_head list; 13 | }; 14 | 15 | struct test1 { 16 | uint64_t other1; 17 | uint64_t other2; 18 | struct list_head list; 19 | }; 20 | 21 | struct test test_storage; 22 | struct test1 test1_storage; 23 | 24 | static void __attribute_noinline__ func3(struct list_head *head) 25 | { 26 | struct test1 *test = list_entry(head, struct test, list); 27 | printf("test: %p\n", test); 28 | } 29 | 30 | static void __attribute_noinline__ func2(struct list_head *head) 31 | { 32 | struct test1 *test = list_entry(head, struct test, list); 33 | printf("test: %p\n", test); 34 | func3(head); 35 | } 36 | 37 | static void __attribute_noinline__ func1() 38 | { 39 | struct list_head test_list; 40 | list_add(&test_storage.list, &test_list); 41 | func2(&test_list); 42 | } 43 | 44 | 45 | /* without this list_add does not have any arguments after optimizations :( */ 46 | struct list_head test_list_decoy; 47 | struct test test_decoy_storage; 48 | 49 | int main(int argc, char*argv[]) { 50 | func1(); 51 | 52 | // break optimizations 53 | list_add(&test_decoy_storage.list, &test_list_decoy); 54 | func2(&test_list_decoy); 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry_correlation04.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "list.h" 7 | 8 | /* list_add is using the same struct type than preceeding list_entry */ 9 | 10 | struct test { 11 | uint64_t other; 12 | struct list_head list; 13 | }; 14 | 15 | struct test1 { 16 | uint64_t other1; 17 | uint64_t other2; 18 | struct list_head list; 19 | }; 20 | 21 | struct test test_storage; 22 | struct test1 test1_storage; 23 | 24 | static void __attribute_noinline__ func3(struct list_head *head) 25 | { 26 | list_add(&test_storage.list, head); 27 | } 28 | 29 | static void __attribute_noinline__ func2(struct list_head *head) 30 | { 31 | struct test1 *test = list_entry(head, struct test, list); 32 | printf("test: %p\n", test); 33 | func3(head); 34 | } 35 | 36 | static void __attribute_noinline__ func1() 37 | { 38 | struct list_head test_list; 39 | list_add(&test_storage.list, &test_list); 40 | func2(&test_list); 41 | } 42 | 43 | 44 | /* without this list_add does not have any arguments after optimizations :( */ 45 | struct list_head test_list_decoy; 46 | struct test test_decoy_storage; 47 | 48 | int main(int argc, char*argv[]) { 49 | func1(); 50 | 51 | // break optimizations 52 | list_add(&test_decoy_storage.list, &test_list_decoy); 53 | func2(&test_list_decoy); 54 | return 0; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry_null.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct list_head { 6 | struct list_head *volatile next, *volatile prev; 7 | }; 8 | 9 | #define container_of(ptr, type, member) ({ \ 10 | void *__mptr = (void *)(ptr); \ 11 | ((type *)(__mptr - offsetof(type, member))); }) 12 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr); 13 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr) { 14 | return ptr; 15 | } 16 | #define list_entry(ptr, type, member)({ \ 17 | type* __tmp_ptr_out = container_of(ptr, type, member); \ 18 | (type*)__uncontained_list_entry_source((void*)__tmp_ptr_out);}) 19 | #define list_next_entry(pos, member) \ 20 | ({ list_entry((pos)->member.next, typeof(*(pos)), member);}) 21 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res); 22 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res) { 23 | return res; 24 | } 25 | #define list_entry_is_head(pos, head, member) \ 26 | ({ __uncontained_list_entry_is_head(&((typeof(pos)) ((void*)pos))->member == (head));}) 27 | 28 | #define list_first_entry(ptr, type, member) \ 29 | ({ list_entry((ptr)->next, type, member);}) 30 | #define list_for_each_entry(pos, head, member) \ 31 | for (pos = list_first_entry(head, typeof(*pos), member); \ 32 | !list_entry_is_head(pos, head, member); \ 33 | pos = list_next_entry(pos, member)) 34 | 35 | #define list_for_each_entry_safe(pos, n, head, member) \ 36 | for (pos = list_first_entry(head, typeof(*pos), member);\ 37 | !list_entry_is_head(pos, head, member); \ 38 | pos = n = list_next_entry(pos, member), n = list_next_entry(n, member)) 39 | 40 | int __attribute_noinline__ list_is_head(const struct list_head *list, const struct list_head *head) 41 | { 42 | return list == head; 43 | } 44 | 45 | #define list_for_each(pos, head) \ 46 | for (pos = (head)->next; \ 47 | !list_is_head(pos, (head)); \ 48 | pos = pos->next) 49 | 50 | #define list_first_entry_or_null(ptr, type, member) ({ \ 51 | struct list_head *head__ = (ptr); \ 52 | struct list_head *pos__ = (head__->next); \ 53 | !list_is_head(pos__, head__) ? list_entry(pos__, type, member) : NULL; \ 54 | }) 55 | 56 | static __attribute_noinline__ int list_empty(const struct list_head *head) 57 | { 58 | return head->next == head; 59 | } 60 | 61 | static inline void __list_del(struct list_head * prev, struct list_head * next) 62 | { 63 | next->prev = prev; 64 | prev->next = next; 65 | } 66 | static inline void __list_del_entry(struct list_head *entry) 67 | { 68 | __list_del(entry->prev, entry->next); 69 | } 70 | 71 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 72 | 73 | #define LIST_HEAD(name) \ 74 | struct list_head name = LIST_HEAD_INIT(name) 75 | 76 | struct S { 77 | struct list_head list; 78 | int i; 79 | }; 80 | 81 | static LIST_HEAD(glob_list); 82 | // fake variable that tracks list size 83 | static volatile int list_size = 1; 84 | static __attribute_noinline__ int func() 85 | { 86 | struct S *_op, *tmp; 87 | 88 | _op = list_first_entry_or_null(&glob_list, struct S, list); 89 | printf("%p\n", _op); 90 | if (_op != NULL) 91 | return _op->i; 92 | return 0; 93 | } 94 | 95 | int main(int argc, char*argv[]) { 96 | printf("%d\n", func()); 97 | return 0; 98 | } 99 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry_null.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | - name: list_entry_null 4 | source: "__uncontained_list_entry_source" 5 | sink: CMPNULL 6 | # ignore all the flows dominated by the sanitizers 7 | sanitizers: 8 | - { function_call: "list_is_last" } 9 | - { function_call: "list_is_first" } 10 | - { function_call: "list_is_head" } 11 | - { function_call: "list_empty" } 12 | - { function_call: "__uncontained_list_entry_is_head" } 13 | - { instruction: "load"} 14 | - { instruction: "phi", operand: 0 } # phi that merges the pointer with NULL 15 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry_null2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct list_head { 6 | struct list_head *volatile next, *volatile prev; 7 | }; 8 | 9 | #define container_of(ptr, type, member) ({ \ 10 | void *__mptr = (void *)(ptr); \ 11 | ((type *)(__mptr - offsetof(type, member))); }) 12 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr); 13 | void* __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_source(void* ptr) { 14 | return ptr; 15 | } 16 | #define list_entry(ptr, type, member)({ \ 17 | type* __tmp_ptr_out = container_of(ptr, type, member); \ 18 | (type*)__uncontained_list_entry_source((void*)__tmp_ptr_out);}) 19 | #define list_next_entry(pos, member) \ 20 | ({ list_entry((pos)->member.next, typeof(*(pos)), member);}) 21 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res); 22 | int __attribute__((weak)) __attribute_noinline__ __uncontained_list_entry_is_head(int res) { 23 | return res; 24 | } 25 | #define list_entry_is_head(pos, head, member) \ 26 | ({ __uncontained_list_entry_is_head(&((typeof(pos)) ((void*)pos))->member == (head));}) 27 | 28 | #define list_first_entry(ptr, type, member) \ 29 | ({ list_entry((ptr)->next, type, member);}) 30 | #define list_for_each_entry(pos, head, member) \ 31 | for (pos = list_first_entry(head, typeof(*pos), member); \ 32 | !list_entry_is_head(pos, head, member); \ 33 | pos = list_next_entry(pos, member)) 34 | 35 | #define list_for_each_entry_safe(pos, n, head, member) \ 36 | for (pos = list_first_entry(head, typeof(*pos), member);\ 37 | !list_entry_is_head(pos, head, member); \ 38 | pos = n = list_next_entry(pos, member), n = list_next_entry(n, member)) 39 | 40 | int __attribute_noinline__ list_is_head(const struct list_head *list, const struct list_head *head) 41 | { 42 | return list == head; 43 | } 44 | 45 | #define list_for_each(pos, head) \ 46 | for (pos = (head)->next; \ 47 | !list_is_head(pos, (head)); \ 48 | pos = pos->next) 49 | 50 | #define list_first_entry_or_null(ptr, type, member) ({ \ 51 | struct list_head *head__ = (ptr); \ 52 | struct list_head *pos__ = (head__->next); \ 53 | !list_is_head(pos__, head__) ? list_entry(pos__, type, member) : NULL; \ 54 | }) 55 | 56 | static __attribute_noinline__ int list_empty(const struct list_head *head) 57 | { 58 | return head->next == head; 59 | } 60 | 61 | static inline void __list_del(struct list_head * prev, struct list_head * next) 62 | { 63 | next->prev = prev; 64 | prev->next = next; 65 | } 66 | static inline void __list_del_entry(struct list_head *entry) 67 | { 68 | __list_del(entry->prev, entry->next); 69 | } 70 | 71 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 72 | 73 | #define LIST_HEAD(name) \ 74 | struct list_head name = LIST_HEAD_INIT(name) 75 | 76 | struct S { 77 | struct list_head list; 78 | int i; 79 | }; 80 | 81 | static LIST_HEAD(glob_list); 82 | // fake variable that tracks list size 83 | static volatile int list_size = 1; 84 | static __attribute_noinline__ int func() 85 | { 86 | struct S *_op, *tmp; 87 | 88 | if (list_empty(&glob_list)) return 0; 89 | 90 | _op = list_first_entry(&glob_list, struct S, list); 91 | printf("%p\n", _op); 92 | if (_op != NULL) 93 | return 1; 94 | return 0; 95 | } 96 | 97 | int main(int argc, char*argv[]) { 98 | printf("%d\n", func()); 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /tests/testDF/test_nohit_list_entry_null2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | - name: list_entry_null 4 | source: "__uncontained_list_entry_source" 5 | sink: CMPNULL 6 | # ignore all the flows dominated by the sanitizers 7 | sanitizers: 8 | - { function_call: "list_is_last", sanitize_source: true } 9 | - { function_call: "list_is_first", sanitize_source: true } 10 | - { function_call: "list_is_head", sanitize_source: true } 11 | - { function_call: "list_empty", sanitize_source: true } 12 | - { function_call: "__uncontained_list_entry_is_head", sanitize_source: true } 13 | - { instruction: "load"} 14 | - { instruction: "phi", operand: 0 } # phi that merges the pointer with NULL 15 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "out", 21 | "dist", 22 | "**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | Thumbs.db 4 | node_modules/ 5 | out/ 6 | .vs/ 7 | tsconfig.lsif.json 8 | *.lsif 9 | *.db 10 | .vscode 11 | !.vscode/launch.json -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | src/** 4 | .gitignore 5 | .yarnrc 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/.eslintrc.json 9 | **/*.map 10 | **/*.ts 11 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "dataflowviewer" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/README.md: -------------------------------------------------------------------------------- 1 | # Dataflow Viewer Visual Studio Code Extension 2 | 3 | This is an extension to look at the results from the SimpleDataflowChecker pass. 4 | The reports are returned in the YAML format and are by default located within the used linux repository. 5 | 6 | ## How to install 7 | 8 | First ensure you are running on a machine with vscode installed. 9 | Then install the extension: 10 | ``` 11 | code --install-extension dataflowviewer-0.0.1.vsix 12 | ``` 13 | or install it from within vscode by selecting `Install from VSIX...`. 14 | 15 | ## How to use 16 | 17 | * Open the `uncontained-linux` repository in vscode and make sure the `reports.yaml` is located in the root directory. 18 | * Click on "Parse Report File" and select the `reports.yaml`. 19 | * Bugs are sorted by the rule they generated and the total amount (grouped by source line) is shown next to it. 20 | * You can use the 'Demote' and 'FP' button to classify the bugs. 21 | * To save the analysis you can select "Export Analysis To File" from the menu and save it including the classification. 22 | * The exported YAML can be reloaded into the extension to restore the previous classification. 23 | 24 | ## How to build 25 | 26 | Run the following commands to rebuild the extension if you did changes: 27 | ``` 28 | npm install -g vsce 29 | vsce package 30 | ``` 31 | 32 | Now you can install the updated vsix file again. 33 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/assets/dark/dataflow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/assets/dark/show.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/assets/light/dataflow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/assets/light/show.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/dataflowviewer-0.0.1.vsix: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vusec/uncontained/d11e9008de929f894a73c8c9999214668e3bad09/vscode-extension/dataflowcheckerviewer/dataflowviewer-0.0.1.vsix -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dataflowviewer", 3 | "displayName": "DataflowViewer", 4 | "description": "", 5 | "version": "0.0.1", 6 | "engines": { 7 | "vscode": "^1.74.0" 8 | }, 9 | "categories": [ 10 | "Other" 11 | ], 12 | "activationEvents": [ 13 | "onView:dataflow_view" 14 | ], 15 | "main": "./out/extension.js", 16 | "contributes": { 17 | "viewsContainers": { 18 | "activitybar": [ 19 | { 20 | "id": "dataflow-view-container", 21 | "title": "Dataflow", 22 | "icon": "assets/dark/dataflow.svg" 23 | } 24 | ] 25 | }, 26 | "views": { 27 | "dataflow-view-container": [ 28 | { 29 | "id": "dataflow_view", 30 | "name": "Viewer", 31 | "contextualTitle": "Dataflow Viewer" 32 | } 33 | ] 34 | }, 35 | "viewsWelcome": [ 36 | { 37 | "view": "dataflow_view", 38 | "contents": "Parse a YAML report file to show dataflows.\n[Parse Report File](command:dataflow_view.parseReportFile)" 39 | } 40 | ], 41 | "commands": [ 42 | { 43 | "command": "dataflow_view.itemClicked", 44 | "title": "dataflow view item" 45 | }, 46 | { 47 | "command": "dataflow_view.parseReportFile", 48 | "title": "Parse Dataflow Report File" 49 | }, 50 | { 51 | "command": "dataflow_view.export", 52 | "title": "Export Analysis To File" 53 | }, 54 | { 55 | "command": "dataflow_view.deactivate", 56 | "title": "Reset Dataflow Viewer" 57 | }, 58 | { 59 | "command": "dataflow_view.showCache", 60 | "title": "Show All Demoted" 61 | }, 62 | { 63 | "command": "dataflow_view.resetCache", 64 | "title": "Reset Demoted" 65 | }, 66 | { 67 | "command": "dataflow_view.makeInactive", 68 | "title": "Demote", 69 | "icon": "$(debug-step-into)" 70 | }, 71 | { 72 | "command": "dataflow_view.makeFP", 73 | "title": "FP", 74 | "icon": "$(panel-close)" 75 | }, 76 | { 77 | "command": "dataflow_view.makeActive", 78 | "title": "Promote", 79 | "icon": "$(debug-step-out)" 80 | }, 81 | { 82 | "command": "dataflow_view.showReport", 83 | "title": "Show Report", 84 | "icon": "$(eye-watch)" 85 | } 86 | ], 87 | "menus": { 88 | "view/title": [ 89 | { 90 | "command": "dataflow_view.parseReportFile", 91 | "when": "view == dataflow_view" 92 | }, 93 | { 94 | "command": "dataflow_view.export", 95 | "when": "view == dataflow_view" 96 | }, 97 | { 98 | "command": "dataflow_view.deactivate", 99 | "when": "view == dataflow_view" 100 | }, 101 | { 102 | "command": "dataflow_view.showCache", 103 | "when": "view == dataflow_view" 104 | }, 105 | { 106 | "command": "dataflow_view.resetCache", 107 | "when": "view == dataflow_view" 108 | } 109 | ], 110 | "view/item/context": [ 111 | { 112 | "command": "dataflow_view.makeFP", 113 | "when": "view == dataflow_view && viewItem == source", 114 | "group": "inline" 115 | }, 116 | { 117 | "command": "dataflow_view.makeInactive", 118 | "when": "view == dataflow_view && viewItem == source", 119 | "group": "inline" 120 | }, 121 | { 122 | "command": "dataflow_view.makeActive", 123 | "when": "view == dataflow_view && viewItem == sourceInactive", 124 | "group": "inline" 125 | }, 126 | { 127 | "command": "dataflow_view.showReport", 128 | "when": "view == dataflow_view && viewItem == report", 129 | "group": "inline" 130 | }, 131 | { 132 | "command": "dataflow_view.showReport", 133 | "when": "view == dataflow_view && viewItem == reportInactive", 134 | "group": "inline" 135 | } 136 | ] 137 | } 138 | }, 139 | "scripts": { 140 | "vscode:prepublish": "npm run compile", 141 | "compile": "tsc -p ./", 142 | "watch": "tsc -watch -p ./", 143 | "pretest": "npm run compile && npm run lint", 144 | "lint": "eslint src --ext ts", 145 | "test": "node ./out/test/runTest.js" 146 | }, 147 | "devDependencies": { 148 | "@types/glob": "^8.0.0", 149 | "@types/js-yaml": "^4.0.5", 150 | "@types/mocha": "^10.0.1", 151 | "@types/node": "16.x", 152 | "@types/vscode": "^1.74.0", 153 | "@typescript-eslint/eslint-plugin": "^5.45.0", 154 | "@typescript-eslint/parser": "^5.45.0", 155 | "@vscode/test-electron": "^2.2.0", 156 | "eslint": "^8.28.0", 157 | "glob": "^8.0.3", 158 | "mocha": "^10.1.0", 159 | "typescript": "^4.9.3" 160 | }, 161 | "dependencies": { 162 | "js-yaml": "^4.1.0" 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/src/extension.ts: -------------------------------------------------------------------------------- 1 | // The module 'vscode' contains the VS Code extensibility API 2 | // Import the module and reference it with the alias vscode in your code below 3 | import * as vscode from 'vscode'; 4 | import { dataflow } from './tree_view'; 5 | 6 | import { SourceItem } from './items/source_item'; 7 | import { ReportItem } from './items/report_item'; 8 | 9 | // This method is called when your extension is activated 10 | // Your extension is activated the very first time the command is executed 11 | export function activate(context: vscode.ExtensionContext) { 12 | 13 | //create a local tree view and register it in vscode 14 | let tree = new dataflow.TreeView(context); 15 | vscode.window.registerTreeDataProvider('dataflow_view', tree); 16 | 17 | vscode.window.onDidChangeActiveTextEditor(() => { 18 | const editor = vscode.window.activeTextEditor; 19 | if (editor === undefined) { 20 | return; 21 | } 22 | const fileName = editor.document.fileName; 23 | tree.highlight(editor, fileName); 24 | }); 25 | 26 | context.subscriptions.push(vscode.commands.registerCommand('dataflow_view.parseReportFile', () => { 27 | tree.refresh(); 28 | })); 29 | context.subscriptions.push(vscode.commands.registerCommand('dataflow_view.deactivate', () => { 30 | tree.deactivate(); 31 | })); 32 | context.subscriptions.push(vscode.commands.registerCommand('dataflow_view.showReport', (report: ReportItem) => { 33 | tree.showReport(report); 34 | })); 35 | context.subscriptions.push(vscode.commands.registerCommand('dataflow_view.makeActive', (source: SourceItem) => { 36 | tree.makeActive(source); 37 | })); 38 | context.subscriptions.push(vscode.commands.registerCommand('dataflow_view.makeInactive', (source: SourceItem) => { 39 | tree.makeInactive(source); 40 | })); 41 | context.subscriptions.push(vscode.commands.registerCommand('dataflow_view.makeFP', (source: SourceItem) => { 42 | tree.makeFP(source); 43 | })); 44 | context.subscriptions.push(vscode.commands.registerCommand('dataflow_view.showCache', (report: ReportItem) => { 45 | tree.showInactive(); 46 | })); 47 | context.subscriptions.push(vscode.commands.registerCommand('dataflow_view.resetCache', (report: ReportItem) => { 48 | tree.clearInactive(); 49 | })); 50 | context.subscriptions.push(vscode.commands.registerCommand('dataflow_view.export', () => { 51 | tree.export(); 52 | })); 53 | 54 | } 55 | 56 | // This method is called when your extension is deactivated 57 | export function deactivate() { } 58 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/src/items/flow_item.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { ReportItem } from './report_item'; 3 | 4 | export class FlowItem extends vscode.TreeItem { 5 | readonly func: string; 6 | readonly file: string; 7 | readonly line: number; 8 | readonly report: ReportItem; 9 | 10 | constructor(report: ReportItem, func: string, file: string, line: number) { 11 | super(func, vscode.TreeItemCollapsibleState.None); 12 | this.report = report; 13 | this.func = func; 14 | this.file = file; 15 | this.line = line; 16 | this.description = file + ":" + line; 17 | this.tooltip = func + " at " + file + ":" + line; 18 | this.collapsibleState = vscode.TreeItemCollapsibleState.None; 19 | 20 | this.command = { command: 'dataflow_view.itemClicked', title : "view: " + func, arguments: [this] }; 21 | } 22 | 23 | public toString() { 24 | return this.description as string; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/src/items/report_item.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { Report } from '../tree_types'; 4 | import { FlowItem } from './flow_item'; 5 | import { SourceItem } from './source_item'; 6 | import { RuleItem } from './rule_item'; 7 | 8 | export class ReportItem extends vscode.TreeItem { 9 | // children represent branches, which are also items 10 | readonly rule: RuleItem; 11 | readonly source: SourceItem; 12 | 13 | public children: FlowItem[] = []; 14 | 15 | private labelStr: string; 16 | // keep track of the original YAML report, as parsing looses information 17 | // on inlines. 18 | public report: Report; 19 | private _toString: string = ""; 20 | private isActive: boolean = true; 21 | 22 | // add all members here, file and line we'll need later 23 | // the label represent the text which is displayed in the tree 24 | // and is passed to the base class 25 | constructor(rule: RuleItem, source: SourceItem, yamlReport: Report) { 26 | let label = yamlReport.flow[0]?.func + " -> " + 27 | yamlReport.flow[yamlReport.flow.length - 1]?.func; 28 | super(label, vscode.TreeItemCollapsibleState.None); 29 | this.rule = rule; 30 | this.report = yamlReport; 31 | this.source = source; 32 | this.description = ""; 33 | this.labelStr = label; 34 | this.collapsibleState = vscode.TreeItemCollapsibleState.None; 35 | } 36 | 37 | // a public method to add childs, and with additional branches 38 | // we want to make the item collabsible 39 | public addFlow(child: FlowItem) { 40 | this.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; 41 | this.children.push(child); 42 | if (this._toString.length) { 43 | this._toString += ","; 44 | } 45 | this._toString += child.toString(); 46 | } 47 | 48 | public toString() { 49 | return this._toString; 50 | } 51 | 52 | public activate() { 53 | if (this.isActive) { 54 | return; 55 | } 56 | // update labels 57 | this.label = this.description?.toString(); 58 | this.labelStr = this.description?.toString() || ""; 59 | this.isActive = true; 60 | this.report.type = ''; 61 | this.contextValue = "report"; 62 | } 63 | 64 | public deactivate(type: string) { 65 | if (!this.isActive) { 66 | return; 67 | } 68 | // update labels 69 | this.description = this.labelStr; 70 | this.label = "~"; 71 | this.labelStr = "~" + this.labelStr; 72 | this.isActive = false; 73 | this.report.type = type; 74 | } 75 | 76 | 77 | public export() { 78 | return this.report; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/src/items/rule_item.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { SourceItem } from './source_item'; 4 | 5 | export class RuleItem extends vscode.TreeItem { 6 | readonly rule: string; 7 | 8 | public nameToSource: Record = {}; 9 | 10 | // children represent branches, which are also items 11 | public children: SourceItem[] = []; 12 | 13 | constructor(rule: string) { 14 | super(rule, vscode.TreeItemCollapsibleState.None); 15 | this.rule = rule; 16 | this.collapsibleState = vscode.TreeItemCollapsibleState.None; 17 | } 18 | 19 | public addSource(child: SourceItem) { 20 | this.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; 21 | this.children.push(child); 22 | this.refreshTitle(); 23 | } 24 | 25 | public refreshTitle() { 26 | const disabled = this.children.filter((source) => source.labelStr.startsWith("~")); 27 | const tpCount = this.children.length - disabled.length; 28 | const fpCount = disabled.length; 29 | 30 | this.label = `${this.rule} (${this.children.length}, TP: ${tpCount}, FP: ${fpCount})`; 31 | } 32 | 33 | public sortSources() { 34 | const disabled = this.children.filter((source) => source.labelStr.startsWith("~")); 35 | const active = this.children.filter((source) => !source.labelStr.startsWith("~")); 36 | disabled.sort((a, b) => a.labelStr.localeCompare(b.labelStr)); 37 | active.sort((a, b) => a.labelStr.localeCompare(b.labelStr)); 38 | this.children = disabled.concat(active); 39 | this.refreshTitle(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/src/items/source_item.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | import { RuleItem } from './rule_item'; 4 | import { ReportItem } from './report_item'; 5 | 6 | export class SourceItem extends vscode.TreeItem { 7 | readonly source: string; 8 | 9 | public labelStr: string; 10 | public rule: RuleItem; 11 | 12 | private isActive: boolean = true; 13 | private _toString: string = ""; 14 | 15 | // children represent branches, which are also items 16 | public children: ReportItem[] = []; 17 | 18 | constructor(source: string, rule: RuleItem) { 19 | super(source, vscode.TreeItemCollapsibleState.None); 20 | this.source = source; 21 | this.rule = rule; 22 | this.collapsibleState = vscode.TreeItemCollapsibleState.None; 23 | this.labelStr = source; 24 | this.contextValue = "source"; 25 | } 26 | 27 | public addReport(child: ReportItem) { 28 | this.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; 29 | this.children.push(child); 30 | if (this._toString.length) { 31 | this._toString += ","; 32 | } 33 | this._toString += child.toString(); 34 | } 35 | 36 | public toString() { 37 | return this._toString; 38 | } 39 | 40 | public activate() { 41 | if (this.isActive) { 42 | return; 43 | } 44 | // update labels 45 | this.label = this.description?.toString(); 46 | this.labelStr = this.description?.toString() || ""; 47 | this.description = ""; 48 | this.contextValue = "source"; 49 | this.isActive = true; 50 | 51 | for (const child of this.children) { 52 | child.activate(); 53 | } 54 | } 55 | 56 | public deactivate() { 57 | if (!this.isActive) { 58 | return; 59 | } 60 | // update labels 61 | this.description = this.labelStr; 62 | this.label = "~"; 63 | this.labelStr = "~" + this.labelStr; 64 | this.contextValue = "sourceInactive"; 65 | this.isActive = false; 66 | 67 | for (const child of this.children) { 68 | child.deactivate('inactive'); 69 | } 70 | } 71 | 72 | public makeFP() { 73 | if (!this.isActive) { 74 | return; 75 | } 76 | // update labels 77 | this.description = this.labelStr; 78 | this.label = "~FP"; 79 | this.labelStr = "~FP" + this.labelStr; 80 | this.contextValue = "sourceInactive"; 81 | this.isActive = false; 82 | 83 | for (const child of this.children) { 84 | child.deactivate('FP'); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/src/tree_types.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | type Flow = { 3 | func: string; 4 | file: string; 5 | inlined_at: string; 6 | }; 7 | 8 | function flowsEqual(flow1: Flow, flow2: Flow): boolean { 9 | return flow1.func === flow2.func && 10 | flow1.file === flow2.file && 11 | flow1.inlined_at === flow2.inlined_at; 12 | } 13 | 14 | type Report = { 15 | rule: string; 16 | type: string; 17 | source: Flow; 18 | sink: Flow; 19 | flow: Array; 20 | }; 21 | 22 | type Reports = { 23 | reports: Array; 24 | }; 25 | 26 | export { Reports, Report, Flow, flowsEqual }; 27 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "outDir": "out", 6 | "lib": [ 7 | "ES2020" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vscode-extension/dataflowcheckerviewer/vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | ## Explore the API 25 | 26 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 27 | 28 | ## Run tests 29 | 30 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 31 | * Press `F5` to run the tests in a new window with your extension loaded. 32 | * See the output of the test result in the debug console. 33 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 34 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 35 | * You can create folders inside the `test` folder to structure your tests any way you want. 36 | 37 | ## Go further 38 | 39 | * [Follow UX guidelines](https://code.visualstudio.com/api/ux-guidelines/overview) to create extensions that seamlessly integrate with VS Code's native interface and patterns. 40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace. 42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 43 | --------------------------------------------------------------------------------