diff options
Diffstat (limited to 'grub-core/loader/machoXX.c')
-rw-r--r-- | grub-core/loader/machoXX.c | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/grub-core/loader/machoXX.c b/grub-core/loader/machoXX.c new file mode 100644 index 0000000..95c3fe5 --- /dev/null +++ b/grub-core/loader/machoXX.c @@ -0,0 +1,384 @@ + +#include <grub/file.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/i18n.h> + +#define min(a,b) (((a) < (b)) ? (a) : (b)) + +static int +SUFFIX (grub_macho_contains_macho) (grub_macho_t macho) +{ + return macho->offsetXX != -1; +} + +void +SUFFIX (grub_macho_parse) (grub_macho_t macho, const char *filename) +{ + union { + struct grub_macho_lzss_header lzss; + grub_macho_header_t macho; + } head; + + /* Is there any candidate at all? */ + if (macho->offsetXX == -1) + return; + + /* Read header and check magic. */ + if (grub_file_seek (macho->file, macho->offsetXX) == (grub_off_t) -1 + || grub_file_read (macho->file, &head, sizeof (head)) + != sizeof (head)) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + macho->offsetXX = -1; + return; + } + if (grub_memcmp (head.lzss.magic, GRUB_MACHO_LZSS_MAGIC, + sizeof (head.lzss.magic)) == 0) + { + macho->compressed_sizeXX = grub_be_to_cpu32 (head.lzss.compressed_size); + macho->uncompressed_sizeXX + = grub_be_to_cpu32 (head.lzss.uncompressed_size); + if (macho->uncompressed_sizeXX < sizeof (head.macho)) + { + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + macho->offsetXX = -1; + return; + } + /* Skip header check. */ + macho->compressedXX = 1; + return; + } + + if (head.macho.magic != GRUB_MACHO_MAGIC) + { + grub_error (GRUB_ERR_BAD_OS, "invalid Mach-O header"); + macho->offsetXX = -1; + return; + } + + /* Read commands. */ + macho->ncmdsXX = head.macho.ncmds; + macho->cmdsizeXX = head.macho.sizeofcmds; + macho->cmdsXX = grub_malloc (macho->cmdsizeXX); + if (! macho->cmdsXX) + return; + if (grub_file_seek (macho->file, macho->offsetXX + + sizeof (grub_macho_header_t)) == (grub_off_t) -1 + || grub_file_read (macho->file, macho->cmdsXX, + (grub_size_t) macho->cmdsizeXX) + != (grub_ssize_t) macho->cmdsizeXX) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + macho->offsetXX = -1; + } +} + +typedef int (*grub_macho_iter_hook_t) +(grub_macho_t , struct grub_macho_cmd *, + void *); + +static grub_err_t +grub_macho_cmds_iterate (grub_macho_t macho, + grub_macho_iter_hook_t hook, + void *hook_arg, + const char *filename) +{ + grub_uint8_t *hdrs; + int i; + + if (macho->compressedXX && !macho->uncompressedXX) + { + grub_uint8_t *tmp; + grub_macho_header_t *head; + macho->uncompressedXX = grub_malloc (macho->uncompressed_sizeXX); + if (!macho->uncompressedXX) + return grub_errno; + tmp = grub_malloc (macho->compressed_sizeXX); + if (!tmp) + { + grub_free (macho->uncompressedXX); + macho->uncompressedXX = 0; + return grub_errno; + } + if (grub_file_seek (macho->file, macho->offsetXX + + GRUB_MACHO_LZSS_OFFSET) == (grub_off_t) -1 + || grub_file_read (macho->file, tmp, + (grub_size_t) macho->compressed_sizeXX) + != (grub_ssize_t) macho->compressed_sizeXX) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + grub_free (tmp); + grub_free (macho->uncompressedXX); + macho->uncompressedXX = 0; + macho->offsetXX = -1; + return grub_errno; + } + if (grub_decompress_lzss (macho->uncompressedXX, + macho->uncompressedXX + + macho->uncompressed_sizeXX, + tmp, tmp + macho->compressed_sizeXX) + != macho->uncompressed_sizeXX) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + grub_free (tmp); + grub_free (macho->uncompressedXX); + macho->uncompressedXX = 0; + macho->offsetXX = -1; + return grub_errno; + } + grub_free (tmp); + head = (grub_macho_header_t *) macho->uncompressedXX; + macho->ncmdsXX = head->ncmds; + macho->cmdsizeXX = head->sizeofcmds; + macho->cmdsXX = macho->uncompressedXX + sizeof (grub_macho_header_t); + if (sizeof (grub_macho_header_t) + macho->cmdsizeXX + >= macho->uncompressed_sizeXX) + { + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + grub_free (macho->uncompressedXX); + macho->uncompressedXX = 0; + macho->offsetXX = -1; + return grub_errno; + } + } + + if (! macho->cmdsXX) + return grub_error (GRUB_ERR_BAD_OS, "couldn't find Mach-O commands"); + hdrs = macho->cmdsXX; + for (i = 0; i < macho->ncmdsXX; i++) + { + struct grub_macho_cmd *hdr = (struct grub_macho_cmd *) hdrs; + if (hook (macho, hdr, hook_arg)) + break; + hdrs += hdr->cmdsize; + } + + return grub_errno; +} + +grub_size_t +SUFFIX (grub_macho_filesize) (grub_macho_t macho) +{ + if (SUFFIX (grub_macho_contains_macho) (macho)) + return macho->endXX - macho->offsetXX; + return 0; +} + +grub_err_t +SUFFIX (grub_macho_readfile) (grub_macho_t macho, + const char *filename, + void *dest) +{ + grub_ssize_t read; + if (! SUFFIX (grub_macho_contains_macho) (macho)) + return grub_error (GRUB_ERR_BAD_OS, + "couldn't read architecture-specific part"); + + if (grub_file_seek (macho->file, macho->offsetXX) == (grub_off_t) -1) + return grub_errno; + + read = grub_file_read (macho->file, dest, + macho->endXX - macho->offsetXX); + if (read != (grub_ssize_t) (macho->endXX - macho->offsetXX)) + { + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + filename); + return grub_errno; + } + return GRUB_ERR_NONE; +} + +struct calcsize_ctx +{ + int flags; + int nr_phdrs; + grub_macho_addr_t *segments_start; + grub_macho_addr_t *segments_end; +}; + +/* Run through the program headers to calculate the total memory size we + should claim. */ +static int +calcsize (grub_macho_t _macho __attribute__ ((unused)), + struct grub_macho_cmd *hdr0, + void *_arg) +{ + grub_macho_segment_t *hdr = (grub_macho_segment_t *) hdr0; + struct calcsize_ctx *ctx = _arg; + if (hdr->cmd != GRUB_MACHO_CMD_SEGMENT) + return 0; + + if (! hdr->vmsize) + return 0; + + if (! hdr->filesize && (ctx->flags & GRUB_MACHO_NOBSS)) + return 0; + + ctx->nr_phdrs++; + if (hdr->vmaddr < *ctx->segments_start) + *ctx->segments_start = hdr->vmaddr; + if (hdr->vmaddr + hdr->vmsize > *ctx->segments_end) + *ctx->segments_end = hdr->vmaddr + hdr->vmsize; + return 0; +} + +/* Calculate the amount of memory spanned by the segments. */ +grub_err_t +SUFFIX (grub_macho_size) (grub_macho_t macho, grub_macho_addr_t *segments_start, + grub_macho_addr_t *segments_end, int flags, + const char *filename) +{ + struct calcsize_ctx ctx = { + .flags = flags, + .nr_phdrs = 0, + .segments_start = segments_start, + .segments_end = segments_end, + }; + + *segments_start = (grub_macho_addr_t) -1; + *segments_end = 0; + + grub_macho_cmds_iterate (macho, calcsize, &ctx, filename); + + if (ctx.nr_phdrs == 0) + return grub_error (GRUB_ERR_BAD_OS, "no program headers present"); + + if (*segments_end < *segments_start) + /* Very bad addresses. */ + return grub_error (GRUB_ERR_BAD_OS, "bad program header load addresses"); + + return GRUB_ERR_NONE; +} + +struct do_load_ctx +{ + int flags; + char *offset; + const char *filename; + int *darwin_version; +}; + +static int +do_load(grub_macho_t _macho, + struct grub_macho_cmd *hdr0, + void *_arg) +{ + grub_macho_segment_t *hdr = (grub_macho_segment_t *) hdr0; + struct do_load_ctx *ctx = _arg; + + if (hdr->cmd != GRUB_MACHO_CMD_SEGMENT) + return 0; + + if (! hdr->filesize && (ctx->flags & GRUB_MACHO_NOBSS)) + return 0; + if (! hdr->vmsize) + return 0; + + if (hdr->filesize) + { + grub_ssize_t read, toread = min (hdr->filesize, hdr->vmsize); + if (_macho->uncompressedXX) + { + if (hdr->fileoff + (grub_size_t) toread + > _macho->uncompressed_sizeXX) + read = -1; + else + { + read = toread; + grub_memcpy (ctx->offset + hdr->vmaddr, + _macho->uncompressedXX + hdr->fileoff, read); + } + } + else + { + if (grub_file_seek (_macho->file, hdr->fileoff + + _macho->offsetXX) == (grub_off_t) -1) + return 1; + read = grub_file_read (_macho->file, ctx->offset + hdr->vmaddr, + toread); + } + + if (read != toread) + { + /* XXX How can we free memory from `load_hook'? */ + if (!grub_errno) + grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), + ctx->filename); + + return 1; + } + if (ctx->darwin_version) + { + const char *ptr = ctx->offset + hdr->vmaddr; + const char *end = ptr + min (hdr->filesize, hdr->vmsize) + - (sizeof ("Darwin Kernel Version ") - 1); + for (; ptr < end; ptr++) + if (grub_memcmp (ptr, "Darwin Kernel Version ", + sizeof ("Darwin Kernel Version ") - 1) == 0) + { + ptr += sizeof ("Darwin Kernel Version ") - 1; + *ctx->darwin_version = 0; + end += (sizeof ("Darwin Kernel Version ") - 1); + while (ptr < end && grub_isdigit (*ptr)) + *ctx->darwin_version = (*ptr++ - '0') + *ctx->darwin_version * 10; + break; + } + } + } + + if (hdr->filesize < hdr->vmsize) + grub_memset (ctx->offset + hdr->vmaddr + hdr->filesize, + 0, hdr->vmsize - hdr->filesize); + return 0; +} + +/* Load every loadable segment into memory specified by `_load_hook'. */ +grub_err_t +SUFFIX (grub_macho_load) (grub_macho_t macho, const char *filename, + char *offset, int flags, int *darwin_version) +{ + struct do_load_ctx ctx = { + .flags = flags, + .offset = offset, + .filename = filename, + .darwin_version = darwin_version + }; + + if (darwin_version) + *darwin_version = 0; + + grub_macho_cmds_iterate (macho, do_load, &ctx, filename); + + return grub_errno; +} + +static int +find_entry_point (grub_macho_t _macho __attribute__ ((unused)), + struct grub_macho_cmd *hdr, + void *_arg) +{ + grub_macho_addr_t *entry_point = _arg; + if (hdr->cmd == GRUB_MACHO_CMD_THREAD) + *entry_point = ((grub_macho_thread_t *) hdr)->entry_point; + return 0; +} + +grub_macho_addr_t +SUFFIX (grub_macho_get_entry_point) (grub_macho_t macho, const char *filename) +{ + grub_macho_addr_t entry_point = 0; + grub_macho_cmds_iterate (macho, find_entry_point, &entry_point, filename); + return entry_point; +} |