grub2/grub-core/loader/xnu.c
Darren Kenny bcb59ece32 loader/xnu: Fix memory leak
The code here is finished with the memory stored in name, but it only
frees it if there curvalue is valid, while it could actually free it
regardless.

The fix is a simple relocation of the grub_free() to before the test
of curvalue.

Fixes: CID 96646

Signed-off-by: Darren Kenny <darren.kenny@oracle.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
2021-03-02 15:54:17 +01:00

1540 lines
39 KiB
C

/* xnu.c - load xnu kernel. Thanks to Florian Idelberger for all the
time he spent testing this
*/
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 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/file.h>
#include <grub/xnu.h>
#include <grub/cpu/xnu.h>
#include <grub/mm.h>
#include <grub/dl.h>
#include <grub/loader.h>
#include <grub/machoload.h>
#include <grub/macho.h>
#include <grub/cpu/macho.h>
#include <grub/command.h>
#include <grub/misc.h>
#include <grub/extcmd.h>
#include <grub/env.h>
#include <grub/i18n.h>
#include <grub/verify.h>
#include <grub/safemath.h>
GRUB_MOD_LICENSE ("GPLv3+");
#if defined (__i386) && !defined (GRUB_MACHINE_EFI)
#include <grub/autoefi.h>
#endif
struct grub_xnu_devtree_key *grub_xnu_devtree_root = 0;
static int driverspackagenum = 0;
static int driversnum = 0;
int grub_xnu_is_64bit = 0;
int grub_xnu_darwin_version = 0;
grub_addr_t grub_xnu_heap_target_start = 0;
grub_size_t grub_xnu_heap_size = 0;
struct grub_relocator *grub_xnu_relocator;
static grub_err_t
grub_xnu_register_memory (const char *prefix, int *suffix,
grub_addr_t addr, grub_size_t size);
grub_err_t
grub_xnu_heap_malloc (int size, void **src, grub_addr_t *target)
{
grub_err_t err;
grub_relocator_chunk_t ch;
grub_addr_t tgt;
if (grub_add (grub_xnu_heap_target_start, grub_xnu_heap_size, &tgt))
return GRUB_ERR_OUT_OF_RANGE;
err = grub_relocator_alloc_chunk_addr (grub_xnu_relocator, &ch, tgt, size);
if (err)
return err;
*src = get_virtual_current_address (ch);
*target = tgt;
grub_xnu_heap_size += size;
grub_dprintf ("xnu", "val=%p\n", *src);
return GRUB_ERR_NONE;
}
/* Make sure next block of the heap will be aligned.
Please notice: aligned are pointers AFTER relocation
and not the current ones. */
grub_err_t
grub_xnu_align_heap (int align)
{
grub_xnu_heap_size
= ALIGN_UP (grub_xnu_heap_target_start+ grub_xnu_heap_size, align)
- grub_xnu_heap_target_start;
return GRUB_ERR_NONE;
}
/* Free subtree pointed by CUR. */
void
grub_xnu_free_devtree (struct grub_xnu_devtree_key *cur)
{
struct grub_xnu_devtree_key *d;
while (cur)
{
grub_free (cur->name);
if (cur->datasize == -1)
grub_xnu_free_devtree (cur->first_child);
else if (cur->data)
grub_free (cur->data);
d = cur->next;
grub_free (cur);
cur = d;
}
}
/* Compute the size of device tree in xnu format. */
static grub_size_t
grub_xnu_writetree_get_size (struct grub_xnu_devtree_key *start,
const char *name)
{
grub_size_t ret;
struct grub_xnu_devtree_key *cur;
/* Key header. */
ret = 2 * sizeof (grub_uint32_t);
/* "name" value. */
ret += 32 + sizeof (grub_uint32_t)
+ grub_strlen (name) + 4
- (grub_strlen (name) % 4);
for (cur = start; cur; cur = cur->next)
if (cur->datasize != -1)
{
int align_overhead;
align_overhead = 4 - (cur->datasize % 4);
if (align_overhead == 4)
align_overhead = 0;
ret += 32 + sizeof (grub_uint32_t) + cur->datasize + align_overhead;
}
else
ret += grub_xnu_writetree_get_size (cur->first_child, cur->name);
return ret;
}
/* Write devtree in XNU format at curptr assuming the head is named NAME.*/
static void *
grub_xnu_writetree_toheap_real (void *curptr,
struct grub_xnu_devtree_key *start,
const char *name)
{
struct grub_xnu_devtree_key *cur;
int nkeys = 0, nvals = 0;
for (cur = start; cur; cur = cur->next)
{
if (cur->datasize == -1)
nkeys++;
else
nvals++;
}
/* For the name. */
nvals++;
*((grub_uint32_t *) curptr) = nvals;
curptr = ((grub_uint32_t *) curptr) + 1;
*((grub_uint32_t *) curptr) = nkeys;
curptr = ((grub_uint32_t *) curptr) + 1;
/* First comes "name" value. */
grub_memset (curptr, 0, 32);
grub_memcpy (curptr, "name", 4);
curptr = ((grub_uint8_t *) curptr) + 32;
*((grub_uint32_t *)curptr) = grub_strlen (name) + 1;
curptr = ((grub_uint32_t *) curptr) + 1;
grub_memcpy (curptr, name, grub_strlen (name));
curptr = ((grub_uint8_t *) curptr) + grub_strlen (name);
grub_memset (curptr, 0, 4 - (grub_strlen (name) % 4));
curptr = ((grub_uint8_t *) curptr) + (4 - (grub_strlen (name) % 4));
/* Then the other values. */
for (cur = start; cur; cur = cur->next)
if (cur->datasize != -1)
{
int align_overhead;
align_overhead = 4 - (cur->datasize % 4);
if (align_overhead == 4)
align_overhead = 0;
grub_memset (curptr, 0, 32);
grub_strncpy (curptr, cur->name, 31);
curptr = ((grub_uint8_t *) curptr) + 32;
*((grub_uint32_t *) curptr) = cur->datasize;
curptr = ((grub_uint32_t *) curptr) + 1;
grub_memcpy (curptr, cur->data, cur->datasize);
curptr = ((grub_uint8_t *) curptr) + cur->datasize;
grub_memset (curptr, 0, align_overhead);
curptr = ((grub_uint8_t *) curptr) + align_overhead;
}
/* And then the keys. Recursively use this function. */
for (cur = start; cur; cur = cur->next)
if (cur->datasize == -1)
{
curptr = grub_xnu_writetree_toheap_real (curptr,
cur->first_child,
cur->name);
if (!curptr)
return 0;
}
return curptr;
}
grub_err_t
grub_xnu_writetree_toheap (grub_addr_t *target, grub_size_t *size)
{
struct grub_xnu_devtree_key *chosen;
struct grub_xnu_devtree_key *memorymap;
struct grub_xnu_devtree_key *driverkey;
struct grub_xnu_extdesc *extdesc;
grub_err_t err;
void *src;
err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
if (err)
return err;
/* Device tree itself is in the memory map of device tree. */
/* Create a dummy value in memory-map. */
chosen = grub_xnu_create_key (&grub_xnu_devtree_root, "chosen");
if (! chosen)
return grub_errno;
memorymap = grub_xnu_create_key (&(chosen->first_child), "memory-map");
if (! memorymap)
return grub_errno;
driverkey = (struct grub_xnu_devtree_key *) grub_malloc (sizeof (*driverkey));
if (! driverkey)
return grub_errno;
driverkey->name = grub_strdup ("DeviceTree");
if (! driverkey->name)
return grub_errno;
driverkey->datasize = sizeof (*extdesc);
driverkey->next = memorymap->first_child;
memorymap->first_child = driverkey;
driverkey->data = extdesc
= (struct grub_xnu_extdesc *) grub_malloc (sizeof (*extdesc));
if (! driverkey->data)
return grub_errno;
/* Allocate the space based on the size with dummy value. */
*size = grub_xnu_writetree_get_size (grub_xnu_devtree_root, "/");
err = grub_xnu_heap_malloc (ALIGN_UP (*size + 1, GRUB_XNU_PAGESIZE),
&src, target);
if (err)
return err;
/* Put real data in the dummy. */
extdesc->addr = *target;
extdesc->size = (grub_uint32_t) *size;
/* Write the tree to heap. */
grub_xnu_writetree_toheap_real (src, grub_xnu_devtree_root, "/");
return GRUB_ERR_NONE;
}
/* Find a key or value in parent key. */
struct grub_xnu_devtree_key *
grub_xnu_find_key (struct grub_xnu_devtree_key *parent, const char *name)
{
struct grub_xnu_devtree_key *cur;
for (cur = parent; cur; cur = cur->next)
if (grub_strcmp (cur->name, name) == 0)
return cur;
return 0;
}
struct grub_xnu_devtree_key *
grub_xnu_create_key (struct grub_xnu_devtree_key **parent, const char *name)
{
struct grub_xnu_devtree_key *ret;
ret = grub_xnu_find_key (*parent, name);
if (ret)
return ret;
ret = (struct grub_xnu_devtree_key *) grub_zalloc (sizeof (*ret));
if (! ret)
return 0;
ret->name = grub_strdup (name);
if (! ret->name)
{
grub_free (ret);
return 0;
}
ret->datasize = -1;
ret->next = *parent;
*parent = ret;
return ret;
}
struct grub_xnu_devtree_key *
grub_xnu_create_value (struct grub_xnu_devtree_key **parent, const char *name)
{
struct grub_xnu_devtree_key *ret;
ret = grub_xnu_find_key (*parent, name);
if (ret)
{
if (ret->datasize == -1)
grub_xnu_free_devtree (ret->first_child);
else if (ret->datasize)
grub_free (ret->data);
ret->datasize = 0;
ret->data = 0;
return ret;
}
ret = (struct grub_xnu_devtree_key *) grub_zalloc (sizeof (*ret));
if (! ret)
return 0;
ret->name = grub_strdup (name);
if (! ret->name)
{
grub_free (ret);
return 0;
}
ret->next = *parent;
*parent = ret;
return ret;
}
static grub_err_t
grub_xnu_unload (void)
{
grub_cpu_xnu_unload ();
grub_xnu_free_devtree (grub_xnu_devtree_root);
grub_xnu_devtree_root = 0;
/* Free loaded image. */
driversnum = 0;
driverspackagenum = 0;
grub_relocator_unload (grub_xnu_relocator);
grub_xnu_relocator = NULL;
grub_xnu_heap_target_start = 0;
grub_xnu_heap_size = 0;
grub_xnu_unlock ();
return GRUB_ERR_NONE;
}
static grub_err_t
grub_cmd_xnu_kernel (grub_command_t cmd __attribute__ ((unused)),
int argc, char *args[])
{
grub_err_t err;
grub_macho_t macho;
grub_uint32_t startcode, endcode;
int i;
char *ptr;
void *loadaddr;
grub_addr_t loadaddr_target;
if (argc < 1)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
grub_xnu_unload ();
macho = grub_macho_open (args[0], GRUB_FILE_TYPE_XNU_KERNEL, 0);
if (! macho)
return grub_errno;
err = grub_macho_size32 (macho, &startcode, &endcode, GRUB_MACHO_NOBSS,
args[0]);
if (err)
{
grub_macho_close (macho);
grub_xnu_unload ();
return err;
}
grub_dprintf ("xnu", "endcode = %lx, startcode = %lx\n",
(unsigned long) endcode, (unsigned long) startcode);
grub_xnu_relocator = grub_relocator_new ();
if (!grub_xnu_relocator)
return grub_errno;
grub_xnu_heap_target_start = startcode;
err = grub_xnu_heap_malloc (endcode - startcode, &loadaddr,
&loadaddr_target);
if (err)
{
grub_macho_close (macho);
grub_xnu_unload ();
return err;
}
/* Load kernel. */
err = grub_macho_load32 (macho, args[0], (char *) loadaddr - startcode,
GRUB_MACHO_NOBSS, &grub_xnu_darwin_version);
if (err)
{
grub_macho_close (macho);
grub_xnu_unload ();
return err;
}
grub_xnu_entry_point = grub_macho_get_entry_point32 (macho, args[0]);
if (! grub_xnu_entry_point)
{
grub_macho_close (macho);
grub_xnu_unload ();
return grub_error (GRUB_ERR_BAD_OS, "couldn't find entry point");
}
grub_macho_close (macho);
err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
if (err)
{
grub_xnu_unload ();
return err;
}
/* Copy parameters to kernel command line. */
ptr = grub_xnu_cmdline;
for (i = 1; i < argc; i++)
{
if (ptr + grub_strlen (args[i]) + 1
>= grub_xnu_cmdline + sizeof (grub_xnu_cmdline))
break;
grub_memcpy (ptr, args[i], grub_strlen (args[i]));
ptr += grub_strlen (args[i]);
*ptr = ' ';
ptr++;
}
/* Replace last space by '\0'. */
if (ptr != grub_xnu_cmdline)
*(ptr - 1) = 0;
err = grub_verify_string (grub_xnu_cmdline, GRUB_VERIFY_KERNEL_CMDLINE);
if (err)
return err;
#if defined (__i386) && !defined (GRUB_MACHINE_EFI)
err = grub_efiemu_autocore ();
if (err)
return err;
#endif
grub_loader_set (grub_xnu_boot, grub_xnu_unload, 0);
grub_xnu_lock ();
grub_xnu_is_64bit = 0;
return 0;
}
static grub_err_t
grub_cmd_xnu_kernel64 (grub_command_t cmd __attribute__ ((unused)),
int argc, char *args[])
{
grub_err_t err;
grub_macho_t macho;
grub_uint64_t startcode, endcode;
int i;
char *ptr;
void *loadaddr;
grub_addr_t loadaddr_target;
if (argc < 1)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
grub_xnu_unload ();
macho = grub_macho_open (args[0], GRUB_FILE_TYPE_XNU_KERNEL, 1);
if (! macho)
return grub_errno;
err = grub_macho_size64 (macho, &startcode, &endcode, GRUB_MACHO_NOBSS,
args[0]);
if (err)
{
grub_macho_close (macho);
grub_xnu_unload ();
return err;
}
startcode &= 0x0fffffff;
endcode &= 0x0fffffff;
grub_dprintf ("xnu", "endcode = %lx, startcode = %lx\n",
(unsigned long) endcode, (unsigned long) startcode);
grub_xnu_relocator = grub_relocator_new ();
if (!grub_xnu_relocator)
return grub_errno;
grub_xnu_heap_target_start = startcode;
err = grub_xnu_heap_malloc (endcode - startcode, &loadaddr,
&loadaddr_target);
if (err)
{
grub_macho_close (macho);
grub_xnu_unload ();
return err;
}
/* Load kernel. */
err = grub_macho_load64 (macho, args[0], (char *) loadaddr - startcode,
GRUB_MACHO_NOBSS, &grub_xnu_darwin_version);
if (err)
{
grub_macho_close (macho);
grub_xnu_unload ();
return err;
}
grub_xnu_entry_point = grub_macho_get_entry_point64 (macho, args[0])
& 0x0fffffff;
if (! grub_xnu_entry_point)
{
grub_macho_close (macho);
grub_xnu_unload ();
return grub_error (GRUB_ERR_BAD_OS, "couldn't find entry point");
}
grub_macho_close (macho);
err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
if (err)
{
grub_xnu_unload ();
return err;
}
/* Copy parameters to kernel command line. */
ptr = grub_xnu_cmdline;
for (i = 1; i < argc; i++)
{
if (ptr + grub_strlen (args[i]) + 1
>= grub_xnu_cmdline + sizeof (grub_xnu_cmdline))
break;
grub_memcpy (ptr, args[i], grub_strlen (args[i]));
ptr += grub_strlen (args[i]);
*ptr = ' ';
ptr++;
}
/* Replace last space by '\0'. */
if (ptr != grub_xnu_cmdline)
*(ptr - 1) = 0;
err = grub_verify_string (grub_xnu_cmdline, GRUB_VERIFY_KERNEL_CMDLINE);
if (err)
return err;
#if defined (__i386) && !defined (GRUB_MACHINE_EFI)
err = grub_efiemu_autocore ();
if (err)
return err;
#endif
grub_loader_set (grub_xnu_boot, grub_xnu_unload, 0);
grub_xnu_lock ();
grub_xnu_is_64bit = 1;
return 0;
}
/* Register a memory in a memory map under name PREFIXSUFFIX
and increment SUFFIX. */
static grub_err_t
grub_xnu_register_memory (const char *prefix, int *suffix,
grub_addr_t addr, grub_size_t size)
{
struct grub_xnu_devtree_key *chosen;
struct grub_xnu_devtree_key *memorymap;
struct grub_xnu_devtree_key *driverkey;
struct grub_xnu_extdesc *extdesc;
if (! grub_xnu_heap_size)
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
chosen = grub_xnu_create_key (&grub_xnu_devtree_root, "chosen");
if (! chosen)
return grub_errno;
memorymap = grub_xnu_create_key (&(chosen->first_child), "memory-map");
if (! memorymap)
return grub_errno;
driverkey = (struct grub_xnu_devtree_key *) grub_malloc (sizeof (*driverkey));
if (! driverkey)
return grub_errno;
if (suffix)
driverkey->name = grub_xasprintf ("%s%d", prefix, (*suffix)++);
else
driverkey->name = grub_strdup (prefix);
if (!driverkey->name)
{
grub_free (driverkey);
return grub_errno;
}
driverkey->datasize = sizeof (*extdesc);
driverkey->next = memorymap->first_child;
driverkey->data = extdesc
= (struct grub_xnu_extdesc *) grub_malloc (sizeof (*extdesc));
if (! driverkey->data)
{
grub_free (driverkey->name);
grub_free (driverkey);
return grub_errno;
}
memorymap->first_child = driverkey;
extdesc->addr = addr;
extdesc->size = (grub_uint32_t) size;
return GRUB_ERR_NONE;
}
static inline char *
get_name_ptr (char *name)
{
char *p = name, *p2;
/* Skip Info.plist. */
p2 = grub_strrchr (p, '/');
if (!p2)
return name;
if (p2 == name)
return name + 1;
p = p2 - 1;
p2 = grub_strrchr (p, '/');
if (!p2)
return name;
if (p2 == name)
return name + 1;
if (grub_memcmp (p2, "/Contents/", sizeof ("/Contents/") - 1) != 0)
return p2 + 1;
p = p2 - 1;
p2 = grub_strrchr (p, '/');
if (!p2)
return name;
return p2 + 1;
}
/* Load .kext. */
static grub_err_t
grub_xnu_load_driver (char *infoplistname, grub_file_t binaryfile,
const char *filename)
{
grub_macho_t macho;
grub_err_t err;
grub_file_t infoplist;
struct grub_xnu_extheader *exthead;
int neededspace = sizeof (*exthead);
grub_uint8_t *buf;
void *buf0;
grub_addr_t buf_target;
grub_size_t infoplistsize = 0, machosize = 0;
char *name, *nameend;
int namelen;
name = get_name_ptr (infoplistname);
nameend = grub_strchr (name, '/');
if (nameend)
namelen = nameend - name;
else
namelen = grub_strlen (name);
neededspace += namelen + 1;
if (! grub_xnu_heap_size)
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
/* Compute the needed space. */
if (binaryfile)
{
macho = grub_macho_file (binaryfile, filename, grub_xnu_is_64bit);
if (!macho)
grub_file_close (binaryfile);
else
{
if (grub_xnu_is_64bit)
machosize = grub_macho_filesize64 (macho);
else
machosize = grub_macho_filesize32 (macho);
}
neededspace += machosize;
}
else
macho = 0;
if (infoplistname)
infoplist = grub_file_open (infoplistname, GRUB_FILE_TYPE_XNU_INFO_PLIST);
else
infoplist = 0;
grub_errno = GRUB_ERR_NONE;
if (infoplist)
{
infoplistsize = grub_file_size (infoplist);
neededspace += infoplistsize + 1;
}
else
infoplistsize = 0;
/* Allocate the space. */
err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
if (err)
goto fail;
err = grub_xnu_heap_malloc (neededspace, &buf0, &buf_target);
if (err)
goto fail;
buf = buf0;
exthead = (struct grub_xnu_extheader *) buf;
grub_memset (exthead, 0, sizeof (*exthead));
buf += sizeof (*exthead);
/* Load the binary. */
if (macho)
{
exthead->binaryaddr = buf_target + (buf - (grub_uint8_t *) buf0);
exthead->binarysize = machosize;
if (grub_xnu_is_64bit)
err = grub_macho_readfile64 (macho, filename, buf);
else
err = grub_macho_readfile32 (macho, filename, buf);
if (err)
goto fail;
grub_macho_close (macho);
buf += machosize;
}
grub_errno = GRUB_ERR_NONE;
/* Load the plist. */
if (infoplist)
{
exthead->infoplistaddr = buf_target + (buf - (grub_uint8_t *) buf0);
exthead->infoplistsize = infoplistsize + 1;
if (grub_file_read (infoplist, buf, infoplistsize)
!= (grub_ssize_t) (infoplistsize))
{
grub_file_close (infoplist);
if (!grub_errno)
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
infoplistname);
return grub_errno;
}
grub_file_close (infoplist);
buf[infoplistsize] = 0;
buf += infoplistsize + 1;
}
grub_errno = GRUB_ERR_NONE;
exthead->nameaddr = (buf - (grub_uint8_t *) buf0) + buf_target;
exthead->namesize = namelen + 1;
grub_memcpy (buf, name, namelen);
buf[namelen] = 0;
buf += namelen + 1;
/* Announce to kernel */
return grub_xnu_register_memory ("Driver-", &driversnum, buf_target,
neededspace);
fail:
if (macho)
grub_macho_close (macho);
return err;
}
/* Load mkext. */
static grub_err_t
grub_cmd_xnu_mkext (grub_command_t cmd __attribute__ ((unused)),
int argc, char *args[])
{
grub_file_t file;
void *loadto;
grub_addr_t loadto_target;
grub_err_t err;
grub_off_t readoff = 0;
grub_ssize_t readlen = -1;
struct grub_macho_fat_header head;
struct grub_macho_fat_arch *archs;
int narchs, i;
if (argc != 1)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
if (! grub_xnu_heap_size)
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
file = grub_file_open (args[0], GRUB_FILE_TYPE_XNU_MKEXT);
if (! file)
return grub_errno;
/* Sometimes caches are fat binary. Errgh. */
if (grub_file_read (file, &head, sizeof (head))
!= (grub_ssize_t) (sizeof (head)))
{
/* I don't know the internal structure of package but
can hardly imagine a valid package shorter than 20 bytes. */
grub_file_close (file);
if (!grub_errno)
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]);
return grub_errno;
}
/* Find the corresponding architecture. */
if (grub_be_to_cpu32 (head.magic) == GRUB_MACHO_FAT_MAGIC)
{
narchs = grub_be_to_cpu32 (head.nfat_arch);
archs = grub_calloc (narchs, sizeof (struct grub_macho_fat_arch));
if (! archs)
{
grub_file_close (file);
return grub_errno;
}
if (grub_file_read (file, archs,
sizeof (struct grub_macho_fat_arch) * narchs)
!= (grub_ssize_t) sizeof(struct grub_macho_fat_arch) * narchs)
{
grub_free (archs);
if (!grub_errno)
grub_error (GRUB_ERR_READ_ERROR, N_("premature end of file %s"),
args[0]);
return grub_errno;
}
for (i = 0; i < narchs; i++)
{
if (!grub_xnu_is_64bit && GRUB_MACHO_CPUTYPE_IS_HOST32
(grub_be_to_cpu32 (archs[i].cputype)))
{
readoff = grub_be_to_cpu32 (archs[i].offset);
readlen = grub_be_to_cpu32 (archs[i].size);
}
if (grub_xnu_is_64bit && GRUB_MACHO_CPUTYPE_IS_HOST64
(grub_be_to_cpu32 (archs[i].cputype)))
{
readoff = grub_be_to_cpu32 (archs[i].offset);
readlen = grub_be_to_cpu32 (archs[i].size);
}
}
grub_free (archs);
}
else
{
/* It's a flat file. Some sane people still exist. */
readoff = 0;
readlen = grub_file_size (file);
}
if (readlen == -1)
{
grub_file_close (file);
return grub_error (GRUB_ERR_BAD_OS, "no suitable architecture is found");
}
/* Allocate space. */
err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
if (err)
{
grub_file_close (file);
return err;
}
err = grub_xnu_heap_malloc (readlen, &loadto, &loadto_target);
if (err)
{
grub_file_close (file);
return err;
}
/* Read the file. */
grub_file_seek (file, readoff);
if (grub_file_read (file, loadto, readlen) != (grub_ssize_t) (readlen))
{
grub_file_close (file);
if (!grub_errno)
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]);
return grub_errno;
}
grub_file_close (file);
/* Pass it to kernel. */
return grub_xnu_register_memory ("DriversPackage-", &driverspackagenum,
loadto_target, readlen);
}
static grub_err_t
grub_cmd_xnu_ramdisk (grub_command_t cmd __attribute__ ((unused)),
int argc, char *args[])
{
grub_file_t file;
void *loadto;
grub_addr_t loadto_target;
grub_err_t err;
grub_size_t size;
if (argc != 1)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
if (! grub_xnu_heap_size)
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
file = grub_file_open (args[0], GRUB_FILE_TYPE_XNU_RAMDISK);
if (! file)
return grub_errno;
err = grub_xnu_align_heap (GRUB_XNU_PAGESIZE);
if (err)
return err;
size = grub_file_size (file);
err = grub_xnu_heap_malloc (size, &loadto, &loadto_target);
if (err)
return err;
if (grub_file_read (file, loadto, size) != (grub_ssize_t) (size))
{
grub_file_close (file);
if (!grub_errno)
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), args[0]);
return grub_errno;
}
return grub_xnu_register_memory ("RAMDisk", 0, loadto_target, size);
}
/* Returns true if the kext should be loaded according to plist
and osbundlereq. Also fill BINNAME. */
static int
grub_xnu_check_os_bundle_required (char *plistname,
const char *osbundlereq,
char **binname)
{
grub_file_t file;
char *buf = 0, *tagstart = 0, *ptr1 = 0, *keyptr = 0;
char *stringptr = 0, *ptr2 = 0;
grub_size_t size;
int depth = 0;
int ret;
int osbundlekeyfound = 0, binnamekeyfound = 0;
if (binname)
*binname = 0;
file = grub_file_open (plistname, GRUB_FILE_TYPE_XNU_INFO_PLIST);
if (! file)
return 0;
size = grub_file_size (file);
buf = grub_malloc (size);
if (! buf)
{
grub_file_close (file);
return 0;
}
if (grub_file_read (file, buf, size) != (grub_ssize_t) (size))
{
grub_file_close (file);
if (!grub_errno)
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), plistname);
return 0;
}
grub_file_close (file);
/* Set the return value for the case when no OSBundleRequired tag is found. */
if (osbundlereq)
ret = grub_strword (osbundlereq, "all") || grub_strword (osbundlereq, "-");
else
ret = 1;
/* Parse plist. It's quite dirty and inextensible but does its job. */
for (ptr1 = buf; ptr1 < buf + size; ptr1++)
switch (*ptr1)
{
case '<':
tagstart = ptr1;
*ptr1 = 0;
if (keyptr && depth == 4
&& grub_strcmp (keyptr, "OSBundleRequired") == 0)
osbundlekeyfound = 1;
if (keyptr && depth == 4 &&
grub_strcmp (keyptr, "CFBundleExecutable") == 0)
binnamekeyfound = 1;
if (stringptr && osbundlekeyfound && osbundlereq && depth == 4)
{
for (ptr2 = stringptr; *ptr2; ptr2++)
*ptr2 = grub_tolower (*ptr2);
ret = grub_strword (osbundlereq, stringptr)
|| grub_strword (osbundlereq, "all");
}
if (stringptr && binnamekeyfound && binname && depth == 4)
{
if (*binname)
grub_free (*binname);
*binname = grub_strdup (stringptr);
}
*ptr1 = '<';
keyptr = 0;
stringptr = 0;
break;
case '>':
if (! tagstart)
{
grub_free (buf);
grub_error (GRUB_ERR_BAD_OS, "can't parse %s", plistname);
return 0;
}
*ptr1 = 0;
if (tagstart[1] == '?' || ptr1[-1] == '/')
{
osbundlekeyfound = 0;
*ptr1 = '>';
break;
}
if (depth == 3 && grub_strcmp (tagstart + 1, "key") == 0)
keyptr = ptr1 + 1;
if (depth == 3 && grub_strcmp (tagstart + 1, "string") == 0)
stringptr = ptr1 + 1;
else if (grub_strcmp (tagstart + 1, "/key") != 0)
{
osbundlekeyfound = 0;
binnamekeyfound = 0;
}
*ptr1 = '>';
if (tagstart[1] == '/')
depth--;
else
depth++;
break;
}
grub_free (buf);
return ret;
}
/* Context for grub_xnu_scan_dir_for_kexts. */
struct grub_xnu_scan_dir_for_kexts_ctx
{
char *dirname;
const char *osbundlerequired;
int maxrecursion;
};
/* Helper for grub_xnu_scan_dir_for_kexts. */
static int
grub_xnu_scan_dir_for_kexts_load (const char *filename,
const struct grub_dirhook_info *info,
void *data)
{
struct grub_xnu_scan_dir_for_kexts_ctx *ctx = data;
char *newdirname;
if (! info->dir)
return 0;
if (filename[0] == '.')
return 0;
if (grub_strlen (filename) < 5 ||
grub_memcmp (filename + grub_strlen (filename) - 5, ".kext", 5) != 0)
return 0;
newdirname
= grub_malloc (grub_strlen (ctx->dirname) + grub_strlen (filename) + 2);
/* It's a .kext. Try to load it. */
if (newdirname)
{
grub_strcpy (newdirname, ctx->dirname);
newdirname[grub_strlen (newdirname) + 1] = 0;
newdirname[grub_strlen (newdirname)] = '/';
grub_strcpy (newdirname + grub_strlen (newdirname), filename);
grub_xnu_load_kext_from_dir (newdirname, ctx->osbundlerequired,
ctx->maxrecursion);
if (grub_errno == GRUB_ERR_BAD_OS)
grub_errno = GRUB_ERR_NONE;
grub_free (newdirname);
}
return 0;
}
/* Load all loadable kexts placed under DIRNAME and matching OSBUNDLEREQUIRED */
grub_err_t
grub_xnu_scan_dir_for_kexts (char *dirname, const char *osbundlerequired,
int maxrecursion)
{
struct grub_xnu_scan_dir_for_kexts_ctx ctx = {
.dirname = dirname,
.osbundlerequired = osbundlerequired,
.maxrecursion = maxrecursion
};
grub_device_t dev;
char *device_name;
grub_fs_t fs;
const char *path;
if (! grub_xnu_heap_size)
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
device_name = grub_file_get_device_name (dirname);
dev = grub_device_open (device_name);
if (dev)
{
fs = grub_fs_probe (dev);
path = grub_strchr (dirname, ')');
if (! path)
path = dirname;
else
path++;
if (fs)
(fs->fs_dir) (dev, path, grub_xnu_scan_dir_for_kexts_load, &ctx);
grub_device_close (dev);
}
grub_free (device_name);
return GRUB_ERR_NONE;
}
/* Context for grub_xnu_load_kext_from_dir. */
struct grub_xnu_load_kext_from_dir_ctx
{
char *dirname;
const char *osbundlerequired;
int maxrecursion;
char *plistname;
char *newdirname;
int usemacos;
};
/* Helper for grub_xnu_load_kext_from_dir. */
static int
grub_xnu_load_kext_from_dir_load (const char *filename,
const struct grub_dirhook_info *info,
void *data)
{
struct grub_xnu_load_kext_from_dir_ctx *ctx = data;
if (grub_strlen (filename) > 15)
return 0;
grub_strcpy (ctx->newdirname + grub_strlen (ctx->dirname) + 1, filename);
/* If the kext contains directory "Contents" all real stuff is in
this directory. */
if (info->dir && grub_strcasecmp (filename, "Contents") == 0)
grub_xnu_load_kext_from_dir (ctx->newdirname, ctx->osbundlerequired,
ctx->maxrecursion - 1);
/* Directory "Plugins" contains nested kexts. */
if (info->dir && grub_strcasecmp (filename, "Plugins") == 0)
grub_xnu_scan_dir_for_kexts (ctx->newdirname, ctx->osbundlerequired,
ctx->maxrecursion - 1);
/* Directory "MacOS" contains executable, otherwise executable is
on the top. */
if (info->dir && grub_strcasecmp (filename, "MacOS") == 0)
ctx->usemacos = 1;
/* Info.plist is the file which governs our future actions. */
if (! info->dir && grub_strcasecmp (filename, "Info.plist") == 0
&& ! ctx->plistname)
ctx->plistname = grub_strdup (ctx->newdirname);
return 0;
}
/* Load extension DIRNAME. (extensions are directories in xnu) */
grub_err_t
grub_xnu_load_kext_from_dir (char *dirname, const char *osbundlerequired,
int maxrecursion)
{
struct grub_xnu_load_kext_from_dir_ctx ctx = {
.dirname = dirname,
.osbundlerequired = osbundlerequired,
.maxrecursion = maxrecursion,
.plistname = 0,
.usemacos = 0
};
grub_device_t dev;
char *newpath;
char *device_name;
grub_fs_t fs;
const char *path;
char *binsuffix;
grub_file_t binfile;
ctx.newdirname = grub_malloc (grub_strlen (dirname) + 20);
if (! ctx.newdirname)
return grub_errno;
grub_strcpy (ctx.newdirname, dirname);
ctx.newdirname[grub_strlen (dirname)] = '/';
ctx.newdirname[grub_strlen (dirname) + 1] = 0;
device_name = grub_file_get_device_name (dirname);
dev = grub_device_open (device_name);
if (dev)
{
fs = grub_fs_probe (dev);
path = grub_strchr (dirname, ')');
if (! path)
path = dirname;
else
path++;
newpath = grub_strchr (ctx.newdirname, ')');
if (! newpath)
newpath = ctx.newdirname;
else
newpath++;
/* Look at the directory. */
if (fs)
(fs->fs_dir) (dev, path, grub_xnu_load_kext_from_dir_load, &ctx);
if (ctx.plistname && grub_xnu_check_os_bundle_required
(ctx.plistname, osbundlerequired, &binsuffix))
{
if (binsuffix)
{
/* Open the binary. */
char *binname = grub_malloc (grub_strlen (dirname)
+ grub_strlen (binsuffix)
+ sizeof ("/MacOS/"));
grub_strcpy (binname, dirname);
if (ctx.usemacos)
grub_strcpy (binname + grub_strlen (binname), "/MacOS/");
else
grub_strcpy (binname + grub_strlen (binname), "/");
grub_strcpy (binname + grub_strlen (binname), binsuffix);
grub_dprintf ("xnu", "%s:%s\n", ctx.plistname, binname);
binfile = grub_file_open (binname, GRUB_FILE_TYPE_XNU_KEXT);
if (! binfile)
grub_errno = GRUB_ERR_NONE;
/* Load the extension. */
grub_xnu_load_driver (ctx.plistname, binfile,
binname);
grub_free (binname);
grub_free (binsuffix);
}
else
{
grub_dprintf ("xnu", "%s:0\n", ctx.plistname);
grub_xnu_load_driver (ctx.plistname, 0, 0);
}
}
grub_free (ctx.plistname);
grub_device_close (dev);
}
grub_free (device_name);
return GRUB_ERR_NONE;
}
static int locked=0;
static grub_dl_t my_mod;
/* Load the kext. */
static grub_err_t
grub_cmd_xnu_kext (grub_command_t cmd __attribute__ ((unused)),
int argc, char *args[])
{
grub_file_t binfile = 0;
if (! grub_xnu_heap_size)
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
if (argc == 2)
{
/* User explicitly specified plist and binary. */
if (grub_strcmp (args[1], "-") != 0)
{
binfile = grub_file_open (args[1], GRUB_FILE_TYPE_XNU_KEXT);
if (! binfile)
return grub_errno;
}
return grub_xnu_load_driver (grub_strcmp (args[0], "-") ? args[0] : 0,
binfile, args[1]);
}
/* load kext normally. */
if (argc == 1)
return grub_xnu_load_kext_from_dir (args[0], 0, 10);
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
}
/* Load a directory containing kexts. */
static grub_err_t
grub_cmd_xnu_kextdir (grub_command_t cmd __attribute__ ((unused)),
int argc, char *args[])
{
if (argc != 1 && argc != 2)
return grub_error (GRUB_ERR_BAD_ARGUMENT, "directory name required");
if (! grub_xnu_heap_size)
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
if (argc == 1)
return grub_xnu_scan_dir_for_kexts (args[0],
"console,root,local-root,network-root",
10);
else
{
char *osbundlerequired = grub_strdup (args[1]), *ptr;
grub_err_t err;
if (! osbundlerequired)
return grub_errno;
for (ptr = osbundlerequired; *ptr; ptr++)
*ptr = grub_tolower (*ptr);
err = grub_xnu_scan_dir_for_kexts (args[0], osbundlerequired, 10);
grub_free (osbundlerequired);
return err;
}
}
static inline int
hextoval (char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'z')
return c - 'a' + 10;
if (c >= 'A' && c <= 'Z')
return c - 'A' + 10;
return 0;
}
static inline void
unescape (char *name, char *curdot, char *nextdot, int *len)
{
char *ptr, *dptr;
dptr = name;
for (ptr = curdot; ptr < nextdot;)
if (ptr + 2 < nextdot && *ptr == '%')
{
*dptr = (hextoval (ptr[1]) << 4) | (hextoval (ptr[2]));
ptr += 3;
dptr++;
}
else
{
*dptr = *ptr;
ptr++;
dptr++;
}
*len = dptr - name;
}
grub_err_t
grub_xnu_fill_devicetree (void)
{
struct grub_env_var *var;
FOR_SORTED_ENV (var)
{
char *nextdot = 0, *curdot;
struct grub_xnu_devtree_key **curkey = &grub_xnu_devtree_root;
struct grub_xnu_devtree_key *curvalue;
char *name = 0, *data;
int len;
if (grub_memcmp (var->name, "XNU.DeviceTree.",
sizeof ("XNU.DeviceTree.") - 1) != 0)
continue;
curdot = var->name + sizeof ("XNU.DeviceTree.") - 1;
nextdot = grub_strchr (curdot, '.');
if (nextdot)
nextdot++;
while (nextdot)
{
name = grub_realloc (name, nextdot - curdot + 1);
if (!name)
return grub_errno;
unescape (name, curdot, nextdot, &len);
name[len - 1] = 0;
curkey = &(grub_xnu_create_key (curkey, name)->first_child);
curdot = nextdot;
nextdot = grub_strchr (nextdot, '.');
if (nextdot)
nextdot++;
}
nextdot = curdot + grub_strlen (curdot) + 1;
name = grub_realloc (name, nextdot - curdot + 1);
if (!name)
return grub_errno;
unescape (name, curdot, nextdot, &len);
name[len] = 0;
curvalue = grub_xnu_create_value (curkey, name);
grub_free (name);
if (!curvalue)
return grub_errno;
data = grub_malloc (grub_strlen (var->value) + 1);
if (!data)
return grub_errno;
unescape (data, var->value, var->value + grub_strlen (var->value),
&len);
curvalue->datasize = len;
curvalue->data = data;
}
return grub_errno;
}
struct grub_video_bitmap *grub_xnu_bitmap = 0;
grub_xnu_bitmap_mode_t grub_xnu_bitmap_mode;
/* Option array indices. */
#define XNU_SPLASH_CMD_ARGINDEX_MODE 0
static const struct grub_arg_option xnu_splash_cmd_options[] =
{
{"mode", 'm', 0, N_("Background image mode."), N_("stretch|normal"),
ARG_TYPE_STRING},
{0, 0, 0, 0, 0, 0}
};
static grub_err_t
grub_cmd_xnu_splash (grub_extcmd_context_t ctxt,
int argc, char *args[])
{
grub_err_t err;
if (argc != 1)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
if (! grub_xnu_heap_size)
return grub_error (GRUB_ERR_BAD_OS, N_("you need to load the kernel first"));
if (ctxt->state[XNU_SPLASH_CMD_ARGINDEX_MODE].set &&
grub_strcmp (ctxt->state[XNU_SPLASH_CMD_ARGINDEX_MODE].arg,
"stretch") == 0)
grub_xnu_bitmap_mode = GRUB_XNU_BITMAP_STRETCH;
else
grub_xnu_bitmap_mode = GRUB_XNU_BITMAP_CENTER;
err = grub_video_bitmap_load (&grub_xnu_bitmap, args[0]);
if (err)
grub_xnu_bitmap = 0;
return err;
}
#ifndef GRUB_MACHINE_EMU
static grub_err_t
grub_cmd_xnu_resume (grub_command_t cmd __attribute__ ((unused)),
int argc, char *args[])
{
if (argc != 1)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
return grub_xnu_resume (args[0]);
}
#endif
void
grub_xnu_lock (void)
{
if (!locked)
grub_dl_ref (my_mod);
locked = 1;
}
void
grub_xnu_unlock (void)
{
if (locked)
grub_dl_unref (my_mod);
locked = 0;
}
static grub_command_t cmd_kernel64, cmd_kernel, cmd_mkext, cmd_kext;
static grub_command_t cmd_kextdir, cmd_ramdisk, cmd_resume;
static grub_extcmd_t cmd_splash;
GRUB_MOD_INIT(xnu)
{
cmd_kernel = grub_register_command ("xnu_kernel", grub_cmd_xnu_kernel, 0,
N_("Load XNU image."));
cmd_kernel64 = grub_register_command ("xnu_kernel64", grub_cmd_xnu_kernel64,
0, N_("Load 64-bit XNU image."));
cmd_mkext = grub_register_command_lockdown ("xnu_mkext", grub_cmd_xnu_mkext, 0,
N_("Load XNU extension package."));
cmd_kext = grub_register_command_lockdown ("xnu_kext", grub_cmd_xnu_kext, 0,
N_("Load XNU extension."));
cmd_kextdir = grub_register_command_lockdown ("xnu_kextdir", grub_cmd_xnu_kextdir,
/*
* TRANSLATORS: OSBundleRequired is
* a variable name in xnu extensions
* manifests. It behaves mostly like
* GNU/Linux runlevels.
*/
N_("DIRECTORY [OSBundleRequired]"),
/*
* TRANSLATORS: There are many extensions
* in extension directory.
*/
N_("Load XNU extension directory."));
cmd_ramdisk = grub_register_command ("xnu_ramdisk", grub_cmd_xnu_ramdisk, 0,
/* TRANSLATORS: ramdisk here isn't identifier. It can be translated. */
N_("Load XNU ramdisk. "
"It will be available in OS as md0."));
cmd_splash = grub_register_extcmd ("xnu_splash",
grub_cmd_xnu_splash, 0, 0,
N_("Load a splash image for XNU."),
xnu_splash_cmd_options);
#ifndef GRUB_MACHINE_EMU
cmd_resume = grub_register_command ("xnu_resume", grub_cmd_xnu_resume,
0, N_("Load an image of hibernated"
" XNU."));
#endif
grub_cpu_xnu_init ();
my_mod = mod;
}
GRUB_MOD_FINI(xnu)
{
#ifndef GRUB_MACHINE_EMU
grub_unregister_command (cmd_resume);
#endif
grub_unregister_command (cmd_mkext);
grub_unregister_command (cmd_kext);
grub_unregister_command (cmd_kextdir);
grub_unregister_command (cmd_ramdisk);
grub_unregister_command (cmd_kernel);
grub_unregister_extcmd (cmd_splash);
grub_unregister_command (cmd_kernel64);
grub_cpu_xnu_fini ();
}