Thanks to visit codestin.com
Credit goes to github.com

Skip to content

mbrock/filnix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

filnix: Nix Packaging for Fil-C

Overview

filnix packages the Fil-C memory-safe C/C++ compiler for Nix.

Fil-C is a project by Filip Pizlo that implements memory-safe C/C++ using runtime capabilities and garbage collection. All pointers carry bounds metadata that is tracked via LLVM instrumentation, shadow memory, and a concurrent garbage collector. The system is fully compatible with existing C/C++ code without requiring unsafe escape hatches.

Technology

The Fil-C system consists of three main components:

  • FilPizlonator LLVM Pass: This component instruments LLVM IR to track capability metadata alongside all pointers.
  • InvisiCap Runtime: The runtime stores capabilities either in registers or shadow memory, and bounds-checks all memory accesses before they occur.
  • FUGC Collector: A concurrent garbage collector built on libpas that prevents use-after-free vulnerabilities by keeping freed objects alive until they are no longer referenced by any pointers.

The system prevents several classes of memory safety vulnerabilities, including use-after-free, out-of-bounds access, and type confusion. The performance overhead ranges from 1.5x to 4x on Linux/X86_64 systems.

Fil-C has been successfully used to run over 100 real-world programs, including OpenSSH, CPython, curl, SQLite, and Emacs. For a complete memory-safe userland implementation, see the Pizlix project.

Why Nix

Nix treats builds as pure functions. Every build must explicitly declare all its inputs, build in complete isolation, and ideally produce the same outputs when given the same inputs. The hash in a Nix store path like /nix/store/abc123-package is computed from the build inputs (dependencies, source code, build scripts), not from the actual build outputs. This means that if two builds use the same inputs, they get the same store path, even if the build process has some non-determinism that produces slightly different binaries. In other words, the store path hash doesn’t guarantee bit-for-bit reproducibility of the build products - it only guarantees that the same inputs were used. This is why different versions of the same package can coexist as separate store paths without conflicts.

For Fil-C specifically, using Nix provides several important benefits:

  • It enables reproducible compiler toolchain builds that can be shared and verified.
  • Package porting becomes straightforward using nixpkgs overrides.
  • All dependencies are managed automatically with proper isolation.
  • You don’t need any directory layout hacks to run mixed safe and unsafe binaries side-by-side. This repository’s packaging ensures Fil-C always uses the intended paths and won’t be confused by your system’s gcc or glibc, even if you’re running Nix on Ubuntu or another distro. (Note: Nix can be used on any Linux distribution and is officially packaged for Debian and Ubuntu.)

Binary caches (such as filc.cachix.org) are like any other binary distribution source - you’re downloading pre-built binaries from someone and trusting them. Nix verifies signatures to ensure the binaries came from the cache operator, but this is just authentication of the source, not verification of correctness. The store path hash is derived from inputs, not outputs, so it provides no cryptographic proof that the binaries match what those inputs should produce. Until an organization with a proper security team and infrastructure (ideally NixOS itself) takes responsibility for a binary cache, Nix’s derivation hashing is not a substitute for auditing your supply chain. If you need assurance about what you’re running, build locally from source.

NixOS: Selective Memory Safety

NixOS stores all packages in /nix/store/hash-package paths instead of traditional locations like /usr. This makes it trivial to run memory-safe and regular binaries side-by-side without any conflicts or special filesystem layouts.

This flake enables selective hardening of your system. You can replace security-critical components with Fil-C versions while keeping other components unchanged:

# flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    filnix.url = "github:mbrock/filnix";
  };

  outputs = { nixpkgs, filnix, ... }: {
    nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ({ pkgs, ... }:
          let
            filc = filnix.packages.${pkgs.system};
          in {
            environment.systemPackages = [
              filc.bash          # Memory-safe shell
              filc.coreutils     # Memory-safe core utilities
              pkgs.vim           # Regular editor
              pkgs.firefox       # Regular browser
            ];

            programs.bash.package = filc.bash;

            # Run SSH server with memory safety
            services.openssh = {
              enable = true;
              package = filc.openssh;
            };
          })
      ];
    };
  };
}

Each package has explicit store path dependencies. This means that filc.bash uses Fil-C’s modified libc while pkgs.vim uses the regular glibc. There’s no need for chroot, containers, or directory tricks - the packages coexist naturally through the Nix store.

This approach is particularly practical for hardening network-facing and privileged components like sshd, bash, and coreutils, while keeping non-critical software unchanged. The SSH server example is especially relevant because sshd runs as root, handles network input, and has had numerous memory safety vulnerabilities over the years. This provides an incremental migration path toward a fully memory-safe userland.

Repository Structure

The upstream fil-c repository is a development repository that uses shell-script builds. This repository instead packages the compiler and runtime as proper Nix derivations, providing several benefits:

  • Modular separation of the compiler from applications
  • Reproducible and cacheable builds
  • Full integration with the nixpkgs ecosystem

Quick Start

# Run memory-safe packages directly (no installation needed)
nix run github:mbrock/filnix#nethack
nix run github:mbrock/filnix#bash
nix run github:mbrock/filnix#kitty-doom

# Development shell with Fil-C compiler
nix develop github:mbrock/filnix

# Build specific package
nix build github:mbrock/filnix#wasm3

# Try CVE testing environment
nix develop github:mbrock/filnix#wasm3-cve-test

# Enable binary cache (optional, saves ~1-2 hours building LLVM)
cachix use filc

Hello World

# In dev shell
echo '#include <stdio.h>
int main() { printf("Hello from Fil-C!\n"); return 0; }' > hello.c

clang -o hello hello.c -g -O
./hello

Memory Safety Demo

echo '#include <stdlib.h>
#include <stdio.h>
int main() {
    int* ptr = malloc(sizeof(int));
    printf("oob memory = %d\n", ptr[10]);  // Out of bounds
    return 0;
}' > bad.c

clang -o bad bad.c -g -O
./bad

The expected output shows a trapped bounds violation with the source location where it occurred.

Architecture

Build Pipeline

The Fil-C toolchain is built in multiple stages:

  1. filc0 - Bootstrap LLVM/Clang with the FilPizlonator pass integrated
  2. filc1 - Add operating system headers to the bootstrap compiler
  3. filc2 - Add the yolo runtime (a minimally-modified glibc)
  4. libpizlo - Build the Fil-C runtime library and FUGC garbage collector
  5. libmojo - Build the memory-safe user glibc (heavily modified for safety)
  6. filc-libcxx - Build the C++ standard library
  7. filcc - Assemble the complete toolchain with all components

Two-Libc Architecture

Application Code
├─ libmojo (user glibc)     ← Applications link here
├─ libpizlo (Fil-C runtime) ← Memory safety + FUGC
├─ libyolo (yolo glibc)     ← Runtime uses this
└─ Linux kernel

Both libc implementations are required for a working system. Applications link against libmojo, which itself depends on libpizlo for memory safety features, which in turn uses libyolo for low-level system calls.

Packages

Core

The repository provides several core components:

  • filcc - The wrapped toolchain (this is what you should use for compilation)
  • filc0-7 - Individual build stages for bootstrapping
  • libpizlo - The Fil-C runtime library
  • libmojo - The memory-safe glibc implementation
  • filc-libcxx - The C++ standard library

Applications

Several applications have been ported and are available through ports.nix, including bash, lua, tmux, sqlite, wasm3, nethack, and nano. You can build any of them like this:

nix build .#wasm3
./result/bin/wasm3 --version

Ports

The ports.nix file is the main entry point for ported applications. It uses nixpkgs packages and applies Fil-C compilation via the fix function, which switches packages to use the Fil-C stdenv. Some packages require patches from the upstream fil-c repository, which are managed in ports/patches.nix.

Development

Default Shell

You can enter a development environment with all necessary tools:

nix develop

This provides the filcc toolchain along with cmake, ninja, gdb, valgrind, ripgrep, fd, and jq.

Package Introspection

You can inspect the details of any package using the query script:

./query-package.sh bash | jq .

This returns the function arguments, build inputs, configure flags, derivation structure, and other metadata.

Porting Packages

To port a new package to Fil-C, use the fix function:

mypackage = fix base.mypackage {
  deps = { inherit zlib openssl; };
  attrs = old: { doCheck = false; };
};

The fix function switches the package to use the Fil-C stdenv and applies any necessary overrides.

Debugging

Several environment variables control Fil-C’s runtime behavior for debugging:

  • FUGC_STW=1 - Forces stop-the-world garbage collection
  • FUGC_SCRIBBLE=1 FUGC_VERIFY=1 - Enables memory debugging and verification
  • FUGC_MIN_THRESHOLD=0 - Enables GC stress testing by collecting aggressively
  • FILC_DUMP_SETUP=1 - Dumps the environment setup to verify configuration

Common Issues

ABI incompatibility is a common issue when porting packages. Fil-C code cannot link with regular C code compiled by standard compilers. You need to port entire dependency chains to Fil-C.

For compilation, always use -g to get proper error messages. When using -g, you must also use -O (optimization) for the compiler to work correctly.

Examples

CVE Mitigation: wasm3

The wasm3 development shell demonstrates real CVE prevention in action:

nix develop .#wasm3-cve-test
wasm3 cve-2022-39974.wasm  # Out-of-bounds read - caught
wasm3 cve-2022-34529.wasm  # Integer overflow - caught

Both of these exploits work successfully in the normal wasm3 interpreter, but they are trapped and prevented by Fil-C’s memory safety. See wasm3-cves.md for more details about these specific vulnerabilities.

Building

Prerequisites

To build filnix, you need:

  • Nix with flakes enabled
  • A Linux/X86_64 system
  • Approximately 20GB of free disk space

Build

To get started:

git clone https://github.com/mbrock/filnix
cd filnix

nix build .#filcc      # Build the toolchain
nix build .#bash       # Build individual ported packages
nix build .#wasm3      # Build another ported package

Resources

Upstream

The upstream Fil-C project is located at:

Documentation

The following documentation is available for understanding Fil-C’s implementation:

Related

Related projects and technologies in the memory safety space:

  • CHERI - Hardware-based capability systems
  • AddressSanitizer - Dynamic analysis tool for finding memory bugs
  • MTE - ARM Memory Tagging Extension

License

This project uses the same licenses as upstream Fil-C:

  • Compiler: Apache 2.0
  • Runtime (libpas): BSD
  • C++ libraries: Apache 2.0
  • Glibc: LGPL

The upstream source is available at https://github.com/pizlonator/fil-c

Contributing

To file issues:

Roadmap

Current: Standalone Flake

This repository packages Fil-C as a standalone flake. Users reference it explicitly in their configurations:

filc = (builtins.getFlake "github:mbrock/filnix").packages.${pkgs.system};

This approach works today, but it requires a manual flake reference for each package you want to use.

Goal: nixpkgs Cross-Platform Integration

Nixpkgs supports cross-compilation targets via the pkgsCross attribute set, which provides alternative toolchains for building packages. For example, pkgsCross.musl.bash builds bash with musl libc, and pkgsCross.mingwW64.curl builds curl for Windows.

The integration plan consists of three main steps:

  1. Add Fil-C as a cross-compilation target to nixpkgs
    • This would be similar to how musl, uclibc, and mingw are integrated
    • It would define a stdenv that uses the Fil-C compiler and runtime
  2. Enable usage via =pkgsCross.filc=
    pkgs.pkgsCross.filc.bash       # Memory-safe bash
    pkgs.pkgsCross.filc.coreutils  # Memory-safe coreutils
    pkgs.pkgsCross.filc.openssh    # Memory-safe openssh
        
  3. Provide automatic access to 80,000+ packages
    • Any nixpkgs package would become available as pkgsCross.filc.package
    • No manual porting would be needed for packages that compile cleanly
    • Patches would live in the nixpkgs tree alongside other package patches

Benefits of Integration

Integration with nixpkgs would provide several significant benefits:

  • Official builds: Hydra CI would automatically build and test Fil-C packages
  • Binary cache: Pre-built binaries would be available from cache.nixos.org
  • Community maintenance: Package updates would be tracked with nixpkgs releases
  • Simple usage: No flake references needed, just use pkgsCross.filc.anything
  • Overlay compatibility: Full integration with existing nixpkgs infrastructure

After integration, system configuration would become much simpler:

{ pkgs, ... }: {
  environment.systemPackages = with pkgs.pkgsCross.filc; [
    bash coreutils openssh curl
  ];
}

Current Status

Fil-C is now fully packaged as a standalone flake. The ports.nix approach allows building any nixpkgs package with Fil-C by overriding the stdenv. Many packages work unmodified, while others require patches that are maintained in the ports/ directory. This includes nontrivial packages like tmux, sqlite, and nethack that work without requiring patches. However, there are still some random stumbling blocks that need to be fixed before everything builds reliably.

The immediate priority is setting up continuous integration infrastructure, possibly using Hydra, that continually verifies the building of all the core packages and catches regressions early.

Additional active work includes:

  • Resolving remaining build issues to get consistent builds across all packages
  • Documenting integration requirements for eventual nixpkgs maintainer review
  • Establishing binary caching infrastructure for faster builds
  • Verifying ABI stability across nixpkgs updates