pefile: Parse a PE binary to find a key and a signature contained therein
Parse a PE binary to find a key and a signature contained therein. Later patches will check the signature and add the key if the signature checks out. 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:
parent
9c87e0f10e
commit
26d1164be3
5 changed files with 235 additions and 0 deletions
|
@ -59,4 +59,13 @@ config PKCS7_TEST_KEY
|
|||
|
||||
This is intended for testing the PKCS#7 parser.
|
||||
|
||||
config SIGNED_PE_FILE_VERIFICATION
|
||||
bool "Support for PE file signature verification"
|
||||
depends on PKCS7_MESSAGE_PARSER=y
|
||||
select ASN1
|
||||
select OID_REGISTRY
|
||||
help
|
||||
This option provides support for verifying the signature(s) on a
|
||||
signed PE binary.
|
||||
|
||||
endif # ASYMMETRIC_KEY_TYPE
|
||||
|
|
|
@ -47,3 +47,11 @@ clean-files += pkcs7-asn1.c pkcs7-asn1.h
|
|||
obj-$(CONFIG_PKCS7_TEST_KEY) += pkcs7_test_key.o
|
||||
pkcs7_test_key-y := \
|
||||
pkcs7_key_type.o
|
||||
|
||||
#
|
||||
# Signed PE binary-wrapped key handling
|
||||
#
|
||||
obj-$(CONFIG_SIGNED_PE_FILE_VERIFICATION) += verify_signed_pefile.o
|
||||
|
||||
verify_signed_pefile-y := \
|
||||
verify_pefile.o
|
||||
|
|
163
crypto/asymmetric_keys/verify_pefile.c
Normal file
163
crypto/asymmetric_keys/verify_pefile.c
Normal file
|
@ -0,0 +1,163 @@
|
|||
/* Parse a signed PE binary
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc. All Rights Reserved.
|
||||
* Written by David Howells (dhowells@redhat.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public Licence
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the Licence, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "PEFILE: "fmt
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/pe.h>
|
||||
#include <crypto/pkcs7.h>
|
||||
#include <crypto/hash.h>
|
||||
#include "verify_pefile.h"
|
||||
|
||||
/*
|
||||
* Parse a PE binary.
|
||||
*/
|
||||
static int pefile_parse_binary(const void *pebuf, unsigned int pelen,
|
||||
struct pefile_context *ctx)
|
||||
{
|
||||
const struct mz_hdr *mz = pebuf;
|
||||
const struct pe_hdr *pe;
|
||||
const struct pe32_opt_hdr *pe32;
|
||||
const struct pe32plus_opt_hdr *pe64;
|
||||
const struct data_directory *ddir;
|
||||
const struct data_dirent *dde;
|
||||
const struct section_header *secs, *sec;
|
||||
size_t cursor, datalen = pelen;
|
||||
|
||||
kenter("");
|
||||
|
||||
#define chkaddr(base, x, s) \
|
||||
do { \
|
||||
if ((x) < base || (s) >= datalen || (x) > datalen - (s)) \
|
||||
return -ELIBBAD; \
|
||||
} while (0)
|
||||
|
||||
chkaddr(0, 0, sizeof(*mz));
|
||||
if (mz->magic != MZ_MAGIC)
|
||||
return -ELIBBAD;
|
||||
cursor = sizeof(*mz);
|
||||
|
||||
chkaddr(cursor, mz->peaddr, sizeof(*pe));
|
||||
pe = pebuf + mz->peaddr;
|
||||
if (pe->magic != PE_MAGIC)
|
||||
return -ELIBBAD;
|
||||
cursor = mz->peaddr + sizeof(*pe);
|
||||
|
||||
chkaddr(0, cursor, sizeof(pe32->magic));
|
||||
pe32 = pebuf + cursor;
|
||||
pe64 = pebuf + cursor;
|
||||
|
||||
switch (pe32->magic) {
|
||||
case PE_OPT_MAGIC_PE32:
|
||||
chkaddr(0, cursor, sizeof(*pe32));
|
||||
ctx->image_checksum_offset =
|
||||
(unsigned long)&pe32->csum - (unsigned long)pebuf;
|
||||
ctx->header_size = pe32->header_size;
|
||||
cursor += sizeof(*pe32);
|
||||
ctx->n_data_dirents = pe32->data_dirs;
|
||||
break;
|
||||
|
||||
case PE_OPT_MAGIC_PE32PLUS:
|
||||
chkaddr(0, cursor, sizeof(*pe64));
|
||||
ctx->image_checksum_offset =
|
||||
(unsigned long)&pe64->csum - (unsigned long)pebuf;
|
||||
ctx->header_size = pe64->header_size;
|
||||
cursor += sizeof(*pe64);
|
||||
ctx->n_data_dirents = pe64->data_dirs;
|
||||
break;
|
||||
|
||||
default:
|
||||
pr_debug("Unknown PEOPT magic = %04hx\n", pe32->magic);
|
||||
return -ELIBBAD;
|
||||
}
|
||||
|
||||
pr_debug("checksum @ %x\n", ctx->image_checksum_offset);
|
||||
pr_debug("header size = %x\n", ctx->header_size);
|
||||
|
||||
if (cursor >= ctx->header_size || ctx->header_size >= datalen)
|
||||
return -ELIBBAD;
|
||||
|
||||
if (ctx->n_data_dirents > (ctx->header_size - cursor) / sizeof(*dde))
|
||||
return -ELIBBAD;
|
||||
|
||||
ddir = pebuf + cursor;
|
||||
cursor += sizeof(*dde) * ctx->n_data_dirents;
|
||||
|
||||
ctx->cert_dirent_offset =
|
||||
(unsigned long)&ddir->certs - (unsigned long)pebuf;
|
||||
ctx->certs_size = ddir->certs.size;
|
||||
|
||||
if (!ddir->certs.virtual_address || !ddir->certs.size) {
|
||||
pr_debug("Unsigned PE binary\n");
|
||||
return -EKEYREJECTED;
|
||||
}
|
||||
|
||||
chkaddr(ctx->header_size, ddir->certs.virtual_address,
|
||||
ddir->certs.size);
|
||||
ctx->sig_offset = ddir->certs.virtual_address;
|
||||
ctx->sig_len = ddir->certs.size;
|
||||
pr_debug("cert = %x @%x [%*ph]\n",
|
||||
ctx->sig_len, ctx->sig_offset,
|
||||
ctx->sig_len, pebuf + ctx->sig_offset);
|
||||
|
||||
ctx->n_sections = pe->sections;
|
||||
if (ctx->n_sections > (ctx->header_size - cursor) / sizeof(*sec))
|
||||
return -ELIBBAD;
|
||||
ctx->secs = secs = pebuf + cursor;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* verify_pefile_signature - Verify the signature on a PE binary image
|
||||
* @pebuf: Buffer containing the PE binary image
|
||||
* @pelen: Length of the binary image
|
||||
* @trust_keyring: Signing certificates to use as starting points
|
||||
* @_trusted: Set to true if trustworth, false otherwise
|
||||
*
|
||||
* Validate that the certificate chain inside the PKCS#7 message inside the PE
|
||||
* binary image intersects keys we already know and trust.
|
||||
*
|
||||
* Returns, in order of descending priority:
|
||||
*
|
||||
* (*) -ELIBBAD if the image cannot be parsed, or:
|
||||
*
|
||||
* (*) -EKEYREJECTED if a signature failed to match for which we have a valid
|
||||
* key, or:
|
||||
*
|
||||
* (*) 0 if at least one signature chain intersects with the keys in the trust
|
||||
* keyring, or:
|
||||
*
|
||||
* (*) -ENOPKG if a suitable crypto module couldn't be found for a check on a
|
||||
* chain.
|
||||
*
|
||||
* (*) -ENOKEY if we couldn't find a match for any of the signature chains in
|
||||
* the message.
|
||||
*
|
||||
* May also return -ENOMEM.
|
||||
*/
|
||||
int verify_pefile_signature(const void *pebuf, unsigned pelen,
|
||||
struct key *trusted_keyring, bool *_trusted)
|
||||
{
|
||||
struct pefile_context ctx;
|
||||
int ret;
|
||||
|
||||
kenter("");
|
||||
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
ret = pefile_parse_binary(pebuf, pelen, &ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return -ENOANO; // Not yet complete
|
||||
}
|
37
crypto/asymmetric_keys/verify_pefile.h
Normal file
37
crypto/asymmetric_keys/verify_pefile.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/* PE Binary parser bits
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc. All Rights Reserved.
|
||||
* Written by David Howells (dhowells@redhat.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public Licence
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the Licence, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/verify_pefile.h>
|
||||
#include <crypto/pkcs7.h>
|
||||
#include <crypto/hash_info.h>
|
||||
|
||||
struct pefile_context {
|
||||
unsigned header_size;
|
||||
unsigned image_checksum_offset;
|
||||
unsigned cert_dirent_offset;
|
||||
unsigned n_data_dirents;
|
||||
unsigned n_sections;
|
||||
unsigned certs_size;
|
||||
unsigned sig_offset;
|
||||
unsigned sig_len;
|
||||
const struct section_header *secs;
|
||||
struct pkcs7_message *pkcs7;
|
||||
|
||||
/* PKCS#7 MS Individual Code Signing content */
|
||||
const void *digest; /* Digest */
|
||||
unsigned digest_len; /* Digest length */
|
||||
enum hash_algo digest_algo; /* Digest algorithm */
|
||||
};
|
||||
|
||||
#define kenter(FMT, ...) \
|
||||
pr_devel("==> %s("FMT")\n", __func__, ##__VA_ARGS__)
|
||||
#define kleave(FMT, ...) \
|
||||
pr_devel("<== %s()"FMT"\n", __func__, ##__VA_ARGS__)
|
18
include/linux/verify_pefile.h
Normal file
18
include/linux/verify_pefile.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
/* Signed PE file verification
|
||||
*
|
||||
* Copyright (C) 2014 Red Hat, Inc. All Rights Reserved.
|
||||
* Written by David Howells (dhowells@redhat.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public Licence
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the Licence, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_VERIFY_PEFILE_H
|
||||
#define _LINUX_VERIFY_PEFILE_H
|
||||
|
||||
extern int verify_pefile_signature(const void *pebuf, unsigned pelen,
|
||||
struct key *trusted_keyring, bool *_trusted);
|
||||
|
||||
#endif /* _LINUX_VERIFY_PEFILE_H */
|
Loading…
Reference in a new issue