Skip to content

Latest commit

 

History

History
185 lines (147 loc) · 13.3 KB

CVE-2021-46829.md

File metadata and controls

185 lines (147 loc) · 13.3 KB

Heap Buffer Overflow in gdk-pixbuf

Disclosure: 2021-11-09 / Last Updated: 2022-07-25

Summary

gdk-pixbuf 2.42.6 and below (before commit 5398f04d772f7f8baf5265715696ed88db0f0512) is affected by a heap buffer overflow when compositing or clearing frames in GIF files. This overflow appears to be controllable and could be abused for code execution, especially in 32 bit systems. Using a GNOME file system browser and browsing to a folder containing the proof of concept files or opening the files in a GNOME image viewer will cause the respective application to crash.

This vulnerability was reported to gdk-pixbuf on 2021-06-02 as private security GitLab issue #190, and a copy of this report is below. It was fixed by pull request #121, which was included in version 2.42.8, released on 2022-03-18. The proof of concept files mentioned in the Original Bug Report below can be downloaded from GitLab or from @pedrib's repository.

This vulnerability was assigned CVE-2021-46829.

Original Bug Report

Buffer overwrite in io-gif-animation.c composite_frame() (possibly exploitable)

I have been fuzzing gdk-pixbuf and found an interesting bug. I did limited research on it, but the bug appears to be an exploitable memory overwrite.

The bug is in composite_frame() in io-gif-animation.c, which I pasted below. It can be triggered by running gdk-pixbuf-pixdata with any of the poc files attached to this report.

		pixels = gdk_pixbuf_get_pixels (anim->last_frame_data);
		for (i = 0; i < n_indexes; i++) {
				guint8 index = index_buffer[i];
				guint x, y;
				int offset;

				if (index == frame->transparent_index)
						continue;

				x = i % frame->width + frame->x_offset;
				y = interlace_rows[i / frame->width] + frame->y_offset;
				if (x >= anim->width || y >= anim->height)
						continue;

				offset = y * gdk_pixbuf_get_rowstride (anim->last_frame_data) + x * 4;
				int rowstride = gdk_pixbuf_get_rowstride (anim->last_frame_data);
				printf("signed: y: %d x: %d height: %d width: %d rowstride: %d offset: %d\n", y, x, anim->height, anim->width, rowstride, offset);
				printf("unsigned: y: %u x: %u height: %u width: %u rowstride: %u offset: %u\n", y, x, anim->height, anim->width, rowstride, offset);
				printf("buf size: %u\n", gdk_pixbuf_get_byte_length (anim->last_frame_data));
				pixels[offset + 0] = frame->color_map[index * 3 + 0];
				pixels[offset + 1] = frame->color_map[index * 3 + 1];
				pixels[offset + 2] = frame->color_map[index * 3 + 2];
				pixels[offset + 3] = 255;
		}

Note that the printf were added by me to aid in debugging. The problem appears to be in the calculation of the offset variable. Actually there are two problems there:

  • because it is a signed int, it can overwrite when it's over INT32_MAX
  • even if it was as an unsigned int, given the calculations, it becomes quite easy to reach very large values

I am attaching two files here. The first is wrap_around.poc, which gives this result:

signed: y: 25926 x: 18759 height: 26725 width: 22016 rowstride: 88064 offset: -2011744996
unsigned: y: 25926 x: 18759 height: 26725 width: 22016 rowstride: 88064 offset: 2283222300
buf size: 2353510400
Segmentation fault

As you can see, we've wrapped around and gone into a negative number, which causes a negative offset access on the buffer. This can be further checked in gdb:

$r11   : 0x00007fff6b247010  →  0x0000000000000000
$r15   : 0xffffffff8817351c

   0x55555556603b <composite_frame+667> lea    eax, [r10+r10*2]
   0x55555556603f <composite_frame+671> movsxd rdx, eax
   0x555555566042 <composite_frame+674> movzx  edx, BYTE PTR [rcx+rdx*1]
 → 0x555555566046 <composite_frame+678> mov    BYTE PTR [r11+r15*1], dl
   0x55555556604a <composite_frame+682> lea    edx, [rax+0x1]
   0x55555556604d <composite_frame+685> mov    rcx, QWORD PTR [rbp+0x20]
   0x555555566051 <composite_frame+689> add    eax, 0x2
   0x555555566054 <composite_frame+692> movsxd rdx, edx
   0x555555566057 <composite_frame+695> cdqe

Here the value of $r11 is the pixels buffer, while r15 is the calculated output variable. Its value is 0xffffffff8817351c, which is exactly -2011744996 as printed out by my added printfs.

The second file I am attaching, more_trouble.poc, shows that this can happen even with a smaller buffer. Its output results in:

signed: y: 32896 x: 128 height: 65526 width: 18303 rowstride: 73212 offset: -1886584832
unsigned: y: 32896 x: 128 height: 65526 width: 18303 rowstride: 73212 offset: 2408382464
buf size: 502322216
Segmentation fault

I haven't tried to exploit this, but looks to me that it is definitely exploitable. We can write to an arbitrary memory address, which is calculated using the offset variable. What I am not sure is how controllable that offset variable is; its value depends on the X and Y and other factors, which might influence the buffer size. I'm sure you know the code better than me and will be able to definitely tell if this is fully exploitable or not.

I also did a naive fix, which works perfectly to avoid this problem, but I'm not sure if its the correct way to go about it. You just need to introduce the following check after the offset calculation and before it is used to access the pixels buffer:

		 gsize len = gdk_pixbuf_get_byte_length (anim->last_frame_data);
		 g_assert (offset + 3 <= len && offset > 0);

This seems to stop every memory overwrite from happening, here's the output of wrap_around.poc with the patch:

signed: y: 25926 x: 18759 height: 26725 width: 22016 rowstride: 88064 offset: -2011744996
unsigned: y: 25926 x: 18759 height: 26725 width: 22016 rowstride: 88064 offset: 2283222300
buf size: 2353510400
**
GdkPixbuf:ERROR:../gdk-pixbuf/io-gif-animation.c:390:composite_frame: assertion failed: (offset + 3 <= len && offset > 0)
Bail out! GdkPixbuf:ERROR:../gdk-pixbuf/io-gif-animation.c:390:composite_frame: assertion failed: (offset + 3 <= len && offset > 0)
Aborted

I spent a bit more time looking at this to try and see what I can manipulate. There is no doubt that with this bug we can write somewhat arbitrary memory. The thing is that this only works with very large buffers, which tend to be allocated in a special heap area. So while we can write arbitrary bytes, it's somewhat limited to where we can write, as this special heap area is very large and might not contain anything useful.

However, there might be a way to use the negative offset to wrap around in the opposite direction, and be able to overwrite something more significant. This is much more likely to be exploitable in a 32 bit system, but I'm not gonna say it's impossible to exploit in x64 either.

I think I will stop digging now as I have limited time to explore this. In summary I'll rate this bug in terms of exploitability a 7/10 (exploitation might be possible under the right conditions).

PS: more bugs are coming, I just need to validate and do a bit of research on them first

Here's an example of a semi-controlled write 2628 bytes after the end of the buffer:

==1676211==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7ffeda4afe84 at pc 0x0000004f13fc bp 0x7fffffffd970 sp 0x7fffffffd968
WRITE of size 1 at 0x7ffeda4afe84 thread T0
	#0 0x4f13fb in composite_frame /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/io-gif-animation.c:383:36
	#1 0x4f065e in gdk_pixbuf_gif_anim_iter_get_pixbuf /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/io-gif-animation.c:428:17
	#2 0x4efa08 in gdk_pixbuf_gif_anim_get_static_image /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/io-gif-animation.c:117:18
	#3 0x518cf2 in gdk_pixbuf_animation_get_static_image /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/gdk-pixbuf-animation.c:616:16
	#4 0x4edceb in gif_get_lzw /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/io-gif.c:467:24
	#5 0x4eb164 in gif_main_loop /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/io-gif.c:753:13
	#6 0x4ea84a in gdk_pixbuf__gif_image_load /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/io-gif.c:855:18
	#7 0x4d08ef in _gdk_pixbuf_generic_image_load /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/gdk-pixbuf-io.c:1072:26
	#8 0x4d0d76 in gdk_pixbuf_new_from_file /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/gdk-pixbuf-io.c:1149:18
	#9 0x4cdd78 in main /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/gdk-pixbuf-pixdata.c:77:12
	#10 0x7ffff7914d09 in __libc_start_main csu/../csu/libc-start.c:308:16
	#11 0x423f19 in _start (/fuzz/pixbuf/gdk-pixbuf-asan/_build/gdk-pixbuf/gdk-pixbuf-pixdata+0x423f19)

0x7ffeda4afe84 is located 2628 bytes to the right of 432401472-byte region [0x7ffec0850800,0x7ffeda4af440)
allocated by thread T0 here:
	#0 0x49df8d in malloc (/fuzz/pixbuf/gdk-pixbuf-asan/_build/gdk-pixbuf/gdk-pixbuf-pixdata+0x49df8d)
	#1 0x7ffff7d97d48 in g_malloc (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x57d48)
	#2 0x4f065e in gdk_pixbuf_gif_anim_iter_get_pixbuf /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/io-gif-animation.c:428:17
	#3 0x4efa08 in gdk_pixbuf_gif_anim_get_static_image /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/io-gif-animation.c:117:18
	#4 0x518cf2 in gdk_pixbuf_animation_get_static_image /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/gdk-pixbuf-animation.c:616:16
	#5 0x4edceb in gif_get_lzw /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/io-gif.c:467:24
	#6 0x4eb164 in gif_main_loop /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/io-gif.c:753:13
	#7 0x4ea84a in gdk_pixbuf__gif_image_load /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/io-gif.c:855:18
	#8 0x4d08ef in _gdk_pixbuf_generic_image_load /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/gdk-pixbuf-io.c:1072:26
	#9 0x4d0d76 in gdk_pixbuf_new_from_file /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/gdk-pixbuf-io.c:1149:18
	#10 0x4cdd78 in main /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/gdk-pixbuf-pixdata.c:77:12
	#11 0x7ffff7914d09 in __libc_start_main csu/../csu/libc-start.c:308:16

SUMMARY: AddressSanitizer: heap-buffer-overflow /fuzz/pixbuf/gdk-pixbuf-asan/_build/../gdk-pixbuf/io-gif-animation.c:383:36 in composite_frame
Shadow bytes around the buggy address:
  0x10005b48df80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x10005b48df90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x10005b48dfa0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x10005b48dfb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x10005b48dfc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x10005b48dfd0:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x10005b48dfe0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x10005b48dff0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x10005b48e000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x10005b48e010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x10005b48e020: 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

Fixes / Mitigations

Upgrade gdk-pixbuf to version 2.42.8 or higher.

Disclaimer

Please note that Agile Information Security Limited (Agile InfoSec) relies on information provided by the vendor / product manufacturer when listing fixed versions, products or releases. Agile InfoSec does not verify this information, except when specifically mentioned in the advisory text and requested or contracted by the vendor to do so. Unconfirmed vendor fixes might be ineffective, incomplete or easy to bypass and it is the vendor's responsibility to ensure all the vulnerabilities found by Agile InfoSec are resolved properly. Agile InfoSec usually provides the information in its advisories free of charge to the vendor, as well as a minimum of six months for the vendor to resolve the vulnerabilities identified in its advisories before they are made public. Agile InfoSec does not accept any responsibility, financial or otherwise, from any material losses, loss of life or reputational loss as a result of misuse of the information or code contained or mentioned in its advisories. It is the vendor's responsibility to ensure their products' security before, during and after release to market.

License

All information, code and binary data in this advisory is released to the public under the GNU General Public License, version 3 (GPLv3). For information, code or binary data obtained from other sources that has a license which is incompatible with GPLv3, the original license prevails.