/** @file Provides services to convert a BMP graphics image to a GOP BLT buffer and from a GOP BLT buffer to a BMP graphics image. Caution: This module requires additional review when modified. This module processes external input - BMP image. This external input must be validated carefully to avoid security issue such as buffer overflow, integer overflow. TranslateBmpToGopBlt() receives untrusted input and performs basic validation. Copyright (c) 2016-2017, Microsoft Corporation Copyright (c) 2018, Intel Corporation. All rights reserved.
All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include // // BMP Image header for an uncompressed 24-bit per pixel BMP image. // const BMP_IMAGE_HEADER mBmpImageHeaderTemplate = { 'B', // CharB 'M', // CharM 0, // Size will be updated at runtime {0, 0}, // Reserved sizeof (BMP_IMAGE_HEADER), // ImageOffset sizeof (BMP_IMAGE_HEADER) - OFFSET_OF (BMP_IMAGE_HEADER, HeaderSize), // HeaderSize 0, // PixelWidth will be updated at runtime 0, // PixelHeight will be updated at runtime 1, // Planes 24, // BitPerPixel 0, // CompressionType 0, // ImageSize will be updated at runtime 0, // XPixelsPerMeter 0, // YPixelsPerMeter 0, // NumberOfColors 0 // ImportantColors }; /** Translate a *.BMP graphics image to a GOP blt buffer. If a NULL Blt buffer is passed in a GopBlt buffer will be allocated by this routine using EFI_BOOT_SERVICES.AllocatePool(). If a GopBlt buffer is passed in it will be used if it is big enough. @param[in] BmpImage Pointer to BMP file. @param[in] BmpImageSize Number of bytes in BmpImage. @param[in, out] GopBlt Buffer containing GOP version of BmpImage. @param[in, out] GopBltSize Size of GopBlt in bytes. @param[out] PixelHeight Height of GopBlt/BmpImage in pixels. @param[out] PixelWidth Width of GopBlt/BmpImage in pixels. @retval RETURN_SUCCESS GopBlt and GopBltSize are returned. @retval RETURN_INVALID_PARAMETER BmpImage is NULL. @retval RETURN_INVALID_PARAMETER GopBlt is NULL. @retval RETURN_INVALID_PARAMETER GopBltSize is NULL. @retval RETURN_INVALID_PARAMETER PixelHeight is NULL. @retval RETURN_INVALID_PARAMETER PixelWidth is NULL. @retval RETURN_UNSUPPORTED BmpImage is not a valid *.BMP image. @retval RETURN_BUFFER_TOO_SMALL The passed in GopBlt buffer is not big enough. The required size is returned in GopBltSize. @retval RETURN_OUT_OF_RESOURCES The GopBlt buffer could not be allocated. **/ RETURN_STATUS EFIAPI TranslateBmpToGopBlt ( IN VOID *BmpImage, IN UINTN BmpImageSize, IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL **GopBlt, IN OUT UINTN *GopBltSize, OUT UINTN *PixelHeight, OUT UINTN *PixelWidth ) { UINT8 *Image; UINT8 *ImageHeader; BMP_IMAGE_HEADER *BmpHeader; BMP_COLOR_MAP *BmpColorMap; EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer; EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Blt; UINT32 BltBufferSize; UINTN Index; UINTN Height; UINTN Width; UINTN ImageIndex; UINT32 DataSizePerLine; BOOLEAN IsAllocated; UINT32 ColorMapNum; RETURN_STATUS Status; UINT32 DataSize; UINT32 Temp; if (BmpImage == NULL || GopBlt == NULL || GopBltSize == NULL) { return RETURN_INVALID_PARAMETER; } if (PixelHeight == NULL || PixelWidth == NULL) { return RETURN_INVALID_PARAMETER; } if (BmpImageSize < sizeof (BMP_IMAGE_HEADER)) { DEBUG ((DEBUG_ERROR, "TranslateBmpToGopBlt: BmpImageSize too small\n")); return RETURN_UNSUPPORTED; } BmpHeader = (BMP_IMAGE_HEADER *)BmpImage; if (BmpHeader->CharB != 'B' || BmpHeader->CharM != 'M') { DEBUG ((DEBUG_ERROR, "TranslateBmpToGopBlt: BmpHeader->Char fields incorrect\n")); return RETURN_UNSUPPORTED; } // // Doesn't support compress. // if (BmpHeader->CompressionType != 0) { DEBUG ((DEBUG_ERROR, "TranslateBmpToGopBlt: Compression Type unsupported.\n")); return RETURN_UNSUPPORTED; } if ((BmpHeader->PixelHeight == 0) || (BmpHeader->PixelWidth == 0)) { DEBUG ((DEBUG_ERROR, "TranslateBmpToGopBlt: BmpHeader->PixelHeight or BmpHeader->PixelWidth is 0.\n")); return RETURN_UNSUPPORTED; } // // Only support BITMAPINFOHEADER format. // BITMAPFILEHEADER + BITMAPINFOHEADER = BMP_IMAGE_HEADER // if (BmpHeader->HeaderSize != sizeof (BMP_IMAGE_HEADER) - OFFSET_OF (BMP_IMAGE_HEADER, HeaderSize)) { DEBUG (( DEBUG_ERROR, "TranslateBmpToGopBlt: BmpHeader->Headership is not as expected. Headersize is 0x%x\n", BmpHeader->HeaderSize )); return RETURN_UNSUPPORTED; } // // The data size in each line must be 4 byte alignment. // Status = SafeUint32Mult ( BmpHeader->PixelWidth, BmpHeader->BitPerPixel, &DataSizePerLine ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "TranslateBmpToGopBlt: invalid BmpImage... PixelWidth:0x%x BitPerPixel:0x%x\n", BmpHeader->PixelWidth, BmpHeader->BitPerPixel )); return RETURN_UNSUPPORTED; } Status = SafeUint32Add (DataSizePerLine, 31, &DataSizePerLine); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "TranslateBmpToGopBlt: invalid BmpImage... DataSizePerLine:0x%x\n", DataSizePerLine )); return RETURN_UNSUPPORTED; } DataSizePerLine = (DataSizePerLine >> 3) &(~0x3); Status = SafeUint32Mult ( DataSizePerLine, BmpHeader->PixelHeight, &BltBufferSize ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "TranslateBmpToGopBlt: invalid BmpImage... DataSizePerLine:0x%x PixelHeight:0x%x\n", DataSizePerLine, BmpHeader->PixelHeight )); return RETURN_UNSUPPORTED; } Status = SafeUint32Mult ( BmpHeader->PixelHeight, DataSizePerLine, &DataSize ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "TranslateBmpToGopBlt: invalid BmpImage... PixelHeight:0x%x DataSizePerLine:0x%x\n", BmpHeader->PixelHeight, DataSizePerLine )); return RETURN_UNSUPPORTED; } if ((BmpHeader->Size != BmpImageSize) || (BmpHeader->Size < BmpHeader->ImageOffset) || (BmpHeader->Size - BmpHeader->ImageOffset != DataSize)) { DEBUG ((DEBUG_ERROR, "TranslateBmpToGopBlt: invalid BmpImage... \n")); DEBUG ((DEBUG_ERROR, " BmpHeader->Size: 0x%x\n", BmpHeader->Size)); DEBUG ((DEBUG_ERROR, " BmpHeader->ImageOffset: 0x%x\n", BmpHeader->ImageOffset)); DEBUG ((DEBUG_ERROR, " BmpImageSize: 0x%lx\n", (UINTN)BmpImageSize)); DEBUG ((DEBUG_ERROR, " DataSize: 0x%lx\n", (UINTN)DataSize)); return RETURN_UNSUPPORTED; } // // Calculate Color Map offset in the image. // Image = BmpImage; BmpColorMap = (BMP_COLOR_MAP *)(Image + sizeof (BMP_IMAGE_HEADER)); if (BmpHeader->ImageOffset < sizeof (BMP_IMAGE_HEADER)) { return RETURN_UNSUPPORTED; } if (BmpHeader->ImageOffset > sizeof (BMP_IMAGE_HEADER)) { switch (BmpHeader->BitPerPixel) { case 1: ColorMapNum = 2; break; case 4: ColorMapNum = 16; break; case 8: ColorMapNum = 256; break; default: ColorMapNum = 0; break; } // // BMP file may has padding data between the bmp header section and the // bmp data section. // if (BmpHeader->ImageOffset - sizeof (BMP_IMAGE_HEADER) < sizeof (BMP_COLOR_MAP) * ColorMapNum) { return RETURN_UNSUPPORTED; } } // // Calculate graphics image data address in the image // Image = ((UINT8 *)BmpImage) + BmpHeader->ImageOffset; ImageHeader = Image; // // Calculate the BltBuffer needed size. // Status = SafeUint32Mult ( BmpHeader->PixelWidth, BmpHeader->PixelHeight, &BltBufferSize ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "TranslateBmpToGopBlt: invalid BltBuffer needed size... PixelWidth:0x%x PixelHeight:0x%x\n", BmpHeader->PixelWidth, BmpHeader->PixelHeight )); return RETURN_UNSUPPORTED; } Temp = BltBufferSize; Status = SafeUint32Mult ( BltBufferSize, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL), &BltBufferSize ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "TranslateBmpToGopBlt: invalid BltBuffer needed size... PixelWidth x PixelHeight:0x%x struct size:0x%x\n", Temp, sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL) )); return RETURN_UNSUPPORTED; } IsAllocated = FALSE; if (*GopBlt == NULL) { // // GopBlt is not allocated by caller. // DEBUG ((DEBUG_INFO, "Bmp Support: Allocating 0x%X bytes of memory\n", BltBufferSize)); *GopBltSize = (UINTN)BltBufferSize; *GopBlt = AllocatePool (*GopBltSize); IsAllocated = TRUE; if (*GopBlt == NULL) { return RETURN_OUT_OF_RESOURCES; } } else { // // GopBlt has been allocated by caller. // if (*GopBltSize < (UINTN)BltBufferSize) { *GopBltSize = (UINTN)BltBufferSize; return RETURN_BUFFER_TOO_SMALL; } } *PixelWidth = BmpHeader->PixelWidth; *PixelHeight = BmpHeader->PixelHeight; DEBUG ((DEBUG_INFO, "BmpHeader->ImageOffset 0x%X\n", BmpHeader->ImageOffset)); DEBUG ((DEBUG_INFO, "BmpHeader->PixelWidth 0x%X\n", BmpHeader->PixelWidth)); DEBUG ((DEBUG_INFO, "BmpHeader->PixelHeight 0x%X\n", BmpHeader->PixelHeight)); DEBUG ((DEBUG_INFO, "BmpHeader->BitPerPixel 0x%X\n", BmpHeader->BitPerPixel)); DEBUG ((DEBUG_INFO, "BmpHeader->ImageSize 0x%X\n", BmpHeader->ImageSize)); DEBUG ((DEBUG_INFO, "BmpHeader->HeaderSize 0x%X\n", BmpHeader->HeaderSize)); DEBUG ((DEBUG_INFO, "BmpHeader->Size 0x%X\n", BmpHeader->Size)); // // Translate image from BMP to Blt buffer format // BltBuffer = *GopBlt; for (Height = 0; Height < BmpHeader->PixelHeight; Height++) { Blt = &BltBuffer[ (BmpHeader->PixelHeight - Height - 1) * BmpHeader->PixelWidth]; for (Width = 0; Width < BmpHeader->PixelWidth; Width++, Image++, Blt++) { switch (BmpHeader->BitPerPixel) { case 1: // // Translate 1-bit (2 colors) BMP to 24-bit color // for (Index = 0; Index < 8 && Width < BmpHeader->PixelWidth; Index++) { Blt->Red = BmpColorMap[ ((*Image) >> (7 - Index)) & 0x1].Red; Blt->Green = BmpColorMap[ ((*Image) >> (7 - Index)) & 0x1].Green; Blt->Blue = BmpColorMap[ ((*Image) >> (7 - Index)) & 0x1].Blue; Blt++; Width++; } Blt--; Width--; break; case 4: // // Translate 4-bit (16 colors) BMP Palette to 24-bit color // Index = (*Image) >> 4; Blt->Red = BmpColorMap[Index].Red; Blt->Green = BmpColorMap[Index].Green; Blt->Blue = BmpColorMap[Index].Blue; if (Width < (BmpHeader->PixelWidth - 1)) { Blt++; Width++; Index = (*Image) & 0x0f; Blt->Red = BmpColorMap[Index].Red; Blt->Green = BmpColorMap[Index].Green; Blt->Blue = BmpColorMap[Index].Blue; } break; case 8: // // Translate 8-bit (256 colors) BMP Palette to 24-bit color // Blt->Red = BmpColorMap[*Image].Red; Blt->Green = BmpColorMap[*Image].Green; Blt->Blue = BmpColorMap[*Image].Blue; break; case 24: // // It is 24-bit BMP. // Blt->Blue = *Image++; Blt->Green = *Image++; Blt->Red = *Image; break; case 32: // //Conver 32 bit to 24bit bmp - just ignore the final byte of each pixel Blt->Blue = *Image++; Blt->Green = *Image++; Blt->Red = *Image++; break; default: // // Other bit format BMP is not supported. // if (IsAllocated) { FreePool (*GopBlt); *GopBlt = NULL; } DEBUG ((DEBUG_ERROR, "Bmp Bit format not supported. 0x%X\n", BmpHeader->BitPerPixel)); return RETURN_UNSUPPORTED; break; }; } ImageIndex = (UINTN)Image - (UINTN)ImageHeader; if ((ImageIndex % 4) != 0) { // // Bmp Image starts each row on a 32-bit boundary! // Image = Image + (4 - (ImageIndex % 4)); } } return RETURN_SUCCESS; } /** Translate a GOP blt buffer to an uncompressed 24-bit per pixel BMP graphics image. If a NULL BmpImage is passed in a BmpImage buffer will be allocated by this routine using EFI_BOOT_SERVICES.AllocatePool(). If a BmpImage buffer is passed in it will be used if it is big enough. @param [in] GopBlt Pointer to GOP blt buffer. @param [in] PixelHeight Height of GopBlt/BmpImage in pixels. @param [in] PixelWidth Width of GopBlt/BmpImage in pixels. @param [in, out] BmpImage Buffer containing BMP version of GopBlt. @param [in, out] BmpImageSize Size of BmpImage in bytes. @retval RETURN_SUCCESS BmpImage and BmpImageSize are returned. @retval RETURN_INVALID_PARAMETER GopBlt is NULL. @retval RETURN_INVALID_PARAMETER BmpImage is NULL. @retval RETURN_INVALID_PARAMETER BmpImageSize is NULL. @retval RETURN_UNSUPPORTED GopBlt cannot be converted to a *.BMP image. @retval RETURN_BUFFER_TOO_SMALL The passed in BmpImage buffer is not big enough. The required size is returned in BmpImageSize. @retval RETURN_OUT_OF_RESOURCES The BmpImage buffer could not be allocated. **/ RETURN_STATUS EFIAPI TranslateGopBltToBmp ( IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *GopBlt, IN UINT32 PixelHeight, IN UINT32 PixelWidth, IN OUT VOID **BmpImage, IN OUT UINT32 *BmpImageSize ) { RETURN_STATUS Status; UINT32 PaddingSize; UINT32 BmpSize; BMP_IMAGE_HEADER *BmpImageHeader; UINT8 *Image; UINTN Col; UINTN Row; EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltPixel; if (GopBlt == NULL || BmpImage == NULL || BmpImageSize == NULL) { return RETURN_INVALID_PARAMETER; } if ((PixelHeight == 0) || (PixelWidth == 0)) { return RETURN_UNSUPPORTED; } // // Allocate memory for BMP file. // PaddingSize = PixelWidth & 0x3; // // First check PixelWidth * 3 + PaddingSize doesn't overflow // Status = SafeUint32Mult (PixelWidth, 3, &BmpSize); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "TranslateGopBltToBmp: GopBlt is too large. PixelHeight:0x%x PixelWidth:0x%x\n", PixelHeight, PixelWidth )); return RETURN_UNSUPPORTED; } Status = SafeUint32Add (BmpSize, PaddingSize, &BmpSize); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "TranslateGopBltToBmp: GopBlt is too large. PixelHeight:0x%x PixelWidth:0x%x\n", PixelHeight, PixelWidth )); return RETURN_UNSUPPORTED; } // // Second check (mLogoWidth * 3 + PaddingSize) * mLogoHeight + sizeof (BMP_IMAGE_HEADER) doesn't overflow // Status = SafeUint32Mult (BmpSize, PixelHeight, &BmpSize); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "TranslateGopBltToBmp: GopBlt is too large. PixelHeight:0x%x PixelWidth:0x%x\n", PixelHeight, PixelWidth )); return RETURN_UNSUPPORTED; } Status = SafeUint32Add (BmpSize, sizeof (BMP_IMAGE_HEADER), &BmpSize); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "TranslateGopBltToBmp: GopBlt is too large. PixelHeight:0x%x PixelWidth:0x%x\n", PixelHeight, PixelWidth )); return RETURN_UNSUPPORTED; } // // The image should be stored in EfiBootServicesData, allowing the system to // reclaim the memory // if (*BmpImage == NULL) { *BmpImage = AllocateZeroPool (BmpSize); if (*BmpImage == NULL) { return EFI_OUT_OF_RESOURCES; } *BmpImageSize = BmpSize; } else if (*BmpImageSize < BmpSize) { *BmpImageSize = BmpSize; return RETURN_BUFFER_TOO_SMALL; } BmpImageHeader = (BMP_IMAGE_HEADER *)*BmpImage; CopyMem (BmpImageHeader, &mBmpImageHeaderTemplate, sizeof (BMP_IMAGE_HEADER)); BmpImageHeader->Size = *BmpImageSize; BmpImageHeader->ImageSize = *BmpImageSize - sizeof (BMP_IMAGE_HEADER); BmpImageHeader->PixelWidth = PixelWidth; BmpImageHeader->PixelHeight = PixelHeight; // // Convert BLT buffer to BMP file. // Image = (UINT8 *)BmpImageHeader + sizeof (BMP_IMAGE_HEADER); for (Row = 0; Row < PixelHeight; Row++) { BltPixel = &GopBlt[(PixelHeight - Row - 1) * PixelWidth]; for (Col = 0; Col < PixelWidth; Col++) { *Image++ = BltPixel->Blue; *Image++ = BltPixel->Green; *Image++ = BltPixel->Red; BltPixel++; } // // Padding for 4 byte alignment. // Image += PaddingSize; } return RETURN_SUCCESS; }