From aacc136b895c5d365eb02c488490d1fac9ee14ac Mon Sep 17 00:00:00 2001 From: Fabrice Derepas Date: Sat, 15 Aug 2026 16:00:20 +0200 Subject: [PATCH] crypto: asymmetric_keys - add KUnit tests for the PE parser Add self-contained KUnit tests for pefile_parse_binary(), exercised through verify_pefile_signature() -- which parses the image before any signature check, so no keyring or real signature is needed. The cases build small malformed PE images in memory: - a PE declaring 0 data-directory entries: the certificate table entry at fixed index 4 is absent and must be rejected (-ELIBBAD), not read out of bounds; - a PE declaring 5 entries with a zero (unsigned) certificate entry gets past the directory bound and is rejected as unsigned (-ENODATA). verify_pefile_signature() is not exported, so the test is built-in only (bool, not tristate). Signed-off-by: Fabrice Derepas Signed-off-by: Herbert Xu --- crypto/asymmetric_keys/Kconfig | 12 +++ crypto/asymmetric_keys/Makefile | 2 + crypto/asymmetric_keys/verify_pefile_test.c | 100 ++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 crypto/asymmetric_keys/verify_pefile_test.c diff --git a/crypto/asymmetric_keys/Kconfig b/crypto/asymmetric_keys/Kconfig index 6a2f664046ef..c73979b2e98e 100644 --- a/crypto/asymmetric_keys/Kconfig +++ b/crypto/asymmetric_keys/Kconfig @@ -114,4 +114,16 @@ config FIPS_SIGNATURE_SELFTEST_ECDSA depends on CRYPTO_SHA256=y || CRYPTO_SHA256=FIPS_SIGNATURE_SELFTEST depends on CRYPTO_ECDSA=y || CRYPTO_ECDSA=FIPS_SIGNATURE_SELFTEST +config VERIFY_PEFILE_KUNIT_TEST + bool "KUnit tests for signed PE file parsing" if !KUNIT_ALL_TESTS + depends on KUNIT=y && SIGNED_PE_FILE_VERIFICATION + default KUNIT_ALL_TESTS + help + Enable KUnit tests for the PE binary parser used by signed PE file + signature verification. The tests build small malformed PE images in + memory and check that pefile_parse_binary() rejects them instead of + reading out of bounds. + + If unsure, say N. + endif # ASYMMETRIC_KEY_TYPE diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile index bc65d3b98dcb..35172685c533 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -79,3 +79,5 @@ verify_signed_pefile-y := \ $(obj)/mscode_parser.o: $(obj)/mscode.asn1.h $(obj)/mscode.asn1.h $(obj)/mscode.asn1.o: $(obj)/mscode.asn1.c $(obj)/mscode.asn1.h + +obj-$(CONFIG_VERIFY_PEFILE_KUNIT_TEST) += verify_pefile_test.o diff --git a/crypto/asymmetric_keys/verify_pefile_test.c b/crypto/asymmetric_keys/verify_pefile_test.c new file mode 100644 index 000000000000..8b42eb41edd5 --- /dev/null +++ b/crypto/asymmetric_keys/verify_pefile_test.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for the signed PE binary parser in verify_pefile.c. + * + * The cases build small malformed PE images in memory and call + * verify_pefile_signature(), which parses the image before any signature + * check, so the parser is exercised without a keyring or a real signature. + */ +#include +#include +#include +#include + +/* + * Build a minimal PE32 image whose optional header declares @data_dirs data + * directory entries. header_size is sized to hold them and the image is a few + * bytes larger. With @data_dirs < 5 the certificate table entry (fixed index + * 4) lies outside the declared directory. + */ +static void *build_pe32(u16 data_dirs, unsigned int *out_len) +{ + size_t cursor = sizeof(struct mz_hdr) + sizeof(struct pe_hdr) + + sizeof(struct pe32_opt_hdr); + size_t header_size = cursor + ((size_t)data_dirs + 1) * + sizeof(struct data_dirent); + size_t pelen = header_size + 8; + struct mz_hdr *mz; + struct pe_hdr *pe; + struct pe32_opt_hdr *opt; + void *buf = kzalloc(pelen, GFP_KERNEL); + + if (!buf) + return NULL; + + mz = buf; + mz->magic = IMAGE_DOS_SIGNATURE; + mz->peaddr = sizeof(struct mz_hdr); + + pe = buf + sizeof(struct mz_hdr); + pe->magic = IMAGE_NT_SIGNATURE; + + opt = buf + sizeof(struct mz_hdr) + sizeof(struct pe_hdr); + opt->magic = IMAGE_NT_OPTIONAL_HDR32_MAGIC; + opt->header_size = header_size; + opt->data_dirs = data_dirs; + + *out_len = pelen; + return buf; +} + +/* + * data_dirs = 0: the certificate table entry (index 4) is absent, so the + * parser must reject the image instead of reading ddir->certs past the end. + */ +static void pefile_missing_certs_dirent(struct kunit *test) +{ + unsigned int len; + void *buf = build_pe32(0, &len); + int ret; + + KUNIT_ASSERT_NOT_NULL(test, buf); + ret = verify_pefile_signature(buf, len, NULL, + VERIFYING_KEXEC_PE_SIGNATURE); + KUNIT_EXPECT_EQ(test, ret, -ELIBBAD); + kfree(buf); +} + +/* + * data_dirs = 5: the certificate table entry is present (and zero, i.e. + * unsigned), so the parser gets past the directory bound and rejects the image + * as unsigned (-ENODATA) rather than -ELIBBAD. + */ +static void pefile_present_certs_dirent(struct kunit *test) +{ + unsigned int len; + void *buf = build_pe32(5, &len); + int ret; + + KUNIT_ASSERT_NOT_NULL(test, buf); + ret = verify_pefile_signature(buf, len, NULL, + VERIFYING_KEXEC_PE_SIGNATURE); + KUNIT_EXPECT_EQ(test, ret, -ENODATA); + kfree(buf); +} + +static struct kunit_case verify_pefile_cases[] = { + KUNIT_CASE(pefile_missing_certs_dirent), + KUNIT_CASE(pefile_present_certs_dirent), + {} +}; + +static struct kunit_suite verify_pefile_suite = { + .name = "verify_pefile", + .test_cases = verify_pefile_cases, +}; + +kunit_test_suite(verify_pefile_suite); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("KUnit tests for the signed PE binary parser");