Skip to content

Commit

Permalink
pefile: Digest the PE binary and compare to the PKCS#7 data
Browse files Browse the repository at this point in the history
Digest the signed parts of the PE binary, canonicalising the section table
before we need it, and then compare the the resulting digest to the one in the
PKCS#7 signed content.

Signed-off-by: David Howells <[email protected]>
Acked-by: Vivek Goyal <[email protected]>
Reviewed-by: Kees Cook <[email protected]>
  • Loading branch information
dhowells committed Jul 9, 2014
1 parent dd7d66f commit af316fc
Showing 1 changed file with 197 additions and 0 deletions.
197 changes: 197 additions & 0 deletions crypto/asymmetric_keys/verify_pefile.c
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,192 @@ static int pefile_strip_sig_wrapper(const void *pebuf,
return -ELIBBAD;
}

/*
* Compare two sections for canonicalisation.
*/
static int pefile_compare_shdrs(const void *a, const void *b)
{
const struct section_header *shdra = a;
const struct section_header *shdrb = b;
int rc;

if (shdra->data_addr > shdrb->data_addr)
return 1;
if (shdrb->data_addr > shdra->data_addr)
return -1;

if (shdra->virtual_address > shdrb->virtual_address)
return 1;
if (shdrb->virtual_address > shdra->virtual_address)
return -1;

rc = strcmp(shdra->name, shdrb->name);
if (rc != 0)
return rc;

if (shdra->virtual_size > shdrb->virtual_size)
return 1;
if (shdrb->virtual_size > shdra->virtual_size)
return -1;

if (shdra->raw_data_size > shdrb->raw_data_size)
return 1;
if (shdrb->raw_data_size > shdra->raw_data_size)
return -1;

return 0;
}

/*
* Load the contents of the PE binary into the digest, leaving out the image
* checksum and the certificate data block.
*/
static int pefile_digest_pe_contents(const void *pebuf, unsigned int pelen,
struct pefile_context *ctx,
struct shash_desc *desc)
{
unsigned *canon, tmp, loop, i, hashed_bytes;
int ret;

/* Digest the header and data directory, but leave out the image
* checksum and the data dirent for the signature.
*/
ret = crypto_shash_update(desc, pebuf, ctx->image_checksum_offset);
if (ret < 0)
return ret;

tmp = ctx->image_checksum_offset + sizeof(uint32_t);
ret = crypto_shash_update(desc, pebuf + tmp,
ctx->cert_dirent_offset - tmp);
if (ret < 0)
return ret;

tmp = ctx->cert_dirent_offset + sizeof(struct data_dirent);
ret = crypto_shash_update(desc, pebuf + tmp, ctx->header_size - tmp);
if (ret < 0)
return ret;

canon = kcalloc(ctx->n_sections, sizeof(unsigned), GFP_KERNEL);
if (!canon)
return -ENOMEM;

/* We have to canonicalise the section table, so we perform an
* insertion sort.
*/
canon[0] = 0;
for (loop = 1; loop < ctx->n_sections; loop++) {
for (i = 0; i < loop; i++) {
if (pefile_compare_shdrs(&ctx->secs[canon[i]],
&ctx->secs[loop]) > 0) {
memmove(&canon[i + 1], &canon[i],
(loop - i) * sizeof(canon[0]));
break;
}
}
canon[i] = loop;
}

hashed_bytes = ctx->header_size;
for (loop = 0; loop < ctx->n_sections; loop++) {
i = canon[loop];
if (ctx->secs[i].raw_data_size == 0)
continue;
ret = crypto_shash_update(desc,
pebuf + ctx->secs[i].data_addr,
ctx->secs[i].raw_data_size);
if (ret < 0) {
kfree(canon);
return ret;
}
hashed_bytes += ctx->secs[i].raw_data_size;
}
kfree(canon);

if (pelen > hashed_bytes) {
tmp = hashed_bytes + ctx->certs_size;
ret = crypto_shash_update(desc,
pebuf + hashed_bytes,
pelen - tmp);
if (ret < 0)
return ret;
}

return 0;
}

/*
* Digest the contents of the PE binary, leaving out the image checksum and the
* certificate data block.
*/
static int pefile_digest_pe(const void *pebuf, unsigned int pelen,
struct pefile_context *ctx)
{
struct crypto_shash *tfm;
struct shash_desc *desc;
size_t digest_size, desc_size;
void *digest;
int ret;

kenter(",%u", ctx->digest_algo);

/* Allocate the hashing algorithm we're going to need and find out how
* big the hash operational data will be.
*/
tfm = crypto_alloc_shash(hash_algo_name[ctx->digest_algo], 0, 0);
if (IS_ERR(tfm))
return (PTR_ERR(tfm) == -ENOENT) ? -ENOPKG : PTR_ERR(tfm);

desc_size = crypto_shash_descsize(tfm) + sizeof(*desc);
digest_size = crypto_shash_digestsize(tfm);

if (digest_size != ctx->digest_len) {
pr_debug("Digest size mismatch (%zx != %x)\n",
digest_size, ctx->digest_len);
ret = -EBADMSG;
goto error_no_desc;
}
pr_debug("Digest: desc=%zu size=%zu\n", desc_size, digest_size);

ret = -ENOMEM;
desc = kzalloc(desc_size + digest_size, GFP_KERNEL);
if (!desc)
goto error_no_desc;

desc->tfm = tfm;
desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
ret = crypto_shash_init(desc);
if (ret < 0)
goto error;

ret = pefile_digest_pe_contents(pebuf, pelen, ctx, desc);
if (ret < 0)
goto error;

digest = (void *)desc + desc_size;
ret = crypto_shash_final(desc, digest);
if (ret < 0)
goto error;

pr_debug("Digest calc = [%*ph]\n", ctx->digest_len, digest);

/* Check that the PE file digest matches that in the MSCODE part of the
* PKCS#7 certificate.
*/
if (memcmp(digest, ctx->digest, ctx->digest_len) != 0) {
pr_debug("Digest mismatch\n");
ret = -EKEYREJECTED;
} else {
pr_debug("The digests match!\n");
}

error:
kfree(desc);
error_no_desc:
crypto_free_shash(tfm);
kleave(" = %d", ret);
return ret;
}

/**
* verify_pefile_signature - Verify the signature on a PE binary image
* @pebuf: Buffer containing the PE binary image
Expand Down Expand Up @@ -252,6 +438,17 @@ int verify_pefile_signature(const void *pebuf, unsigned pelen,
pr_debug("Digest: %u [%*ph]\n",
ctx.digest_len, ctx.digest_len, ctx.digest);

/* Generate the digest and check against the PKCS7 certificate
* contents.
*/
ret = pefile_digest_pe(pebuf, pelen, &ctx);
if (ret < 0)
goto error;

ret = pkcs7_verify(pkcs7);
if (ret < 0)
goto error;

ret = -ENOANO; // Not yet complete

error:
Expand Down

0 comments on commit af316fc

Please sign in to comment.