From patchwork Fri Sep 2 06:59:42 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Chen Yu X-Patchwork-Id: 9310497 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 87CDB607D2 for ; Fri, 2 Sep 2016 06:56:38 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 7784D2956E for ; Fri, 2 Sep 2016 06:56:38 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 6C26F296A3; Fri, 2 Sep 2016 06:56:38 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D38052956E for ; Fri, 2 Sep 2016 06:56:37 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751218AbcIBG4h (ORCPT ); Fri, 2 Sep 2016 02:56:37 -0400 Received: from mga06.intel.com ([134.134.136.31]:19316 "EHLO mga06.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751124AbcIBG4g (ORCPT ); Fri, 2 Sep 2016 02:56:36 -0400 Received: from fmsmga003.fm.intel.com ([10.253.24.29]) by orsmga104.jf.intel.com with ESMTP; 01 Sep 2016 23:51:59 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.30,270,1470726000"; d="scan'208";a="756426425" Received: from sharon.sh.intel.com ([10.239.160.134]) by FMSMGA003.fm.intel.com with ESMTP; 01 Sep 2016 23:51:55 -0700 From: Chen Yu To: linux-pm@vger.kernel.org Cc: "Rafael J . Wysocki" , Pavel Machek , Thomas Gleixner , Ingo Molnar , "H. Peter Anvin" , x86@kernel.org, linux-kernel@vger.kernel.org, Chen Yu , Lee Chun-Yi , Borislav Petkov Subject: [PATCH][v9] PM / hibernate: Verify the consistent of e820 memory map by md5 value Date: Fri, 2 Sep 2016 14:59:42 +0800 Message-Id: <1472799582-21115-1-git-send-email-yu.c.chen@intel.com> X-Mailer: git-send-email 2.7.4 Sender: linux-pm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP On some platforms, there is occasional panic triggered when trying to resume from hibernation, a typical panic looks like: "BUG: unable to handle kernel paging request at ffff880085894000 IP: [] load_image_lzo+0x8c2/0xe70" This is because e820 map has been changed by BIOS across hibernation, and one of the page frames from suspend kernel is right located in restore kernel's unmapped region, so panic comes out when accessing unmapped kernel address. In order to expose this issue earlier, the md5 hash of e820 map is passed from suspend kernel to restore kernel, and the restore kernel will terminate the resume process once it finds the md5 hash are not the same. As the format of image header has been modified, the magic number should also be adjusted as kernels with the same RESTORE_MAGIC have to use the same header format and interpret all of the fields in it in the same way. Note: 1. Without this patch applied, it is possible that BIOS has provided an inconsistent memory map, but the resume kernel is still able to restore the image anyway(e.g, E820_RAM region is the superset of the previous one), although the system might be unstable. So this patch tries to treat any inconsistent e820 as illegal. 2. Another case is, this patch replies on comparing the e820_saved, but currently the e820_save might not be strictly the same across hibernation, even if BIOS has provided consistent e820 map - In theory mptable might modify the BIOS-provided e820_saved dynamically in early_reserve_e820_mpc_new, which would allocate a buffer from E820_RAM, and marks it from E820_RAM to E820_RESERVED). This is a potential and rare case we need to deal with in OS in the future. Suggested-by: Pavel Machek Suggested-by: Rafael J. Wysocki Cc: Lee Chun-Yi Cc: Borislav Petkov Acked-by: Pavel Machek Signed-off-by: Chen Yu --- v9: - Only do the md5 check when CONFIG_CRYPTO_MD5 is built in. Change the image head magic number. Remove CONFIG_HIBERNATION_CHECK_E820 and make the md5 check mandatory. Introduce e820_digest_available to indicate whether we should check the md5 hash. And some other modifications. v8: - Panic the system once the e820 is found to be inconsistent during resume. Fix the md5 hash len from 128 bytes to 16 bytes. v7: - Use md5 hash to compare the e820 map. v6: - Fix some compiling errors reported by 0day/LKP, adjust Kconfig/variable namings. v5: - Rewrite this patch to just warn user of the broken BIOS when panic. v4: - Add __attribute__ ((unused)) for swsusp_page_is_valid, to eliminate the warnning of: 'swsusp_page_is_valid' defined but not used on non-x86 platforms. v3: - Adjust the logic to exclude the end_pfn boundary in pfn_mapped when invoking mark_valid_pages, because the end_pfn is not a mapped page frame, we should not regard it as a valid page. Move the sanity check of valid pages to a early stage in resuming process(moved to mark_unsafe_pages), in this way, we can avoid unnecessarily accessing these invalid pages in later stage(yes, move to the original position Joey once introduced in: Commit 84c91b7ae07c ("PM / hibernate: avoid unsafe pages in e820 reserved regions") With v3 patch applied, I did 30 cycles on my problematic platform, no panic triggered anymore(50% reproducible before patched, by plugging/unplugging memory peripheral during hibernation), and it just warns of invalid pages. v2: - According to Ingo's suggestion, rewrite this patch. New version just checks each page frame according to pfn_mapped array. So that we do not need to touch existing code related to E820_RESERVED_KERN. And this method can naturely guarantee that the system before/after hibernation do not need to be of the same memory size on x86_64. --- arch/x86/power/hibernate_64.c | 88 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/arch/x86/power/hibernate_64.c b/arch/x86/power/hibernate_64.c index 9634557..252cc19 100644 --- a/arch/x86/power/hibernate_64.c +++ b/arch/x86/power/hibernate_64.c @@ -11,6 +11,10 @@ #include #include #include +#include +#include + +#include #include #include @@ -177,14 +181,84 @@ int pfn_is_nosave(unsigned long pfn) return (pfn >= nosave_begin_pfn) && (pfn < nosave_end_pfn); } +#define MD5_DIGEST_SIZE 16 + struct restore_data_record { unsigned long jump_address; unsigned long jump_address_phys; unsigned long cr3; unsigned long magic; + u8 e820_digest[MD5_DIGEST_SIZE]; + bool e820_digest_available; }; -#define RESTORE_MAGIC 0x123456789ABCDEF0UL +#define RESTORE_MAGIC 0x23456789ABCDEF01UL + +#if IS_BUILTIN(CONFIG_CRYPTO_MD5) +/** + * get_e820_md5 - calculate md5 according to e820 map + * + * @map: the e820 map to be calculated + * @buf: the md5 result to be stored to + */ +static int get_e820_md5(struct e820map *map, void *buf) +{ + struct scatterlist sg; + struct crypto_ahash *tfm; + struct ahash_request *req; + int ret = 0; + + tfm = crypto_alloc_ahash("md5", 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(tfm)) + return -ENOMEM; + + req = ahash_request_alloc(tfm, GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + goto free_ahash; + } + + sg_init_one(&sg, (u8 *)map, sizeof(struct e820map)); + ahash_request_set_callback(req, 0, NULL, NULL); + ahash_request_set_crypt(req, &sg, buf, sizeof(struct e820map)); + + if (crypto_ahash_digest(req)) + ret = -EINVAL; + + ahash_request_free(req); + free_ahash: + crypto_free_ahash(tfm); + + return ret; +} + +static bool hibernation_e820_save(void *buf) +{ + return get_e820_md5(&e820_saved, buf) ? false : true; +} + +static bool hibernation_e820_mismatch(void *buf) +{ + int ret; + u8 result[MD5_DIGEST_SIZE] = {0}; + + ret = get_e820_md5(&e820_saved, result); + if (ret) + return true; + + return memcmp(result, buf, MD5_DIGEST_SIZE) ? true : false; +} +#else +static bool hibernation_e820_save(void *buf) +{ + return false; +} + +static bool hibernation_e820_mismatch(void *buf) +{ + return false; +} +#endif /** * arch_hibernation_header_save - populate the architecture specific part @@ -201,6 +275,10 @@ int arch_hibernation_header_save(void *addr, unsigned int max_size) rdr->jump_address_phys = __pa_symbol(&restore_registers); rdr->cr3 = restore_cr3; rdr->magic = RESTORE_MAGIC; + + if (hibernation_e820_save(rdr->e820_digest)) + rdr->e820_digest_available = true; + return 0; } @@ -211,10 +289,16 @@ int arch_hibernation_header_save(void *addr, unsigned int max_size) */ int arch_hibernation_header_restore(void *addr) { + bool e820_mismatch = false; struct restore_data_record *rdr = addr; restore_jump_address = rdr->jump_address; jump_address_phys = rdr->jump_address_phys; restore_cr3 = rdr->cr3; - return (rdr->magic == RESTORE_MAGIC) ? 0 : -EINVAL; + + if (rdr->e820_digest_available) + e820_mismatch = hibernation_e820_mismatch(rdr->e820_digest); + + return (rdr->magic == RESTORE_MAGIC) ? + (e820_mismatch ? -ENODEV : 0) : -EINVAL; }