]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
pefile: Digest the PE binary and compare to the PKCS#7 data
authorDavid Howells <dhowells@redhat.com>
Tue, 15 Jan 2013 15:33:41 +0000 (15:33 +0000)
committerDavid Howells <dhowells@redhat.com>
Sat, 19 Jan 2013 01:05:20 +0000 (01:05 +0000)
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>
Reviewed-by: Kees Cook <keescook@chromium.org>
crypto/asymmetric_keys/pefile_parser.c

index f1c8cc1b995d906e3a8c14dd0c4eb683fdc8b784..dfdb85e180ca9610bd49c0d95c77272c77d6420b 100644 (file)
@@ -200,6 +200,193 @@ static int pefile_strip_sig_wrapper(struct key_preparsed_payload *prep,
        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(struct key_preparsed_payload *prep,
+                                    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, prep->data, ctx->image_checksum_offset);
+       if (ret < 0)
+               return ret;
+
+       tmp = ctx->image_checksum_offset + sizeof(uint32_t);
+       ret = crypto_shash_update(desc, prep->data + 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, prep->data + 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,
+                                         prep->data + 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 (prep->datalen > hashed_bytes) {
+               tmp = hashed_bytes + ctx->certs_size;
+               ret = crypto_shash_update(desc,
+                                         prep->data + hashed_bytes,
+                                         prep->datalen - 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(struct key_preparsed_payload *prep,
+                           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(pkey_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_devel("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(prep, ctx, desc);
+       if (ret < 0)
+               goto error;
+
+       digest = (void *)desc + desc_size;
+       ret = crypto_shash_final(desc, digest);
+       if (ret < 0)
+               goto error;
+
+       pr_devel("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;
+}
+
 /*
  * Parse a PE binary.
  */
@@ -237,6 +424,17 @@ static int pefile_key_preparse(struct key_preparsed_payload *prep)
 
        pr_devel("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(prep, &ctx);
+       if (ret < 0)
+               goto error;
+
+       ret = pkcs7_verify(pkcs7);
+       if (ret < 0)
+               goto error;
+
        ret = -ENOANO; // Not yet complete
 
 error: