PanicFI: An Infrastructure for Fixing Panic Bugs in Real-World Rust Programs
The Rust programming language has garnered significant attention due to its robust safety features and memory management capabilities. Despite its guaranteed memory safety, Rust programs suffer from runtime errors that are unmanageable, i.e., panic errors. Notably, traditional memory issues such as null pointer dereferences, which are prevalent in other languages, are less likely to be triggered in Rust due to its strict ownership rules. However, the unique nature of Rust's panic bugs, which arise from the language's stringent safety and ownership paradigms, presents a distinct challenge. Over half of the bugs in rustc, Rust's own compiler, are attributable to crashes stemming from panic errors. However, addressing Rust panic bugs is challenging and requires significant effort, as existing fix patterns are not directly applicable due to the design and feature of Rust language. Therefore, developing foundational infrastructure, including datasets, fixing patterns, and automated repair tools, is both critical and urgent. This paper introduces a comprehensive infrastructure, namely PanicFI , aimed at providing support for understanding Rust panic bugs and developing automated techniques. In PanicFI , we construct a dataset, Panic4R , comprising 102 real panic bugs and their fixes from the top 500 most downloaded open-source crates. Then, through an analysis of the Rust compiler implementation, we identify Rust-specific patterns for fixing panic bugs, providing insights and guidance for generating patches. Moreover, based on these patterns, we develop an automated fixing tool, namely PanicKiller , as an artifact, which has already contributed to the resolution of 28 panic bugs in open-source projects. The practicality and efficiency of PanicKiller confirm the effectiveness of the patterns mined within PanicFI . Furthermore, Panic4R serves as a benchmark for evaluating APR tools focused on Rust panic bugs. We believe the construction and release of PanicFI could enable the expansion of automated repair research tailored specifically to Rust programs, addressing unique challenges and contributing significantly to advancements in this field.
- Conference Article
35
- 10.1145/3144555.3144562
- Oct 28, 2017
System-level development has been dominated by traditional programming languages such as C and C++ for decades. These languages are inherently unsafe regarding memory management. Even experienced developers make mistakes that open up security holes or compromise the safety properties of software. The Rust programming language is targeted at the systems domain and aims to eliminate memory-related programming errors by enforcing a strict memory model at the language and compiler level. Unfortunately, these compile-time guarantees no longer hold when a Rust program is linked against a library written in unsafe C, which is commonly required for functionality where an implementation in Rust is not yet available.
- Research Article
39
- 10.1145/3586037
- Apr 6, 2023
- Proceedings of the ACM on Programming Languages
The Rust programming language provides a powerful type system that checks linearity and borrowing, allowing code to safely manipulate memory without garbage collection and making Rust ideal for developing low-level, high-assurance systems. For such systems, formal verification can be useful to prove functional correctness properties beyond type safety. This paper presents Verus, an SMT-based tool for formally verifying Rust programs. With Verus, programmers express proofs and specifications using the Rust language, allowing proofs to take advantage of Rust's linear types and borrow checking. We show how this allows proofs to manipulate linearly typed permissions that let Rust code safely manipulate memory, pointers, and concurrent resources. Verus organizes proofs and specifications using a novel mode system that distinguishes specifications, which are not checked for linearity and borrowing, from executable code and proofs, which are checked for linearity and borrowing. We formalize Verus' linearity, borrowing, and modes in a small lambda calculus, for which we prove type safety and termination of specifications and proofs. We demonstrate Verus on a series of examples, including pointer-manipulating code (an xor-based doubly linked list), code with interior mutability, and concurrent code.
- Conference Article
21
- 10.1145/3176258.3176330
- Mar 13, 2018
The Rust programming language has a safe memory model that promises to eliminate critical memory bugs. While the language is strong in doing so, its memory guarantees are lost when any unsafe blocks are used. Unsafe code is often needed to call library functions written in an unsafe language inside a Rust program. We present Fidelius Charm (FC), a system that protects a programmer-specified subset of data in memory from unauthorized access through vulnerable unsafe libraries. FC does this by limiting access to the program's memory while executing unsafe libraries. FC uses standard features of Rust and utilizes the Linux kernel as a trusted base for splitting the address space into a trusted privileged region under the control of functions written in Rust and a region available to unsafe external libraries. This paper presents our design and implementation of FC, presents two case studies for using FC in Rust TLS libraries, and reports on experiments showing its performance overhead is low for typical uses.
- Conference Article
101
- 10.1145/1985793.1985889
- May 21, 2011
This paper describes practical experience building and using pluggable type-checkers. A pluggable type-checker refines (strengthens) the built-in type system of a programming language. This permits programmers to detect and prevent, at compile time, defects that would otherwise have been manifested as run-time errors. The prevented defects may be generally applicable to all programs, such as null pointer dereferences. Or, an application-specific pluggable type system may be designed for a single application.
- Conference Article
55
- 10.1145/1273463.1273478
- Jul 9, 2007
This paper describes an analysis approach based on a of static and dynamic techniques to ?nd run-time errors in Java code. It uses symbolic execution to ?nd constraints under which an error (e.g. a null pointer dereference, array out of bounds access, or assertion violation) may occur and then solves these constraints to ?nd test inputs that may expose the error. It only alerts the user to the possibility of a real error when it detects the expected exception during a program run.The analysis is customizable in two important ways. First, we can adjust how deeply to follow calls from each top-level method. Second, we can adjust the path termination tion for the symbolic execution engine to be either a bound on the path condition length or a bound on the number of times each instruction can be revisited.We evaluated the tool on a set of benchmarks from the literature as well as a number of real-world systems that range in size from a few thousand to 50,000 lines of code. The tool discovered all known errors in the benchmarks (as well as some not previously known) and reported on average 8 errors per 1000 lines of code for the industrial examples. In both cases the interprocedural call depth played little role in the error detection. That is, an intraprocedural analysis seems adequate for the class of errors we detect.
- Dissertation
- 10.14232/phd.4138
- Aug 2, 2018
It is a big challenge in software engineering to produce huge, reliable and robust software systems. In industry, developers typically have to focus on solving problems quickly. The importance of code quality in time pressure is frequently secondary. However, software code quality is very important, because a too complex, hard-to-maintain code results in more bugs, and makes further development more expensive. The research work behind this thesis is inspired by the wish to develop high quality software systems in industry in a more effective and easier way to make the lives of customers and eventually end-users more comfortable and more effective. The thesis consists of two main topics: the utilization of symbolic execution for runtime error detection and the investigation of practical refactoring activity. Both topics address the area of program source code quality. Symbolic execution is a program analysis technique which explores the possible execution paths of a program by handling the inputs as unknown variables (symbolic variables). The main usages of symbolic execution are generating inputs of program failure and high-coverage test cases. It is able to expose defects that would be very difficult and timeconsuming to find through manual testing, and would be exponentially more costly to fix if they were not detected until runtime. In this work, we focus on runtime error detection (such as null pointer dereference, bad array indexing, division by zero, etc.) by discovering critical execution paths in Java programs. One of the greater challenges in symbolic execution is the very large number of possible execution paths, which increas exponentially. Our research proposes approaches to handling the aforementioned problem of path explosion by applying symbolic execution at the level of methods. We also investigated the limitations of this state space together with the development of efficient search heuristics. To make the detection of runtime errors more accurate, we propose a novel algorithm that keeps track of the conditions above symbolic variables during the analysis. Source code refactoring is a popular and powerful technique for improving the internal structure of software systems. The concept of refactoring was introduced by Martin Fowler. He originally proposed that detecting code smells should be the primary technique for identifying refactoring opportunities in the code. However, we lack empirical research results on how, when and why refactoring is used in everyday software development, what are its effects on short- and long-term maintainability and costs. By getting answers to these questions, we could understand how developers refactor code in practice, which would help propose new methods and tools for them that are aligned with their current habits leading to more effective software engineering methodologies in the industry. To help further empirical investigations of code refactoring, we proposed a publicly available refactoring dataset. The dataset consists of refactorings and source code metrics of open-source Java systems. We subjected the dataset to an analysis of the effects of code refactoring on source code metrics and maintainability, which are primary quality attributes in software development.
- Book Chapter
40
- 10.1007/978-3-030-01090-4_32
- Jan 1, 2018
Rust is an emerging systems programming language with guaranteed memory safety and modern language features that has been extensively adopted to build safety-critical software. However, there is currently a lack of automated software verifiers for Rust. In this work, we present our experience extending the SMACK verifier to enable its usage on Rust programs. We evaluate SMACK on a set of Rust programs to demonstrate a wide spectrum of language features it supports.
- Conference Article
- 10.1109/cisis.2016.57
- Jul 1, 2016
There are many illegal memory access (IMA) defects in C programs, for example, null pointer dereference, buffer overflow and array out of bounds. When C programs are running, these defects may cause software failure, and may lead to serious consequences. In order to fully detect these IMA defects, we apply abstract regions to simulate memory blocks that allocated to memory objects at the runtime, and use the region-based model to describe memory states of each program point based on the result of dataflow analysis. Based on the region-based model, the determining criterion of IMA defects is introduced for detecting these defects. The method has been implemented in the defect detection tool DTSC, and 5 open source projects are tested, the test results show the validity of our method.
- Conference Article
1
- 10.1145/2993717.2993722
- Sep 18, 2016
Active random testing is a powerful technique to find concurrency bugs through predicting the potential buggy inter-leaves. It helps improve the effectiveness of random testing such that the buggy scenarios are selected actively from trivial ones. However, applying active random testing to find null pointer dereference (NPD) still faces a strong challenge in that these NPDS are usually caused by the nontrivial data races, and therefore it is insufficient to adopt a general dynamic prediction approach to find them. In this paper, we propose a race-driven active random testing approach, RADIATE, to detect NPDs. The essential idea of RADIATE is to perform a race-driven prediction of the original trace for obtaining the potential NPD scenarios, and then use active random testing technique to actively control the thread schedules for exposing the real NPDs. We have implemented our RADIATE approach, and evaluated it over 7 benchmark programs. The evaluation results show that RADIATE can effectively find the indiscoverable NPDs.
- Research Article
4
- 10.1109/access.2021.3091209
- Jan 1, 2021
- IEEE Access
Null pointer dereference (NPD) is a widespread vulnerability that occurs whenever an executing program attempts to dereference a null pointer. NPD vulnerability can be exploited by hackers to maliciously crash a process to cause a denial of service or execute an arbitrary code under specific conditions. This typical taint-style vulnerability requires an accurate data dependency analysis to trace whether a source is propagated to a sensitive sink without proper sanitization. The primary challenge in data dependency analysis is pointer aliasing, which may significantly affect the vulnerability detection accuracy. Although there have been many studies and open-source tools, they still have limitations when detecting a real-world binary. In this paper, we propose a static binary analysis approach to detect an NPD vulnerability. To improve detection accuracy and practicality, we first identify two challenges that affect the accuracy of binary NPD detection: (i) pointer aliasing, and (ii) untrusted source identification. Then we implement a prototype of the proposed approach, NPDHunter, and evaluate it against 318 test cases provided by Juliet Test Suite v1.3. For the Juliet dataset, NPDHunter is accurate in detecting NPDs and generates 0% false negatives; as compared to bap-toolkit and cwe_checker, which have false-negative rates of 70.89% and 89.81%, respectively. We also evaluate NPDHunter for real-world binaries which recently reported NPD vulnerability. We have analyzed XNU kernel (large-scale), Redis, Bitlbee, libredwg, and libvncserver binaries and NPDHunter can detect all NPD cases, which justifies its usefulness for real-world binaries; compiled for x86_64 architecture.
- Conference Article
19
- 10.1145/3453483.3454084
- Jun 18, 2021
Rust’s type system ensures the safety of Rust programs; however, programmers can side-step some of the strict typing rules by using the unsafe keyword. A common use of unsafe Rust is by libraries. Bugs in these libraries undermine the safety of the entire Rust program. Therefore, it is crucial to thoroughly test library APIs to rule out bugs. Unfortunately, such testing relies on programmers to manually construct test cases, which is an inefficient and ineffective process.
- Research Article
1
- 10.1371/journal.pone.0312813
- Oct 31, 2024
- PloS one
Compartmentalization is vital for cell biological processes. The field of rule-based stochastic simulation has acknowledged this, and many tools and methods have capabilities for compartmentalization. However, mostly, this is limited to a static compartmental hierarchy and does not integrate compartmental changes. Integrating compartmental dynamics is challenging for the design of the modeling language and the simulation engine. The language should support a concise yet flexible modeling of compartmental dynamics. Our work is based on ML-Rules, a rule-based language for multi-level cell biological modeling that supports a wide variety of compartmental dynamics, whose syntax we slightly adapt. To develop an efficient simulation engine for compartmental dynamics, we combine specific data structures and new and existing algorithms and implement them in the Rust programming language. We evaluate the concept and implementation using two case studies from existing cell-biological models. The execution of these models outperforms previous simulations of ML-Rules by two orders of magnitude. Finally, we present a prototype of a WebAssembly-based implementation to allow for a low barrier of entry when exploring the language and associated models without the need for local installation.
- Book Chapter
- 10.1007/978-3-031-98685-7_18
- Jan 1, 2025
With the explosion in popularity of the Rust programming language, a wealth of tools have recently been developed to analyze, verify, and test Rust programs. Alas, the Rust ecosystem remains relatively young, meaning that every one of these tools has had to re-implement difficult, time-consuming machinery to interface with the Rust compiler and its cargo build system, to hook into the Rust compiler’s internal representation, and to expose an abstract syntax tree (AST) that is suitable for analysis rather than optimized for efficiency. We address this missing building block of the Rust ecosystem, and propose Charon, an analysis framework for Rust. Charon acts as a swiss-army knife for analyzing Rust programs, and deals with all of the tedium above, providing clients with an AST that can serve as the foundation of many analyses. We demonstrate the usefulness of Charon through a series of case studies, ranging from a Rust verification framework (Aeneas), a compiler from Rust to C (Eurydice), and a novel taint-checker for cryptographic code. To drive the point home, we also re-implement a popular existing analysis (Rudra), and show that it can be replicated by leveraging the Charon framework.
- Conference Article
2
- 10.1109/scam51674.2020.00025
- Sep 1, 2020
In programming, concurrency allows threads to share processing units interleaving and seemingly simultaneous to improve resource utilization and performance. Previous research has found that concurrency faults are hard to avoid, hard to find, often leading to undesired and unpredictable behavior. Further, with the growing availability of multi-core devices and adaptation of concurrency features in high-level languages, concurrency faults occur reportedly often, which is why countermeasures must be investigated to limit harm. Reactive programming provides an abstraction to simplify complex concurrent and asynchronous tasks through reactive language extensions such as the RxJava and Project Reactor libraries for Java. Still, blocking violations are possibly resulting in concurrency faults with no Java compiler warnings. BlockHound is a tool that detects incorrect blocking by wrapping the original code and intercepting blocking calls to provide appropriate runtime errors. In this study, we seek an understanding of how common blocking violations are and whether a tool such as BlockHound can give us insight into the root-causes to highlight them as pitfalls to developers. The investigated Softwares are Java-based open-source projects using reactive frameworks selected based on high star ratings and large fork quantities that indicate high adoption. We activated BlockHound in the project’s test-suites and analyzed log files for common patterns to reveal blocking violations in 7/29 investigated open-source projects with 5024 stars and 1437 forks. A small number of system calls could be identified as root-causes. We here present countermeasures that successfully removed the uncertainty of blocking violations. The code’s intentional logic was retained in all validated projects through passing unit-tests.
- Dissertation
- 10.26083/tuprints-00019286
- Jan 1, 2021
Whether applications or libraries, today’s software heavily reuses existing code to build more gigantic software faster. To ensure a smooth user experience for an application’s end-user and a reliable software library for the developer, the shipped piece of software should be as bug-free as possible. Besides manual or automatic software testing, static program analysis is one possible way to find unintended behavior. While static analysis tools can detect simple problems using pattern matching, advanced problems often require complex interprocedural control- and data-flow analyses, which, in turn, presume call graphs. For example, call graphs enable static analyses to track inputs over method boundaries to find SQL-injections or null pointer dereferences. The research community proposed many different call-graph algorithms with different precision and scalability properties. However, the following three aspects are often neglected. First, a comprehensive understanding of unsoundness sources, their relevance, and the capabilities of existing call-graph algorithms in this respect is missing. These sources of unsoundness can originate from programming language features and core APIs that impact call-graph construction, e.g., reflection, but are not (entirely) modeled by the call-graph algorithm. Without understanding the sources of unsoundness’ relevance and the frequency in which they occur, it is impossible to estimate their immediate effect on either the call graph or the analysis relying on it. Second, most call-graph research examines how to build call graphs for applications, neglecting to investigate the peculiarities of building call graphs for libraries. However, the use of libraries is ubiquitous in software development. Consequently, disregarding call-graph construction for libraries is unfortunate for both library users and developers, as it is crucial to ensure that their library behaves as intended regardless of its usage. Third call-graph algorithms, are traditionally organized in an imperative monolithic style, i.e., one super-analysis computes the whole graph. Such a design can hardly hold up to the task, as different programs and analysis problems require the support for different subsets of language features and APIs. Moreover, configuring the algorithm to one’s needs is not easy. For instance, adding, removing, and exchanging support for individual features to trade-off the call graph’s precision, scalability, and soundiness. To address the first aspect, we propose a method and a corresponding toolchain for both a) understanding sources of unsoundness and b) improving the soundness of call graphs. We use our approach to assess multiple call-graph algorithms from state-of- the-art static analysis frameworks. Furthermore, we study how these features occur in real-world applications and the effort to improve a call graph’s soundness. Regarding aspect two, we show that the current practice of using call-graph algorithms designed for applications to analyze libraries leads to call graphs that both a) lack relevant call edges and b) contain unnecessary edges. Ergo, motivating the need for call-graph construction algorithms dedicated to libraries. Unlike algorithms for applications, call-graph construction algorithms for libraries must consider the goals of subsequent analyses. Concretely, we show that it is essential to distinguish between the analysis’s usage scenario. Whereas an analysis searching for potentially exploitable vulnerabilities must be conservative, an analysis for general software quality attributes, e.g., dead methods or unused fields, can safely apply optimizations. Since building one call graph that fits all needs is nonsensical, we propose two concrete algorithms, each addressing one use case. Concerning the third aspect, we devise a generic approach for collaborative static analysis featuring modular analysis that are independently compilable, exchangeable, and extensible. In particular, we decouple mutually dependent analyses, enabling their isolated development. This approach facilitates highly configurable call-graph algorithms, allowing pluggable precision, scalability, and soundiness by either switching analysis modules for features and APIs on/off, or exchanging their implementations. By addressing these three aspects, we advance the state-of-the-art in call-graph construction in multiple dimensions. First, our systematic assessment of unsoundness sources and call-graph algorithms reveals import limitations with state-of-the-art. All frameworks lack support for many features frequently found in-the-wild and produce vastly different CGs, rendering comparisons of call-graph-based static analyses infeasible. Furthermore, we leave both developers and users of call graphs with suggestions that improve the entire situation. Second, our discussion concerning library call graphs raises the awareness of considering the analysis scenario and opens up a new facet in call-graph research. Third, by featuring modular call-graph algorithms we ease to design, implement, and test them. Additionally, it allows project-based configurations, enabling puggable precision, scalability, and sound(i)ness.
- Ask R Discovery
- Chat PDF
AI summaries and top papers from 250M+ research sources.