/* lzopio.c - decompression support for lzop */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 2011 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 . */ #include #include #include #include #include #include "minilzo.h" GRUB_MOD_LICENSE ("GPLv3+"); #define LZOP_MAGIC "\x89\x4c\x5a\x4f\x00\x0d\x0a\x1a\x0a" #define LZOP_MAGIC_SIZE 9 #define CHECK_SIZE 4 #define NEW_LZO_LIB 0x0940 #define MIN_HEADER_SIZE 0 #define MAX_HEADER_SIZE 0 /* Header flags - copied from conf.h of LZOP source code. */ #define F_ADLER32_D 0x00000001L #define F_ADLER32_C 0x00000002L #define F_STDIN 0x00000004L #define F_STDOUT 0x00000008L #define F_NAME_DEFAULT 0x00000010L #define F_DOSISH 0x00000020L #define F_H_EXTRA_FIELD 0x00000040L #define F_H_GMTDIFF 0x00000080L #define F_CRC32_D 0x00000100L #define F_CRC32_C 0x00000200L #define F_MULTIPART 0x00000400L #define F_H_FILTER 0x00000800L #define F_H_CRC32 0x00001000L #define F_H_PATH 0x00002000L #define F_MASK 0x00003FFFL struct block_header { grub_uint32_t usize; grub_uint32_t csize; grub_uint32_t ucheck; grub_uint32_t ccheck; unsigned char *cdata; unsigned char *udata; }; struct grub_lzopio { grub_file_t file; int ucheck; /* XXX Use gcry to validate checks. */ int ccheck; grub_off_t saved_off; /* Rounded down to block boundary. */ grub_off_t start_block_off; struct block_header block; }; typedef struct grub_lzopio *grub_lzopio_t; static struct grub_fs grub_lzopio_fs; /* Some helper functions. Memory allocated by those function is free either * on next read_block_header() or on close() so no risk of leaks. This makes * those function simpler. */ /* Read block header from file, after successful exit file points to * beginning of block data. */ static int read_block_header (struct grub_lzopio *lzopio) { /* Free cached block data if any. */ grub_free (lzopio->block.udata); grub_free (lzopio->block.cdata); lzopio->block.udata = NULL; lzopio->block.cdata = NULL; if (grub_file_read (lzopio->file, &lzopio->block.usize, sizeof (lzopio->block.usize)) != sizeof (lzopio->block.usize)) return -1; lzopio->block.usize = grub_be_to_cpu32 (lzopio->block.usize); /* Last block has uncompressed data size == 0 and no other fields. */ if (lzopio->block.usize == 0) { if (grub_file_tell (lzopio->file) == grub_file_size (lzopio->file)) return 0; else return -1; } /* Read compressed data block size. */ if (grub_file_read (lzopio->file, &lzopio->block.csize, sizeof (lzopio->block.csize)) != sizeof (lzopio->block.csize)) return -1; lzopio->block.csize = grub_be_to_cpu32 (lzopio->block.csize); /* XXX Handle incompressible data case here rather than in uncompress_block. * This will allow to handle switch of ccheck/ucheck easier and also * make uncompress_block() a bit simpler. */ /* Read data checks. */ if (lzopio->ucheck) { if (grub_file_read (lzopio->file, &lzopio->block.ucheck, sizeof (lzopio->block.ucheck)) != sizeof (lzopio->block.ucheck)) return -1; } if (lzopio->ccheck) { if (grub_file_read (lzopio->file, &lzopio->block.ccheck, sizeof (lzopio->block.ccheck)) != sizeof (lzopio->block.ccheck)) return -1; } return 0; } /* Read block data into memory. File must be set to beginning of block data. * Can't be called on last block. */ static int read_block_data (struct grub_lzopio *lzopio) { lzopio->block.cdata = grub_malloc (lzopio->block.csize); if (!lzopio->block.cdata) return -1; if (grub_file_read (lzopio->file, lzopio->block.cdata, lzopio->block.csize) != (grub_ssize_t) lzopio->block.csize) return -1; if (lzopio->ccheck) { /* XXX Validate data checksum here. */ } return 0; } /* Read block data, uncompressed and also store it in memory. */ /* XXX Investigate possibility of in-place decompression to reduce memory * footprint. */ static int uncompress_block (struct grub_lzopio *lzopio) { lzo_uint usize = lzopio->block.usize; if (read_block_data (lzopio) < 0) return -1; if (lzopio->block.usize > lzopio->block.csize) { lzopio->block.udata = grub_malloc (lzopio->block.usize); if (!lzopio->block.udata) return -1; if (lzo1x_decompress_safe (lzopio->block.cdata, lzopio->block.csize, lzopio->block.udata, &usize, NULL) != LZO_E_OK) return -1; if (lzopio->ucheck) { /* XXX Validate data checksum here. */ } /* Compressed data can be free now. */ grub_free (lzopio->block.cdata); lzopio->block.cdata = NULL; } /* Incompressible block of data. */ else if (lzopio->block.usize == lzopio->block.csize) { lzopio->block.udata = lzopio->block.cdata; lzopio->block.cdata = NULL; } else { return -1; } return 0; } /* Jump to next block and read its header. */ static int jump_block (struct grub_lzopio *lzopio) { /* only jump if block was not decompressed (and read from disk) */ if (!lzopio->block.udata) { grub_off_t off = grub_file_tell (lzopio->file) + lzopio->block.csize; if (grub_file_seek (lzopio->file, off) == ((grub_off_t) - 1)) return -1; } lzopio->saved_off += lzopio->block.usize; return read_block_header (lzopio); } static int calculate_uncompressed_size (grub_file_t file) { grub_lzopio_t lzopio = file->data; grub_off_t usize_total = 0; if (read_block_header (lzopio) < 0) return 0; /* FIXME: Don't do this for not easily seekable files. */ while (lzopio->block.usize != 0) { usize_total += lzopio->block.usize; if (jump_block (lzopio) < 0) return 0; } file->size = usize_total; return 1; } /* XXX Do something with this function, it is insane now:) */ static int test_header (grub_file_t file) { grub_lzopio_t lzopio = file->data; unsigned char magic[LZOP_MAGIC_SIZE]; grub_uint16_t lzopver, ver, ver_ext; grub_uint8_t method, level, name_len; grub_uint32_t flags, mode, filter, mtime_lo, mtime_hi, checksum; unsigned char *name = NULL; if (grub_file_read (lzopio->file, magic, sizeof (magic)) != sizeof (magic)) { grub_error (GRUB_ERR_BAD_FILE_TYPE, "no lzop magic found"); return 0; } if (grub_memcmp (magic, LZOP_MAGIC, LZOP_MAGIC_SIZE) != 0) { grub_error (GRUB_ERR_BAD_FILE_TYPE, "no lzop magic found"); return 0; } /* LZOP version. */ if (grub_file_read (lzopio->file, &lzopver, sizeof (lzopver)) != sizeof (lzopver)) goto CORRUPTED; /* LZO lib version. */ if (grub_file_read (lzopio->file, &ver, sizeof (ver)) != sizeof (ver)) goto CORRUPTED; ver = grub_be_to_cpu16 (ver); if (ver >= NEW_LZO_LIB) { /* Read version of lib needed to extract data. */ if (grub_file_read (lzopio->file, &ver_ext, sizeof (ver_ext)) != sizeof (ver_ext)) goto CORRUPTED; /* Too new version, should upgrade minilzo? */ if (grub_be_to_cpu16 (ver_ext) > MINILZO_VERSION) { grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "unsupported LZO version"); return 0; } } if (grub_file_read (lzopio->file, &method, sizeof (method)) != sizeof (method)) goto CORRUPTED; if (ver >= NEW_LZO_LIB) { if (grub_file_read (lzopio->file, &level, sizeof (level)) != sizeof (level)) goto CORRUPTED; } if (grub_file_read (lzopio->file, &flags, sizeof (flags)) != sizeof (flags)) goto CORRUPTED; flags = grub_be_to_cpu32 (flags); if (flags & F_CRC32_D) lzopio->ucheck = 1; else if (flags & F_ADLER32_D) lzopio->ucheck = 2; if (flags & F_CRC32_C) lzopio->ccheck = 1; else if (flags & F_ADLER32_C) lzopio->ccheck = 2; if (flags & F_H_FILTER) { if (grub_file_read (lzopio->file, &filter, sizeof (filter)) != sizeof (filter)) goto CORRUPTED; } if (grub_file_read (lzopio->file, &mode, sizeof (mode)) != sizeof (mode)) goto CORRUPTED; if (grub_file_read (lzopio->file, &mtime_lo, sizeof (mtime_lo)) != sizeof (mtime_lo)) goto CORRUPTED; if (ver >= NEW_LZO_LIB) { if (grub_file_read (lzopio->file, &mtime_hi, sizeof (mtime_hi)) != sizeof (mtime_hi)) goto CORRUPTED; } if (grub_file_read (lzopio->file, &name_len, sizeof (name_len)) != sizeof (name_len)) goto CORRUPTED; if (name_len != 0) { name = grub_malloc (name_len); if (!name) return 0; if (grub_file_read (lzopio->file, name, name_len) != name_len) { grub_free (name); goto CORRUPTED; } } if (grub_file_read (lzopio->file, &checksum, sizeof (checksum)) != sizeof (checksum)) { grub_free (name); goto CORRUPTED; } grub_free (name); /* XXX Validate header checksum here. */ if (checksum == checksum) { lzopio->start_block_off = grub_file_tell (lzopio->file); if (calculate_uncompressed_size (file) < 0) goto CORRUPTED; lzopio->saved_off = 0; /* Get back to start block. */ grub_file_seek (lzopio->file, lzopio->start_block_off); /* Read first block - grub_lzopio_read() expects valid block. */ if (read_block_header (lzopio) < 0) goto CORRUPTED; return 1; } CORRUPTED: grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "lzop file corrupted"); return 0; } static grub_file_t grub_lzopio_open (grub_file_t io) { grub_file_t file; grub_lzopio_t lzopio; file = (grub_file_t) grub_zalloc (sizeof (*file)); if (!file) return 0; lzopio = grub_zalloc (sizeof (*lzopio)); if (!lzopio) { grub_free (file); return 0; } lzopio->file = io; file->device = io->device; file->offset = 0; file->data = lzopio; file->read_hook = 0; file->fs = &grub_lzopio_fs; file->size = GRUB_FILE_SIZE_UNKNOWN; file->not_easily_seekable = 1; if (grub_file_tell (lzopio->file) != 0) grub_file_seek (lzopio->file, 0); if (!test_header (file)) { grub_errno = GRUB_ERR_NONE; grub_file_seek (io, 0); grub_free (lzopio); grub_free (file); return io; } return file; } static grub_ssize_t grub_lzopio_read (grub_file_t file, char *buf, grub_size_t len) { grub_lzopio_t lzopio = file->data; grub_ssize_t ret = 0; /* Backward seek before last read block. */ if (lzopio->saved_off > grub_file_tell (file)) { lzopio->saved_off = 0; grub_file_seek (lzopio->file, lzopio->start_block_off); if (read_block_header (lzopio) < 0) goto CORRUPTED; } /* Forward to first block with requested data. */ while (lzopio->saved_off + lzopio->block.usize <= grub_file_tell (file)) { /* EOF, could be possible files with unknown size. */ if (lzopio->block.usize == 0) return 0; if (jump_block (lzopio) < 0) goto CORRUPTED; } while (len != 0 && lzopio->block.usize != 0) { grub_off_t off = grub_file_tell (file) - lzopio->saved_off; long to_copy; /* Block not decompressed yet. */ if (!lzopio->block.udata && uncompress_block (lzopio) < 0) goto CORRUPTED; /* Copy requested data into buffer. */ to_copy = grub_min (lzopio->block.usize - off, len); grub_memcpy (buf, lzopio->block.udata + off, to_copy); len -= to_copy; buf += to_copy; ret += to_copy; /* Read to next block if needed. */ if (len > 0 && read_block_header (lzopio) < 0) goto CORRUPTED; } return ret; CORRUPTED: grub_error (GRUB_ERR_BAD_COMPRESSED_DATA, "lzop file corrupted"); return -1; } /* Release everything, including the underlying file object. */ static grub_err_t grub_lzopio_close (grub_file_t file) { grub_lzopio_t lzopio = file->data; grub_file_close (lzopio->file); grub_free (lzopio->block.cdata); grub_free (lzopio->block.udata); grub_free (lzopio); /* Device must not be closed twice. */ file->device = 0; return grub_errno; } static struct grub_fs grub_lzopio_fs = { .name = "lzopio", .dir = 0, .open = 0, .read = grub_lzopio_read, .close = grub_lzopio_close, .label = 0, .next = 0 }; GRUB_MOD_INIT (lzopio) { grub_file_filter_register (GRUB_FILE_FILTER_LZOPIO, grub_lzopio_open); } GRUB_MOD_FINI (lzopio) { grub_file_filter_unregister (GRUB_FILE_FILTER_LZOPIO); }