The Linux Foundation Projects
Skip to main content

At Alpha-Omega, we are proud to support groundbreaking projects that advance software security and transparency. One such initiative is the Rust Foundation’s integration of the Capslock effort—a collaborative, cross-language project originally started by Google—to map and analyze source code behavior through capabilities and call graphs. Thanks to our grant, the Rust team has made impressive progress in adapting Capslock for the Rust ecosystem, enabling detailed insights into Rust libraries, their permissions, and their interactions across system calls and foreign function interfaces. We’re excited to share this important milestone as Rust continues to lead in building safer, more trustworthy software.

We encourage you to read the full blog from the Rust Foundation to explore how this innovative approach works under the hood and what it means for the future of secure software development.

CRustabilities: Capabilities, Rust and Capslock

Written By: Walter Pearce, Lead Security Engineer, The Rust Foundation

Introduction

In 2024, The Rust Foundation was invited to participate in a collaborative effort among ecosystems and languages to map the behavior of libraries, called Capslock. This effort, first started by Google for the Go language, is a best-effort to map source code actions, and flag anything matching a given behavior. Put simply, it tries to determine what a set of source code possibly wants to do. This has evolved into a concerted effort to emit this information among various programming languages – giving us a unified data format to describe the behavior and capabilities of source code. This would allow each of us (language ecosystems and repositories) to surface this information to end users; and is another tool for automated analysis of these repositories for malicious behavior. 

In 2025, Alpha-Omega has funded a project in the Rust Foundation to integrate this with the Rust programming language. We have started work in earnest to determine what capabilities for Rust could look like, making changes to Capslock to support that, and experimenting with the required tooling for Rust to emit call graphs and capabilities.  

What are capabilities?

Capabilities are a natural evolution of permissions from the Linux kernel, AppArmor, to the Android permission model as a more common example – where any given app, depending on its behavior, has capabilities (or permissions) – what it is allowed to do on a phone and which is then enforced by Android. Obviously, today Android’s model is pretty complete and enforced at the kernel level – and has proven effective over the years in both preventing attacks on devices and improving user trust (you can always see what permissions any given app requests and can deny them). 

Specific to Capslock, the first proof-of-concept by Google was to map the set of standard library functions in Go to a set of common capabilities, and then parsing source code to emit what the capabilities of any given code was. This comes with many caveats – most of which can, with effort, be resolved. 

The Capslock effort attempts to answer two questions about any given set of code: 

  1. What significant actions does it make?  i.e., what are its capabilities?
  2. What is the call graph to make that action? i.e., where is this capability exercised?

By themselves, having a list of capabilities (#1) for a library is still useful – I expect my base64 encoding library will never make network calls. I expect my math library to never access the filesystem. With a call graph (#2), however, we can begin to get much more granular information about a library and usage of it. 

Capslock gathers both of these sets of data, allowing us to answer a more significant question:  Where does this library access the filesystem, and does my usage of that library do so? Is a shell command being executed at build time, or is the filesystem accessed only in a debug logging function? A call graph lets us narrow down these cases, and lets us eliminate a lot of the noise which comes from this kind of static analysis, making it much more useful.

Rust Capabilities

In our effort to bring this to Rust, we wanted to address some of the caveats in the current Go implementation and explore the possibility of providing capabilities not only for Rust, but an initial mapping for other languages to use too. Under the hood, the Rust standard library (std) calls the libc crate; which calls the declared libc implementation (or windows library functions). On top of this, a significant portion of our ecosystem is built on various C libraries via FFI – an overlooked area if we were to simply map std functions to capabilities. Another consideration is that Rust is largely statically linked, Although not universal, this default gives us a significant advantage: in any given compiled Rust artifact, we have a much clearer picture of what not only the Rust source code does, but also its dependencies and FFI libraries. 

Our approach to capabilities, instead, is to map capabilities to system calls and then generate a call graph for the Rust source determining what system calls it makes. This indirectly also gives us a capabilities suite for libc, and transitively C. In the end, we gain some insight into capabilities across FFI boundaries. 

Call Graph Generation

The Rust Foundation has already done significant work in generating call graphs for Rust, under the project Painter. This project was initially aimed at creating an ecosystem-wide callgraph of the entirety of the crates.io ecosystem; a call graph of all versions of all crates of all time. This gave us insights into crate usage over time, how those crates were used, what common C libraries are used, and the ability to reduce vulnerability false positives by determining if code paths were actually used by a given Rust crate. 

The approach in Painter was to emit the LLVM bytecode for each crate and generate our call graphs at this level. In the context of Capslock, this means we had already created a method in generating call graphs for Rust at a level which lets us identify system calls. We plan to leverage this code in its own independent library, to allow end-users to generate call graphs for their local projects. 

The Approach

These efforts have coalesced into the following approach which we are taking to capabilities: 

  • Map system calls to capabilities 

Prior art already exists around system call capabilities via the Linux Kernel, AppArmor, and others who have already mapped system call behavior to capabilities. We simply leverage this mapping to then generate our own higher level sets.  This incidentally makes Capslock support C.

  1. Generate a capabilities map for the Rust libc crate from system call capabilities

Walking a call graph of this crate, we can then map Rust libc functions down to their system calls, thus giving us capabilities.

  1. Generate a capabilities map for the Rust Standard Library, from both system calls and the libc capabilities

Because Capslock allows us to define capabilities at a function level within a call graph, we decided to provide higher level capabilities definitions to short circuit the process and have a well-defined capabilities set for the Standard Library.  

  • Provide a Cargo subcommand to emit a Capslock ingestion file for a Rust project

Our efforts today mainly focus on this – for any given Rust project, we are working on a tool (cargo-cgsec) which can emit Capslock data for any compilable Rust project. 

 

This approach is being realized in two technical tools being developed by the Rust Foundation:

libpainter

Painter is being ported into a Rust library, which cargo-cgsec will consume, resulting in the generation of a low-level call graph of any Rust project in the capslock call graph format. This will be a complete call-graph of the Rust binary – including paths across FFI boundaries, any statically linked C libraries included.

cargo-cgsec

A cargo subcommand which instantiates libpainter, and then populates the call graph with capabilities based on the functions and system calls present.

Ours, like Go, comes with caveats as well; many of them the same as the Go implementation – but we feel that we have narrowed the gap due to our visibility past FFI boundaries.

  • Dynamic libraries are still opaque without manual capability definitions
  • Dynamic code execution can’t be analyzed, as we are not executing the call paths
  • We are currently only targeting Linux system calls; further work will be needed for other operating systems. 
  • False positives will still be present; no code analysis is done to determine the viability of call paths and thus will appear as a capability; we can only determine that code exists and has a valid call somewhere.
  • cargo-cgsec is locally building the call graph, so only the declared versions of dependencies are analyzed

The Future

We hope to release our work in the coming months, in collaboration with Capslock, to be one of the first additional languages to be supported by the tool. 

We would like to sincerely thank Alpha-Omega for their support of this work and all of the other work that is part of the Rust Foundation’s Security Initiative.