Path: blob/master/crypto/asymmetric_keys/verify_pefile.c
26282 views
// SPDX-License-Identifier: GPL-2.0-or-later1/* Parse a signed PE binary2*3* Copyright (C) 2014 Red Hat, Inc. All Rights Reserved.4* Written by David Howells ([email protected])5*/67#define pr_fmt(fmt) "PEFILE: "fmt8#include <linux/module.h>9#include <linux/kernel.h>10#include <linux/slab.h>11#include <linux/err.h>12#include <linux/pe.h>13#include <linux/asn1.h>14#include <linux/verification.h>15#include <crypto/hash.h>16#include "verify_pefile.h"1718/*19* Parse a PE binary.20*/21static int pefile_parse_binary(const void *pebuf, unsigned int pelen,22struct pefile_context *ctx)23{24const struct mz_hdr *mz = pebuf;25const struct pe_hdr *pe;26const struct pe32_opt_hdr *pe32;27const struct pe32plus_opt_hdr *pe64;28const struct data_directory *ddir;29const struct data_dirent *dde;30const struct section_header *sec;31size_t cursor, datalen = pelen;3233kenter("");3435#define chkaddr(base, x, s) \36do { \37if ((x) < base || (s) >= datalen || (x) > datalen - (s)) \38return -ELIBBAD; \39} while (0)4041chkaddr(0, 0, sizeof(*mz));42if (mz->magic != IMAGE_DOS_SIGNATURE)43return -ELIBBAD;44cursor = sizeof(*mz);4546chkaddr(cursor, mz->peaddr, sizeof(*pe));47pe = pebuf + mz->peaddr;48if (pe->magic != IMAGE_NT_SIGNATURE)49return -ELIBBAD;50cursor = mz->peaddr + sizeof(*pe);5152chkaddr(0, cursor, sizeof(pe32->magic));53pe32 = pebuf + cursor;54pe64 = pebuf + cursor;5556switch (pe32->magic) {57case IMAGE_NT_OPTIONAL_HDR32_MAGIC:58chkaddr(0, cursor, sizeof(*pe32));59ctx->image_checksum_offset =60(unsigned long)&pe32->csum - (unsigned long)pebuf;61ctx->header_size = pe32->header_size;62cursor += sizeof(*pe32);63ctx->n_data_dirents = pe32->data_dirs;64break;6566case IMAGE_NT_OPTIONAL_HDR64_MAGIC:67chkaddr(0, cursor, sizeof(*pe64));68ctx->image_checksum_offset =69(unsigned long)&pe64->csum - (unsigned long)pebuf;70ctx->header_size = pe64->header_size;71cursor += sizeof(*pe64);72ctx->n_data_dirents = pe64->data_dirs;73break;7475default:76pr_warn("Unknown PEOPT magic = %04hx\n", pe32->magic);77return -ELIBBAD;78}7980pr_debug("checksum @ %x\n", ctx->image_checksum_offset);81pr_debug("header size = %x\n", ctx->header_size);8283if (cursor >= ctx->header_size || ctx->header_size >= datalen)84return -ELIBBAD;8586if (ctx->n_data_dirents > (ctx->header_size - cursor) / sizeof(*dde))87return -ELIBBAD;8889ddir = pebuf + cursor;90cursor += sizeof(*dde) * ctx->n_data_dirents;9192ctx->cert_dirent_offset =93(unsigned long)&ddir->certs - (unsigned long)pebuf;94ctx->certs_size = ddir->certs.size;9596if (!ddir->certs.virtual_address || !ddir->certs.size) {97pr_warn("Unsigned PE binary\n");98return -ENODATA;99}100101chkaddr(ctx->header_size, ddir->certs.virtual_address,102ddir->certs.size);103ctx->sig_offset = ddir->certs.virtual_address;104ctx->sig_len = ddir->certs.size;105pr_debug("cert = %x @%x [%*ph]\n",106ctx->sig_len, ctx->sig_offset,107ctx->sig_len, pebuf + ctx->sig_offset);108109ctx->n_sections = pe->sections;110if (ctx->n_sections > (ctx->header_size - cursor) / sizeof(*sec))111return -ELIBBAD;112ctx->secs = pebuf + cursor;113114return 0;115}116117/*118* Check and strip the PE wrapper from around the signature and check that the119* remnant looks something like PKCS#7.120*/121static int pefile_strip_sig_wrapper(const void *pebuf,122struct pefile_context *ctx)123{124struct win_certificate wrapper;125const u8 *pkcs7;126unsigned len;127128if (ctx->sig_len < sizeof(wrapper)) {129pr_warn("Signature wrapper too short\n");130return -ELIBBAD;131}132133memcpy(&wrapper, pebuf + ctx->sig_offset, sizeof(wrapper));134pr_debug("sig wrapper = { %x, %x, %x }\n",135wrapper.length, wrapper.revision, wrapper.cert_type);136137/* sbsign rounds up the length of certificate table (in optional138* header data directories) to 8 byte alignment. However, the PE139* specification states that while entries are 8-byte aligned, this is140* not included in their length, and as a result, pesign has not141* rounded up since 0.110.142*/143if (wrapper.length > ctx->sig_len) {144pr_warn("Signature wrapper bigger than sig len (%x > %x)\n",145ctx->sig_len, wrapper.length);146return -ELIBBAD;147}148if (wrapper.revision != WIN_CERT_REVISION_2_0) {149pr_warn("Signature is not revision 2.0\n");150return -ENOTSUPP;151}152if (wrapper.cert_type != WIN_CERT_TYPE_PKCS_SIGNED_DATA) {153pr_warn("Signature certificate type is not PKCS\n");154return -ENOTSUPP;155}156157/* It looks like the pkcs signature length in wrapper->length and the158* size obtained from the data dir entries, which lists the total size159* of certificate table, are both aligned to an octaword boundary, so160* we may have to deal with some padding.161*/162ctx->sig_len = wrapper.length;163ctx->sig_offset += sizeof(wrapper);164ctx->sig_len -= sizeof(wrapper);165if (ctx->sig_len < 4) {166pr_warn("Signature data missing\n");167return -EKEYREJECTED;168}169170/* What's left should be a PKCS#7 cert */171pkcs7 = pebuf + ctx->sig_offset;172if (pkcs7[0] != (ASN1_CONS_BIT | ASN1_SEQ))173goto not_pkcs7;174175switch (pkcs7[1]) {176case 0 ... 0x7f:177len = pkcs7[1] + 2;178goto check_len;179case ASN1_INDEFINITE_LENGTH:180return 0;181case 0x81:182len = pkcs7[2] + 3;183goto check_len;184case 0x82:185len = ((pkcs7[2] << 8) | pkcs7[3]) + 4;186goto check_len;187case 0x83 ... 0xff:188return -EMSGSIZE;189default:190goto not_pkcs7;191}192193check_len:194if (len <= ctx->sig_len) {195/* There may be padding */196ctx->sig_len = len;197return 0;198}199not_pkcs7:200pr_warn("Signature data not PKCS#7\n");201return -ELIBBAD;202}203204/*205* Compare two sections for canonicalisation.206*/207static int pefile_compare_shdrs(const void *a, const void *b)208{209const struct section_header *shdra = a;210const struct section_header *shdrb = b;211int rc;212213if (shdra->data_addr > shdrb->data_addr)214return 1;215if (shdrb->data_addr > shdra->data_addr)216return -1;217218if (shdra->virtual_address > shdrb->virtual_address)219return 1;220if (shdrb->virtual_address > shdra->virtual_address)221return -1;222223rc = strcmp(shdra->name, shdrb->name);224if (rc != 0)225return rc;226227if (shdra->virtual_size > shdrb->virtual_size)228return 1;229if (shdrb->virtual_size > shdra->virtual_size)230return -1;231232if (shdra->raw_data_size > shdrb->raw_data_size)233return 1;234if (shdrb->raw_data_size > shdra->raw_data_size)235return -1;236237return 0;238}239240/*241* Load the contents of the PE binary into the digest, leaving out the image242* checksum and the certificate data block.243*/244static int pefile_digest_pe_contents(const void *pebuf, unsigned int pelen,245struct pefile_context *ctx,246struct shash_desc *desc)247{248unsigned *canon, tmp, loop, i, hashed_bytes;249int ret;250251/* Digest the header and data directory, but leave out the image252* checksum and the data dirent for the signature.253*/254ret = crypto_shash_update(desc, pebuf, ctx->image_checksum_offset);255if (ret < 0)256return ret;257258tmp = ctx->image_checksum_offset + sizeof(uint32_t);259ret = crypto_shash_update(desc, pebuf + tmp,260ctx->cert_dirent_offset - tmp);261if (ret < 0)262return ret;263264tmp = ctx->cert_dirent_offset + sizeof(struct data_dirent);265ret = crypto_shash_update(desc, pebuf + tmp, ctx->header_size - tmp);266if (ret < 0)267return ret;268269canon = kcalloc(ctx->n_sections, sizeof(unsigned), GFP_KERNEL);270if (!canon)271return -ENOMEM;272273/* We have to canonicalise the section table, so we perform an274* insertion sort.275*/276canon[0] = 0;277for (loop = 1; loop < ctx->n_sections; loop++) {278for (i = 0; i < loop; i++) {279if (pefile_compare_shdrs(&ctx->secs[canon[i]],280&ctx->secs[loop]) > 0) {281memmove(&canon[i + 1], &canon[i],282(loop - i) * sizeof(canon[0]));283break;284}285}286canon[i] = loop;287}288289hashed_bytes = ctx->header_size;290for (loop = 0; loop < ctx->n_sections; loop++) {291i = canon[loop];292if (ctx->secs[i].raw_data_size == 0)293continue;294ret = crypto_shash_update(desc,295pebuf + ctx->secs[i].data_addr,296ctx->secs[i].raw_data_size);297if (ret < 0) {298kfree(canon);299return ret;300}301hashed_bytes += ctx->secs[i].raw_data_size;302}303kfree(canon);304305if (pelen > hashed_bytes) {306tmp = hashed_bytes + ctx->certs_size;307ret = crypto_shash_update(desc,308pebuf + hashed_bytes,309pelen - tmp);310if (ret < 0)311return ret;312}313314return 0;315}316317/*318* Digest the contents of the PE binary, leaving out the image checksum and the319* certificate data block.320*/321static int pefile_digest_pe(const void *pebuf, unsigned int pelen,322struct pefile_context *ctx)323{324struct crypto_shash *tfm;325struct shash_desc *desc;326size_t digest_size, desc_size;327void *digest;328int ret;329330kenter(",%s", ctx->digest_algo);331332/* Allocate the hashing algorithm we're going to need and find out how333* big the hash operational data will be.334*/335tfm = crypto_alloc_shash(ctx->digest_algo, 0, 0);336if (IS_ERR(tfm))337return (PTR_ERR(tfm) == -ENOENT) ? -ENOPKG : PTR_ERR(tfm);338339desc_size = crypto_shash_descsize(tfm) + sizeof(*desc);340digest_size = crypto_shash_digestsize(tfm);341342if (digest_size != ctx->digest_len) {343pr_warn("Digest size mismatch (%zx != %x)\n",344digest_size, ctx->digest_len);345ret = -EBADMSG;346goto error_no_desc;347}348pr_debug("Digest: desc=%zu size=%zu\n", desc_size, digest_size);349350ret = -ENOMEM;351desc = kzalloc(desc_size + digest_size, GFP_KERNEL);352if (!desc)353goto error_no_desc;354355desc->tfm = tfm;356ret = crypto_shash_init(desc);357if (ret < 0)358goto error;359360ret = pefile_digest_pe_contents(pebuf, pelen, ctx, desc);361if (ret < 0)362goto error;363364digest = (void *)desc + desc_size;365ret = crypto_shash_final(desc, digest);366if (ret < 0)367goto error;368369pr_debug("Digest calc = [%*ph]\n", ctx->digest_len, digest);370371/* Check that the PE file digest matches that in the MSCODE part of the372* PKCS#7 certificate.373*/374if (memcmp(digest, ctx->digest, ctx->digest_len) != 0) {375pr_warn("Digest mismatch\n");376ret = -EKEYREJECTED;377} else {378pr_debug("The digests match!\n");379}380381error:382kfree_sensitive(desc);383error_no_desc:384crypto_free_shash(tfm);385kleave(" = %d", ret);386return ret;387}388389/**390* verify_pefile_signature - Verify the signature on a PE binary image391* @pebuf: Buffer containing the PE binary image392* @pelen: Length of the binary image393* @trusted_keys: Signing certificate(s) to use as starting points394* @usage: The use to which the key is being put.395*396* Validate that the certificate chain inside the PKCS#7 message inside the PE397* binary image intersects keys we already know and trust.398*399* Returns, in order of descending priority:400*401* (*) -ELIBBAD if the image cannot be parsed, or:402*403* (*) -EKEYREJECTED if a signature failed to match for which we have a valid404* key, or:405*406* (*) 0 if at least one signature chain intersects with the keys in the trust407* keyring, or:408*409* (*) -ENODATA if there is no signature present.410*411* (*) -ENOPKG if a suitable crypto module couldn't be found for a check on a412* chain.413*414* (*) -ENOKEY if we couldn't find a match for any of the signature chains in415* the message.416*417* May also return -ENOMEM.418*/419int verify_pefile_signature(const void *pebuf, unsigned pelen,420struct key *trusted_keys,421enum key_being_used_for usage)422{423struct pefile_context ctx;424int ret;425426kenter("");427428memset(&ctx, 0, sizeof(ctx));429ret = pefile_parse_binary(pebuf, pelen, &ctx);430if (ret < 0)431return ret;432433ret = pefile_strip_sig_wrapper(pebuf, &ctx);434if (ret < 0)435return ret;436437ret = verify_pkcs7_signature(NULL, 0,438pebuf + ctx.sig_offset, ctx.sig_len,439trusted_keys, usage,440mscode_parse, &ctx);441if (ret < 0)442goto error;443444pr_debug("Digest: %u [%*ph]\n",445ctx.digest_len, ctx.digest_len, ctx.digest);446447/* Generate the digest and check against the PKCS7 certificate448* contents.449*/450ret = pefile_digest_pe(pebuf, pelen, &ctx);451452error:453kfree_sensitive(ctx.digest);454return ret;455}456457458