Changeset - r25572:a4cef7f43442
[Not reviewed]
master
0 1 0
rubidium42 - 3 years ago 2021-05-29 12:02:04
rubidium@openttd.org
Fix: limit heightmap sizes to something reasonable to prevent crafted heightmaps to OOM-crash the game
1 file changed with 38 insertions and 4 deletions:
0 comments (0 inline, 0 general)
src/heightmap.cpp
Show inline comments
 
@@ -14,24 +14,58 @@
 
#include "error.h"
 
#include "saveload/saveload.h"
 
#include "bmp.h"
 
#include "gfx_func.h"
 
#include "fios.h"
 
#include "fileio_func.h"
 

	
 
#include "table/strings.h"
 

	
 
#include "safeguards.h"
 

	
 
/**
 
 * Maximum number of pixels for one dimension of a heightmap image.
 
 * Do not allow images for which the longest side is twice the maximum number of
 
 * tiles along the longest side of the (tile) map.
 
 */
 
static const uint MAX_HEIGHTMAP_SIDE_LENGTH_IN_PIXELS = 2 * MAX_MAP_SIZE;
 

	
 
/*
 
 * Maximum size in pixels of the heightmap image.
 
 */
 
static const uint MAX_HEIGHTMAP_SIZE_PIXELS = 256 << 20; // ~256 million
 
/*
 
 * When loading a PNG or BMP the 24 bpp variant requires at least 4 bytes per pixel
 
 * of memory to load the data. Make sure the "reasonable" limit is well within the
 
 * maximum amount of memory allocatable on 32 bit platforms.
 
 */
 
static_assert(MAX_HEIGHTMAP_SIZE_PIXELS < UINT32_MAX / 8);
 

	
 
/**
 
 * Check whether the loaded dimension of the heightmap image are considered valid enough
 
 * to attempt to load the image. In other words, the width and height are not beyond the
 
 * #MAX_HEIGHTMAP_SIDE_LENGTH_IN_PIXELS limit and the total number of pixels does not
 
 * exceed #MAX_HEIGHTMAP_SIZE_PIXELS. A width or height less than 1 are disallowed too.
 
 * @param width The width of the to be loaded height map.
 
 * @param height The height of the to be loaded height map.
 
 * @return True iff the dimensions are within the limits.
 
 */
 
static inline bool IsValidHeightmapDimension(size_t width, size_t height)
 
{
 
	return (uint64)width * height <= MAX_HEIGHTMAP_SIZE_PIXELS &&
 
		width > 0 && width <= MAX_HEIGHTMAP_SIDE_LENGTH_IN_PIXELS &&
 
		height > 0 && height <= MAX_HEIGHTMAP_SIDE_LENGTH_IN_PIXELS;
 
}
 

	
 
/**
 
 * Convert RGB colours to Grayscale using 29.9% Red, 58.7% Green, 11.4% Blue
 
 *  (average luminosity formula, NTSC Colour Space)
 
 */
 
static inline byte RGBToGrayscale(byte red, byte green, byte blue)
 
{
 
	/* To avoid doubles and stuff, multiply it with a total of 65536 (16bits), then
 
	 *  divide by it to normalize the value to a byte again. */
 
	return ((red * 19595) + (green * 38470) + (blue * 7471)) / 65536;
 
}
 

	
 

	
 
#ifdef WITH_PNG
 
@@ -137,26 +171,25 @@ static bool ReadHeightmapPNG(const char 
 
	/* Maps of wrong colour-depth are not used.
 
	 * (this should have been taken care of by stripping alpha and 16-bit samples on load) */
 
	if ((png_get_channels(png_ptr, info_ptr) != 1) && (png_get_channels(png_ptr, info_ptr) != 3) && (png_get_bit_depth(png_ptr, info_ptr) != 8)) {
 
		ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_PNGMAP_IMAGE_TYPE, WL_ERROR);
 
		fclose(fp);
 
		png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
 
		return false;
 
	}
 

	
 
	uint width = png_get_image_width(png_ptr, info_ptr);
 
	uint height = png_get_image_height(png_ptr, info_ptr);
 

	
 
	/* Check if image dimensions don't overflow a size_t to avoid memory corruption. */
 
	if ((uint64)width * height >= (size_t)-1) {
 
	if (!IsValidHeightmapDimension(width, height)) {
 
		ShowErrorMessage(STR_ERROR_PNGMAP, STR_ERROR_HEIGHTMAP_TOO_LARGE, WL_ERROR);
 
		fclose(fp);
 
		png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
 
		return false;
 
	}
 

	
 
	if (map != nullptr) {
 
		*map = MallocT<byte>(width * height);
 
		ReadHeightmapPNGImageData(*map, png_ptr, info_ptr);
 
	}
 

	
 
	*x = width;
 
@@ -246,26 +279,25 @@ static bool ReadHeightmapBMP(const char 
 
		return false;
 
	}
 

	
 
	BmpInitializeBuffer(&buffer, f);
 

	
 
	if (!BmpReadHeader(&buffer, &info, &data)) {
 
		ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_BMPMAP_IMAGE_TYPE, WL_ERROR);
 
		fclose(f);
 
		BmpDestroyData(&data);
 
		return false;
 
	}
 

	
 
	/* Check if image dimensions don't overflow a size_t to avoid memory corruption. */
 
	if ((uint64)info.width * info.height >= (size_t)-1 / (info.bpp == 24 ? 3 : 1)) {
 
	if (!IsValidHeightmapDimension(info.width, info.height)) {
 
		ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_HEIGHTMAP_TOO_LARGE, WL_ERROR);
 
		fclose(f);
 
		BmpDestroyData(&data);
 
		return false;
 
	}
 

	
 
	if (map != nullptr) {
 
		if (!BmpReadBitmap(&buffer, &info, &data)) {
 
			ShowErrorMessage(STR_ERROR_BMPMAP, STR_ERROR_BMPMAP_IMAGE_TYPE, WL_ERROR);
 
			fclose(f);
 
			BmpDestroyData(&data);
 
			return false;
 
@@ -286,24 +318,26 @@ static bool ReadHeightmapBMP(const char 
 

	
 
/**
 
 * Converts a given grayscale map to something that fits in OTTD map system
 
 * and create a map of that data.
 
 * @param img_width  the with of the image in pixels/tiles
 
 * @param img_height the height of the image in pixels/tiles
 
 * @param map        the input map
 
 */
 
static void GrayscaleToMapHeights(uint img_width, uint img_height, byte *map)
 
{
 
	/* Defines the detail of the aspect ratio (to avoid doubles) */
 
	const uint num_div = 16384;
 
	/* Ensure multiplication with num_div does not cause overflows. */
 
	static_assert(num_div <= std::numeric_limits<uint>::max() / MAX_HEIGHTMAP_SIDE_LENGTH_IN_PIXELS);
 

	
 
	uint width, height;
 
	uint row, col;
 
	uint row_pad = 0, col_pad = 0;
 
	uint img_scale;
 
	uint img_row, img_col;
 
	TileIndex tile;
 

	
 
	/* Get map size and calculate scale and padding values */
 
	switch (_settings_game.game_creation.heightmap_rotation) {
 
		default: NOT_REACHED();
 
		case HM_COUNTER_CLOCKWISE:
0 comments (0 inline, 0 general)