/** @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;
}