After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 776694 - bmp: integer multiplication overflow
bmp: integer multiplication overflow
Status: RESOLVED FIXED
Product: gdk-pixbuf
Classification: Platform
Component: loaders
git master
Other Linux
: Normal normal
: ---
Assigned To: gdk-pixbuf-maint
gdk-pixbuf-maint
Depends on:
Blocks:
 
 
Reported: 2017-01-01 22:36 UTC by Tobias Mueller
Modified: 2017-07-13 17:52 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
test file (1.82 KB, patch)
2017-01-01 22:38 UTC, Tobias Mueller
committed Details | Review
WIP patch (2.73 KB, patch)
2017-01-08 14:32 UTC, Tobias Mueller
needs-work Details | Review
bmp: Prevent multiplication overflow in DecodeHeader (2.68 KB, patch)
2017-07-13 17:51 UTC, Bastien Nocera
committed Details | Review

Description Tobias Mueller 2017-01-01 22:36:04 UTC
afl created a file which ASan complains about:

io-bmp.c:475:63: runtime error: signed integer overflow: 524672 * 4096 cannot be represented in type 'int'
=================================================================
==23213==ERROR: AddressSanitizer: negative-size-param: (size=-2145910784)
    #0 0x7face7d0cc69 in __asan_memset (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x8cc69)
    #1 0x7face1cca778 in DecodeHeader /home/muelli/vcs/gdk-pixbuf/gdk-pixbuf/io-bmp.c:475
    #2 0x7face1cd7c6e in gdk_pixbuf__bmp_image_load_increment /home/muelli/vcs/gdk-pixbuf/gdk-pixbuf/io-bmp.c:1270
    #3 0x7face79d6f6d in gdk_pixbuf_loader_load_module /home/muelli/vcs/gdk-pixbuf/gdk-pixbuf/gdk-pixbuf-loader.c:443
    #4 0x7face79d727e in gdk_pixbuf_loader_eat_header_write /home/muelli/vcs/gdk-pixbuf/gdk-pixbuf/gdk-pixbuf-loader.c:465
    #5 0x7face79d75ef in gdk_pixbuf_loader_write /home/muelli/vcs/gdk-pixbuf/gdk-pixbuf/gdk-pixbuf-loader.c:511
    #6 0x400e3a in test_loader /home/muelli/vcs/gdk-pixbuf/tests/pixbuf-read.c:31
    #7 0x40124c in main /home/muelli/vcs/gdk-pixbuf/tests/pixbuf-read.c:75
    #8 0x7face637982f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #9 0x400d38 in _start (/home/muelli/vcs/gdk-pixbuf/tests/.libs/lt-pixbuf-read+0x400d38)

0x7fac61b29800 is located 0 bytes inside of 2149056512-byte region [0x7fac61b29800,0x7face1ca9800)
allocated by thread T0 here:
    #0 0x7face7d18602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
    #1 0x7face79bd2d0 in gdk_pixbuf_new /home/muelli/vcs/gdk-pixbuf/gdk-pixbuf/gdk-pixbuf.c:463
    #2 0x7face1cca07e in DecodeHeader /home/muelli/vcs/gdk-pixbuf/gdk-pixbuf/io-bmp.c:451
    #3 0x7face1cd7c6e in gdk_pixbuf__bmp_image_load_increment /home/muelli/vcs/gdk-pixbuf/gdk-pixbuf/io-bmp.c:1270
    #4 0x7face79d6f6d in gdk_pixbuf_loader_load_module /home/muelli/vcs/gdk-pixbuf/gdk-pixbuf/gdk-pixbuf-loader.c:443
    #5 0x7face79d727e in gdk_pixbuf_loader_eat_header_write /home/muelli/vcs/gdk-pixbuf/gdk-pixbuf/gdk-pixbuf-loader.c:465
    #6 0x7face79d75ef in gdk_pixbuf_loader_write /home/muelli/vcs/gdk-pixbuf/gdk-pixbuf/gdk-pixbuf-loader.c:511
    #7 0x400e3a in test_loader /home/muelli/vcs/gdk-pixbuf/tests/pixbuf-read.c:31
    #8 0x40124c in main /home/muelli/vcs/gdk-pixbuf/tests/pixbuf-read.c:75
    #9 0x7face637982f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

SUMMARY: AddressSanitizer: negative-size-param ??:0 __asan_memset
==23213==ABORTING
Comment 1 Tobias Mueller 2017-01-01 22:38:48 UTC
Created attachment 342703 [details] [review]
test file

Note that I haven't checked whether this does indeed cause a new test case to be run as I cannot run the vanilla test suite in first place.
Comment 2 Tobias Mueller 2017-01-08 14:32:06 UTC
Created attachment 343120 [details] [review]
WIP patch

I don't know how much of an issue GCC<5 breakage really is.
Also, I wonder whether GLib should provide something like in https://lwn.net/Articles/623368/.
I'm also not clear whether it's possible to catch this condition earlier, because the patch does not fully fix this issue. It crashes later during decompression, so the approach taken in this patch might be bad.
Comment 3 Tobias Mueller 2017-01-08 14:37:54 UTC
FWIW: Coverity identified this as 1388542 and complains about the height being tained, because it's provided by the user.
Comment 4 Philip Withnall 2017-01-20 09:38:51 UTC
Review of attachment 343120 [details] [review]:

::: gdk-pixbuf/io-bmp.c
@@ +468,3 @@
+		unsigned header_height = State->Header.height;
+		size_t length;
+		if (__builtin_mul_overflow(rowstride, header_height, &length)) {

GLib has `g_uint_checked_mul()` (since 2.48; might need to bump the dependency) which would be a bit simpler to use here.
Comment 5 Tobias Mueller 2017-03-19 17:32:56 UTC
hm. In this case, I made "length" a size_t, because the later memset call wants "length" to be a size_t.  After I changed the __builtin_mul_overflow to g_uint_checked_mul, my compiler complained about the size_t not being a uint, as required by the glib macro:

io-bmp.c:482:27: warning: passing argument 1 of ‘_GLIB_CHECKED_MUL_U32’ from incompatible pointer type [-Wincompatible-pointer-types]
   if (g_uint_checked_mul (&length, rowstride, header_height)) {
                           ^
/usr/include/glib-2.0/glib/gtypes.h:394:27: note: in definition of macro ‘g_uint_checked_mul’
     _GLIB_CHECKED_MUL_U32(dest, a, b)
                           ^
/usr/include/glib-2.0/glib/gtypes.h:419:24: note: expected ‘guint32 * {aka unsigned int *}’ but argument is of type ‘size_t * {aka long unsigned int *}’


Should length not be a size_t?  Should we just make it an uint32?  Is there any existing glib macro to do the check for us here?
Comment 6 Bastien Nocera 2017-07-13 17:01:48 UTC
(In reply to Philip Withnall from comment #4)
> Review of attachment 343120 [details] [review] [review]:
> 
> ::: gdk-pixbuf/io-bmp.c
> @@ +468,3 @@
> +		unsigned header_height = State->Header.height;
> +		size_t length;
> +		if (__builtin_mul_overflow(rowstride, header_height, &length)) {
> 
> GLib has `g_uint_checked_mul()` (since 2.48; might need to bump the
> dependency) which would be a bit simpler to use here.

Was bumped for bug 777315.
Comment 7 Bastien Nocera 2017-07-13 17:26:27 UTC
Review of attachment 343120 [details] [review]:

::: gdk-pixbuf/io-bmp.c
@@ +458,3 @@
 			}
+
+		/* We are going to multi rowstride and heigth and use them

height.

@@ +463,3 @@
+		   with a negative number yields a negative number.
+		 */
+		g_assert (State->pixbuf->rowstride >= 0);

No assertion in a library!

@@ +465,3 @@
+		g_assert (State->pixbuf->rowstride >= 0);
+		g_assert (State->Header.height >= 0);
+		unsigned rowstride = State->pixbuf->rowstride;

Declaration should be at the top of the block.

@@ +484,3 @@
 		if (State->Compressed == BI_RLE4 || State->Compressed == BI_RLE8) {
+			memset (State->pixbuf->pixels, 0, length);
+			/* length = rowstride * height.  Hence

This comment explains the change from the old code to the new code, but means nothing with just the new code :)
Comment 8 Bastien Nocera 2017-07-13 17:33:53 UTC
I've moved the test file to tests/test-images/fail/

The files in randomly-modified are just randomly modified, they're not checked for validity. So you could end up modifying the broken image in such a way that it now works ;)
Comment 9 Bastien Nocera 2017-07-13 17:51:17 UTC
Created attachment 355543 [details] [review]
bmp: Prevent multiplication overflow in DecodeHeader

The multiplication can overflow as UBSan complained:

io-bmp.c:475:63: runtime error: signed integer overflow: 524672 * 4096
cannot be represented in type 'int'

Fix this by checking the header dimensions for sanity, and then checking
whether the rowstride * height multiplication does not overflow and fits
within the range.
Comment 10 Bastien Nocera 2017-07-13 17:51:55 UTC
Attachment 355543 [details] pushed as 4154d4f - bmp: Prevent multiplication overflow in DecodeHeader