CVE-2023-38583
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.
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
GTKWave - https://gtkwave.sourceforge.net
7.8 - CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-120 - Buffer Copy without Checking Size of Input (‘Classic Buffer Overflow’)
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.
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)
Fixed in version 3.3.118, available from https://sourceforge.net/projects/gtkwave/files/gtkwave-3.3.118/
2023-08-11 - Vendor Disclosure
2023-12-31 - Vendor Patch Release
2024-01-08 - Public Release
Discovered by Dave McDaniel and Claudio Bozzato of Cisco Talos.