pefile: Digest the PE binary and compare to the PKCS#7 data

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 <dhowells@redhat.com>
Acked-by: Vivek Goyal <vgoyal@redhat.com>
Reviewed-by: Kees Cook <keescook@chromium.org>
This commit is contained in:
David Howells 2014-07-01 16:02:52 +01:00
parent dd7d66f21b
commit af316fc442

View file

@ -185,6 +185,192 @@ static int pefile_strip_sig_wrapper(const void *pebuf,
return -ELIBBAD; 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 * verify_pefile_signature - Verify the signature on a PE binary image
* @pebuf: Buffer containing the PE binary image * @pebuf: Buffer containing the PE binary image
@ -252,6 +438,17 @@ int verify_pefile_signature(const void *pebuf, unsigned pelen,
pr_debug("Digest: %u [%*ph]\n", pr_debug("Digest: %u [%*ph]\n",
ctx.digest_len, ctx.digest_len, ctx.digest); 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 ret = -ENOANO; // Not yet complete
error: error: