Control-Flow Integrity: Precision, Security, and PerformanceNATHAN BUROW and SCOTT A. CARR, Purdue UniversityJOSEPH NASH, PER LARSEN, and MICHAEL FRANZ, University of California, IrvineSTEFAN BRUNTHALER, Paderborn University & SBA ResearchMATHIAS PAYER, Purdue UniversityMemory corruption errors in C/C programs remain the most common source of security vulnerabilities intoday’s systems. Control-flow hijacking attacks exploit memory corruption vulnerabilities to divert programexecution away from the intended control flow. Researchers have spent more than a decade studying andrefining defenses based on Control-Flow Integrity (CFI); this technique is now integrated into several production compilers. However, so far, no study has systematically compared the various proposed CFI mechanismsnor is there any protocol on how to compare such mechanisms. We compare a broad range of CFI mechanismsusing a unified nomenclature based on (i) a qualitative discussion of the conceptual security guarantees, (ii) aquantitative security evaluation, and (iii) an empirical evaluation of their performance in the same test environment. For each mechanism, we evaluate (i) protected types of control-flow transfers and (ii) precision ofthe protection for forward and backward edges. For open-source, compiler-based implementations, we alsoevaluate (iii) generated equivalence classes and target sets and (iv) runtime performance.rCCS Concepts:Security and privacy Systems security; Software and application security;Information flow control;General and reference Surveys and overviews;rAdditional Key Words and Phrases: Control-flow integrity, control-flow hijacking, return-oriented programming, shadow stackACM Reference Format:Nathan Burow, Scott A. Carr, Joseph Nash, Per Larsen, Michael Franz, Stefan Brunthaler, and MathiasPayer. 2017. Control-flow integrity: Precision, security, and performance. ACM Comput. Surv. 50, 1, Article 16(April 2017), 33 pages.DOI: INTRODUCTIONSystems programming languages, such as C and C , give programmers a high degreeof freedom to optimize and control how their code uses available resources. While thisfacilitates the construction of highly efficient programs, requiring the programmer toThis material is based on work supported, in part, by the National Science Foundation under Grant Nos. CNS1464155, CNS-1513783, CNS-1657711, CNS-1513837, CNS-1619211, and IIP-1520552; and by the DefenseAdvanced Research Projects Agency (DARPA) under contracts FA8750-15-C-0124, FA8750-15-C-0085, andFA8750-10-C-0237; and by COMET K1 of the Austrian Research Promotion Agency (FFG); as well as giftsfrom Intel, Mozilla, Oracle, and Qualcomm. Any opinions, findings, and conclusions or recommendationsexpressed in this material are those of the authors and do not necessarily reflect the views of the DefenseAdvanced Research Projects Agency (DARPA), its Contracting Agents, the National Science Foundation, orany other agency of the U.S. Government.Authors’ addresses: N. Burow, S. A. Carr, and M. Payer, Purdue University, Department of Computer Science;emails: {burow, carr27, mpayer}; J. Nash, P. Larsen, and M. Franz, University of California–Irvine, Department of Computer Science; emails: {jmnash, perl, franz}; S. Brunthaler, PaderbornUniversity, Department of Computer Science; email: [email protected] to make digital or hard copies of part or all of this work for personal or classroom use is grantedwithout fee provided that copies are not made or distributed for profit or commercial advantage and thatcopies show this notice on the first page or initial screen of a display along with the full citation. Copyrights forcomponents of this work owned by others than ACM must be honored. Abstracting with credit is permitted.To copy otherwise, to republish, to post on servers, to redistribute to lists, or to use any component of thiswork in other works requires prior specific permission and/or a fee. Permissions may be requested fromPublications Dept., ACM, Inc., 2 Penn Plaza, Suite 701, New York, NY 10121-0701 USA, fax 1 (212)869-0481, or [email protected] 2017 ACM 0360-0300/2017/04-ART16 15.00 DOI: Computing Surveys, Vol. 50, No. 1, Article 16, Publication date: April 2017.

16:2N. Burow et al.manually manage memory and observe typing rules leads to security vulnerabilitiesin practice. Memory corruptions, such as buffer overflows, are routinely exploited byattackers. Despite significant research into exploit mitigations, very few of these mitigations have entered practice [Szekeres et al. 2013]. The combination of three suchdefenses, (i) Address Space Layout Randomization (ASLR) [PaX-Team 2003a], (ii) stackcanaries [van de Ven and Molnar 2004], and (iii) Data Execution Prevention (DEP)[Microsoft 2006] protects against code-injection attacks but are unable to fully preventcode-reuse attacks. Modern exploits use Return-Oriented Programming (ROP) or variants thereof to bypass currently deployed defenses and divert the control flow to amalicious payload. Common objectives of such payloads include arbitrary code execution, privilege escalation, and exfiltration of sensitive information.The goal of Control-Flow Integrity (CFI) [Abadi et al. 2005a] is to restrict the set ofpossible control-flow transfers to those that are strictly required for correct programexecution. This prevents code-reuse techniques such as ROP from working because theywould cause the program to execute control-flow transfers, which are illegal under CFI.Conceptually, most CFI mechanisms follow a two-phase process. An analysis phaseconstructs the Control-Flow Graph (CFG), which approximates the set of legitimatecontrol-flow transfers. This CFG is then used at runtime by an enforcement componentto ensure that all executed branches correspond to edges in the CFG.During the analysis phase, the CFG is computed by analyzing either the sourcecode or binary of a given program. In either case, the limitations of static programanalysis lead to an overapproximation of the control-flow transfers that can actuallytake place at runtime. This overapproximation limits the security of the enforced CFIpolicy because some nonessential edges are included in the CFG.The enforcement phase ensures that control-flow transfers that are potentially controlled by an attacker—that is, those whose targets are computed at runtime, such asindirect branches and return instructions—correspond to edges in the CFG producedby the analysis phase. These targets are commonly divided into forward edges, suchas indirect calls, and backward edges, like return instructions (so called because theyreturn control back to the calling function). All CFI mechanisms protect forward edges,but some do not handle backward edges. Assuming that code is static and immutable1 ,CFI can be enforced by instrumenting existing indirect control-flow transfers at compile time through a modified compiler, ahead of time through static binary rewriting, orduring execution through dynamic binary translation. The types of indirect transfersthat are subject to such validation and the number of valid targets per branch variesgreatly between different CFI defenses. These differences have a major impact on thesecurity and performance of the CFI mechanism.CFI does not seek to prevent memory corruption, which is the root cause of most vulnerabilities in C and C code. While mechanisms that enforce spatial [Nagarakatteet al. 2009] and temporal [Nagarakatte et al. 2010] memory safety eliminate memorycorruption (and thus control-flow hijacking attacks), existing mechanisms are considered prohibitively expensive. In contrast, CFI defenses offer reasonably low overheadswhile making it substantially harder for attackers to gain arbitrary code executionin vulnerable programs. Moreover, CFI requires few changes to existing source code,which allows complex software to be protected in a mostly automatic fashion. While theidea of restricting branch instructions based on target sets predates CFI [Kirianskyet al. 2002; Kiriansky 2013; PaX-Team 2003b], the seminal paper Abadi et al. [2005a]was the first formal description of CFI with an accompanying implementation. Since1 DEPmarks code pages as executable and readable by default. Programs may subsequently change permissions to make code pages writable using platform-specific APIs, such as mprotect. Mitigations such as PaXMPROTECT, SELinux [McCarty 2004], and the ProcessDynamicCodePolicy Windows API restrict how pagepermissions can be changed to prevent code injection and modification.ACM Computing Surveys, Vol. 50, No. 1, Article 16, Publication date: April 2017.

Control-Flow Integrity: Precision, Security, and Performance16:3this paper was published over a decade ago, the research community has proposed alarge number of variations of the original idea. More recently, CFI implementationshave been integrated into production-quality compilers, tools, and operating systems.Current CFI mechanisms can be compared along two major axes: performance andsecurity. In the scientific literature, performance overhead is usually measured throughthe SPEC CPU2006 benchmarks. Unfortunately, sometimes only a subset of the benchmarks is used for evaluation. To evaluate security, many authors have used the AverageIndirect target Reduction (AIR) [Zhang and Sekar 2013] metric, which counts the overall reduction of targets for any indirect control-flow transfer.Current evaluation techniques do not adequately distinguish among CFI mechanisms along these axes. Performance measurements are all in the same range, between 0% and 20% across different benchmarks, with only slight variations for thesame benchmark. Since the benchmarks are evaluated on different machines withdifferent compilers and software versions, these numbers are close to the margin ofmeasurement error. On the security axis, AIR is not a desirable metric for two reasons.First, all CFI mechanisms report similar AIR numbers (a 99% reduction of branchtargets), which makes AIR unfit to compare individual CFI mechanisms against eachother. Second, even a large reduction of targets often leaves enough targets for anattacker to achieve the desired goals [Göktas et al. 2014; Davi et al. 2014b; Carliniand Wagner 2014], making AIR unable to evaluate security of CFI mechanisms on anabsolute scale.We systematize the different CFI mechanisms (here, “mechanism” captures both theanalysis and enforcement aspects of an implementation) and compare them againstmetrics for security and performance. By introducing metrics for these areas, our analysis allows the objective comparison of different CFI mechanisms both on an absolutelevel and relatively against other mechanisms. This, in turn, allows potential usersto assess the trade-offs of individual CFI mechanisms and choose the one that is bestsuited to their use case. Further, our systematization provides a more meaningful wayto classify CFI mechanism than the ill-defined and inconsistently used “coarse”- and“fine”-grained classifications.To evaluate the security of CFI mechanisms, we follow a comprehensive approach,classifying them according to a qualitative and a quantitative analysis. In the qualitative security discussion, we compare the strengths of individual solutions on a conceptual level by evaluating the CFI policy of each mechanism along several axes:(i) precision in the forward direction, (ii) precision in the backward direction, (iii) supported control-flow transfer types according to the source programming language, and(iv) reported performance. In the quantitative evaluation, we measure the target setsgenerated by each CFI mechanism for the SPEC CPU2006 benchmarks. The precisionand security guarantees of a CFI mechanism depend on the precision of target sets usedat runtime, that is, across all control-flow transfers, how many superfluous targets arereachable through an individual control-flow transfer. We compute these target sets forall available CFI mechanisms and compare the ranked sizes of the sets against eachother. This methodology lets us compare the actual sets used for the integrity checks ofone mechanism against other mechanisms. In addition, we collect all indirect controlflow targets used for the individual SPEC CPU2006 benchmarks and use these sets asa lower bound on the set of required targets. We use this lower bound to compute howclose a mechanism is to an ideal CFI mechanism. An ideal CFI mechanism is one inwhich the enforced CFG’s edges exactly correspond to the executed branches.As a second metric, we evaluate the performance impact of open-sourced, compilerbased CFI mechanisms. In their corresponding publications, each mechanism was evaluated on different hardware, different libraries, and different operating systems, usingeither the full or a partial set of SPEC CPU2006 benchmarks. We cannot port allACM Computing Surveys, Vol. 50, No. 1, Article 16, Publication date: April 2017.

16:4N. Burow et al.evaluated CFI mechanisms to the same baseline compiler. Therefore, we measure theoverhead of each mechanism relative to the compiler it was integrated into. This applesto-apples comparison highlights which SPEC CPU2006 benchmarks are most usefulwhen evaluating CFI.The article is structured as follows. We first give a detailed background of the theoryunderlying the analysis phase of CFI mechanisms in Section 2. This allows us to thenqualitatively compare the different mechanisms on the precision of their analysisin Section 3. We then quantify this comparison with a novel metric. In Section 4,we present our performance results for the different implementations. Finally, inSection 5, we highlight best practices and future research directions for the CFI community identified during our evaluation of the different mechanisms, and present ourconclusions.Overall, we offer the following contributions:(1) a systematization of CFI mechanisms with a focus on discussing the major differentCFI mechanisms and their respective trade-offs,(2) a taxonomy for classifying the underlying analysis of a CFI mechanism,(3) presentation of both a qualitative and quantitative security metric and the evaluation of existing CFI mechanisms along these metrics, and(4) a detailed performance study of existing CFI mechanisms.2. FOUNDATIONAL CONCEPTSWe first introduce CFI and discuss the two components of most CFI mechanisms:(i) the analysis that defines the CFG (which inherently limits the precision that canbe achieved) and (ii) the runtime instrumentation that enforces the generated CFG.Second, we classify and systematize different types of control-flow transfers and howthey are used in programming languages. Finally, we briefly discuss the CFG precision achievable with different types of static analysis. For those interested, a morecomprehensive overview of static analysis techniques is available in Appendix A.2.1. Control-Flow IntegrityCFI is a policy that restricts the execution flow of a program at runtime to a predetermined CFG by validating indirect control-flow transfers. On the machine level,indirect control-flow transfers may target any executable address of mapped memory,but in the source language (C, C , or Objective-C) the targets are restricted to validlanguage constructs, such as functions, methods, and switch statement cases. Sincethe aforementioned languages rely on manual memory management, it is left to theprogrammer to ensure that noncontrol data accesses do not interfere with accesses tocontrol data such that programs execute legitimate control flows. Absent any securitypolicy, an attacker can therefore exploit memory corruption to redirect the control flowto an arbitrary memory location, which is called control-flow hijacking. CFI closes thegap between machine and source code semantics by restricting the allowed control-flowtransfers to a smaller set of target locations. This smaller set is determined per indirectcontrol-flow location. Note that languages providing complete memory and type safetygenerally do not need to be protected by CFI. However, many of these “safe” languagesrely on virtual machines and libraries written in C or C that will benefit from CFIprotection.Most CFI mechanisms determine the set of valid targets for each indirect controlflow transfer by computing the CFG of the program. The security guarantees of a CFImechanism depend on the precision of the CFG that it constructs. The CFG cannot beperfectly precise for nontrivial programs. Because the CFG is statically determined,there is always some overapproximation due to imprecision of the static analysis. AnACM Computing Surveys, Vol. 50, No. 1, Article 16, Publication date: April 2017.

Control-Flow Integrity: Precision, Security, and Performance16:5Fig. 1. Simplified example of overapproximation in static analysis.equivalence class is the set of valid targets for a given indirect control-flow transfer.Throughout the following, we reference Figure 1. Assuming an analysis based on function types or a flow-insensitive analysis, both foo() and bar() end up in the sameequivalence class. Thus, at line 12 and line 15, either function can be called. However,from the source code, we can tell that at line 12 only foo() should be called, and atline 15 only bar() should be called. While this specific problem can be addressed witha flow-sensitive analysis, all known static program analysis techniques are subject tosome overapproximation (see Appendix A).Once the CFI mechanism has computed an approximate CFG, it has to enforce itssecurity policy. We first note that CFI does not have to enforce constraints for controlflows due to direct branches because their targets are immune to memory corruptionthanks to DEP. Instead, it focuses on attacker-corruptible branches, such as indirectcalls, jumps, and returns. In particular, it must protect control-flow transfers that allowruntime-dependent targets such as void (*fptr)(int) in Figure 1. These targets arestored in either a register or a memory location depending on the compiler and theexact source code. The indirection that such targets provides allows flexibility as, forexample, the target of a function may depend on a call-back that is passed from anothermodule. Another example of indirect control-flow transfers is return instructions thatread the return address from the stack. Without such an indirection, a function wouldhave to explicitly enumerate all possible callers and check to which location to returnto based on an explicit comparison.For indirect call sites, the CFI enforcement component validates target addressesbefore they are used in an indirect control-flow transfer. This approach detects codepointers (including return addresses) that were modified by an attacker – if the attacker’s chosen target is not a member of the statically determined set.2.2. Classification of Control-Flow TransfersControl-flow transfers can broadly be separated into two categories: forward and backward. Forward control-flow transfers are those that move control to a new locationACM Computing Surveys, Vol. 50, No. 1, Article 16, Publication date: April 2017.

16:6N. Burow et al.inside a program. When a program returns control to a prior location, we call this abackward control flow2 .A CPU’s instruction-set architecture (ISA) usually offers two forward control-flowtransfer instructions: call and jump. Both of these are either direct or indirect, resultingin four different types of forward control flow:—Direct jump: A jump to a constant, statically determined target address. Most localcontrol flow, such as loops or if-then-else cascaded statements, use direct jumps tomanage control.—Direct call: A call to a constant, statically determined target address. Static functioncalls, for example, use direct call instructions.—Indirect jump: A jump to a computed, that is, dynamically determined target address. Examples for indirect jumps are switch-case statements using a dispatchtable, Procedure Linkage Tables (PLTs), and the threaded code interpreter dispatchoptimization [Bell 1973; Kogge 1982; Debaere and van Campenhout 1990].—Indirect call: A call to a computed, that is, dynamically determined target address.The following three examples are relevant in practice:Function pointers are often used to emulate object-oriented method dispatch inclassical record data structures, such as C structs, or for passing call-backs to otherfunctions.vtable dispatch is the preferred way to implement dynamic dispatch to C methods. A C object keeps a pointer to its vtable, a table containing pointers to allvirtual methods of its dynamic type. A method call, therefore, requires (i) dereferencing the vtable pointer, (ii) computing the table index using the method offsetdetermined by the object’s static type, and (iii) an indirect call instruction to thetable entry referenced in the previous step. In the presence of multiple inheritance,or multiple dispatch, dynamic dispatch is slightly more complicated.Smalltalk-style send-method dispatch that requires a dynamic type lookup. Such a dynamic dispatch using a send-method in Smalltalk, Objective-C, orJavaScript requires walking the class hierarchy (or the prototype chain in JavaScript)and selecting the first method with a matching identifier. This procedure is requiredfor all method calls and therefore impacts performance negatively. Note that, forexample, Objective-C uses a lookup cache to reduce the overhead.We note that jump instructions can also be either conditional or unconditional. Forthe purposes of this article, this distinction is irrelevant.All common ISAs support backward and forward indirect control-flow transfers. Forexample, the x86 ISA supports backward control-flow transfers using just one instruction: return, or just ret. A return instruction is the symmetric counterpart of a callinstruction, and a compiler emits function prologues and epilogues to form such pairs.A call instruction pushes the address of the immediately following instruction ontothe native machine stack. A return instruction pops the address off the native machinestack and updates the CPU’s instruction pointer to point to this address. Note that a return instruction is conceptually similar to an indirect jump instruction, since the returnaddress is unknown a priori. Furthermore, compilers are emitting call-return pairs byconvention that hardware usually does not enforce. By modifying return addresses onthe stack, an attacker can “return” to all addresses in a program, the foundation ofreturn-oriented programming [Shacham 2007; Checkoway et al. 2010; Roemer et al.2012].2 Notethe ambiguity of a backward edge in machine code (i.e., a backward jump to an earlier memorylocation), which is different from a backward control-flow transfer as used in CFI.ACM Computing Surveys, Vol. 50, No. 1, Article 16, Publication date: April 2017.

Control-Flow Integrity: Precision, Security, and Performance16:7Control-flow transfers can become more complicated in the presence of exceptions.Exception handling complicates control flows locally, that is, within a function, for example, by moving control from a try-block into a catch-block. Global exception-triggeredcontrol-flow manipulation, that is, interprocedural control flows, require unwindingstack frames on the current stack until a matching exception handler is found.Other control flow–related issues that CFI mechanisms should (but not always do)address are (i) separate compilation, (ii) dynamic linking, and (iii) compiling libraries.These present challenges because the entire CFG may not be known at compile time.This problem can be solved by relying on LTO or dynamically constructing the combined CFG. Finally, keep in mind that, in general, not all control-flow transfers can berecovered from a binary.Summing up, our classification scheme of control-flow transfers is as follows:—CF.1: backward control flow,—CF.2: forward control flow using direct jumps,—CF.3: forward control flow using direct calls,—CF.4: forward control flow using indirect jumps,—CF.5: forward control flow using indirect calls supporting function pointers,—CF.6: forward control flow using indirect calls supporting vtables,—CF.7: forward control flow using indirect calls supporting Smalltalk-style methoddispatch,—CF.8: complex control flow to support exception handling,—CF.9: control flow supporting language features, such as dynamic linking, separatecompilation, and so on.According to this classification, the C programming language uses control-flow transfers 1–5, 8 (for setjmp/longjmp) and 9, whereas the C programming language allowsall control-flow transfers except 7.2.3. Classification of Static Analysis PrecisionAs we saw in Section 2.1, the security guarantees of a CFI mechanism ultimatelydepend on the precision of the CFG that it computes. This precision is, in turn, determined by the type of static analysis used. For the purposes of this article, the followingclassification summarizes prior work to determine forward control-flow transfer analysis precision (see Appendix A for full details). In order of increasing static analysisprecision (SAP), our classifications are:—SAP.F.0: No forward branch validation—SAP.F.1a: ad-hoc algorithms and heuristics—SAP.F.1b: context- and flow-insensitive analysis—SAP.F.1c: labeling equivalence classes—SAP.F.2: class-hierarchy analysis—SAP.F.3: rapid-type analysis—SAP.F.4a: flow-sensitive analysis—SAP.F.4b: context-sensitive analysis—SAP.F.5: context- and flow-sensitive analysis—SAP.F.6: dynamic analysis (optimistic)The following classification summarizes prior work to determine backward controlflow transfer analysis precision:—SAP.B.0: No backward branch validation—SAP.B.1: Labeling equivalence classes—SAP.B.2: Shadow stackACM Computing Surveys, Vol. 50, No. 1, Article 16, Publication date: April 2017.

16:8N. Burow et al.Note that there is well-established and vast prior work in static analysis that goeswell beyond the scope of this article [Nielson et al. 2009]. The goal of our systematizationis merely to summarize the most relevant aspects and use them to shed more light onthe precision aspects of CFI.2.4. Nomenclature and TaxonomyPrior work on CFI usually classifies mechanisms into fine-grained and coarse-grainedmechanisms. Over time, however, these terms have been used to describe differentsystems with varying granularity and have, therefore, become overloaded and imprecise. In addition, prior work uses only a rough separation into forward and backwardcontrol-flow transfers without considering subtypes or precision. We hope that the classifications here will allow a more precise and consistent definition of the precision ofCFI mechanisms underlying analysis and will encourage the CFI community to usethe most precise techniques available from the static analysis literature.3. SECURITYIn this section, we present a security analysis of existing CFI implementations. Drawing on the foundational knowledge in Section 2, we present a qualitative analysis of thetheoretical security of different CFI mechanisms based on the policies that they implement. We then give a quantitative evaluation of a selection of CFI implementations.Finally, we survey previous security evaluations and known attacks against CFI.3.1. Qualitative Security GuaranteesOur qualitative analysis of prior work and proposed CFI implementations relies on theclassifications of Section 2 to provide a higher resolution view of precision and security. Figure 2 summarizes our findings among four dimensions based on the author’sreported results and analysis techniques. Figure 3 presents our verified results foropen-source LLVM-based implementations that we have selected. Further, it adds aquantitative argument based on our work in Section 3.2.In Figure 2, the axes and values were calculated as follows. Note that (i) the scaleof each axis varies based on the number of data points required and (ii) weaker/sloweralways scores lower and stronger/faster higher. Therefore, the area of the spider plotroughly estimates the security/precision of a given mechanism:—CF: Supported control-flow transfers, assigned based on our classification scheme inSection 2.2;—RP: Reported performance numbers. Performance is quantified on a scale of 1 to10 by taking the arctangent of reported runtime overhead and normalizing for highgranularity near the median overhead. An implementation with no overhead receivesa full score of 10, and one with about 35% or greater overhead receives a minimumscore of 1.—SAP.F: SAP of forward control flows, assigned based on our classification inSection 2.3; and—SAP.B: SAP of backward control flows, assigned based on our classification inSection 2.3.The shown CFI implementations are ordered chronologically by publication year,and the colors indicate whether a CFI implementation works on the binary level(blue), relies on source code (green), or uses other mechanisms (red), such as hardwareimplementations.Our classification and categorization efforts for reported performance were hinderedby methodological variances in benchmarking. Experiments were conducted on different machines, different operating systems, and different or incomplete benchmarkACM Computing Surveys, Vol. 50, No. 1, Article 16, Publication date: April 2017.

Control-Flow Integrity: Precision, Security, and Performance16:9Fig. 2. CFI implementation comparison: supported control flows (CF), reported performance (RP), staticanalysis precision: forward (SAP.F) and backward (SAP.B). Backward (SAP.B) is omitted for mechanismsthat do not support back edges. Color coding of CFI implementations: binary are blue, source-based aregreen, others red.suites. Classifying and categorizing SAP was impeded by the high-level, imprecisedescriptions of the implemented static analysis by various authors. Both of these impediments, natur

16 Control-Flow Integrity: Precision, Security, and Performance NATHAN BUROW and SCOTT A. CARR, Purdue University JOSEPH NASH, PER LARSEN, and MICHAEL FRANZ, University of California, Irvine STEFAN BRUNTHALER, Paderborn University & SBA Research MATHIAS PAYER, Purdue University Memory corruption errors in C/C programs remain the most common source of security vulnerabilities in