Talos Vulnerability Report

TALOS-2023-1827

GTKWave LXT2 lxt2_rd_expand_integer_to_bits stack-based buffer overflow vulnerability

January 8, 2024
CVE Number

CVE-2023-38583

SUMMARY

A stack-based buffer overflow vulnerability exists in the LXT2 lxt2_rd_expand_integer_to_bits function of GTKWave 3.3.115. A specially crafted .lxt2 file can lead to arbitrary code execution. A victim would need to open a malicious file to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

GTKWave 3.3.115

PRODUCT URLS

GTKWave - https://gtkwave.sourceforge.net

CVSSv3 SCORE

7.8 - CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-120 - Buffer Copy without Checking Size of Input (‘Classic Buffer Overflow’)

DETAILS

GTKWave is a wave viewer, often used to analyze FPGA simulations and logic analyzer captures. It includes a GUI to view and analyze traces, as well as convert across several file formats (.lxt, .lxt2, .vzt, .fst, .ghw, .vcd, .evcd) either by using the UI or its command line tools. GTKWave is available for Linux, Windows and MacOS. Trace files can be shared within teams or organizations, for example to compare results of simulation runs across different design implementations, to analyze protocols captured with logic analyzers or just as a reference when porting design implementations.

During parsing of an LXT2 file, a vulnerable function lxt2_rd_expand_integer_to_bits() (in lxt2_read.c) is called.

137  static char *lxt2_rd_expand_integer_to_bits(int len, unsigned int value)
138  {
139  static char s[33];  // Overflow this buffer [1]
140  char *p = s;
141  int i;
142  int len2 = len-1;
143  
144  for(i=0;i<len;i++)
145          {
146          *(p++) = '0' | ((value & (1<<(len2-i)))!=0);  // <<<-- Overflow here [2]
147          }
148  *p = 0;
149  
150  return(s);
151  }

Which is called from:

229  case LXT2_RD_ENC_ADD4: x=lxt2_rd_expand_bits_to_integer(lt->len[idx], lt->value[idx]); x+= (vch-LXT2_RD_ENC_ADD1+1);

The len value is calculated in lxt2_rd_init()

919  lt->len[i] = (lt->msb[i] <= lt->lsb[i]) ? (lt->lsb[i] - lt->msb[i] + 1) : (lt->msb[i] - lt->lsb[i] + 1);

Since the lt->msb and lt->lsb values are specified in the file itself, an attacker can control the len parameter sent to the vulnerable function lxt2_rd_expand_integer_to_bits().

In this example, we have specified the following lsb and msb values:

lt->msb = 0xc6c6c6c9
lt->lsb = 0xc6c6c63f

The result of the conditional assignment at line 919 with these values will result in lt->len[0] == 0x8b

   gef➤  p 0xc6c6c6c9-0xc6c6c63f+1
   $48 = 0x8b

When the lxt2_rd_expand_integer_to_bits() function is called with a length longer than the s[33] buffer [1], the buffer will be overwritten and will continue to write out of bounds into other variables [2]:

Before the loop at line 144:

gef➤  hexdump --size 256 p
0x000055555555f120 <s+0000>           00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
0x000055555555f130 <s+0010>           00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
0x000055555555f140 <s+0020>           00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
0x000055555555f150 <flat_earth+0000>  00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00    ................
0x000055555555f160 <fv+0000>          a0 56 f5 f7 ff 7f 00 00 01 00 00 00 00 00 00 00    .V..............
0x000055555555f170 <buf+0000>         21 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    !...............
0x000055555555f180 <nhold+0000>       00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................

After the loop at line 148:

gef➤  hexdump --size 256 0x000055555555f120
0x000055555555f120 <s+0000>           30 30 30 30 30 30 30 30 31 30 30 30 30 30 30 30    0000000010000000
0x000055555555f130 <s+0010>           30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30    0000000000000000
0x000055555555f140 <s+0020>           30 30 30 30 30 30 30 30 31 30 30 30 30 30 30 30    0000000010000000
0x000055555555f150 <flat_earth+0000>  30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30    0000000000000000
0x000055555555f160 <fv+0000>          30 30 30 30 30 30 30 30 31 30 30 30 30 30 30 30    0000000010000000
0x000055555555f170 <buf+0000>         30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30    0000000000000000
0x000055555555f180 <nhold+0000>       30 30 30 30 30 30 30 30 31 30 30 30 30 30 30 30    0000000010000000
0x000055555555f190                    30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30    0000000000000000
0x000055555555f1a0                    30 30 30 30 30 30 30 30 31 30 30 00 00 00 00 00    00000000100.....

This issue can be exploited to overwrite the stack arbitrarily, leading to arbitrary code execution.

Crash Information

LXTLOAD | 1 facilities
LXTLOAD | Read 1 block header OK
LXTLOAD | [589828] start time
LXTLOAD | [-1801158375971946456] end time
LXTLOAD | 
$date
    Tue Jul 25 13:48:36 2023
$end
$version
    lxt2vcd
$end
$timescale 1s  $end
$var real 1 !  $end
$enddefinitions $end
LXTLOAD | block [0] processing 589828 / -1801158375971946456
LXTLOAD | Time backtracking encountered: this VCD might load incorrectly in gtkwave.
#10
$dumpvars
$dumpoff
$end
#20
$dumpon
r0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 !

Program received signal SIGSEGV, Segmentation fault.
__vfprintf_internal (s=0x3030303030303030, format=0x55555555c51c "#%ld\n", ap=ap@entry=0x7fffffffd840, mode_flags=mode_flags@entry=0x0) at vfprintf-internal.c:1328
1328	vfprintf-internal.c: No such file or directory.

[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x8b              
$rcx   : 0x0               
$rdx   : 0x00007fffffffd840  →  0x0000003000000010
$rsp   : 0x00007fffffffd2c0  →  0x00007ffff7f7d510  →  0x088b089ad98d865b
$rbp   : 0x00007fffffffd830  →  0x00007fffffffd960  →  0x00007fffffffd9d0  →  0x00007fffffffda30  →  0x00007fffffffdb50  →  0x00007fffffffdbd0  →  0x00007fffffffdc30  →  0x0000000000000000
$rsi   : 0x000055555555c51c  →  0x6424000a646c2523 ("#%ld\n"?)
$rdi   : 0x3030303030303030 ("00000000"?)
$rip   : 0x00007ffff7dde8a6  →  <__vfprintf_internal+70> mov eax, DWORD PTR [rdi+0xc0]
$r8    : 0x000055555555a1b9  →  <vcd_callback+0> endbr64 
$r9    : 0x8f              
$r10   : 0x000055555555c546  →  0x732520732573000a ("\n"?)
$r11   : 0x246             
$r12   : 0x3030303030303030 ("00000000"?)
$r13   : 0x000055555555c51c  →  0x6424000a646c2523 ("#%ld\n"?)
$r14   : 0x00007fffffffd840  →  0x0000003000000010
$r15   : 0x0               
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd2c0│+0x0000: 0x00007ffff7f7d510  →  0x088b089ad98d865b	 ← $rsp
0x00007fffffffd2c8│+0x0008: 0x00007ffff7fda88a  →  <do_lookup_x+970> add rsp, 0x30
0x00007fffffffd2d0│+0x0010: 0x0000000000000019
0x00007fffffffd2d8│+0x0018: 0x00007fff00000000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
   0x7ffff7dde896 <__vfprintf_internal+54> mov    rax, QWORD PTR [rip+0x1755d3]        # 0x7ffff7f53e70
   0x7ffff7dde89d <__vfprintf_internal+61> mov    eax, DWORD PTR fs:[rax]
   0x7ffff7dde8a0 <__vfprintf_internal+64> mov    DWORD PTR [rbp-0x4d4], eax
 → 0x7ffff7dde8a6 <__vfprintf_internal+70> mov    eax, DWORD PTR [rdi+0xc0]
   0x7ffff7dde8ac <__vfprintf_internal+76> test   eax, eax
   0x7ffff7dde8ae <__vfprintf_internal+78> jne    0x7ffff7ddeaf0 <__vfprintf_internal+656>
   0x7ffff7dde8b4 <__vfprintf_internal+84> mov    DWORD PTR [rdi+0xc0], 0xffffffff
   0x7ffff7dde8be <__vfprintf_internal+94> mov    r15d, DWORD PTR [r12]
   0x7ffff7dde8c2 <__vfprintf_internal+98> test   r15b, 0x8
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "lxt2vcd", stopped 0x7ffff7dde8a6 in __vfprintf_internal (), reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7ffff7dde8a6 → __vfprintf_internal(s=0x3030303030303030, format=0x55555555c51c "#%ld\n", ap=0x7fffffffd840, mode_flags=0x0)
[#1] 0x7ffff7dc9c6a → __fprintf(stream=<optimized out>, format=<optimized out>)
[#2] 0x55555555a2c1 → vcd_callback(lt=0x7fffffffd978, pnt_time=0x555555560a48, pnt_facidx=0x7fffffffd98c, pnt_value=0x5555555610e0)
[#3] 0x55555555623c → lxt2_rd_iter_radix(lt=0x555555560780, b=0x5555555611e0)
[#4] 0x55555555752a → lxt2_rd_process_block(lt=0x555555560780, b=0x5555555611e0)
[#5] 0x555555559daa → lxt2_rd_iter_blocks(lt=0x555555560780, value_change_callback=0x55555555a1b9 <vcd_callback>, user_callback_data_pointer=0x0)
[#6] 0x55555555a98a → process_lxt(fname=0x555555560750 "buffer_overflow_testcase_working.lxt")
[#7] 0x55555555ae33 → main(argc=0x3, argv=0x7fffffffdd28)
VENDOR RESPONSE

Fixed in version 3.3.118, available from https://sourceforge.net/projects/gtkwave/files/gtkwave-3.3.118/

TIMELINE

2023-08-11 - Vendor Disclosure
2023-12-31 - Vendor Patch Release
2024-01-08 - Public Release

Credit

Discovered by Dave McDaniel and Claudio Bozzato of Cisco Talos.