CVE Wednesday - CVE-2022-46456

The vulnerability for today’s CVE Wednesday is CVE-2022-46456.

From NVD:

NASM v2.16 was discovered to contain a global buffer overflow in the component dbgdbg_typevalue at /output/outdbg.c

Additionally, NVD links to the NASM bugzilla for this CVE

Please note that I am not the original discoverer of this vulnerability. I merely saw the CVE in my CVE feed. All credit for discovery is due to the original researchers.

Background

NASM (Netwide-Assembler) is an assembler for Intel x86/64 assembly code. The idea is the user feeds NASM a file containing valid Intel x86/64 assembly code and NASM will output an executable file of that assembly code.

Threat Model and Impact

Upon first glance, it is unclear exactly what the impact is here. NASM is a standalone executable generally used by developers to create executable binaries from assembly code. So when we talk about NASM’s general usage, it generally used from a commandline, running as a userspace process that runs, dumps out a file, then exits. What does an attacker gain by executing code as the process which runs NASM (which at least in Linux is generally run as the same user)? I mean if you have the ability to submit assembly code and have it run through an assembler, why not just use the assembly code to specify your run time program instead of trying to build an exploit which runs in the NASM process space?

As I thought about this question, I came up with a few scenarios where this type of exploit might have more serious consequences. First, there are online assemblers, at least one of which seems to be backed by NASM. If someone was able to craft an exploit using this vulnerability and submit the exploit assembly code to one of these online services, it might be possible to gain code execution on the server which performs the assembly function. This effectively crosses the security boundary of the online assembly service which is meant to accept code as input but not execute code.

Root Cause Analysis

ASAN Output:

=================================================================
==17070==ERROR: AddressSanitizer: global-buffer-overflow on address 0x55598fd7b170 at pc 0x55598fc87761 bp 0x7ffe3cbb5800 sp 0x7ffe3cbb57f0
READ of size 8 at 0x55598fd7b170 thread T0
    #0 0x55598fc87760 in dbgdbg_typevalue output/outdbg.c:460
    #1 0x55598fc02bab in debug_set_db_type asm/assemble.c:1017
    #2 0x55598fc0377b in insn_size asm/assemble.c:1140
    #3 0x55598fbee764 in process_insn asm/nasm.c:1595
    #4 0x55598fbef208 in assemble_file asm/nasm.c:1734
    #5 0x55598fbea6fd in main asm/nasm.c:716
    #6 0x7f36524c7d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #7 0x7f36524c7e3f in __libc_start_main_impl ../csu/libc-start.c:392
    #8 0x55598fbe7cc4 in _start (/home/nick/Documents/nasm/nasm+0x12bcc4)

0x55598fd7b170 is located 16 bytes to the right of global variable 'types' defined in 'output/outdbg.c:411:27' (0x55598fd7b120) of size 64
0x55598fd7b170 is located 16 bytes to the left of global variable 'dbgdbg_pragma_list' defined in 'output/outdbg.c:524:37' (0x55598fd7b180) of size 32
SUMMARY: AddressSanitizer: global-buffer-overflow output/outdbg.c:460 in dbgdbg_typevalue
Shadow bytes around the buggy address:
  0x0aabb1fa75d0: 00 00 00 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
  0x0aabb1fa75e0: 00 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
  0x0aabb1fa75f0: 00 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
  0x0aabb1fa7600: 00 f9 f9 f9 f9 f9 f9 f9 00 00 00 00 00 f9 f9 f9
  0x0aabb1fa7610: f9 f9 f9 f9 00 00 f9 f9 f9 f9 f9 f9 00 00 f9 f9
=>0x0aabb1fa7620: f9 f9 f9 f9 00 00 00 00 00 00 00 00 f9 f9[f9]f9
  0x0aabb1fa7630: 00 00 00 00 f9 f9 f9 f9 00 00 00 00 00 00 00 00
  0x0aabb1fa7640: 00 00 00 00 00 f9 f9 f9 f9 f9 f9 f9 00 00 00 00
  0x0aabb1fa7650: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0aabb1fa7660: 00 00 00 00 00 00 00 00 00 00 00 00 f9 f9 f9 f9
  0x0aabb1fa7670: 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
==17070==ABORTING

Based on the ASAN output, it seems as though the vulnerability exists within the function dbgdbg_typevalue. This function is used as part of the process which generates debug output to correspond to a given assembly code file. That is an additional constraint to any potential exploit: debug output must be enabled. Indeed, without the -g and -f dbg flag we do not see a segfault:

$ ./nasm -o poc.out nasm-bof-poc
../nasm-bof-poc:1: error: integer supplied as 256-bit data

What does the -f dbg format flag do? As per nasm -hf:

[...]
dbg                  Trace of all info passed to output stage
[...]

Let’s take a look at the proof of concept:

$ xxd poc
00000000: 6459 2030                                dY 0

Yes, the proof of concept is four bytes in size.

Source Diving

Here is the dbgdbg_typevalue function:

static void dbgdbg_typevalue(int32_t type)
{
    fprintf(ofile, "dbg typevalue: %s(%"PRIX32")\n",
            types[TYM_TYPE(type) >> 3], TYM_ELEMENTS(type));
}

types is defined thusly:

static const char * const types[] = {
    "unknown", "label", "byte", "word", "dword", "float", "qword", "tbyte"
};

types[TYM_TYPE(type) >> 3] is the vulnerable clause. Let’s take a look at the TYM_TYPE macro:

#define TYM_TYPE(x)     ((x) & 0xF8)

Debugging

Doing a little debugging in gdb reveals that given the proof of concept, TYM_TYPE(type) >> 3 evaluates to 10. Keep in mind given the definition of types[], the only valid values for indices are 0-7, thus the buffer overflow. Passing a value of 10 results in the program trying to access memory beyond the bounds of the array.

Potential Fixes

The only fix I can come up with is to check the length of types and make sure the index passed in is within the bounds of the length check for types. There is probably a more elegant solution that prevents the bad value from being passed into dbgdbg_typevalue, but without really diving into the code I am not sure.

Is it worth writing an exploit for this vulnerability?

Not for free. :-)

Home