Vulnerability Research Using Fuzzing | CVE-2026-30451

Overview

During a course at OS3 focused on various vulnerabilities, me and a few students thought – “Why not try discovering a new vulnerability?” Thus, this lead us down a rabbit hole of research, and ultimately we discoved CVE-2026-30451. This post details our steps and methods used.

Note: This is more a crude technical write-up, a great introduction to this vulnerability can be found here.

Disclaimer

This vulnerability research is strictly aimed at research purposes. Vulnerability research allows researchers to discover new vulnerabilities, however, this also means that researchers should adhere to ethical considerations. As the discovered vulnerabilities could have real world security implications, its important they follow a responsible disclosure process, for them to not be misused. Refer to Coordinated vulnerability disclosure for more details.

Project Selection

Before we even started, we did some initial research. Finding vulnerabilities could either be done by looking at source code, or by fuzzing. Since our research was strict on time, we choose to use fuzzing as a means to find vulnerabilities in certain programs. We theorized, good targets would be projects implementing parsing, and written in languages that are not memory-safe.

Thus we looked for project that had the following criteria:

  • Written in C / C++
  • Parser implementations
  • Open-source

Using this criteria on GitHub, we found these projects:

This post will focus on mXparser, since we had the most promising results with this project.

Stage 1: Fuzzing Workspace

The next step was setting up a fuzzing environment. For the actual fuzzer, we decided to use AFL++. We drew inspiration from ASU, where they did similar vulnerability research as this fuzzer supported both ASAN (Address Sanitization) aswell as UBSAN (Undefined Beheviour Sanitization), which would be important.

AddressSanitizer (ASAN) and UndefinedBehaviorSanitizer (UBSAN) make fuzzing far more effective by turning silent memory corruption and undefined behavior into detectable crashes. ASAN catches issues like heap overflows, use-after-free bugs, and stack corruption, while UBSAN detects dangerous undefined behavior such as integer overflows, invalid shifts, and type confusion. Together, they increase bug visibility and improve crash reproducibility, allowing fuzzers to uncover vulnerabilities that would otherwise go unnoticed.

These next subchapters list the steps taken on a fresh VM, in order to prepare it for fuzzing.

1.1 System Preparation

Update and upgrade the system:

sudo apt update && sudo apt upgrade -y

Install required dependencies:

sudo apt install -y \
build-essential \
clang llvm lld llvm \
gcc g++ \
make cmake ninja-build git \
python3 python3-pip \
gdb lldb \
libtool automake \
pkg-config \
qemu-system-x86 \
curl wget \
libstdc++-12-dev

Installs all tools required for building and fuzzing:

  • git – clone source repositories
  • build-essential – GCC, make, and standard build tools
  • clang / llvm – required for AFL++ instrumentation
  • cmake / ninja-build – build system generators
  • python3 / pip – AFL++ utilities and scripts
  • wget / curl – download files if needed
  • libstdc++-12-dev – modern C++ standard library headers

1.2 Fuzzing Workspace Setup

Create direcory:

sudo mkdir /srv/Fuzzing

At this point, additional users / fuzzer groups can be added, and permissions set to this directory.

1.3 Core Dump Configuration

Configure the kernel to write core dumps as core files in the current directory:

echo core | sudo tee /proc/sys/kernel/core_pattern

This is essential for debugging crashes found by AFL++.

1.4 AFL++ Installation

Clone the AFL++ repository:

cd ~
git clone https://github.com/AFLplusplus/AFLplusplus.git
cd AFLplusplus

Build AFL++:

make -j$(nproc)
sudo make install

Verify installation:

afl-fuzz -h | head

Confirms AFL++ is installed and runnable.

1.5 Target Setup: mXparser

Create target directory:

mkdir /srv/Fuzzing/targets
cd /srv/Fuzzing/targets

Clone mXparser:

git clone https://github.com/mariuszgromada/MathParser.org-mXparser.git
cd MathParser.org-mXparser

1.6 AFL++ Instrumentation Environment

Set instrumentation environment variables:

export CC=afl-clang-fast
export CXX=afl-clang-fast++
export AFL_USE_ASAN=1
export AFL_USE_UBSAN=1
export AFL_LLVM_ALLOWLIST=$PWD/allowlist.txt

  • afl-clang-fast / afl-clang-fast++ – LLVM-based AFL instrumentation
  • ASAN – detects memory corruption
  • UBSAN – detects undefined behavior
  • ALLOWLIST – limits instrumentation to selected code paths

Verify environment:

printenv

Displays all environment variables to confirm configuration.

1.7 Instrumentation Allowlist

Add restrictions to allowlist.txt:

cat > allowlist.txt <<EOF
org/mariuszgromada/math/*
fuzz/*
EOF

Restricts AFL instrumentation to:

  • mXparser math logic
  • Custom fuzzing code

This improves performance and signal quality.

1.8 AFL++ Fuzzing Harness

Create harness directory:

mkdir -p fuzz

Create fuzzing harness from source:

nano fuzz/fuzz_expr.cpp

#include <iostream>
#include <string>
#include "org/mariuszgromada/math/mxparser/Expression.hpp"

int main() {

	// Confirm non-commercial use
	org::mariuszgromada::math::mxparser::License::iConfirmNonCommercialUse("John Doe");
	
	std::string input;
	std::getline(std::cin, input);
	
	org::mariuszgromada::math::mxparser::Expression e(input);
	e.calculate();
	
	return 0;
}


The purpose of this program:

  • Reads one line from stdin
  • Passes it to Expression
  • Triggers parsing and evaluation

This is the fuzzing entry point.

1.9 Build mXparser (Static Library)

Create build directory:

cd CURRENT/cpp
mkdir build
cd build

Configure with CMake:

cmake .. \
-DCMAKE_C_COMPILER=$CC \
-DCMAKE_CXX_COMPILER=$CXX \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DBUILD_SHARED_LIBS=OFF

  • Uses AFL-instrumented compilers
  • Includes debug symbols
  • Builds a static library (.a)

Compile:

make -j$(nproc)

This produces lib/libMathParser.org-mXparser.so

1.10 Building Fuzzing Binary

Change directory:

cd /srv/Fuzzing/targets/MathParser.org-mXparser

Build fuzzing binary:

afl-clang-fast++ \
-O1 \
-g \
-fsanitize=address,undefined \
-fno-omit-frame-pointer \
-I CURRENT/cpp/lib \
-DMXPARSER_BUILD_PROJECT_VERSION_MAJOR=5 \
-DMXPARSER_BUILD_PROJECT_VERSION_MINOR=0 \
-DMXPARSER_BUILD_PROJECT_VERSION_PATCH=0 \
fuzz/fuzz_expr.cpp \
-L CURRENT/cpp/build/lib \
-lMathParser.org-mXparser \
-Wl,-rpath,'$ORIGIN' \
-o fuzz_expr

Verify binary is working:

./fuzz_expr <<< "1+2*3"

Confirms correct parsing and execution.

1.11 Seed Corpus

Create input directory:

mkdir -p inputs

Create some initial expressions:

echo "1+1" > inputs/simple.txt
echo "sin(1)" > inputs/sin.txt
echo "2^8+sqrt(9)" > inputs/mixed.txt

Provides initial valid expressions for AFL++ mutation.

1.12 Running Fuzzer

Run fuzzer:

afl-fuzz \
-i inputs \
-o findings \
-- ./fuzz_expr

Fig. 1 – Image of scaled fuzzer master instance for refference

At this stage of our research, we had successfully set up a running fuzzer. However, we noticed that this fuzzer was only utilizing 25% of our CPU. We did some digging. Turns out, by default, a single instance of a AFL++ fuzzer is tied to 1 CPU core. Since our CPU had 4 cores, this meant that we were significantly underutilizing our CPU ponential.

1.13 Running AFL++ Parrallel Fuzzing

Hence, we decided to start scaling up. The idea was simple, we would set up multiple instances of the same fuzzer, where one was a “master” instance, that would control the other ones. Thus we stopped the current fuzzer with CTRL+C, and started implementing the changes.

Fig. 2. – Fuzzer scaling

Each fuzzer instance could be accessed via tmux sessions:

tmux new -s mathfuzz_main
tmux new -s mathfuzz_slave01
tmux new -s mathfuzz_slave02
tmux new -s mathfuzz_slave03

Create master instance (This instance will coordinate the fuzzing campaign.):

Fig. 3. Fuzzer tmux setup
afl-fuzz \
-i inputs \
-o findings \
-M master \
-- ./fuzz_expr

Within mathfuzz_slave01:

afl-fuzz \
-i inputs \
-o findings \
-S slave01 \
-- ./fuzz_expr

Within mathfuzz_slave02:

afl-fuzz \
-i inputs \
-o findings \
-S slave02 \
-- ./fuzz_expr

Within mathfuzz_slave03:

afl-fuzz \
-i inputs \
-o findings \
-S slave03 \
-- ./fuzz_expr

Each slave instance:

  • Shares coverage data
  • Explores different mutation strategies

At this stage of the research, we had a fuzzing setup, that was utilizing 100% of our CPU. Thus we left it for some time, for it to start finding crashes.

Fig. 4. – Fuzzer scaled

Stage 2: Vulnerability

2.1 Discovered Crashes

At this point, we had left the fuzzer running for a few days, and the fuzzer had been finding crashes during this time. At this point of our research, we began digging deeper into the crashes themselves, trying to find what inputs caused them.

Output fuzzing results:

cd /srv/Fuzzing/targets/MathParser.org-mXparser
afl-whatsup findings

Fig. 5. – AFL++ findings

Looking deeper into some of these crashes, we noticed a pattern: the input had been causing recursive loops, which made the program run out of memory and crash. This was likely caused by the program not correctly checking for recursion.

2.2 Understanding the Error

Usually, the library does keeps track of recursion, and it should return NaN when it reaches max recursion. In theory, this should prevent infinite recursion. This is visually showcased below.

Fig. 6. – Normal recursion

However, in this function, every new function call the state gets cloned. This includes the recursion counter. When one branch returns NaN, it continues to calculate all the other branches. Ultimately, this causes excessive memory usage, leading to the program crashing.

Fig. 7. – Exponential recursion

2.3 Affected Software Search

Thus, knowing that mXparser was vulnerable to such an error, we began looking for programs that might implement this as a library, where they would also be affected by this vulnerability. This was done by looking for the vulnerable function name within GitHub, to track down related projects.

One of the ones that stood out was a Minecraft plugin, called calcmod. What followed was setting up a server, and attempting to recreate the same crash. This plugin allows a unprivileged player to execute a command within in-game chat, in order to interact with the plugin. When crafted to include nested recursion, it causes the server to crash. This vulnerability effectively allowed any player to crash any server, which had this plugin installed. Being the case that this plugin had over 2+ million downloads, this had some significance, as individuals with malicious intent could cause denial of service to these servers.

Stage 3: Disclosure

What followed next, was contacting the library maintainer, as well as the plugin developer. We notified both them, and made them aware of the issue, as well as the specific function that causes the vulnerability. Both the library maintainer and plugin developer have confirmed patches.

MITRE CVE Submission

Our next step was making a CVE submission. Since neither of us had experience reporting CVEs, this was quite a confusing step for us. We found this blog post had useful information on the steps involved. Two main forms within MITRE that needed to be filled out were “Request CVE ID”, and “Notify CVE about a publication” after all parties confirmed patches.

It took quite a while to get a response from MITRE, but they confirmed to use CVE-2026-30451.

Conclusion

Ultimately, this research allowed us to discover CVE-2026-30451. We learned a great deal about program fuzzing, and how it can be used to discover new vulnerabilities. Even though something seems difficult, it might not always be the case.

CVE Details

Number of vulnerabilities reported or IDs requested (1-10): 1
Vulnerability type: Other or Unknown
Other vulnerability type: Algorithmic complexity vulnerability
Vendor of the product(s): Mariusz Gromada
Affected product(s)/code base:
mXparser library, versions 1.0 – 6.1 (Updated binary underway), issue fixed
CalcMod plugin, versions 1.1.0 – 1.4.3

Categories
Share
Related post
Continue reading