Skip to content

Stack Buffer Overflow in OpenJPIP get_fieldparam() #1635

@Microsvuln

Description

@Microsvuln

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.cparse_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.cget_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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions