Summary
get_fieldparam() writes the field name and field value of every name=value[&…] pair into two fixed-size stack buffers in its caller (fieldname[10] and fieldval[128]), using strncpy lengths taken straight from the offsets of = and & in the attacker's input. There is no upper bound. A single request with a field name longer than 10 bytes or a value longer than 128 bytes overflows the caller's stack frame, including saved RBP and saved RIP.
2. Vulnerable code
src/lib/openjpip/query_parser.c — parse_query declares the stack destinations:
query_param_t * parse_query(const char *query_string)
{
query_param_t *query_param;
const char *pquery;
char fieldname[MAX_LENOFFIELDNAME], fieldval[MAX_LENOFFIELDVAL]; /* 10 and 128 */
...
while (pquery != NULL) {
pquery = get_fieldparam(pquery, fieldname, fieldval);
...
}
}
src/lib/openjpip/query_parser.c — get_fieldparam writes into them with attacker-controlled lengths:
char * get_fieldparam(const char *stringptr, char *fieldname, char *fieldval)
{
char *eqp, *andp, *nexfieldptr;
if ((eqp = strchr(stringptr, '=')) == NULL) { ... return NULL; }
if ((andp = strchr(stringptr, '&')) == NULL) {
andp = strchr(stringptr, '\0');
nexfieldptr = NULL;
} else {
nexfieldptr = andp + 1;
}
assert((size_t)(eqp - stringptr));
strncpy(fieldname, stringptr, (size_t)(eqp - stringptr)); /* (1) OOB write */
fieldname[eqp - stringptr] = '\0'; /* (2) OOB NUL */
assert(andp - eqp - 1 >= 0);
strncpy(fieldval, eqp + 1, (size_t)(andp - eqp - 1)); /* (3) OOB write */
fieldval[andp - eqp - 1] = '\0'; /* (4) OOB NUL */
return nexfieldptr;
}
(eqp - stringptr) is the number of bytes before the first = in the input. Fully attacker-controlled.
(andp - eqp - 1) is the number of bytes between = and the next & (or end of string). Fully attacker-controlled.
- Neither is clamped against
MAX_LENOFFIELDNAME / MAX_LENOFFIELDVAL.
- The two
[…] = '\0'; lines additionally write a NUL at an attacker-chosen stack offset.
- The two
assert(...) calls are compiled out in release builds (-DNDEBUG).
PoC attached :
./poc_openjpip_asan
[*] passing 200-byte field name to libopenjpip's parse_query()
[*] (this is the same code path opj_server takes for the HTTP QUERY_STRING)
=================================================================
==2218455==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe58ca2cca at pc 0x73cf3e45bd76 bp 0x7ffe58ca2c20 sp 0x7ffe58ca23c8
WRITE of size 200 at 0x7ffe58ca2cca thread T0
#0 0x73cf3e45bd75 in __interceptor_strncpy ../../../../src/libsanitizer/asan/asan_interceptors.cpp:485
#1 0x73cf3e3d87b0 in get_fieldparam /home/arash/Documents/vr/openjpeg/src/lib/openjpip/query_parser.c:229
#2 0x73cf3e3d783d in parse_query /home/arash/Documents/vr/openjpeg/src/lib/openjpip/query_parser.c:100
#3 0x5d245f2614e2 in main /home/arash/Documents/vr/openjpeg/poc/poc_openjpip_asan.c:67
#4 0x73cf3e029d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#5 0x73cf3e029e3f in __libc_start_main_impl ../csu/libc-start.c:392
#6 0x5d245f261224 in _start (/home/arash/Documents/vr/openjpeg/poc/poc_openjpip_asan+0x1224)
Address 0x7ffe58ca2cca is located in stack of thread T0 at offset 42 in frame
#0 0x73cf3e3d7776 in parse_query /home/arash/Documents/vr/openjpeg/src/lib/openjpip/query_parser.c:89
This frame has 2 object(s):
[32, 42) 'fieldname' (line 92)
[64, 192) 'fieldval' (line 92) <== Memory access at offset 42 partially underflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow ../../../../src/libsanitizer/asan/asan_interceptors.cpp:485 in __interceptor_strncpy
Shadow bytes around the buggy address:
0x10004b18c540: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10004b18c550: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10004b18c560: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10004b18c570: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10004b18c580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10004b18c590: 00 00 00 00 f1 f1 f1 f1 00[02]f2 f2 00 00 00 00
0x10004b18c5a0: 00 00 00 00 00 00 00 00 00 00 00 00 f3 f3 f3 f3
0x10004b18c5b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10004b18c5c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10004b18c5d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10004b18c5e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
Shadow gap: cc
==2218455==ABORTING
poc_openjpip_asan.zip
Summary
get_fieldparam()writes the field name and field value of everyname=value[&…]pair into two fixed-size stack buffers in its caller (fieldname[10]andfieldval[128]), usingstrncpylengths taken straight from the offsets of=and&in the attacker's input. There is no upper bound. A single request with a field name longer than 10 bytes or a value longer than 128 bytes overflows the caller's stack frame, including saved RBP and saved RIP.2. Vulnerable code
src/lib/openjpip/query_parser.c—parse_querydeclares the stack destinations:src/lib/openjpip/query_parser.c—get_fieldparamwrites into them with attacker-controlled lengths:(eqp - stringptr)is the number of bytes before the first=in the input. Fully attacker-controlled.(andp - eqp - 1)is the number of bytes between=and the next&(or end of string). Fully attacker-controlled.MAX_LENOFFIELDNAME/MAX_LENOFFIELDVAL.[…] = '\0';lines additionally write a NUL at an attacker-chosen stack offset.assert(...)calls are compiled out in release builds (-DNDEBUG).PoC attached :
poc_openjpip_asan.zip