From patchwork Sun Sep 15 00:57:01 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Chun-Yi Lee X-Patchwork-Id: 2894101 Return-Path: X-Original-To: patchwork-linux-pm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 94EF1BFF05 for ; Sun, 15 Sep 2013 00:59:25 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 6401D202F7 for ; Sun, 15 Sep 2013 00:59:24 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 0BFED202E8 for ; Sun, 15 Sep 2013 00:59:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932803Ab3IOA7G (ORCPT ); Sat, 14 Sep 2013 20:59:06 -0400 Received: from mail-bk0-f48.google.com ([209.85.214.48]:55663 "EHLO mail-bk0-f48.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932743Ab3IOA7C (ORCPT ); Sat, 14 Sep 2013 20:59:02 -0400 Received: by mail-bk0-f48.google.com with SMTP id my13so972021bkb.21 for ; Sat, 14 Sep 2013 17:59:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=+y2lvRI+anPk+19r3nnLgZo1YLe2uLRdp/1y6pLU2wg=; b=R39F4p3C1kgl4/ZpWBVXqLbmaJ7M3fj5HQA943R35S8h9m6NVon/AAKFxA66tNd19e TS43MEWFQsbt9YgR2YRF+oPWPdLMQVC0J0k3eqFwPxuFyN8Rldmgq88s716xtcQFoAE7 ksGqlSKHpCq2IBaZaQ/9hVDkcXBJZX0TZdLNGyapbgQISs6KAJYbdrf62ODNlZm7K8a0 LwvMyNxEdNb7CmsSrUk2pCEs8j8ySf6Fb9dKMMtJVRriZMvLaqrrKhiFMRI6wbFB/f0B nNKNVZxztQhzmzlZ3W1I3zruRE3SnppaclKA45N4XOafNwem2tfT7zKm7xWEqmZnJcbU cNPA== X-Received: by 10.204.63.7 with SMTP id z7mr17847851bkh.23.1379206740390; Sat, 14 Sep 2013 17:59:00 -0700 (PDT) Received: from localhost.localdomain ([124.11.22.254]) by mx.google.com with ESMTPSA id zl3sm4941613bkb.4.1969.12.31.16.00.00 (version=TLSv1 cipher=RC4-SHA bits=128/128); Sat, 14 Sep 2013 17:59:00 -0700 (PDT) From: "Lee, Chun-Yi" To: linux-kernel@vger.kernel.org Cc: linux-security-module@vger.kernel.org, linux-efi@vger.kernel.org, linux-pm@vger.kernel.org, linux-crypto@vger.kernel.org, opensuse-kernel@opensuse.org, David Howells , "Rafael J. Wysocki" , Matthew Garrett , Len Brown , Pavel Machek , Josh Boyer , Vojtech Pavlik , Matt Fleming , James Bottomley , Greg KH , JKosina@suse.com, Rusty Russell , Herbert Xu , "David S. Miller" , "H. Peter Anvin" , Michal Marek , Gary Lin , Vivek Goyal , "Lee, Chun-Yi" Subject: [PATCH V4 15/15] Hibernate: adapt to UEFI secure boot with signature check Date: Sun, 15 Sep 2013 08:57:01 +0800 Message-Id: <1379206621-18639-16-git-send-email-jlee@suse.com> X-Mailer: git-send-email 1.6.0.2 In-Reply-To: <1379206621-18639-1-git-send-email-jlee@suse.com> References: <1379206621-18639-1-git-send-email-jlee@suse.com> Sender: linux-pm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org X-Spam-Status: No, score=-7.7 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, KHOP_BIG_TO_CC, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Base on Matthew Garrett's 2 patches in "[PATCH] Add additional security checks when module loading is restricted" series [PATCH 01/10] Add secure_modules() call [PATCH V3 11/11] Add option to automatically enforce module signatures when in Secure Boot mode This patch introduced EFI_SECURE_BOOT_SNAPSHOT_SIG_ENFORCE kernel config, it's provide user to binding UEFI secure boot with SIG_ENFORCE flag of hibernate. In current solution, the snapshot signature check used the RSA key-pair that are generated by bootloader(e.g. shim) then pass the key-pair to kernel through EFI variables. Simply say: The root of trust is base on UEFI secure boot enabled. So, that makes sense to binding the snapshot signature check mechanism with UEFI secure boot for provide stronger protection of hibernate. The behavior when enabled EFI_SECURE_BOOT_SNAPSHOT_SIG_ENFORCE as following: + UEFI Secure Boot ON (means SIG_ENFORCE on), and Kernel found key-pair from bootloader: Will do the S4 signature check. + UEFI Secure Boot ON, (SIG_ENFORCE on) but Kernel didn't find key-pair from shim: Will lock down S4 function. + UEFI Secure Boot OFF (means SIG_ENFORCE off) taint kernel when signature check fail or didn't find key-pair. V3: Use helper function secure_hibernate() to reduce ifdef block. V2: Replace sign_key_data_loaded() by skey_data_available() to check sign key data is available for hibernate. Reviewed-by: Jiri Kosina Signed-off-by: Lee, Chun-Yi --- arch/x86/kernel/setup.c | 7 ++++++ kernel/power/hibernate.c | 17 ++++++++++++++ kernel/power/hibernate_keys.c | 16 +++++++++++++ kernel/power/main.c | 7 +++++- kernel/power/power.h | 13 +++++++++++ kernel/power/snapshot.c | 49 +++++++++++++++++++++------------------- kernel/power/swap.c | 4 +- kernel/power/user.c | 5 +++- 8 files changed, 91 insertions(+), 27 deletions(-) diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c index deeb7bc..b3878b4 100644 --- a/arch/x86/kernel/setup.c +++ b/arch/x86/kernel/setup.c @@ -50,6 +50,7 @@ #include #include #include +#include #include #include @@ -1135,6 +1136,12 @@ void __init setup_arch(char **cmdline_p) } #endif +#ifdef CONFIG_EFI_SECURE_BOOT_SNAPSHOT_SIG_ENFORCE + if (boot_params.secure_boot) { + enforce_signed_snapshot(); + } +#endif + /* * Parse the ACPI tables for possible boot-time SMP configuration. */ diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index 6336499..abbc5b0 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "power.h" @@ -633,6 +634,9 @@ int hibernate(void) { int error; + if (secure_hibernate(SIG_ENFORCE | SIG_CHECK_SKEY)) + return -EPERM; + lock_system_sleep(); /* The snapshot device should not be opened while we're running */ if (!atomic_add_unless(&snapshot_device_available, -1, 0)) { @@ -800,6 +804,11 @@ static int software_resume(void) if (error) goto Unlock; + if (secure_hibernate(SIG_ENFORCE | SIG_CHECK_WKEY)) { + mutex_unlock(&pm_mutex); + return -EPERM; + } + /* The snapshot device should not be opened while we're running */ if (!atomic_add_unless(&snapshot_device_available, -1, 0)) { error = -EBUSY; @@ -893,6 +902,11 @@ static ssize_t disk_show(struct kobject *kobj, struct kobj_attribute *attr, int i; char *start = buf; + if (secure_hibernate(SIG_ENFORCE | SIG_CHECK_SKEY)) { + buf += sprintf(buf, "[%s]\n", "disabled"); + return buf-start; + } + for (i = HIBERNATION_FIRST; i <= HIBERNATION_MAX; i++) { if (!hibernation_modes[i]) continue; @@ -927,6 +941,9 @@ static ssize_t disk_store(struct kobject *kobj, struct kobj_attribute *attr, char *p; int mode = HIBERNATION_INVALID; + if (secure_hibernate(SIG_ENFORCE | SIG_CHECK_SKEY)) + return -EPERM; + p = memchr(buf, '\n', n); len = p ? p - buf : n; diff --git a/kernel/power/hibernate_keys.c b/kernel/power/hibernate_keys.c index 72d5c7a..b70f397 100644 --- a/kernel/power/hibernate_keys.c +++ b/kernel/power/hibernate_keys.c @@ -367,6 +367,22 @@ static int clean_key_regen_flag(void) return ret; } +bool secure_hibernate(u8 check_items) +{ + bool ret = true; + + if (check_items & SIG_ENFORCE) + ret = sig_enforce; + + /* check S4 key to lock hibernate when not available */ + if (ret && (check_items & SIG_CHECK_SKEY)) + ret = ret && !skey_data_available(); + if (ret && (check_items & SIG_CHECK_WKEY)) + ret = ret && (wkey_data_available() != 0); + + return ret; +} + static int __init init_sign_key_data(void) { skey_data = (void *)get_zeroed_page(GFP_KERNEL); diff --git a/kernel/power/main.c b/kernel/power/main.c index 1d1bf63..10ff23f 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "power.h" @@ -301,7 +302,11 @@ static ssize_t state_show(struct kobject *kobj, struct kobj_attribute *attr, } #endif #ifdef CONFIG_HIBERNATION - s += sprintf(s, "%s\n", "disk"); + if (secure_hibernate(SIG_ENFORCE | SIG_CHECK_SKEY)) { + s += sprintf(s, "\n"); + } else { + s += sprintf(s, "%s\n", "disk"); + } #else if (s != buf) /* convert the last space to a newline */ diff --git a/kernel/power/power.h b/kernel/power/power.h index da5733f..8fc94cc 100644 --- a/kernel/power/power.h +++ b/kernel/power/power.h @@ -2,6 +2,7 @@ #include #include #include +#include /* The maximum length of snapshot signature */ #define SIG_LEN 512 @@ -174,6 +175,11 @@ extern int swsusp_unmark(void); #endif /* kernel/power/hibernate_key.c */ +#define SIG_ENFORCE (1<<0) /* Check sig_enforce flag */ +#define SIG_CHECK_SKEY (1<<1) /* Check S4SignKey exist */ +#define SIG_CHECK_WKEY (1<<2) /* Check S4WakeKey exist */ +#define SIG_SECURE_LOCKDOWN (1<<3) /* TODO: binding to new secure level */ + #ifdef CONFIG_SNAPSHOT_VERIFICATION extern bool skey_data_available(void); extern struct key *get_sign_key(void); @@ -189,6 +195,7 @@ extern unsigned long get_sig_forward_info_pfn(void); extern void fill_sig_forward_info(void *page_addr, int sig_check_ret); extern bool sig_enforced(void); extern int set_key_regen_flag(void); +extern bool secure_hibernate(u8 check_items); #else static inline bool skey_data_available(void) { @@ -207,6 +214,12 @@ static inline int set_key_regen_flag(void) { return 0; } +static inline bool secure_hibernate(u8 check_items) +{ + /* TODO: adapt to kernel lockdown */ + + return false; +} #endif /* !CONFIG_SNAPSHOT_VERIFICATION */ /* kernel/power/block_io.c */ diff --git a/kernel/power/snapshot.c b/kernel/power/snapshot.c index 896f11d..29f1ec1 100644 --- a/kernel/power/snapshot.c +++ b/kernel/power/snapshot.c @@ -1759,13 +1759,15 @@ asmlinkage int swsusp_save(void) nr_copy_pages = nr_pages; nr_meta_pages = DIV_ROUND_UP(nr_pages * sizeof(long), PAGE_SIZE); - if (skey_data_available()) { - ret = swsusp_generate_signature(©_bm, nr_pages); - if (ret) - return ret; - } else - /* set zero signature if skey doesn't exist */ - memset(signature, 0, SIG_LEN); + if (secure_hibernate(0)) { + if (skey_data_available()) { + ret = swsusp_generate_signature(©_bm, nr_pages); + if (ret) + return ret; + } else + /* set zero signature if skey doesn't exist */ + memset(signature, 0, SIG_LEN); + } printk(KERN_INFO "PM: Hibernation image created (%d pages copied)\n", nr_pages); @@ -2398,14 +2400,15 @@ int snapshot_write_next(struct snapshot_handle *handle) if (error) return error; -#ifdef CONFIG_SNAPSHOT_VERIFICATION /* Allocate void * array to keep buffer point for generate hash, * handle_buffers will freed in snapshot_image_verify(). */ - handle_buffers = kmalloc(sizeof(void *) * nr_copy_pages, GFP_KERNEL); - if (!handle_buffers) - pr_err("Allocate hash buffer fail!\n"); -#endif + if (secure_hibernate(0)) { + handle_buffers = + kmalloc(sizeof(void *) * nr_copy_pages, GFP_KERNEL); + if (!handle_buffers) + pr_err("Allocate hash buffer fail!\n"); + } error = memory_bm_create(©_bm, GFP_ATOMIC, PG_ANY); if (error) @@ -2433,10 +2436,8 @@ int snapshot_write_next(struct snapshot_handle *handle) handle->sync_read = 0; if (IS_ERR(handle->buffer)) return PTR_ERR(handle->buffer); -#ifdef CONFIG_SNAPSHOT_VERIFICATION - if (handle_buffers) + if (secure_hibernate(0) && handle_buffers) *handle_buffers = handle->buffer; -#endif } } else { copy_last_highmem_page(); @@ -2447,13 +2448,15 @@ int snapshot_write_next(struct snapshot_handle *handle) return PTR_ERR(handle->buffer); if (handle->buffer != buffer) handle->sync_read = 0; -#ifdef CONFIG_SNAPSHOT_VERIFICATION - if (handle_buffers) - *(handle_buffers + (handle->cur - nr_meta_pages - 1)) = handle->buffer; - /* Keep the buffer of sign key in snapshot */ - if (pfn == sig_forward_info_pfn) - sig_forward_info_buf = handle->buffer; -#endif + if (secure_hibernate(0)) { + if (handle_buffers) { + unsigned int offset = handle->cur - nr_meta_pages - 1; + *(handle_buffers + offset) = handle->buffer; + } + /* Keep the buffer of sign key in snapshot */ + if (pfn == sig_forward_info_pfn) + sig_forward_info_buf = handle->buffer; + } } handle->cur++; return PAGE_SIZE; @@ -2546,7 +2549,7 @@ int snapshot_image_verify(void) { struct timeval start; struct timeval stop; - struct crypto_shash *tfm = NULL + struct crypto_shash *tfm = NULL; struct shash_desc *desc; u8 *digest = NULL; size_t digest_size, desc_size; diff --git a/kernel/power/swap.c b/kernel/power/swap.c index 5aef236..19cb393 100644 --- a/kernel/power/swap.c +++ b/kernel/power/swap.c @@ -1004,7 +1004,7 @@ static int load_image(struct swap_map_handle *handle, snapshot_write_finalize(snapshot); if (!snapshot_image_loaded(snapshot)) ret = -ENODATA; - else + else if (secure_hibernate(0)) ret = snapshot_image_verify(); } swsusp_show_speed(&start, &stop, nr_to_read, "Read"); @@ -1360,7 +1360,7 @@ out_finish: } } } - if (!ret) + if (!ret && secure_hibernate(0)) ret = snapshot_image_verify(); } swsusp_show_speed(&start, &stop, nr_to_read, "Read"); diff --git a/kernel/power/user.c b/kernel/power/user.c index 3d3632b..6c75427 100644 --- a/kernel/power/user.c +++ b/kernel/power/user.c @@ -48,6 +48,9 @@ static int snapshot_open(struct inode *inode, struct file *filp) struct snapshot_data *data; int error; + if (secure_hibernate(SIG_ENFORCE | SIG_CHECK_SKEY | SIG_SECURE_LOCKDOWN)) + return -EPERM; + lock_system_sleep(); if (!atomic_add_unless(&snapshot_device_available, -1, 0)) { @@ -254,7 +257,7 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd, error = -EPERM; break; } - if (snapshot_image_verify()) { + if (secure_hibernate(0) && snapshot_image_verify()) { error = -EPERM; break; }