mirror of
https://git.savannah.gnu.org/git/grub.git
synced 2026-10-21 14:59:34 +00:00
In grub-core/video/readers/jpeg.c, the height and width of a JPEG image don't have an upper limit for how big the JPEG image can be. In Coverity, this is getting flagged as an untrusted loop bound. This issue can also seen in PNG and TGA format images as well but Coverity isn't flagging it. To prevent this, the constant IMAGE_HW_MAX_PX is being added to include/grub/bitmap.h, which has a value of 16384, to act as an artificial limit and restrict the height and width of images. This value was picked as it is double the current max resolution size, which is 8K. Fixes: CID 292450 Signed-off-by: Alec Brown <alec.r.brown@oracle.com> Reviewed-by: Darren Kenny <darren.kenny@oracle.com> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
1153 lines
25 KiB
C
1153 lines
25 KiB
C
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2008,2009 Free Software Foundation, Inc.
|
|
*
|
|
* GRUB is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GRUB is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <grub/bitmap.h>
|
|
#include <grub/types.h>
|
|
#include <grub/normal.h>
|
|
#include <grub/dl.h>
|
|
#include <grub/mm.h>
|
|
#include <grub/misc.h>
|
|
#include <grub/bufio.h>
|
|
#include <grub/safemath.h>
|
|
|
|
GRUB_MOD_LICENSE ("GPLv3+");
|
|
|
|
/* Uncomment following define to enable PNG debug. */
|
|
//#define PNG_DEBUG
|
|
|
|
enum
|
|
{
|
|
PNG_COLOR_TYPE_GRAY = 0,
|
|
PNG_COLOR_MASK_PALETTE = 1,
|
|
PNG_COLOR_MASK_COLOR = 2,
|
|
PNG_COLOR_MASK_ALPHA = 4,
|
|
PNG_COLOR_TYPE_PALETTE = (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE),
|
|
};
|
|
|
|
#define PNG_COMPRESSION_BASE 0
|
|
|
|
#define PNG_INTERLACE_NONE 0
|
|
#define PNG_INTERLACE_ADAM7 1
|
|
|
|
#define PNG_FILTER_TYPE_BASE 0
|
|
|
|
#define PNG_FILTER_VALUE_NONE 0
|
|
#define PNG_FILTER_VALUE_SUB 1
|
|
#define PNG_FILTER_VALUE_UP 2
|
|
#define PNG_FILTER_VALUE_AVG 3
|
|
#define PNG_FILTER_VALUE_PAETH 4
|
|
#define PNG_FILTER_VALUE_LAST 5
|
|
|
|
enum
|
|
{
|
|
PNG_CHUNK_IHDR = 0x49484452,
|
|
PNG_CHUNK_IDAT = 0x49444154,
|
|
PNG_CHUNK_IEND = 0x49454e44,
|
|
PNG_CHUNK_PLTE = 0x504c5445
|
|
};
|
|
|
|
#define Z_DEFLATED 8
|
|
#define Z_FLAG_DICT 32
|
|
|
|
#define INFLATE_STORED 0
|
|
#define INFLATE_FIXED 1
|
|
#define INFLATE_DYNAMIC 2
|
|
|
|
#define WSIZE 0x8000
|
|
|
|
#define DEFLATE_HCLEN_BASE 4
|
|
#define DEFLATE_HCLEN_MAX 19
|
|
#define DEFLATE_HLIT_BASE 257
|
|
#define DEFLATE_HLIT_MAX 288
|
|
#define DEFLATE_HDIST_BASE 1
|
|
#define DEFLATE_HDIST_MAX 30
|
|
|
|
#define DEFLATE_HUFF_LEN 16
|
|
|
|
#ifdef PNG_DEBUG
|
|
static grub_command_t cmd;
|
|
#endif
|
|
|
|
struct huff_table
|
|
{
|
|
int *values, *maxval, *offset;
|
|
int num_values, max_length;
|
|
};
|
|
|
|
struct grub_png_data
|
|
{
|
|
grub_file_t file;
|
|
struct grub_video_bitmap **bitmap;
|
|
|
|
int bit_count, bit_save;
|
|
|
|
grub_uint32_t next_offset;
|
|
|
|
unsigned image_width, image_height;
|
|
int bpp, is_16bit;
|
|
int raw_bytes, is_alpha, is_palette;
|
|
int row_bytes, color_bits;
|
|
grub_uint8_t *image_data;
|
|
|
|
int inside_idat, idat_remain;
|
|
|
|
int code_values[DEFLATE_HLIT_MAX];
|
|
int code_maxval[DEFLATE_HUFF_LEN];
|
|
int code_offset[DEFLATE_HUFF_LEN];
|
|
|
|
int dist_values[DEFLATE_HDIST_MAX];
|
|
int dist_maxval[DEFLATE_HUFF_LEN];
|
|
int dist_offset[DEFLATE_HUFF_LEN];
|
|
|
|
grub_uint8_t palette[256][3];
|
|
|
|
struct huff_table code_table;
|
|
struct huff_table dist_table;
|
|
|
|
grub_uint8_t slide[WSIZE];
|
|
int wp;
|
|
|
|
grub_uint8_t *cur_rgb;
|
|
|
|
int cur_column, cur_filter, first_line;
|
|
};
|
|
|
|
static grub_uint32_t
|
|
grub_png_get_dword (struct grub_png_data *data)
|
|
{
|
|
grub_uint32_t r;
|
|
|
|
r = 0;
|
|
grub_file_read (data->file, &r, sizeof (grub_uint32_t));
|
|
|
|
return grub_be_to_cpu32 (r);
|
|
}
|
|
|
|
static grub_uint8_t
|
|
grub_png_get_byte (struct grub_png_data *data)
|
|
{
|
|
grub_uint8_t r;
|
|
grub_ssize_t bytes_read = 0;
|
|
|
|
if ((data->inside_idat) && (data->idat_remain == 0))
|
|
{
|
|
grub_uint32_t len, type;
|
|
|
|
do
|
|
{
|
|
/* Skip crc checksum. */
|
|
grub_png_get_dword (data);
|
|
|
|
if (data->file->offset != data->next_offset)
|
|
{
|
|
grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: chunk size error");
|
|
return 0;
|
|
}
|
|
|
|
len = grub_png_get_dword (data);
|
|
type = grub_png_get_dword (data);
|
|
if (type != PNG_CHUNK_IDAT)
|
|
{
|
|
grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: unexpected end of data");
|
|
return 0;
|
|
}
|
|
|
|
data->next_offset = data->file->offset + len + 4;
|
|
}
|
|
while (len == 0);
|
|
data->idat_remain = len;
|
|
}
|
|
|
|
r = 0;
|
|
bytes_read = grub_file_read (data->file, &r, 1);
|
|
|
|
if (bytes_read != 1)
|
|
{
|
|
grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: unexpected end of data");
|
|
return 0;
|
|
}
|
|
|
|
if (data->inside_idat)
|
|
data->idat_remain--;
|
|
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
grub_png_get_bits (struct grub_png_data *data, int num)
|
|
{
|
|
int code, shift;
|
|
|
|
if (data->bit_count == 0)
|
|
{
|
|
data->bit_save = grub_png_get_byte (data);
|
|
data->bit_count = 8;
|
|
}
|
|
|
|
code = 0;
|
|
shift = 0;
|
|
while (grub_errno == 0)
|
|
{
|
|
int n;
|
|
|
|
n = data->bit_count;
|
|
if (n > num)
|
|
n = num;
|
|
|
|
code += (int) (data->bit_save & ((1 << n) - 1)) << shift;
|
|
num -= n;
|
|
if (!num)
|
|
{
|
|
data->bit_count -= n;
|
|
data->bit_save >>= n;
|
|
break;
|
|
}
|
|
|
|
shift += n;
|
|
|
|
data->bit_save = grub_png_get_byte (data);
|
|
data->bit_count = 8;
|
|
}
|
|
|
|
return code;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_png_decode_image_palette (struct grub_png_data *data,
|
|
unsigned len)
|
|
{
|
|
unsigned i = 0, j;
|
|
|
|
if (len == 0)
|
|
return GRUB_ERR_NONE;
|
|
|
|
grub_errno = GRUB_ERR_NONE;
|
|
for (i = 0; 3 * i < len && i < 256 && grub_errno == GRUB_ERR_NONE; i++)
|
|
for (j = 0; j < 3; j++)
|
|
data->palette[i][j] = grub_png_get_byte (data);
|
|
for (i *= 3; i < len && grub_errno == GRUB_ERR_NONE; i++)
|
|
grub_png_get_byte (data);
|
|
|
|
grub_png_get_dword (data);
|
|
|
|
return grub_errno;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_png_decode_image_header (struct grub_png_data *data)
|
|
{
|
|
int color_type;
|
|
int color_bits;
|
|
enum grub_video_blit_format blt;
|
|
|
|
if (data->image_width || data->image_height)
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE, "png: two image headers found");
|
|
|
|
data->image_width = grub_png_get_dword (data);
|
|
data->image_height = grub_png_get_dword (data);
|
|
|
|
grub_dprintf ("png", "image height: %d\n", data->image_height);
|
|
grub_dprintf ("png", "image width: %d\n", data->image_width);
|
|
|
|
if ((!data->image_height) || (!data->image_width) ||
|
|
(data->image_height > IMAGE_HW_MAX_PX) || (data->image_width > IMAGE_HW_MAX_PX))
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE, "png: invalid image size");
|
|
|
|
color_bits = grub_png_get_byte (data);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
data->is_16bit = (color_bits == 16);
|
|
|
|
color_type = grub_png_get_byte (data);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
|
|
/* According to PNG spec, no other types are valid. */
|
|
if ((color_type & ~(PNG_COLOR_MASK_ALPHA | PNG_COLOR_MASK_COLOR))
|
|
&& (color_type != PNG_COLOR_TYPE_PALETTE))
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: color type not supported");
|
|
if (color_type == PNG_COLOR_TYPE_PALETTE)
|
|
data->is_palette = 1;
|
|
if (data->is_16bit && data->is_palette)
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: color type not supported");
|
|
if (color_type & PNG_COLOR_MASK_ALPHA)
|
|
blt = GRUB_VIDEO_BLIT_FORMAT_RGBA_8888;
|
|
else
|
|
blt = GRUB_VIDEO_BLIT_FORMAT_RGB_888;
|
|
if (data->is_palette)
|
|
data->bpp = 1;
|
|
else if (color_type & PNG_COLOR_MASK_COLOR)
|
|
data->bpp = 3;
|
|
else
|
|
{
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: color type not supported");
|
|
}
|
|
|
|
if ((color_bits != 8) && (color_bits != 16)
|
|
&& (color_bits != 4
|
|
|| !data->is_palette))
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: bit depth must be 8 or 16");
|
|
|
|
if (color_type & PNG_COLOR_MASK_ALPHA)
|
|
data->bpp++;
|
|
|
|
if (grub_video_bitmap_create (data->bitmap, data->image_width,
|
|
data->image_height,
|
|
blt))
|
|
return grub_errno;
|
|
|
|
if (data->is_16bit)
|
|
data->bpp <<= 1;
|
|
|
|
data->color_bits = color_bits;
|
|
|
|
if (grub_mul (data->image_width, data->bpp, &data->row_bytes))
|
|
return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected"));
|
|
|
|
if (data->color_bits <= 4)
|
|
{
|
|
if (grub_mul (data->image_width, data->color_bits + 7, &data->row_bytes))
|
|
return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("overflow is detected"));
|
|
|
|
data->row_bytes >>= 3;
|
|
}
|
|
|
|
#ifndef GRUB_CPU_WORDS_BIGENDIAN
|
|
if (data->is_16bit || data->is_palette)
|
|
#endif
|
|
{
|
|
data->image_data = grub_calloc (data->image_height, data->row_bytes);
|
|
if (grub_errno)
|
|
return grub_errno;
|
|
|
|
data->cur_rgb = data->image_data;
|
|
}
|
|
#ifndef GRUB_CPU_WORDS_BIGENDIAN
|
|
else
|
|
{
|
|
data->image_data = 0;
|
|
data->cur_rgb = (*data->bitmap)->data;
|
|
}
|
|
#endif
|
|
|
|
data->raw_bytes = data->image_height * (data->row_bytes + 1);
|
|
|
|
data->cur_column = 0;
|
|
data->first_line = 1;
|
|
|
|
if (grub_png_get_byte (data) != PNG_COMPRESSION_BASE)
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: compression method not supported");
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
|
|
if (grub_png_get_byte (data) != PNG_FILTER_TYPE_BASE)
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: filter method not supported");
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
|
|
if (grub_png_get_byte (data) != PNG_INTERLACE_NONE)
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: interlace method not supported");
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
|
|
/* Skip crc checksum. */
|
|
grub_png_get_dword (data);
|
|
|
|
return grub_errno;
|
|
}
|
|
|
|
/* Order of the bit length code lengths. */
|
|
static const grub_uint8_t bitorder[] = {
|
|
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
|
|
};
|
|
|
|
/* Copy lengths for literal codes 257..285. */
|
|
static const int cplens[] = {
|
|
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
|
|
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
|
|
};
|
|
|
|
/* Extra bits for literal codes 257..285. */
|
|
static const grub_uint8_t cplext[] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
|
|
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99
|
|
}; /* 99==invalid */
|
|
|
|
/* Copy offsets for distance codes 0..29. */
|
|
static const int cpdist[] = {
|
|
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
|
|
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
|
|
8193, 12289, 16385, 24577
|
|
};
|
|
|
|
/* Extra bits for distance codes. */
|
|
static const grub_uint8_t cpdext[] = {
|
|
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
|
|
7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
|
|
12, 12, 13, 13
|
|
};
|
|
|
|
static void
|
|
grub_png_init_huff_table (struct huff_table *ht, int cur_maxlen,
|
|
int *cur_values, int *cur_maxval, int *cur_offset)
|
|
{
|
|
ht->values = cur_values;
|
|
ht->maxval = cur_maxval;
|
|
ht->offset = cur_offset;
|
|
ht->num_values = 0;
|
|
ht->max_length = cur_maxlen;
|
|
grub_memset (cur_maxval, 0, sizeof (int) * cur_maxlen);
|
|
}
|
|
|
|
static void
|
|
grub_png_insert_huff_item (struct huff_table *ht, int code, int len)
|
|
{
|
|
int i, n;
|
|
|
|
if (len == 0)
|
|
return;
|
|
|
|
if (len > ht->max_length)
|
|
{
|
|
grub_error (GRUB_ERR_BAD_FILE_TYPE, "png: invalid code length");
|
|
return;
|
|
}
|
|
|
|
n = 0;
|
|
for (i = len; i < ht->max_length; i++)
|
|
n += ht->maxval[i];
|
|
|
|
if (n > ht->num_values)
|
|
{
|
|
grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: out of range inserting huffman table item");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < n; i++)
|
|
ht->values[ht->num_values - i] = ht->values[ht->num_values - i - 1];
|
|
|
|
ht->values[ht->num_values - n] = code;
|
|
ht->num_values++;
|
|
ht->maxval[len - 1]++;
|
|
}
|
|
|
|
static void
|
|
grub_png_build_huff_table (struct huff_table *ht)
|
|
{
|
|
int base, ofs, i;
|
|
|
|
base = 0;
|
|
ofs = 0;
|
|
for (i = 0; i < ht->max_length; i++)
|
|
{
|
|
base += ht->maxval[i];
|
|
ofs += ht->maxval[i];
|
|
|
|
ht->maxval[i] = base;
|
|
ht->offset[i] = ofs - base;
|
|
|
|
base <<= 1;
|
|
}
|
|
}
|
|
|
|
static int
|
|
grub_png_get_huff_code (struct grub_png_data *data, struct huff_table *ht)
|
|
{
|
|
int code, i;
|
|
|
|
code = 0;
|
|
for (i = 0; i < ht->max_length && grub_errno == GRUB_ERR_NONE; i++)
|
|
{
|
|
code = (code << 1) + grub_png_get_bits (data, 1);
|
|
if (code < ht->maxval[i])
|
|
return ht->values[code + ht->offset[i]];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_png_init_fixed_block (struct grub_png_data *data)
|
|
{
|
|
int i;
|
|
|
|
grub_png_init_huff_table (&data->code_table, DEFLATE_HUFF_LEN,
|
|
data->code_values, data->code_maxval,
|
|
data->code_offset);
|
|
|
|
for (i = 0; i < 144; i++)
|
|
grub_png_insert_huff_item (&data->code_table, i, 8);
|
|
|
|
for (; i < 256; i++)
|
|
grub_png_insert_huff_item (&data->code_table, i, 9);
|
|
|
|
for (; i < 280; i++)
|
|
grub_png_insert_huff_item (&data->code_table, i, 7);
|
|
|
|
for (; i < DEFLATE_HLIT_MAX; i++)
|
|
grub_png_insert_huff_item (&data->code_table, i, 8);
|
|
|
|
grub_png_build_huff_table (&data->code_table);
|
|
|
|
grub_png_init_huff_table (&data->dist_table, DEFLATE_HUFF_LEN,
|
|
data->dist_values, data->dist_maxval,
|
|
data->dist_offset);
|
|
|
|
for (i = 0; i < DEFLATE_HDIST_MAX; i++)
|
|
grub_png_insert_huff_item (&data->dist_table, i, 5);
|
|
|
|
grub_png_build_huff_table (&data->dist_table);
|
|
|
|
return grub_errno;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_png_init_dynamic_block (struct grub_png_data *data)
|
|
{
|
|
int nl, nd, nb, i, prev;
|
|
struct huff_table cl;
|
|
int cl_values[sizeof (bitorder)];
|
|
int cl_maxval[8];
|
|
int cl_offset[8];
|
|
grub_uint8_t lens[DEFLATE_HCLEN_MAX];
|
|
|
|
nl = DEFLATE_HLIT_BASE + grub_png_get_bits (data, 5);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
nd = DEFLATE_HDIST_BASE + grub_png_get_bits (data, 5);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
nb = DEFLATE_HCLEN_BASE + grub_png_get_bits (data, 4);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
|
|
if ((nl > DEFLATE_HLIT_MAX) || (nd > DEFLATE_HDIST_MAX) ||
|
|
(nb > DEFLATE_HCLEN_MAX))
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE, "png: too much data");
|
|
|
|
grub_png_init_huff_table (&cl, 8, cl_values, cl_maxval, cl_offset);
|
|
|
|
for (i = 0; i < nb; i++)
|
|
lens[bitorder[i]] = grub_png_get_bits (data, 3);
|
|
|
|
for (; i < DEFLATE_HCLEN_MAX; i++)
|
|
lens[bitorder[i]] = 0;
|
|
|
|
for (i = 0; i < DEFLATE_HCLEN_MAX; i++)
|
|
grub_png_insert_huff_item (&cl, i, lens[i]);
|
|
|
|
grub_png_build_huff_table (&cl);
|
|
|
|
grub_png_init_huff_table (&data->code_table, DEFLATE_HUFF_LEN,
|
|
data->code_values, data->code_maxval,
|
|
data->code_offset);
|
|
|
|
grub_png_init_huff_table (&data->dist_table, DEFLATE_HUFF_LEN,
|
|
data->dist_values, data->dist_maxval,
|
|
data->dist_offset);
|
|
|
|
prev = 0;
|
|
for (i = 0; i < nl + nd && grub_errno == GRUB_ERR_NONE; i++)
|
|
{
|
|
int n, code;
|
|
struct huff_table *ht;
|
|
|
|
if (grub_errno)
|
|
return grub_errno;
|
|
|
|
if (i < nl)
|
|
{
|
|
ht = &data->code_table;
|
|
code = i;
|
|
}
|
|
else
|
|
{
|
|
ht = &data->dist_table;
|
|
code = i - nl;
|
|
}
|
|
|
|
n = grub_png_get_huff_code (data, &cl);
|
|
if (n < 16)
|
|
{
|
|
grub_png_insert_huff_item (ht, code, n);
|
|
prev = n;
|
|
}
|
|
else if (n == 16)
|
|
{
|
|
int c;
|
|
|
|
c = 3 + grub_png_get_bits (data, 2);
|
|
while (c > 0)
|
|
{
|
|
grub_png_insert_huff_item (ht, code++, prev);
|
|
i++;
|
|
c--;
|
|
}
|
|
i--;
|
|
}
|
|
else if (n == 17)
|
|
i += 3 + grub_png_get_bits (data, 3) - 1;
|
|
else
|
|
i += 11 + grub_png_get_bits (data, 7) - 1;
|
|
}
|
|
|
|
grub_png_build_huff_table (&data->code_table);
|
|
grub_png_build_huff_table (&data->dist_table);
|
|
|
|
return grub_errno;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_png_output_byte (struct grub_png_data *data, grub_uint8_t n)
|
|
{
|
|
if (--data->raw_bytes < 0)
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE, "image size overflown");
|
|
|
|
if (data->cur_column == 0)
|
|
{
|
|
if (n >= PNG_FILTER_VALUE_LAST)
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE, "invalid filter value");
|
|
|
|
data->cur_filter = n;
|
|
}
|
|
else
|
|
*data->cur_rgb++ = n;
|
|
|
|
data->cur_column++;
|
|
if (data->cur_column == data->row_bytes + 1)
|
|
{
|
|
grub_uint8_t *blank_line = NULL;
|
|
grub_uint8_t *cur = data->cur_rgb - data->row_bytes;
|
|
grub_uint8_t *left = cur;
|
|
grub_uint8_t *up;
|
|
|
|
if (data->first_line)
|
|
{
|
|
blank_line = grub_zalloc (data->row_bytes);
|
|
if (blank_line == NULL)
|
|
return grub_errno;
|
|
|
|
up = blank_line;
|
|
}
|
|
else
|
|
up = cur - data->row_bytes;
|
|
|
|
switch (data->cur_filter)
|
|
{
|
|
case PNG_FILTER_VALUE_SUB:
|
|
{
|
|
int i;
|
|
|
|
cur += data->bpp;
|
|
for (i = data->bpp; i < data->row_bytes; i++, cur++, left++)
|
|
*cur += *left;
|
|
|
|
break;
|
|
}
|
|
case PNG_FILTER_VALUE_UP:
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < data->row_bytes; i++, cur++, up++)
|
|
*cur += *up;
|
|
|
|
break;
|
|
}
|
|
case PNG_FILTER_VALUE_AVG:
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < data->bpp; i++, cur++, up++)
|
|
*cur += *up >> 1;
|
|
|
|
for (; i < data->row_bytes; i++, cur++, up++, left++)
|
|
*cur += ((int) *up + (int) *left) >> 1;
|
|
|
|
break;
|
|
}
|
|
case PNG_FILTER_VALUE_PAETH:
|
|
{
|
|
int i;
|
|
grub_uint8_t *upper_left = up;
|
|
|
|
for (i = 0; i < data->bpp; i++, cur++, up++)
|
|
*cur += *up;
|
|
|
|
for (; i < data->row_bytes; i++, cur++, up++, left++, upper_left++)
|
|
{
|
|
int a, b, c, pa, pb, pc;
|
|
|
|
a = *left;
|
|
b = *up;
|
|
c = *upper_left;
|
|
|
|
pa = b - c;
|
|
pb = a - c;
|
|
pc = pa + pb;
|
|
|
|
if (pa < 0)
|
|
pa = -pa;
|
|
|
|
if (pb < 0)
|
|
pb = -pb;
|
|
|
|
if (pc < 0)
|
|
pc = -pc;
|
|
|
|
*cur += ((pa <= pb) && (pa <= pc)) ? a : (pb <= pc) ? b : c;
|
|
}
|
|
}
|
|
}
|
|
|
|
grub_free (blank_line);
|
|
|
|
data->cur_column = 0;
|
|
data->first_line = 0;
|
|
}
|
|
|
|
return grub_errno;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_png_read_dynamic_block (struct grub_png_data *data)
|
|
{
|
|
while (grub_errno == 0)
|
|
{
|
|
int n;
|
|
|
|
n = grub_png_get_huff_code (data, &data->code_table);
|
|
if (n < 256)
|
|
{
|
|
data->slide[data->wp] = n;
|
|
grub_png_output_byte (data, n);
|
|
|
|
data->wp++;
|
|
if (data->wp >= WSIZE)
|
|
data->wp = 0;
|
|
}
|
|
else if (n == 256)
|
|
break;
|
|
else
|
|
{
|
|
int len, dist, pos;
|
|
|
|
n -= 257;
|
|
if (((unsigned int) n) >= ARRAY_SIZE (cplens))
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: invalid huff code");
|
|
len = cplens[n];
|
|
if (cplext[n])
|
|
len += grub_png_get_bits (data, cplext[n]);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
|
|
n = grub_png_get_huff_code (data, &data->dist_table);
|
|
if (((unsigned int) n) >= ARRAY_SIZE (cpdist))
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: invalid huff code");
|
|
dist = cpdist[n];
|
|
if (cpdext[n])
|
|
dist += grub_png_get_bits (data, cpdext[n]);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
|
|
pos = data->wp - dist;
|
|
if (pos < 0)
|
|
pos += WSIZE;
|
|
|
|
while (len > 0 && grub_errno == GRUB_ERR_NONE)
|
|
{
|
|
data->slide[data->wp] = data->slide[pos];
|
|
grub_png_output_byte (data, data->slide[data->wp]);
|
|
|
|
data->wp++;
|
|
if (data->wp >= WSIZE)
|
|
data->wp = 0;
|
|
|
|
pos++;
|
|
if (pos >= WSIZE)
|
|
pos = 0;
|
|
|
|
len--;
|
|
}
|
|
}
|
|
}
|
|
|
|
return grub_errno;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_png_decode_image_data (struct grub_png_data *data)
|
|
{
|
|
grub_uint8_t cmf, flg;
|
|
int final;
|
|
|
|
cmf = grub_png_get_byte (data);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
flg = grub_png_get_byte (data);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
|
|
if ((cmf & 0xF) != Z_DEFLATED)
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: only support deflate compression method");
|
|
|
|
if (flg & Z_FLAG_DICT)
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: dictionary not supported");
|
|
|
|
do
|
|
{
|
|
int block_type;
|
|
|
|
final = grub_png_get_bits (data, 1);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
block_type = grub_png_get_bits (data, 2);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
|
|
switch (block_type)
|
|
{
|
|
case INFLATE_STORED:
|
|
{
|
|
grub_uint16_t i, len;
|
|
|
|
data->bit_count = 0;
|
|
len = grub_png_get_byte (data);
|
|
len += ((grub_uint16_t) grub_png_get_byte (data)) << 8;
|
|
|
|
/* Skip NLEN field. */
|
|
grub_png_get_byte (data);
|
|
grub_png_get_byte (data);
|
|
|
|
for (i = 0; i < len && grub_errno == GRUB_ERR_NONE; i++)
|
|
grub_png_output_byte (data, grub_png_get_byte (data));
|
|
|
|
break;
|
|
}
|
|
|
|
case INFLATE_FIXED:
|
|
grub_png_init_fixed_block (data);
|
|
grub_png_read_dynamic_block (data);
|
|
break;
|
|
|
|
case INFLATE_DYNAMIC:
|
|
grub_png_init_dynamic_block (data);
|
|
grub_png_read_dynamic_block (data);
|
|
break;
|
|
|
|
default:
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: unknown block type");
|
|
}
|
|
}
|
|
while ((!final) && (grub_errno == 0));
|
|
|
|
/* Skip adler checksum. */
|
|
grub_png_get_dword (data);
|
|
|
|
/* Skip crc checksum. */
|
|
grub_png_get_dword (data);
|
|
|
|
return grub_errno;
|
|
}
|
|
|
|
static const grub_uint8_t png_magic[8] =
|
|
{ 0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0x0a };
|
|
|
|
static void
|
|
grub_png_convert_image (struct grub_png_data *data)
|
|
{
|
|
unsigned i;
|
|
grub_uint8_t *d1, *d2;
|
|
|
|
d1 = (*data->bitmap)->data;
|
|
d2 = data->image_data + data->is_16bit;
|
|
|
|
#ifndef GRUB_CPU_WORDS_BIGENDIAN
|
|
#define R4 3
|
|
#define G4 2
|
|
#define B4 1
|
|
#define A4 0
|
|
#define R3 2
|
|
#define G3 1
|
|
#define B3 0
|
|
#else
|
|
#define R4 0
|
|
#define G4 1
|
|
#define B4 2
|
|
#define A4 3
|
|
#define R3 0
|
|
#define G3 1
|
|
#define B3 2
|
|
#endif
|
|
|
|
if (data->color_bits <= 4)
|
|
{
|
|
grub_uint8_t palette[16][3];
|
|
grub_uint8_t *d1c, *d2c;
|
|
int shift;
|
|
int mask = (1 << data->color_bits) - 1;
|
|
unsigned j;
|
|
|
|
grub_memcpy (palette, data->palette, 3 << data->color_bits);
|
|
d1c = d1;
|
|
d2c = d2;
|
|
for (j = 0; j < data->image_height; j++, d1c += data->image_width * 3,
|
|
d2c += data->row_bytes)
|
|
{
|
|
d1 = d1c;
|
|
d2 = d2c;
|
|
shift = 8 - data->color_bits;
|
|
for (i = 0; i < data->image_width; i++, d1 += 3)
|
|
{
|
|
grub_uint8_t col = (d2[0] >> shift) & mask;
|
|
d1[R3] = data->palette[col][2];
|
|
d1[G3] = data->palette[col][1];
|
|
d1[B3] = data->palette[col][0];
|
|
shift -= data->color_bits;
|
|
if (shift < 0)
|
|
{
|
|
d2++;
|
|
shift += 8;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (data->is_palette)
|
|
{
|
|
for (i = 0; i < (data->image_width * data->image_height);
|
|
i++, d1 += 3, d2++)
|
|
{
|
|
d1[R3] = data->palette[d2[0]][2];
|
|
d1[G3] = data->palette[d2[0]][1];
|
|
d1[B3] = data->palette[d2[0]][0];
|
|
}
|
|
return;
|
|
}
|
|
|
|
{
|
|
/* Only copy the upper 8 bit. */
|
|
#ifndef GRUB_CPU_WORDS_BIGENDIAN
|
|
for (i = 0; i < (data->image_width * data->image_height * data->bpp >> 1);
|
|
i++, d1++, d2 += 2)
|
|
*d1 = *d2;
|
|
#else
|
|
switch (data->bpp)
|
|
{
|
|
/* 16-bit with alpha. */
|
|
case 8:
|
|
for (i = 0; i < (data->image_width * data->image_height);
|
|
i++, d1 += 4, d2+=8)
|
|
{
|
|
d1[0] = d2[7];
|
|
d1[1] = d2[5];
|
|
d1[2] = d2[3];
|
|
d1[3] = d2[1];
|
|
}
|
|
break;
|
|
/* 16-bit without alpha. */
|
|
case 6:
|
|
for (i = 0; i < (data->image_width * data->image_height);
|
|
i++, d1 += 3, d2+=6)
|
|
{
|
|
d1[0] = d2[5];
|
|
d1[1] = d2[3];
|
|
d1[2] = d2[1];
|
|
}
|
|
break;
|
|
case 4:
|
|
/* 8-bit with alpha. */
|
|
for (i = 0; i < (data->image_width * data->image_height);
|
|
i++, d1 += 4, d2 += 4)
|
|
{
|
|
d1[0] = d2[3];
|
|
d1[1] = d2[2];
|
|
d1[2] = d2[1];
|
|
d1[3] = d2[0];
|
|
}
|
|
break;
|
|
/* 8-bit without alpha. */
|
|
case 3:
|
|
for (i = 0; i < (data->image_width * data->image_height);
|
|
i++, d1 += 3, d2 += 3)
|
|
{
|
|
d1[0] = d2[2];
|
|
d1[1] = d2[1];
|
|
d1[2] = d2[0];
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_png_decode_png (struct grub_png_data *data)
|
|
{
|
|
grub_uint8_t magic[8];
|
|
|
|
if (grub_file_read (data->file, &magic[0], 8) != 8)
|
|
return grub_errno;
|
|
|
|
if (grub_memcmp (magic, png_magic, sizeof (png_magic)))
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE, "png: not a png file");
|
|
|
|
while (1)
|
|
{
|
|
grub_uint32_t len, type;
|
|
|
|
len = grub_png_get_dword (data);
|
|
type = grub_png_get_dword (data);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
break;
|
|
data->next_offset = data->file->offset + len + 4;
|
|
|
|
switch (type)
|
|
{
|
|
case PNG_CHUNK_IHDR:
|
|
grub_png_decode_image_header (data);
|
|
break;
|
|
|
|
case PNG_CHUNK_PLTE:
|
|
grub_png_decode_image_palette (data, len);
|
|
break;
|
|
|
|
case PNG_CHUNK_IDAT:
|
|
data->inside_idat = 1;
|
|
data->idat_remain = len;
|
|
data->bit_count = 0;
|
|
|
|
grub_png_decode_image_data (data);
|
|
|
|
data->inside_idat = 0;
|
|
break;
|
|
|
|
case PNG_CHUNK_IEND:
|
|
if (data->image_data)
|
|
grub_png_convert_image (data);
|
|
|
|
return grub_errno;
|
|
|
|
default:
|
|
grub_file_seek (data->file, data->file->offset + len + 4);
|
|
}
|
|
|
|
if (grub_errno)
|
|
break;
|
|
|
|
if (data->file->offset != data->next_offset)
|
|
return grub_error (GRUB_ERR_BAD_FILE_TYPE,
|
|
"png: chunk size error");
|
|
}
|
|
|
|
return grub_errno;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_video_reader_png (struct grub_video_bitmap **bitmap,
|
|
const char *filename)
|
|
{
|
|
grub_file_t file;
|
|
struct grub_png_data *data;
|
|
|
|
file = grub_buffile_open (filename, GRUB_FILE_TYPE_PIXMAP, 0);
|
|
if (!file)
|
|
return grub_errno;
|
|
|
|
data = grub_zalloc (sizeof (*data));
|
|
if (data != NULL)
|
|
{
|
|
data->file = file;
|
|
data->bitmap = bitmap;
|
|
|
|
grub_png_decode_png (data);
|
|
|
|
grub_free (data->image_data);
|
|
grub_free (data);
|
|
}
|
|
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
{
|
|
grub_video_bitmap_destroy (*bitmap);
|
|
*bitmap = 0;
|
|
}
|
|
|
|
grub_file_close (file);
|
|
return grub_errno;
|
|
}
|
|
|
|
#if defined(PNG_DEBUG)
|
|
static grub_err_t
|
|
grub_cmd_pngtest (grub_command_t cmd_d __attribute__ ((unused)),
|
|
int argc, char **args)
|
|
{
|
|
struct grub_video_bitmap *bitmap = 0;
|
|
|
|
if (argc != 1)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
|
|
|
|
grub_video_reader_png (&bitmap, args[0]);
|
|
if (grub_errno != GRUB_ERR_NONE)
|
|
return grub_errno;
|
|
|
|
grub_video_bitmap_destroy (bitmap);
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
#endif
|
|
|
|
static struct grub_video_bitmap_reader png_reader = {
|
|
.extension = ".png",
|
|
.reader = grub_video_reader_png,
|
|
.next = 0
|
|
};
|
|
|
|
GRUB_MOD_INIT (png)
|
|
{
|
|
grub_video_bitmap_reader_register (&png_reader);
|
|
#if defined(PNG_DEBUG)
|
|
cmd = grub_register_command ("pngtest", grub_cmd_pngtest,
|
|
"FILE",
|
|
"Tests loading of PNG bitmap.");
|
|
#endif
|
|
}
|
|
|
|
GRUB_MOD_FINI (png)
|
|
{
|
|
#if defined(PNG_DEBUG)
|
|
grub_unregister_command (cmd);
|
|
#endif
|
|
grub_video_bitmap_reader_unregister (&png_reader);
|
|
}
|