/*
* GRUB -- GRand Unified Bootloader
* Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
*
* 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/>.
*/
#define BBLK_EINFO_VERSION (1)
#define EINFO_MAGIC "EXTINFO"
#define EINFO_MAGIC_SIZE (7)
enum bblk_hash_types_t {
BBLK_NO_HASH = 0,
BBLK_HASH_MD5
};
/* Extra header preceding the payloads at the end of the bootblock. */
typedef struct _bb_extra_header {
uint32_t size;
uint32_t checksum;
} bb_header_ext_t;
#pragma pack(1)
typedef struct _extended_info {
char magic[EINFO_MAGIC_SIZE];
uint8_t version;
uint8_t flags;
uint32_t str_off;
uint16_t str_size;
uint8_t hash_type;
uint32_t hash_off;
uint16_t hash_size;
char rsvd[32];
} bblk_einfo_t;
#pragma pack()
typedef struct _hashing_function {
unsigned int type;
unsigned int size;
char name[16];
void (*compute_hash)(void *, const void *, unsigned int);
} bblk_hash_t;
typedef struct _hashing_source {
unsigned char *src_buf;
unsigned int src_size;
} bblk_hs_t;
uint32_t compute_checksum(char *, uint32_t);
int add_einfo(char *, char *, bblk_hs_t *, uint32_t, int, uint32_t *);
int prepare_and_write_einfo(unsigned char *, char *, bblk_hs_t *,
uint32_t, uint32_t *, int);
/*************************************************************************/
void
grub_md5_calc(void *output, const void *input, unsigned int inlen)
{
grub_uint8_t ctx[GRUB_MD_MD5->contextsize];
unsigned char *digest;
GRUB_MD_MD5->init (ctx);
GRUB_MD_MD5->write (ctx, input, inlen);
digest = GRUB_MD_MD5->read (ctx);
GRUB_MD_MD5->final (ctx);
memcpy(output, digest, 0x10);
}
/*************************************************************************/
bblk_hash_t bblk_no_hash = {BBLK_NO_HASH, 0, "(no hash)", NULL};
bblk_hash_t bblk_md5_hash = {BBLK_HASH_MD5, 0x10, "MD5", grub_md5_calc};
static int
compute_hash(bblk_hs_t *hs, unsigned char *dest, bblk_hash_t *hash)
{
if (hs == NULL || dest == NULL || hash == NULL)
return (-1);
hash->compute_hash(dest, hs->src_buf, hs->src_size);
return (0);
}
int
prepare_and_write_einfo(unsigned char *dest, char *infostr, bblk_hs_t *hs,
uint32_t maxsize, uint32_t *used_space, int doit)
{
uint16_t hash_size;
uint32_t hash_off;
unsigned char *data;
bblk_einfo_t *einfo = (bblk_einfo_t *)dest;
bblk_hash_t *hashinfo = &bblk_md5_hash;
/*
* 'dest' might be both containing the buffer we want to hash and
* containing our einfo structure: delay any update of it after the
* hashing has been calculated.
*/
hash_size = hashinfo->size;
hash_off = sizeof (bblk_einfo_t);
if (hash_off + hash_size > maxsize) {
(void) fprintf(stderr, gettext("Unable to add extended info, "
"not enough space\n"));
return (-1);
}
if (doit) {
data = dest + hash_off;
if (compute_hash(hs, data, hashinfo) < 0) {
(void) fprintf(stderr, gettext("%s hash operation failed\n"),
hashinfo->name);
einfo->hash_type = bblk_no_hash.type;
einfo->hash_size = bblk_no_hash.size;
} else {
einfo->hash_type = hashinfo->type;
einfo->hash_size = hashinfo->size;
}
(void) memcpy(einfo->magic, EINFO_MAGIC, EINFO_MAGIC_SIZE);
einfo->version = BBLK_EINFO_VERSION;
einfo->flags = 0;
einfo->hash_off = hash_off;
einfo->hash_size = hash_size;
einfo->str_off = einfo->hash_off + einfo->hash_size + 1;
}
if (infostr == NULL) {
(void) fprintf(stderr, gettext("Unable to add extended info, "
"string is empty\n"));
return (-1);
}
if (doit)
einfo->str_size = strlen(infostr);
if (einfo->str_off + einfo->str_size > maxsize) {
(void) fprintf(stderr, gettext("Unable to add extended info, "
"not enough space\n"));
return (-1);
}
if (!doit) {
*used_space = hash_off + hash_size + 1 + strlen(infostr);
return (0);
}
data = dest + einfo->str_off;
(void) memcpy(data, infostr, einfo->str_size);
*used_space = einfo->str_off + einfo->str_size;
return (0);
}
/*************************************************************************/
/*
* Common functions to deal with the fake-multiboot encapsulation of the
* bootblock and the location of the extra information area.
*/
/* mboot checksum routine. */
uint32_t
compute_checksum(char *data, uint32_t size)
{
uint32_t *ck_ptr;
uint32_t cksum = 0;
int i;
ck_ptr = (uint32_t *)data;
for (i = 0; i < size; i += sizeof (uint32_t))
cksum += *ck_ptr++;
return (-cksum);
}
/*
* Given a pointer to the extra area, add the extended information structure
* encapsulated by a bb_header_ext_t structure.
*/
int
add_einfo(char *extra, char *updt_str, bblk_hs_t *hs, uint32_t avail_space,
int doit, uint32_t *space_usedp)
{
bb_header_ext_t *ext_hdr;
unsigned char *dest;
uint32_t used_space;
int ret;
assert(extra != NULL);
if (updt_str == NULL) {
return (1);
}
/* Reserve space for the extra header. */
ext_hdr = (bb_header_ext_t *)extra;
dest = (unsigned char *)extra + sizeof (*ext_hdr);
/* Place the extended information structure. */
ret = prepare_and_write_einfo(dest, updt_str, hs, avail_space,
&used_space, doit);
if (ret != 0) {
if (doit)
(void) fprintf(stderr, gettext("Unable to write the extended "
"versioning information\n"));
return(1);
}
if (!doit) {
if (space_usedp)
*space_usedp = ALIGN_UP(used_space, 8);
return (0);
}
/* Fill the extended information associated header. */
ext_hdr->size = ALIGN_UP(used_space, 8);
ext_hdr->checksum = compute_checksum((char *)dest, ext_hdr->size);
return (0);
}
/*************************************************************************/
static void
grub_solaris_get_size_with_versioning(char *core_img, size_t core_size,
size_t buflen, char *vers_str, size_t *new_core_size)
{
bblk_hs_t hs;
uint32_t avail_space;
bb_header_ext_t *ext_hdr;
uint32_t space = 0;
hs.src_buf = (unsigned char *)core_img;
hs.src_size = core_size;
avail_space = buflen - ALIGN_UP(core_size, 8);
ext_hdr = (bb_header_ext_t *)(core_img + ALIGN_UP(core_size, 8));
if (add_einfo((char *)ext_hdr, vers_str, &hs, avail_space, 0, &space)
== 0) {
*new_core_size = ALIGN_UP(core_size, 8) + space +
sizeof (*ext_hdr);
if (verbosity)
printf("Size of versioning info: %d\n",
*new_core_size - core_size);
}
}
static void
grub_solaris_add_versioning_payload(char *core_img, size_t core_size,
size_t buflen, char *vers_str)
{
bblk_hs_t hs;
uint32_t avail_space;
bb_header_ext_t *ext_hdr;
hs.src_buf = (unsigned char *)core_img;
hs.src_size = core_size;
avail_space = buflen - ALIGN_UP(core_size, 8);
ext_hdr = (bb_header_ext_t *)(core_img + ALIGN_UP(core_size, 8));
if (add_einfo((char *)ext_hdr, vers_str, &hs, avail_space, 1, NULL)
== 0) {
if (verbosity)
printf("Size before versioning: %d, after: %d\n",
core_size, ALIGN_UP(core_size, 8) + ext_hdr->size +
sizeof (*ext_hdr));
}
}