GdkPixbuf - Heap Buffer Overflow in lzw_decoder_new
18 Jul 2021The GdkPixbuf library is vulnerable to heap-buffer overflow vulnerability when decoding the lzw compressed stream of image data in GIF files with lzw minimum code size equals to 12.
Affected versions
- 2.42.6 (latest)
Gitlab Advisory URL
- https://gitlab.gnome.org/GNOME/gdk-pixbuf/-/issues/136
Technical details
When parsing the Image Data section of GIF files with lzw minimum code size equals to 12, the library miscalculates the maximum indexes the LZWDecoder->code_table
can have, overwriting the LZWDecoder->code_table_size
in _LZWDecoder
structure.
In the following code snippets, we can observe that the LZW_CODE_MAX is set to 12 at line:28 in gdk-pixbuf/lzw.h which when left shifted at line:22 in gdk-pixbuf/lzw.c will yield MAX_CODES value to 4096. This means that the LZW decoding operations ideally should always write to maximum 4096 indexes of LZWDecoder->code_table.
file gdk-pixbif/lzw.h
27:/* Maximum code size in bits */
28:#define LZW_CODE_MAX 12
file: gdk-pixbuf/lzw.c
21: /* Maximum number of codes */
22:#define MAX_CODES (1 << LZW_CODE_MAX)
23:
24:typedef struct
25:{
26: /* Last index this code represents */
27: guint8 index;
28:
29: /* Codeword of previous index or the EOI code if doesn't extend */
30: guint16 extends;
31:} LZWCode;
32:
33:struct _LZWDecoder
34:{
35: GObject parent_instance;
36:
37: /* Initial code size */
38: int min_code_size;
39:
40: /* Current code size */
41: int code_size;
42:
43: /* Code table and special codes */
44: int clear_code;
45: int eoi_code;
46: LZWCode code_table[MAX_CODES];
47: int code_table_size;
48:
49: /* Current code being assembled */
50: int code;
51: int code_bits;
52:
53: /* Last code processed */
54: int last_code;
55:};
The following code snippets shows that the library first initializes the lzw_decoder
object by calling lzw_decoder_new
function wih the user controlled frame->lzw_code_size
value at line:339 in gdk-pixbuf/io-gif-animation.c.
When the value of frame->lzw_code_size
is set to 12 (0xc), the lzw_decoder_new
gets called with the code_size value equals to 13, which ultimately sets the value of self->eoi_code
(end of information) to 4097 at line:131 in gdk-pixbuf/lzw.c, thus writing the values to overall 4098 indexes instead of predefined 4096 at line:133-137 in gdk-pixbuf/lzw.c.
file: gdk-pixbuf/io-gif-animation.c
317:static void
318:composite_frame (GdkPixbufGifAnim *anim, GdkPixbufFrame *frame)
319:{
320: LZWDecoder *lzw_decoder = NULL;
.
.
.
338:
339: lzw_decoder = lzw_decoder_new (frame->lzw_code_size + 1);
340: index_buffer = g_new (guint8, frame->width * frame->height);
file: gdk-pixbuf/lzw.c
118:LZWDecoder *
119:lzw_decoder_new (guint8 code_size)
120:{
121: LZWDecoder *self;
122: int i;
123:
124: self = g_object_new (lzw_decoder_get_type (), NULL);
125:
126: self->min_code_size = code_size;
127: self->code_size = code_size;
128:
129: /* Add special clear and end of information codes */
130: self->clear_code = 1 << (code_size - 1);
131: self->eoi_code = self->clear_code + 1;
132:
133: for (i = 0; i <= self->eoi_code; i++) {
134: self->code_table[i].index = i;
135: self->code_table[i].extends = self->eoi_code;
136: self->code_table_size++;
137: }
138:
139: /* Start with an empty codeword following an implicit clear codeword */
140: self->code = 0;
141: self->last_code = self->clear_code;
142:
143: return self;
144:}
Dynamic Analysis
With the following gdb output, we can confirm that the lzw_decoder_new
function writes to two more indexes than predefined 4096 indexes of LZWDecoder->code_table
and thus overwrites the value of LZWDecoder->code_table_size
from 4096 to 268505089.
$43 = 4096
$44 = (int *) 0x629000018228
0x629000018228: 0x0000000000001000
$45 = 4096
Breakpoint 4, lzw_decoder_new (code_size=13 '\r') at ../gdk-pixbuf/lzw.c:134
134 self->code_table[i].index = i;
(gdb) c
Continuing.
$46 = 4097
$47 = (int *) 0x629000018228
0x629000018228: 0x0000000010011001
$48 = 268505089
Breakpoint 4, lzw_decoder_new (code_size=13 '\r') at ../gdk-pixbuf/lzw.c:134
134 self->code_table[i].index = i;
(gdb) c
Continuing.
$49 = 4098
$50 = (int *) 0x629000018228
0x629000018228: 0x1001000110011002
$51 = 268505090
Breakpoint 7, lzw_decoder_new (code_size=13 '\r') at ../gdk-pixbuf/lzw.c:140
140 self->code = 0;
Steps to reproduce
- Compile the gdk-pixbuf with address sanitizer.
- Execute the gdk-pixbuf/gdk-pixbuf-pixdata binary with the accompanied heap-buffer-overflow.gif file as follows and observed the ASAN dump:
command:
./gdk-pixbuf/gdk-pixbuf-pixdata ../../gdk-pixbuf/poc/heap_buffer_overflow.gif /tmp/bbbb
ASAN Dump:
==5565==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x62900001336a at pc 0x00000053b142 bp 0x7fffc5dd1200 sp 0x7fffc5dd11f8
READ of size 2 at 0x62900001336a thread T0
#0 0x53b141 in write_indexes /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/lzw.c:88:36
#1 0x53a8d9 in lzw_decoder_feed /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/lzw.c:212:46
#2 0x5374c1 in composite_frame /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/io-gif-animation.c:364:21
#3 0x5351ed in gdk_pixbuf_gif_anim_iter_get_pixbuf /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/io-gif-animation.c:421:17
#4 0x5347bf in gdk_pixbuf_gif_anim_get_static_image /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/io-gif-animation.c:117:16
#5 0x58e217 in gdk_pixbuf_animation_get_static_image /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/gdk-pixbuf-animation.c:586:16
#6 0x532d08 in gif_get_lzw /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/io-gif.c:522:24
#7 0x52caf7 in gif_main_loop /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/io-gif.c:821:13
#8 0x52b370 in gdk_pixbuf__gif_image_load_increment /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/io-gif.c:1013:11
#9 0x503404 in gdk_pixbuf_loader_load_module /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/gdk-pixbuf-loader.c:467:16
#10 0x502127 in gdk_pixbuf_loader_close /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/gdk-pixbuf-loader.c:835:25
#11 0x581dbb in gdk_pixbuf__qtif_image_load /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/io-qtif.c:217:21
#12 0x4f47ec in _gdk_pixbuf_generic_image_load /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/gdk-pixbuf-io.c:1064:26
#13 0x4f502b in gdk_pixbuf_new_from_file /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/gdk-pixbuf-io.c:1135:18
#14 0x4efee7 in main /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/gdk-pixbuf-pixdata.c:77:12
#15 0x7fe6f0e7582f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#16 0x41e518 in _start (/root/gdk-pixbuf-poc/_build/gdk-pixbuf/gdk-pixbuf-pixdata+0x41e518)
0x62900001336a is located 306 bytes to the right of 16440-byte region [0x62900000f200,0x629000013238)
allocated by thread T0 here:
#0 0x4be648 in malloc (/root/gdk-pixbuf-poc/_build/gdk-pixbuf/gdk-pixbuf-pixdata+0x4be648)
#1 0x7fe6f297e7b8 in g_malloc (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x4f7b8)
SUMMARY: AddressSanitizer: heap-buffer-overflow /root/gdk-pixbuf-poc/_build/../gdk-pixbuf/lzw.c:88:36 in write_indexes
Shadow bytes around the buggy address:
0x0c527fffa610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c527fffa620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c527fffa630: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c527fffa640: 00 00 00 00 00 00 00 fa fa fa fa fa fa fa fa fa
0x0c527fffa650: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c527fffa660: fa fa fa fa fa fa fa fa fa fa fa fa fa[fa]fa fa
0x0c527fffa670: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c527fffa680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c527fffa690: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c527fffa6a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c527fffa6b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
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
==5565==ABORTING