-
Notifications
You must be signed in to change notification settings - Fork 609
Description
Dear Maintainers,
We are a graduate student team working on applied software security analysis, and during our project on fuzz testing open-source libraries, we identified a vulnerability in libqrencode that we’d like to responsibly disclose.
Issue Summary:
While fuzzing libqrencode using a custom harness and AFL++, we encountered multiple heap-buffer-overflow crashes, all consistently pointing to the function:
memcpy(entry->data, data, (size_t)size); // in QRinput_List_newEntry
This is due to improper memory allocation for entry->data, especially when malformed or edge-case inputs are passed via the encoding API (QRcode_encodeString, QRcode_encodeData).
Fuzzing Details:
Fuzzer: AFL++
Harness: Custom C++ harness calling QRcode_encodeString and QRcode_encodeData based on mutated input.
Below is the harness code used:
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <qrencode.h>
#include <limits.h>
// Maximum size limit for payload to prevent slowdowns or heap overflows
#define MAX_PAYLOAD_SIZE 1024
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (!data || size < 6 || size > MAX_PAYLOAD_SIZE + 6) return 0;
// Extract last 6 bytes as fuzzing parameters
int version = data[size - 6] % 41; // 0–40
QRecLevel eclevel = (QRecLevel)(data[size - 5] % 4); // L, M, Q, H
QRencodeMode mode = (QRencodeMode)(data[size - 4] % 4); // MODE_8, MODE_KANJI, etc.
int case_sensitive = data[size - 3] % 2;
int use_string = data[size - 2] % 2;
int pad_null = data[size - 1] % 2;
// Actual data for input buffer
size_t payload_size = size - 6;
if (payload_size == 0) return 0;
char *buffer = (char *)malloc(payload_size + 1);
if (!buffer) return 0;
memcpy(buffer, data, payload_size);
buffer[payload_size] = pad_null ? '\0' : 'X'; // Ensure null-termination if needed
QRcode *qrcode = nullptr;
if (use_string) {
// Use string-based encoding
buffer[payload_size] = '\0'; // Just to be safe
qrcode = QRcode_encodeString(buffer, version, eclevel, mode, case_sensitive);
} else {
// Use binary data encoding
qrcode = QRcode_encodeData(payload_size, (unsigned char *)buffer, version, eclevel);
}
if (qrcode) {
QRcode_free(qrcode); // Free result to prevent memory leaks
}
free(buffer); // Clean up input buffer
return 0;
}
Below is stack trace observed:
./fuzz_qrencode out/default/crashes/id:000019,sig:06,src:000196,time:41604,execs:42884,op:havoc,rep:2
Running LLVMFuzzerInitialize ...
continue...
Reading 53 bytes from out/default/crashes/id:000019,sig:06,src:000196,time:41604,execs:42884,op:havoc,rep:2
=================================================================
==39702==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x604000000400 at pc 0x000103101984 bp 0x00016d42ea70 sp 0x00016d42e220
READ of size 3 at 0x604000000400 thread T0
#0 0x000103101980 in memcpy+0x480 (libclang_rt.asan_osx_dynamic.dylib:arm64+0x51980)
#1 0x0001029d9f30 in QRinput_List_newEntry+0xf8 (fuzz_qrencode:arm64+0x100009f30)
#2 0x0001029d9d3c in QRinput_append+0x48 (fuzz_qrencode:arm64+0x100009d3c)
#3 0x0001029e2fac in Split_eat8+0x3a8 (fuzz_qrencode:arm64+0x100012fac)
#4 0x0001029e220c in Split_splitString+0x1e0 (fuzz_qrencode:arm64+0x10001220c)
#5 0x0001029d7904 in QRcode_encodeStringReal+0x108 (fuzz_qrencode:arm64+0x100007904)
#6 0x0001029d6078 in LLVMFuzzerTestOneInput+0x238 (fuzz_qrencode:arm64+0x100006078)
#7 0x0001029d5dd0 in ExecuteFilesOnyByOne+0xfc (fuzz_qrencode:arm64+0x100005dd0)
#8 0x0001029d5c78 in LLVMFuzzerRunDriver+0x510 (fuzz_qrencode:arm64+0x100005c78)
#9 0x0001029d574c in main+0xc4 (fuzz_qrencode:arm64+0x10000574c)
#10 0x00018137c270 (<unknown module>)
#11 0xf313fffffffffffc (<unknown module>)
0x604000000400 is located 0 bytes after 48-byte region [0x6040000003d0,0x604000000400)
allocated by thread T0 here:
#0 0x0001031034f0 in malloc+0x70 (libclang_rt.asan_osx_dynamic.dylib:arm64+0x534f0)
#1 0x0001029d5f80 in LLVMFuzzerTestOneInput+0x140 (fuzz_qrencode:arm64+0x100005f80)
#2 0x0001029d5dd0 in ExecuteFilesOnyByOne+0xfc (fuzz_qrencode:arm64+0x100005dd0)
#3 0x0001029d5c78 in LLVMFuzzerRunDriver+0x510 (fuzz_qrencode:arm64+0x100005c78)
#4 0x0001029d574c in main+0xc4 (fuzz_qrencode:arm64+0x10000574c)
#5 0x00018137c270 (<unknown module>)
#6 0xf313fffffffffffc (<unknown module>)
SUMMARY: AddressSanitizer: heap-buffer-overflow (fuzz_qrencode:arm64+0x100009f30) in QRinput_List_newEntry+0xf8
Shadow bytes around the buggy address:
0x604000000180: fa fa 00 00 00 00 00 05 fa fa 00 00 00 00 00 00
0x604000000200: fa fa 00 00 00 00 00 05 fa fa 00 00 00 00 00 00
0x604000000280: fa fa 00 00 00 00 00 07 fa fa 00 00 00 00 00 00
0x604000000300: fa fa 00 00 00 00 00 00 fa fa 00 00 00 00 00 00
0x604000000380: fa fa 00 00 00 00 00 05 fa fa 00 00 00 00 00 00
=>0x604000000400:[fa]fa 00 00 00 00 00 fa fa fa fa fa fa fa fa fa
0x604000000480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x604000000500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x604000000580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x604000000600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x604000000680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==39702==ABORTING
zsh: abort ./fuzz_qrencode
Crashes reproducible with multiple mutated inputs of size ~21–53 bytes.
AddressSanitizer reports consistent out-of-bounds reads at QRinput_List_newEntry.
Sample Crash Input:
We can share sanitized crash inputs (e.g., [id:000018,sig:06,...]) and the full harness code upon request.
Recommendation:
We believe adding bounds checks or validating data size before memcpy, and ensuring proper allocation of entry->data, could mitigate this issue.
Please let us know if there’s a preferred channel for submitting PoCs or further technical details.
Thank you for your time and for maintaining this valuable open-source project.
Sincerely,
Pranav Madhav Bagepalli
University of Utah
u1471494@umail.utah.edu