src/video/SDL_bmp.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 18 Feb 2019 07:48:23 -0800
branchSDL-1.2
changeset 12611 19d8c3b9c251
parent 6227 e11fd9609d10
child 12645 08f3b4992538
permissions -rw-r--r--
Fixed bug 4500 - Heap-Buffer Overflow in Map1toN pertaining to SDL_pixels.c

Petr Pisar

The reproducer has these data in BITMAPINFOHEADER:

biSize = 40
biBitCount = 8
biClrUsed = 131075

SDL_LoadBMP_RW() function passes biBitCount as a color depth to SDL_CreateRGBSurface(), thus 256-color pallete is allocated. But then biClrUsed colors are read from a file and stored into the palette. SDL_LoadBMP_RW should report an error if biClrUsed is greater than 2^biBitCount.
     1 /*
     2     SDL - Simple DirectMedia Layer
     3     Copyright (C) 1997-2012 Sam Lantinga
     4 
     5     This library is free software; you can redistribute it and/or
     6     modify it under the terms of the GNU Lesser General Public
     7     License as published by the Free Software Foundation; either
     8     version 2.1 of the License, or (at your option) any later version.
     9 
    10     This library is distributed in the hope that it will be useful,
    11     but WITHOUT ANY WARRANTY; without even the implied warranty of
    12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13     Lesser General Public License for more details.
    14 
    15     You should have received a copy of the GNU Lesser General Public
    16     License along with this library; if not, write to the Free Software
    17     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    18 
    19     Sam Lantinga
    20     slouken@libsdl.org
    21 */
    22 #include "SDL_config.h"
    23 
    24 /* 
    25    Code to load and save surfaces in Windows BMP format.
    26 
    27    Why support BMP format?  Well, it's a native format for Windows, and
    28    most image processing programs can read and write it.  It would be nice
    29    to be able to have at least one image format that we can natively load
    30    and save, and since PNG is so complex that it would bloat the library,
    31    BMP is a good alternative. 
    32 
    33    This code currently supports Win32 DIBs in uncompressed 8 and 24 bpp.
    34 */
    35 
    36 #include "SDL_video.h"
    37 #include "SDL_endian.h"
    38 
    39 /* Compression encodings for BMP files */
    40 #ifndef BI_RGB
    41 #define BI_RGB		0
    42 #define BI_RLE8		1
    43 #define BI_RLE4		2
    44 #define BI_BITFIELDS	3
    45 #endif
    46 
    47 
    48 SDL_Surface * SDL_LoadBMP_RW (SDL_RWops *src, int freesrc)
    49 {
    50 	SDL_bool was_error;
    51 	long fp_offset = 0;
    52 	int bmpPitch;
    53 	int i, pad;
    54 	SDL_Surface *surface;
    55 	Uint32 Rmask;
    56 	Uint32 Gmask;
    57 	Uint32 Bmask;
    58 	SDL_Palette *palette;
    59 	Uint8 *bits;
    60 	Uint8 *top, *end;
    61 	SDL_bool topDown;
    62 	int ExpandBMP;
    63 
    64 	/* The Win32 BMP file header (14 bytes) */
    65 	char   magic[2];
    66 	Uint32 bfSize;
    67 	Uint16 bfReserved1;
    68 	Uint16 bfReserved2;
    69 	Uint32 bfOffBits;
    70 
    71 	/* The Win32 BITMAPINFOHEADER struct (40 bytes) */
    72 	Uint32 biSize;
    73 	Sint32 biWidth;
    74 	Sint32 biHeight;
    75 	Uint16 biPlanes;
    76 	Uint16 biBitCount;
    77 	Uint32 biCompression;
    78 	Uint32 biSizeImage;
    79 	Sint32 biXPelsPerMeter;
    80 	Sint32 biYPelsPerMeter;
    81 	Uint32 biClrUsed;
    82 	Uint32 biClrImportant;
    83 
    84 	/* Make sure we are passed a valid data source */
    85 	surface = NULL;
    86 	was_error = SDL_FALSE;
    87 	if ( src == NULL ) {
    88 		was_error = SDL_TRUE;
    89 		goto done;
    90 	}
    91 
    92 	/* Read in the BMP file header */
    93 	fp_offset = SDL_RWtell(src);
    94 	SDL_ClearError();
    95 	if ( SDL_RWread(src, magic, 1, 2) != 2 ) {
    96 		SDL_Error(SDL_EFREAD);
    97 		was_error = SDL_TRUE;
    98 		goto done;
    99 	}
   100 	if ( SDL_strncmp(magic, "BM", 2) != 0 ) {
   101 		SDL_SetError("File is not a Windows BMP file");
   102 		was_error = SDL_TRUE;
   103 		goto done;
   104 	}
   105 	bfSize		= SDL_ReadLE32(src);
   106 	bfReserved1	= SDL_ReadLE16(src);
   107 	bfReserved2	= SDL_ReadLE16(src);
   108 	bfOffBits	= SDL_ReadLE32(src);
   109 
   110 	/* Read the Win32 BITMAPINFOHEADER */
   111 	biSize		= SDL_ReadLE32(src);
   112 	if ( biSize == 12 ) {
   113 		biWidth		= (Uint32)SDL_ReadLE16(src);
   114 		biHeight	= (Uint32)SDL_ReadLE16(src);
   115 		biPlanes	= SDL_ReadLE16(src);
   116 		biBitCount	= SDL_ReadLE16(src);
   117 		biCompression	= BI_RGB;
   118 		biSizeImage	= 0;
   119 		biXPelsPerMeter	= 0;
   120 		biYPelsPerMeter	= 0;
   121 		biClrUsed	= 0;
   122 		biClrImportant	= 0;
   123 	} else {
   124 		biWidth		= SDL_ReadLE32(src);
   125 		biHeight	= SDL_ReadLE32(src);
   126 		biPlanes	= SDL_ReadLE16(src);
   127 		biBitCount	= SDL_ReadLE16(src);
   128 		biCompression	= SDL_ReadLE32(src);
   129 		biSizeImage	= SDL_ReadLE32(src);
   130 		biXPelsPerMeter	= SDL_ReadLE32(src);
   131 		biYPelsPerMeter	= SDL_ReadLE32(src);
   132 		biClrUsed	= SDL_ReadLE32(src);
   133 		biClrImportant	= SDL_ReadLE32(src);
   134 	}
   135 
   136 	/* stop some compiler warnings. */
   137 	(void) bfSize;
   138 	(void) bfReserved1;
   139 	(void) bfReserved2;
   140 	(void) biPlanes;
   141 	(void) biSizeImage;
   142 	(void) biXPelsPerMeter;
   143 	(void) biYPelsPerMeter;
   144 	(void) biClrImportant;
   145 
   146 	if (biHeight < 0) {
   147 		topDown = SDL_TRUE;
   148 		biHeight = -biHeight;
   149 	} else {
   150 		topDown = SDL_FALSE;
   151 	}
   152 
   153 	/* Check for read error */
   154 	if ( SDL_strcmp(SDL_GetError(), "") != 0 ) {
   155 		was_error = SDL_TRUE;
   156 		goto done;
   157 	}
   158 
   159 	/* Expand 1 and 4 bit bitmaps to 8 bits per pixel */
   160 	switch (biBitCount) {
   161 		case 1:
   162 		case 4:
   163 			ExpandBMP = biBitCount;
   164 			biBitCount = 8;
   165 			break;
   166 		default:
   167 			ExpandBMP = 0;
   168 			break;
   169 	}
   170 
   171 	/* We don't support any BMP compression right now */
   172 	Rmask = Gmask = Bmask = 0;
   173 	switch (biCompression) {
   174 		case BI_RGB:
   175 			/* If there are no masks, use the defaults */
   176 			if ( bfOffBits == (14+biSize) ) {
   177 				/* Default values for the BMP format */
   178 				switch (biBitCount) {
   179 					case 15:
   180 					case 16:
   181 						Rmask = 0x7C00;
   182 						Gmask = 0x03E0;
   183 						Bmask = 0x001F;
   184 						break;
   185 					case 24:
   186 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   187 					        Rmask = 0x000000FF;
   188 					        Gmask = 0x0000FF00;
   189 					        Bmask = 0x00FF0000;
   190 						break;
   191 #endif
   192 					case 32:
   193 						Rmask = 0x00FF0000;
   194 						Gmask = 0x0000FF00;
   195 						Bmask = 0x000000FF;
   196 						break;
   197 					default:
   198 						break;
   199 				}
   200 				break;
   201 			}
   202 			/* Fall through -- read the RGB masks */
   203 
   204 		case BI_BITFIELDS:
   205 			switch (biBitCount) {
   206 				case 15:
   207 				case 16:
   208 				case 32:
   209 					Rmask = SDL_ReadLE32(src);
   210 					Gmask = SDL_ReadLE32(src);
   211 					Bmask = SDL_ReadLE32(src);
   212 					break;
   213 				default:
   214 					break;
   215 			}
   216 			break;
   217 		default:
   218 			SDL_SetError("Compressed BMP files not supported");
   219 			was_error = SDL_TRUE;
   220 			goto done;
   221 	}
   222 
   223 	/* Create a compatible surface, note that the colors are RGB ordered */
   224 	surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
   225 			biWidth, biHeight, biBitCount, Rmask, Gmask, Bmask, 0);
   226 	if ( surface == NULL ) {
   227 		was_error = SDL_TRUE;
   228 		goto done;
   229 	}
   230 
   231 	/* Load the palette, if any */
   232 	palette = (surface->format)->palette;
   233 	if ( palette ) {
   234 		if ( biClrUsed == 0 ) {
   235 			biClrUsed = 1 << biBitCount;
   236 		} else if ( biClrUsed > (1 << biBitCount) ) {
   237 			SDL_SetError("BMP file has an invalid number of colors");
   238 			was_error = SDL_TRUE;
   239 			goto done;
   240 		}
   241 		if ( biSize == 12 ) {
   242 			for ( i = 0; i < (int)biClrUsed; ++i ) {
   243 				SDL_RWread(src, &palette->colors[i].b, 1, 1);
   244 				SDL_RWread(src, &palette->colors[i].g, 1, 1);
   245 				SDL_RWread(src, &palette->colors[i].r, 1, 1);
   246 				palette->colors[i].unused = 0;
   247 			}	
   248 		} else {
   249 			for ( i = 0; i < (int)biClrUsed; ++i ) {
   250 				SDL_RWread(src, &palette->colors[i].b, 1, 1);
   251 				SDL_RWread(src, &palette->colors[i].g, 1, 1);
   252 				SDL_RWread(src, &palette->colors[i].r, 1, 1);
   253 				SDL_RWread(src, &palette->colors[i].unused, 1, 1);
   254 			}	
   255 		}
   256 		palette->ncolors = biClrUsed;
   257 	}
   258 
   259 	/* Read the surface pixels.  Note that the bmp image is upside down */
   260 	if ( SDL_RWseek(src, fp_offset+bfOffBits, RW_SEEK_SET) < 0 ) {
   261 		SDL_Error(SDL_EFSEEK);
   262 		was_error = SDL_TRUE;
   263 		goto done;
   264 	}
   265 	top = (Uint8 *)surface->pixels;
   266 	end = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
   267 	switch (ExpandBMP) {
   268 		case 1:
   269 			bmpPitch = (biWidth + 7) >> 3;
   270 			pad  = (((bmpPitch)%4) ? (4-((bmpPitch)%4)) : 0);
   271 			break;
   272 		case 4:
   273 			bmpPitch = (biWidth + 1) >> 1;
   274 			pad  = (((bmpPitch)%4) ? (4-((bmpPitch)%4)) : 0);
   275 			break;
   276 		default:
   277 			pad  = ((surface->pitch%4) ?
   278 					(4-(surface->pitch%4)) : 0);
   279 			break;
   280 	}
   281 	if ( topDown ) {
   282 		bits = top;
   283 	} else {
   284 		bits = end - surface->pitch;
   285 	}
   286 	while ( bits >= top && bits < end ) {
   287 		switch (ExpandBMP) {
   288 			case 1:
   289 			case 4: {
   290 			Uint8 pixel = 0;
   291 			int   shift = (8-ExpandBMP);
   292 			for ( i=0; i<surface->w; ++i ) {
   293 				if ( i%(8/ExpandBMP) == 0 ) {
   294 					if ( !SDL_RWread(src, &pixel, 1, 1) ) {
   295 						SDL_SetError(
   296 					"Error reading from BMP");
   297 						was_error = SDL_TRUE;
   298 						goto done;
   299 					}
   300 				}
   301 				*(bits+i) = (pixel>>shift);
   302 				pixel <<= ExpandBMP;
   303 			} }
   304 			break;
   305 
   306 			default:
   307 			if ( SDL_RWread(src, bits, 1, surface->pitch)
   308 							 != surface->pitch ) {
   309 				SDL_Error(SDL_EFREAD);
   310 				was_error = SDL_TRUE;
   311 				goto done;
   312 			}
   313 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
   314 			/* Byte-swap the pixels if needed. Note that the 24bpp
   315 			   case has already been taken care of above. */
   316 			switch(biBitCount) {
   317 				case 15:
   318 				case 16: {
   319 				        Uint16 *pix = (Uint16 *)bits;
   320 					for(i = 0; i < surface->w; i++)
   321 					        pix[i] = SDL_Swap16(pix[i]);
   322 					break;
   323 				}
   324 
   325 				case 32: {
   326 				        Uint32 *pix = (Uint32 *)bits;
   327 					for(i = 0; i < surface->w; i++)
   328 					        pix[i] = SDL_Swap32(pix[i]);
   329 					break;
   330 				}
   331 			}
   332 #endif
   333 			break;
   334 		}
   335 		/* Skip padding bytes, ugh */
   336 		if ( pad ) {
   337 			Uint8 padbyte;
   338 			for ( i=0; i<pad; ++i ) {
   339 				SDL_RWread(src, &padbyte, 1, 1);
   340 			}
   341 		}
   342 		if ( topDown ) {
   343 			bits += surface->pitch;
   344 		} else {
   345 			bits -= surface->pitch;
   346 		}
   347 	}
   348 done:
   349 	if ( was_error ) {
   350 		if ( src ) {
   351 			SDL_RWseek(src, fp_offset, RW_SEEK_SET);
   352 		}
   353 		if ( surface ) {
   354 			SDL_FreeSurface(surface);
   355 		}
   356 		surface = NULL;
   357 	}
   358 	if ( freesrc && src ) {
   359 		SDL_RWclose(src);
   360 	}
   361 	return(surface);
   362 }
   363 
   364 int SDL_SaveBMP_RW (SDL_Surface *saveme, SDL_RWops *dst, int freedst)
   365 {
   366 	long fp_offset;
   367 	int i, pad;
   368 	SDL_Surface *surface;
   369 	Uint8 *bits;
   370 
   371 	/* The Win32 BMP file header (14 bytes) */
   372 	char   magic[2] = { 'B', 'M' };
   373 	Uint32 bfSize;
   374 	Uint16 bfReserved1;
   375 	Uint16 bfReserved2;
   376 	Uint32 bfOffBits;
   377 
   378 	/* The Win32 BITMAPINFOHEADER struct (40 bytes) */
   379 	Uint32 biSize;
   380 	Sint32 biWidth;
   381 	Sint32 biHeight;
   382 	Uint16 biPlanes;
   383 	Uint16 biBitCount;
   384 	Uint32 biCompression;
   385 	Uint32 biSizeImage;
   386 	Sint32 biXPelsPerMeter;
   387 	Sint32 biYPelsPerMeter;
   388 	Uint32 biClrUsed;
   389 	Uint32 biClrImportant;
   390 
   391 	/* Make sure we have somewhere to save */
   392 	surface = NULL;
   393 	if ( dst ) {
   394 		if ( saveme->format->palette ) {
   395 			if ( saveme->format->BitsPerPixel == 8 ) {
   396 				surface = saveme;
   397 			} else {
   398 				SDL_SetError("%d bpp BMP files not supported",
   399 						saveme->format->BitsPerPixel);
   400 			}
   401 		}
   402 		else if ( (saveme->format->BitsPerPixel == 24) &&
   403 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   404 				(saveme->format->Rmask == 0x00FF0000) &&
   405 				(saveme->format->Gmask == 0x0000FF00) &&
   406 				(saveme->format->Bmask == 0x000000FF)
   407 #else
   408 				(saveme->format->Rmask == 0x000000FF) &&
   409 				(saveme->format->Gmask == 0x0000FF00) &&
   410 				(saveme->format->Bmask == 0x00FF0000)
   411 #endif
   412 			  ) {
   413 			surface = saveme;
   414 		} else {
   415 			SDL_Rect bounds;
   416 
   417 			/* Convert to 24 bits per pixel */
   418 			surface = SDL_CreateRGBSurface(SDL_SWSURFACE,
   419 					saveme->w, saveme->h, 24,
   420 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
   421 					0x00FF0000, 0x0000FF00, 0x000000FF,
   422 #else
   423 					0x000000FF, 0x0000FF00, 0x00FF0000,
   424 #endif
   425 					0);
   426 			if ( surface != NULL ) {
   427 				bounds.x = 0;
   428 				bounds.y = 0;
   429 				bounds.w = saveme->w;
   430 				bounds.h = saveme->h;
   431 				if ( SDL_LowerBlit(saveme, &bounds, surface,
   432 							&bounds) < 0 ) {
   433 					SDL_FreeSurface(surface);
   434 					SDL_SetError(
   435 					"Couldn't convert image to 24 bpp");
   436 					surface = NULL;
   437 				}
   438 			}
   439 		}
   440 	}
   441 
   442 	if ( surface && (SDL_LockSurface(surface) == 0) ) {
   443 		const int bw = surface->w*surface->format->BytesPerPixel;
   444 
   445 		/* Set the BMP file header values */
   446 		bfSize = 0;		 /* We'll write this when we're done */
   447 		bfReserved1 = 0;
   448 		bfReserved2 = 0;
   449 		bfOffBits = 0;		/* We'll write this when we're done */
   450 
   451 		/* Write the BMP file header values */
   452 		fp_offset = SDL_RWtell(dst);
   453 		SDL_ClearError();
   454 		SDL_RWwrite(dst, magic, 2, 1);
   455 		SDL_WriteLE32(dst, bfSize);
   456 		SDL_WriteLE16(dst, bfReserved1);
   457 		SDL_WriteLE16(dst, bfReserved2);
   458 		SDL_WriteLE32(dst, bfOffBits);
   459 
   460 		/* Set the BMP info values */
   461 		biSize = 40;
   462 		biWidth = surface->w;
   463 		biHeight = surface->h;
   464 		biPlanes = 1;
   465 		biBitCount = surface->format->BitsPerPixel;
   466 		biCompression = BI_RGB;
   467 		biSizeImage = surface->h*surface->pitch;
   468 		biXPelsPerMeter = 0;
   469 		biYPelsPerMeter = 0;
   470 		if ( surface->format->palette ) {
   471 			biClrUsed = surface->format->palette->ncolors;
   472 		} else {
   473 			biClrUsed = 0;
   474 		}
   475 		biClrImportant = 0;
   476 
   477 		/* Write the BMP info values */
   478 		SDL_WriteLE32(dst, biSize);
   479 		SDL_WriteLE32(dst, biWidth);
   480 		SDL_WriteLE32(dst, biHeight);
   481 		SDL_WriteLE16(dst, biPlanes);
   482 		SDL_WriteLE16(dst, biBitCount);
   483 		SDL_WriteLE32(dst, biCompression);
   484 		SDL_WriteLE32(dst, biSizeImage);
   485 		SDL_WriteLE32(dst, biXPelsPerMeter);
   486 		SDL_WriteLE32(dst, biYPelsPerMeter);
   487 		SDL_WriteLE32(dst, biClrUsed);
   488 		SDL_WriteLE32(dst, biClrImportant);
   489 
   490 		/* Write the palette (in BGR color order) */
   491 		if ( surface->format->palette ) {
   492 			SDL_Color *colors;
   493 			int       ncolors;
   494 
   495 			colors = surface->format->palette->colors;
   496 			ncolors = surface->format->palette->ncolors;
   497 			for ( i=0; i<ncolors; ++i ) {
   498 				SDL_RWwrite(dst, &colors[i].b, 1, 1);
   499 				SDL_RWwrite(dst, &colors[i].g, 1, 1);
   500 				SDL_RWwrite(dst, &colors[i].r, 1, 1);
   501 				SDL_RWwrite(dst, &colors[i].unused, 1, 1);
   502 			}
   503 		}
   504 
   505 		/* Write the bitmap offset */
   506 		bfOffBits = SDL_RWtell(dst)-fp_offset;
   507 		if ( SDL_RWseek(dst, fp_offset+10, RW_SEEK_SET) < 0 ) {
   508 			SDL_Error(SDL_EFSEEK);
   509 		}
   510 		SDL_WriteLE32(dst, bfOffBits);
   511 		if ( SDL_RWseek(dst, fp_offset+bfOffBits, RW_SEEK_SET) < 0 ) {
   512 			SDL_Error(SDL_EFSEEK);
   513 		}
   514 
   515 		/* Write the bitmap image upside down */
   516 		bits = (Uint8 *)surface->pixels+(surface->h*surface->pitch);
   517 		pad  = ((bw%4) ? (4-(bw%4)) : 0);
   518 		while ( bits > (Uint8 *)surface->pixels ) {
   519 			bits -= surface->pitch;
   520 			if ( SDL_RWwrite(dst, bits, 1, bw) != bw) {
   521 				SDL_Error(SDL_EFWRITE);
   522 				break;
   523 			}
   524 			if ( pad ) {
   525 				const Uint8 padbyte = 0;
   526 				for ( i=0; i<pad; ++i ) {
   527 					SDL_RWwrite(dst, &padbyte, 1, 1);
   528 				}
   529 			}
   530 		}
   531 
   532 		/* Write the BMP file size */
   533 		bfSize = SDL_RWtell(dst)-fp_offset;
   534 		if ( SDL_RWseek(dst, fp_offset+2, RW_SEEK_SET) < 0 ) {
   535 			SDL_Error(SDL_EFSEEK);
   536 		}
   537 		SDL_WriteLE32(dst, bfSize);
   538 		if ( SDL_RWseek(dst, fp_offset+bfSize, RW_SEEK_SET) < 0 ) {
   539 			SDL_Error(SDL_EFSEEK);
   540 		}
   541 
   542 		/* Close it up.. */
   543 		SDL_UnlockSurface(surface);
   544 		if ( surface != saveme ) {
   545 			SDL_FreeSurface(surface);
   546 		}
   547 	}
   548 
   549 	if ( freedst && dst ) {
   550 		SDL_RWclose(dst);
   551 	}
   552 	return((SDL_strcmp(SDL_GetError(), "") == 0) ? 0 : -1);
   553 }