865 words
4 minutes
CVE-2017-9048: libxml2
2026-03-09
2026-03-10

CVE-2017-9048#

Description#

Compile#

Download#

Terminal window
git clone https://github.com/GNOME/libxml2.git && cd libxml2
git checkout v2.9.4

Build#

由于这个漏洞发生在 valid.cxmlSnprintfElementContent 中,所以我们可以禁用一些无关的东西来提高 fuzzing 效率。

Terminal window
./autogen.sh
AFL_USE_ASAN=1 \
CC=afl-clang-lto \
CXX=afl-clang-lto++ \
./configure \
--prefix="$(realpath ../libxml2-fuzz-asan)" \
--disable-shared \
--without-debug \
--without-ftp \
--without-http \
--without-legacy \
--without-python
make clean && \
AFL_USE_ASAN=1 make -j`nproc` && \
AFL_USE_ASAN=1 make install

每次耗时最长的就是「如何编译」……

看了一圈,报错原因全都是因为 invalid token at start of a preprocessor expression,而这个错误的产生又是因为 .in 文件中 @@ 占位多了一个空格,比如 @WITH_PUSH @,把那个空格去掉即可。

如果使用 grug-far.nvim,那可以用 @([^@\s]+)\s+@ 搜索,@$1@ 替换:

之后再次编译就没问题了。

说实话这个插桩数量确实有点吓人了,光插桩就用了十三分钟……所以一会儿我们必须多线程 fuzz 才行。

为了检查插桩是否成功,可以使用 ASAN_OPTIONS=help=1 ../libxml2-fuzz-asan/bin/xmllint,或者查看符号:nm ../libxml2-fuzz-asan/bin/xmllint | rg -i asan

由于 ASAN 会占用大量内存,并造成 2x - 10x 的减速,所以建议是只开一个 ASAN 线程用来找错,剩下线程都用普通插桩的版本来快速探路提高吞吐量。

更多信息可参考 Notes for using ASAN with afl-fuzz

再编译一个普通版:

Terminal window
CC=afl-clang-lto \
CXX=afl-clang-lto++ \
./configure \
--prefix="$(realpath ../libxml2-fuzz-lite)" \
--disable-shared \
--without-debug \
--without-ftp \
--without-http \
--without-legacy \
--without-python
make clean && \
make -j`nproc` && \
make install

Samples#

由于随机编译很难凑出一个正确的 xml tag, 所以我们可以给 fuzzer 加上字典,增加它撞到正确格式的概率。AFL++ 提供了一些常用字典:

至于 corpus, libxml2 自己的 test 目录下就有一些,然后我又自己写了两个 <!DOCTYPE a []><a b="c">d</a>

Fuzzing#

xmllint 有很多选项,理想情况是尽可能都组合上用一遍,以便让它能探索到更多路径。

我写了个自动起多线程 fuzz 的脚本,并在参数池中随便放了几个参数:

#!/bin/bash
MASTER_BIN="../libxml2-fuzz-asan/bin/xmllint"
SLAVE_BIN="../libxml2-fuzz-lite/bin/xmllint"
INPUT_CORPUS="corpus"
OUTPUT_DIR="outs"
SHM_BASE="/dev/shm/fuzz"
# Arguments for the Master instance
MASTER_ARGS="--debug --valid"
# Argument pool for Slave instances
SLAVE_ARGS_POOL=(
"--memory --oldxml10"
"--postvalid"
)
# --- Dictionary Support ---
DICT_PATH="./dict/xml.dict"
DICT_OPT=""
if [ -d "$DICT_PATH" ] || [ -f "$DICT_PATH" ]; then
DICT_OPT="-x $DICT_PATH"
fi
# --- Environment Check ---
if [ ! -f "$MASTER_BIN" ] || [ ! -f "$SLAVE_BIN" ]; then
echo "[-] Error: Fuzzing binaries not found. Check your paths."
exit 1
fi
TOTAL_THREADS=${1:-4}
if [ "$TOTAL_THREADS" -lt 1 ]; then
echo "Usage: $0 [total_threads]"
exit 1
fi
# --- Resume Logic ---
if [ -d "$OUTPUT_DIR/master" ]; then
echo "[*] Existing output detected. Resuming fuzzing session..."
INPUT_OPT="-i -"
else
echo "[*] First run. Using input corpus: $INPUT_CORPUS"
mkdir -p "$OUTPUT_DIR"
INPUT_OPT="-i $INPUT_CORPUS"
fi
mkdir -p "$SHM_BASE"
# --- Launch Master (ASAN) ---
echo "[+] Launching Master (ASAN) | Args: $MASTER_ARGS @@"
mkdir -p "$SHM_BASE/master"
AFL_TMPDIR="$SHM_BASE/master" \
afl-fuzz $INPUT_OPT \
-o "$OUTPUT_DIR" \
-m none \
$DICT_OPT \
-M master \
-- "$MASTER_BIN" $MASTER_ARGS @@ >"$OUTPUT_DIR/master.log" 2>&1 &
# Brief sleep to let Master initialize
sleep 2
# --- Launch Slaves (Non-ASAN) ---
NUM_VARIANTS=${#SLAVE_ARGS_POOL[@]}
for i in $(seq 1 $((TOTAL_THREADS - 1))); do
SLAVE_NAME="slave_$i"
ARG_INDEX=$(((i - 1) % NUM_VARIANTS))
CURRENT_ARGS=${SLAVE_ARGS_POOL[$ARG_INDEX]}
echo "[+] Launching $SLAVE_NAME | Args: $CURRENT_ARGS @@"
mkdir -p "$SHM_BASE/$SLAVE_NAME"
AFL_TMPDIR="$SHM_BASE/$SLAVE_NAME" \
afl-fuzz $INPUT_OPT \
-o "$OUTPUT_DIR" \
-m none \
$DICT_OPT \
-S "$SLAVE_NAME" \
-- "$SLAVE_BIN" $CURRENT_ARGS @@ >/dev/null 2>&1 &
done
echo "------------------------------------------------------"
echo "[!] Successfully started $TOTAL_THREADS instances with @@ input mode."
echo "[!] Check status: afl-whatsup $OUTPUT_DIR"
echo "[!] Stop all: pkill afl-fuzz"
echo "------------------------------------------------------"

搞明白怎么多线程 fuzz, 这个 chall 最关键的部分就算是完成了,至于跑出来的 crashes 中有没有我们期望的那个,要跑多久,已经意义不大了。所以,拜拜。

说着,滚床上干了一个小时论文调研,下来一看发现已经有不少 crashes 了。

哎呀这个 ASAN 又帅又好用,好喜欢 uwu

CVE-2017-9048: libxml2
https://cubeyond.net/posts/fuzz/libxml2-cve-2017-9048/
Author
CuB3y0nd
Published at
2026-03-09
License
CC BY-NC-SA 4.0