Source-linked AI summary
Debloating Software through Piece-Wise Compilation and Loading
Anh Quach, Aravind Prakash, Lok Kwong Yan
TL;DR
Programs carry substantial unused code because reusable and dynamically linked modules package functionality beyond each application's needs. The paper combines dependency-aware piece-wise compilation with load-time debloating to remove unused code. It reports large code and gadget reductions while preserving tested functionality and incurring low load-time overhead.
Problem
Reusable and dynamically linked software modules force applications to carry unused functionality, but conventional static elimination cannot remove dead code from shared libraries.
Method
The framework combines static dependency tracking with a backward-compatible piece-wise loader that dynamically loads and removes only code needed by each program.
Results
The toolchain eliminates over 79% of GNU coreutils code, reduces reusable gadgets, removes unused vulnerable code, and introduces low load-time overhead.
Takeaways & Limitations
Piece-wise debloating reduces the code and attack space that software defenses must analyze and protect, while retaining dynamic linking and tested functionality.
Takeaways & Limitations
Conventional static elimination remains unable to remove unused code from dynamically linked libraries, motivating the framework's load-time approach.
Abstract
from arXiv · showhide
Programs are bloated. Our study shows that only 5% of libc is used on average across the Ubuntu Desktop environment (2016 programs); the heaviest user, vlc media player, only needed 18%. In this paper: (1) We present a debloating framework built on a compiler toolchain that can successfully debloat programs (shared/static libraries and executables). Our solution can successfully compile and load most libraries on Ubuntu Desktop 16.04. (2) We demonstrate the elimination of over 79% of code from coreutils and 86% of code from SPEC CPU 2006 benchmark programs without affecting functionality. We show that even complex programs such as Firefox and curl can be debloated without a need to recompile. (3) We demonstrate the security impact of debloating by eliminating over 71% of reusable code gadgets from the coreutils suite and show that unused code that contains real-world vulnerabilities can also be successfully eliminated without adverse effects on the program. (4) We incur a low load time overhead.
1 Introduction
Software reuse leaves applications carrying unused library functionality, increasing bloat and attack surface. The paper introduces piece-wise compilation and loading to remove unused code while preserving compatibility, reducing code and security exposure with low load-time overhead.
- Motivation: Unused code broadens attack surface and burdens defenses such as CFI that do not distinguish used from unused features.Removing unused code can reduce the code that security defenses must analyze.
- Challenges: Static dead-code elimination cannot remove dead code from dynamically linked libraries, while static linking complicates updates and licensing.The paper therefore retains dynamic linking for practical, backward-compatible debloating.
- Approach: The framework combines compile-time dependency tracking with load-time elimination of functions that are not needed by a program.The compiler records dependencies in ELF metadata, and the piece-wise loader uses that information to load only required functions.
- Motivation: 95% of glibc code is never used on average across over 2016 Ubuntu Desktop programs.The study examined diverse applications and their shared-library usage.
- Evaluation: Over 79% of code is eliminated from GNU coreutils while passing its tests, with low load-time overhead.The toolchain also targets shared libraries, static libraries, executables, and unused vulnerable code.
2 Bloating
The Ubuntu study finds substantial unused functionality in shared libraries, including libc, because modules package broad and overlapping feature sets. Several design and compilation practices contribute to this bloat, and dynamic libraries remain difficult for conventional elimination techniques.
- Study: Only 10.22% of functions in the 15 most-used shared libraries are used on average.The study measured imported functions and recursively identified their intra-module dependencies.
- Study: 18% of libc code is used even by vlc, the least-bloated program in the study.The study also reports that libstdc++ uses 37.77% of its library and libgcc as little as 4%.
- Root Causes: Shared libraries package the union of functionality required by their users, including as many as 30 disjoint libc features.Examples include memory management, file I/O, and string manipulation.
- Root Causes: Weak aliasing leaves redundant weak implementations in memory when stronger definitions override them.glibc 2.19 contains 610 weak-symbol functions, including calloc; Firefox and mongodb provide custom memory-management implementations.
- Root Causes: Unused functions can remain after ordinary compiler optimization, and dynamically loaded libraries cannot eliminate them at compile time.Removing such functions requires additional compiler and linker options even in statically compiled code.
3 Overview
Piece-wise debloating bridges compile-time dependency knowledge and load-time code elimination to remove unused code from dynamically linked programs while preserving compatibility and correctness goals.
- 3 Overview: Programs depend on complex, late-bound library graphs whose function-level dependencies can be difficult to determine precisely.Challenges include cyclic modular dependencies, load-order-dependent symbol binding, code pointers, handwritten assembly, and dynamically loaded libraries.
- 3 Overview: Existing compile- and link-time optimizations do not remove bloat that persists in dynamically loaded shared modules.Fine-grained library fragmentation could increase internal fragmentation, relocation costs, library count, and incompatibility with existing binaries.
- 3 Overview: The framework combines a piece-wise compiler with a loader that builds intra- and inter-modular dependency information before eliminating unneeded code.The compiler records functionality-specific dependencies, while the loader constructs a full-program dependency graph and removes code outside it.
- 3 Overview: The design retains dynamic-library code reuse and static-linking dead-code elimination while allowing existing binaries to benefit without full recompilation.Metadata is stored in an optional ELF section that unmodified loaders ignore, preserving backward compatibility.
- 3 Overview: Correctness requires conservatively retaining every code fragment that runtime execution may need.The compiler therefore aims for function-level dependency graphs with zero false negatives.
4 Piece-wise Compilation
Piece-wise compilation constructs function-level dependency metadata across direct and indirect calls, using conservative analyses to retain possible targets while enabling more aggressive localized debloating.
- 4 Piece-wise Compilation: The compiler generates an annotated call graph and derives dependencies for each exported function from a complete module-wide call graph.This separates function-level dependency tracking from basic-block dead-code analysis.
- 4 Piece-wise Compilation: Indirect code references require handling function pointers, pointer tables, composite structures, callbacks, and virtual dispatch.The compiler classifies three indirect-reference categories and analyzes code-pointer uses to recover possible dependencies.
- 4 Piece-wise Compilation: External function-pointer targets remain available for reconciliation at load time because the compiler retains their symbol information.The implementation is intended to preserve this information across module boundaries.
- 4 Piece-wise Compilation: The compiler scans code-pointer references and records potential indirect targets as dependencies in the optional module metadata.Full-module scanning preserves all functions whose addresses are referenced, while localized scanning can unload targets not actually used by relevant functions.
- 4 Piece-wise Compilation: Localized scanning improves dependency-graph correctness and debloating aggressiveness by using symbol-binding and use-def information, at the cost of analysis performance.Use-def chains are recursively traversed to connect functions containing referring instructions with referenced functions.
- 4 Piece-wise Compilation: Pointer analysis supplies points-to constraints for resolving indirect code-pointer dependencies within libraries.The approach maintains points-to sets and uses solved constraints to extract concrete pointer values or value sets at reference points.
5 Piece-Wise Loader
The piece-wise loader preloads dependent libraries, resolves symbols, follows embedded dependency metadata, and removes unneeded functions while preserving ordinary-loader compatibility.
- 5 Piece-Wise Loader: The loader maps dependent libraries, performs relocation, and eliminates dead code from piece-wise-compiled libraries before user code executes.The workflow supports position-independent code and is designed for deployment in existing Linux ecosystems.
- 5.1 Pre-Loading Dependencies: The loader preloads all dependent shared libraries to obtain complete symbol information before execution.It recursively follows DT_NEEDED entries, maps dependency segments, and transfers control only after dependent code is loaded.
- 5.1 Pre-Loading Dependencies: Dynamically generated library names are handled through training that records libraries loaded by dlopen and functions invoked through dlsym.At load time, the recorded libraries are preloaded and only invoked functions are retained.
- 5.1 Pre-Loading Dependencies: Only 64/2226 (2.9%) studied programs dynamically compute module names, and observed computations were hard-coded or format-string based.The authors report that training common workloads reveals required shared-library dependencies.
- 5.2 Symbol Resolution & Relocation: Symbol resolution and the embedded .dep dependency graph identify imported functions and determine which definitions and dependencies must remain.Pre-binding follows ELF library order, and duplicate definitions not selected by resolution can be removed.
- 5.3 Removal of Dead Code: The prototype loads whole modules and removes unneeded functions, preserving offsets between functions and avoiding unnecessary code modifications.Functions outside direct or indirect dependencies are invalidated; fully unused pages are instead marked non-executable.
- 5.4 Compatibility and Overhead: Piece-wise modules and loaders remain backward compatible because ordinary loaders ignore the optional .dep section.A piece-wise loader without that section behaves like a regular loader.
- 5.4 Compatibility and Overhead: Partial page removal can incur copy-on-write memory overhead, especially when long-lived processes share large libraries or unused code spans many pages.Marking an entire page non-executable incurs no memory overhead, and the authors report that few pages typically require copy-on-write.
6 Evaluation
The evaluation tests correctness, compatibility, debloating effectiveness, and security-related reductions across libraries, coreutils, SPEC CPU2006, COTS binaries, and C++ code. The toolchain removes substantial code while preserving tested functionality, supports unmodified programs, and approaches—but does not match—the reduction of static linking.
- Correctness: All 109 coreutils programs passed their packaged test suite without errors.The experiments used piece-wise compiled musl-libc and evaluated correctness and performance.
- Musl-libc experiments: 79% and 78% debloating were achieved with localized code-pointer scanning and pointer analysis, respectively, compared with 58% for full-module scanning.For make-prime-list, localized scanning removed 91% of libc code without errors.
- SPEC CPU2006: 86% attack-space reduction was achieved in the best SPEC CPU2006 cases, while all programs passed their reference workload.The worst case reported 60% code reduction for full-module pointer handling.
- COTS binaries: Unmodified Firefox, curl, git, ssh, and LibreOffice programs ran normally with piece-wise compiled libraries, while curl achieved over 39.84% average bloat reduction.The loader also successfully loaded unmodified shared libraries, and glibc itself did not need to be piece-wise compiled for these COTS experiments.
- C++ libraries: 46.09% of libFLAC++ functions and 66.90% of its instructions were removed when used with Audacity.This experiment demonstrated support for C++ code and libraries.
- Comparison with static linking: Piece-wise dead-code elimination was comparable to, but less efficient than, static linking because of analysis accuracy and code retained for loading and removal.Static linking provides the upper bound; localized scanning removed the most code among the piece-wise approaches.
6.3 Performance Overhead
The evaluation measures compile-time and load-time costs separately. Compilation is usually modest except for points-to analysis on large libraries, while load-time overhead affects startup and increases under concurrent debloating.
- Compile-time overhead: Full-module scanning and localized scanning incur worst-case compile-time overhead below 800 ms.Points-to analysis is slower because of constraint solving and reached up to 4 minutes for libheimsqlite.so.
- Compile-time overhead: Points-to analysis has a greater-than-linear overhead increase with code size.The authors characterize its one-time cost as reasonable given the attack-space reduction it provides.
- Load-time overhead: Load-time overhead is confined to program startup because the modified loader removes unused shared-library code before control reaches the program.The loader adds no code to normal program execution; overhead comes from debloating logic and copy-on-write on modified code pages.
- Load-time overhead: 49 milliseconds of overhead per process was observed when all 106 coreutils programs ran concurrently.This measures the additional overhead caused by the piece-wise loader under concurrent debloating.
6.4 Attack Space Reduction
The security evaluation measures reductions in reusable gadgets and examines whether removing unused library code also removes reported vulnerabilities. Debloating reduced gadget availability and eliminated vulnerabilities in removed code, although exploitation was not tested.
- Gadget elimination: 71% of reusable gadgets were removed from musl-libc in the coreutils and SPEC CPU2006 evaluations.The measured gadget classes included syscall, SPU, COP, CS, JOP, and EP gadgets.
- Gadget elimination: The gadget analysis did not test exploitation, but the authors state that removing high-impact gadgets would in principle hamper return-to-libc and code-reuse exploits.Gadget reduction is presented as an estimate of reduced attack space rather than proof that all attacks are prevented.
- Vulnerability elimination: The study cross-referenced removed functions in tested shared libraries with reported CVEs to identify vulnerabilities eliminated by debloating.The paper also demonstrates this security benefit through a libcurl vulnerability example.
6.5 Case Study: CVE-2014-3707
The case study examines CVE-2014-3707 in libcurl and shows that debloating removes the vulnerable functions when libcurl is used by programs such as curl or cmake. As a result, the bug can no longer support the described memory-disclosure or denial-of-service attacks.
- Vulnerability: CVE-2014-3707 is an out-of-bound read in curl_easy_duphandle affecting libcurl versions 7.17.1 to 7.38.0.The vulnerability can enable memory disclosure and denial-of-service attacks.
- Debloating result: Debloating libcurl completely removes the affected functions when it is used with programs such as curl or cmake.The evaluation demonstrates this removal in the deployed program context.
- Security impact: The removed functions can no longer be exploited for memory disclosure or denial-of-service attacks through a return-to-libc payload.The result applies to the evaluated debloated configurations.
7 Related Work
Related work covers attack-space reduction, feature-based software customization, and pointer analysis. These lines of work address program defenses, customization, runtime overhead, and dependency precision through different mechanisms.
- Attack-Space Reduction Approaches: CFI and related defenses reduce or constrain attack paths through control-flow analysis and instrumentation.The surveyed approaches extract control-flow graphs and add checks to binaries using source, debugging, or binary information.
- Attack-Space Reduction Approaches: ASLR, binary stirring, and live re-randomization reduce exploit reuse by changing program or kernel-text locations across executions.The passage describes increasingly frequent or context-specific randomization strategies.
- Feature-based Software Customization: Feature-based customization treats debloating as either a software-engineering task or a runtime-management problem with memory and execution overhead.Managed languages can incur runtime overhead or bloat from execution-environment logic.
- Pointer Analysis: Pointer analysis determines pointer targets at compile time, balancing precision against scalability across flow, context, and heap-modeling dimensions.Flow-sensitive and context-sensitive analyses provide more precise information but involve different analysis costs and scopes.
8 Conclusion
The conclusion reports a study of 2016 Ubuntu Desktop programs and a prototype for piece-wise compilation and loading. The evaluation shows that libc can lose substantial code fragments, reducing attack space.
- 8 Conclusion: The study spans 2016 real-world programs on Ubuntu Desktop 16.04 and finds that most libc code is seldom used.This establishes broad unused-code prevalence across the examined program set.
- 8 Conclusion: The prototype performs piece-wise compilation and loading to debloat libc and remove significant code fragments from memory.The conclusion presents this as the implemented system’s central capability.
- 8 Conclusion: Removing code fragments from memory reduces the attack space.The conclusion directly links the debloating outcome to attack-space reduction.
A Appendix
The appendix references tables and a figure that organize functional dependencies and code-footprint measurements for libraries and features. These materials provide views of library use and feature-level footprint.
- A Appendix: Table 10 presents library-wise functional dependency information.It is described as a presentation of dependencies organized by library.
- A Appendix: Table 10 lists frequently used shared libraries and their function-level code utility.The table caption identifies both the library selection and the utility measure.
- A Appendix: Table 11 presents the functionality-size code footprint in musl, while Figure 2 presents musl’s code footprint by features.The two artifacts organize footprint information by functionality and feature.