← Back to Research Vault

Breaking the Compilation Boundary: From Suspected Zero-Day to Common Lisp Trust Model

By Shay Mordechai | Published: June 5, 2026 (Research Conducted: July 31, 2025) | Category: Vulnerability Research, Compiler Internals
Status: Reported to CERT-IL (#11265) and MITRE for documentation and classification review. As of writing, no CVE has been recognized.
Introduction: The Collision of Theory and Reality
This research was born from the collision of two seemingly opposing disciplines I studied simultaneously: Reverse Engineering and Compiler Construction.

In RE, while performing binary patching with IDA Pro, I learned how to "break the rules"β€”for example, persisting a local variable across functions via CPU registers. Conversely, my compiler coursework emphasized the rigid theory of Lexical Scopes, which supposedly guarantee absolute variable isolation. This contradiction sparked my curiosity: Could these strict static rules be bypassed during the compilation phase itself?

Utilizing a custom AI-assisted semantic fuzzer, I set out to stress-test the trust boundaries of the SBCL compiler (LISP).

Core Concepts: SBCL & Lisp Architectural Glossary

To understand the mechanics of the isolated vectors, we must clarify the unique vocabulary of Lisp-based environments and runtime systems:

Lexical Variable
A variable whose visibility is bound to a strict physical layout in the source tree (such as a let block). Modern software engineers intuitively treat this as a solid security barrier during compile-time.
Macro
A Common Lisp macro is dynamic code executed by the compiler during compile-time. It evaluates inside the compiler's memory space, meaning it operates as fully privileged Trusted Code.
proclaim
A global administrative configuration function directed at the compiler.
intern
A function that takes a string and retrieves a direct pointer to its global, single-instance Symbol object in the system's core Symbol Table.
shadow
An isolation management feature for packages that forces the environment to override or hide an imported symbol, altering the compiler's default path routing.

Chapter 1: The Hypothesis & Mechanisms

Coming from stricter development environments like Java or C#, I intuitively assumed that a lexical variable provides an isolation boundary even at compile-time. However, my semantic fuzzer successfully isolated three mechanisms where macros could influence the compilation environment in ways that might appear as clear lexical isolation breaches:

1. Dynamic Binding Modification

Utilizing the proclaim directive within a macro to alter the target symbol's metadata flag.

[Step 1: Front-End] Read & Parse (AST Generation)
                      |
                      v
[Step 2: Lexical Analysis] Register "secret-token" as LOCAL in LEXENV
                      |
                      v
[Step 3: Macro Expansion] -> Runs macro modifying global state
                           * Evaluates: (proclaim '(special secret-token))
                      |
                      v
[Step 4: Code Generation]
                           * Compiler resolves the symbol according to
                             the updated compilation environment.
;; Vector 1 PoC
(defmacro isolation-bypass-attack (target new-value)
  `(progn
     (proclaim '(special ,target))
     (setf ,target ,new-value)))
Why It Looked Like a Vulnerability:
At first glance, it appears the macro successfully "hijacks" a variable that the compiler has already registered as a local lexical variable. For a developer coming from Java or C#, the very possibility that compile-time code alters the symbol resolution mechanism after the lexical analysis phase seems like a classic isolation breach. In reality, under Lisp's execution model, the macro operates as an integral part of the compilation process and is thus authorized to update the compilation environment.

2. Global Symbol Table Pollution

Using the intern function grants the macro a direct public pointer to the global symbol object representing the local variable, allowing property list (plist) mutation and data exfiltration.

;; Vector 2 PoC
(defmacro pollute-symbol-table (sym-name)
  (let ((global-sym (intern (string sym-name))))
    (setf (get global-sym 'compromised-leak) t)
    `(quote ,global-sym)))
Why It Looked Like a Vulnerability:
The ability to acquire a reference to a global Symbol object sharing the same name as a local variable might appear as exfiltrating data from a private Scope to a public space. Upon deeper inspection, the macro is not "breaking into" the lexical variable itself; it simply operates on the global symbol object defined by the Common Lisp data model.

3. Cross-Package Namespace Confusion

Using the shadow command during Macro Expansion allows altering the Package Namespace in a way that may contradict the developer's intuitive assumptions regarding the identity of the symbol being resolved at compile-time.

;; Vector 3 PoC
(defmacro hijack-namespace (target-sym)
  (let ((shadow-pkg (make-package "EPHEMERAL-ATTACK-PKG")))
    (shadow (string target-sym) shadow-pkg)
    nil))
Why It Looked Like a Vulnerability:
Swapping name resolution during compilation creates a state where the exact same name can reference different objects depending on the active namespace. To a developer unfamiliar with Lisp's Package system, this behavior might look like a local variable takeover, when in fact it is standard Namespace mechanics.

Chapter 2: Reality Check β€” Trust but Verify

At this stage, this looked like a severe CVE. This suspicion was heavily amplified when I discovered a series of urgent commits pushed to the SBCL codebase, implementing pseudo-atomic constraints around dynbind operations. It strongly resembled a classic Silent Patch.

At that point, I came across a post by veteran systems engineer Ira J. Perlow regarding compiler bugs, where he advised: "Trust but Test and Verify!". That quote gave me the drive to push past my initial suspicions. I decided to give the SBCL developers the benefit of the doubt, challenge my own bias, and investigate the anomaly to its absolute core.

After all, a security researcher is measured by their ability to falsify their own theories.
Taking my original PoC (initially tested on Windows OS), I conducted rigorous Differential Testing in a modern Linux environment against the latest release at the time (July 31, 2025), and again against the versions following the "suspicious" commits. The result was unequivocal: The PoC successfully bypassed the scope on both versions.

The Architectural Pivot: The behavior is an Architectural By-Design feature dictated by the ANSI Common Lisp Specification. The suspicious historical patches were actually engineered to resolve Thread Safety synchronization issues, not to construct a security sandbox. In other words, Lexical Scope in Common Lisp is a semantic mechanism for the program, but it does not constitute a security boundary against macro code executing at compile-time.

Chapter 3: Conclusion β€” When Lexical Scope Is Not a Security Boundary

From the perspective of a developer coming from languages with a more conservative compilation model, this behavior can create a false sense of security regarding isolation boundaries during the Build phase. Although the behavior conforms to the ANSI Common Lisp specification and does not constitute a bug in SBCL, it demonstrates how executing code at compile-time creates an attack surface that is generally overlooked in traditional security models.

Important Clarification: The PoCs in this research do not actually demonstrate stealing live API Keys. They demonstrate that a macro inherently possesses the capability to execute global modifications at compile-time. Since compilation code is implicitly trusted, this capability could theoretically be weaponized within a Supply Chain attack.

Supply Chain Poisoning Risk:
In modern CI/CD pipelines, a developer importing a seemingly benign third-party library could enable a poisoned macro to exfiltrate hardcoded local API keys during the build phase, leaving no trace in the resulting binary for Dynamic Application Security Testing (DAST) tools to detect.

This research did not expose an implementation flaw in SBCL, but rather a gap between the expectations developers bring from modern languages with conservative compilation models and the actual trust model of Common Lisp. For those coming from Java, C#, or Rust, the fact that a macro can execute code with global implications at compile-time is highly counterintuitive. The research value here lies not in proving a CVE, but in clarifying the exact boundary where Lexical Scope ceases to be an isolation mechanism and becomes merely a semantic concept.


πŸ”— Research Resources & Source Code

The complete empirical pipeline is fully open-sourced for educational and verification purposes. The repository contains the semantic fuzzer pipeline, dynamic exploration harnesses, and the audited upstream differentials:

πŸ“‚ Official GitHub Repository: AI-Augmented-SBCL-Vulnerability-Research