@@ -369,6 +369,96 @@ free_handle:
return status;
}
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+static efi_status_t setup_s4_keys(struct boot_params *params)
+{
+ struct setup_data *data;
+ unsigned long datasize;
+ u32 attr;
+ struct efi_s4_key *s4key;
+ efi_status_t status;
+
+ data = (struct setup_data *)params->hdr.setup_data;
+
+ while (data && data->next)
+ data = (struct setup_data *)(unsigned long)data->next;
+
+ status = efi_call_phys3(sys_table->boottime->allocate_pool,
+ EFI_LOADER_DATA, sizeof(*s4key), &s4key);
+ if (status != EFI_SUCCESS) {
+ efi_printk("Failed to alloc memory for efi_s4_key\n");
+ goto error_setup;
+ }
+
+ s4key->data.type = SETUP_S4_KEY;
+ s4key->data.len = sizeof(struct efi_s4_key) -
+ sizeof(struct setup_data);
+ s4key->data.next = 0;
+ s4key->skey_dsize = 0;
+ s4key->err_status = 0;
+
+ if (data)
+ data->next = (unsigned long)s4key;
+ else
+ params->hdr.setup_data = (unsigned long)s4key;
+
+ /* obtain the size of key data */
+ datasize = 0;
+ status = efi_call_phys5(sys_table->runtime->get_variable,
+ EFI_S4_SIGN_KEY_NAME, &EFI_HIBERNATE_GUID,
+ NULL, &datasize, NULL);
+ if (status != EFI_BUFFER_TOO_SMALL) {
+ efi_printk("Couldn't get S4 key data size\n");
+ goto error_size;
+ }
+ if (datasize > SKEY_DBUF_MAX_SIZE) {
+ efi_printk("The size of S4 sign key is too large\n");
+ status = EFI_UNSUPPORTED;
+ goto error_size;
+ }
+
+ s4key->skey_dsize = datasize;
+ status = efi_call_phys3(sys_table->boottime->allocate_pool,
+ EFI_LOADER_DATA, s4key->skey_dsize,
+ &s4key->skey_data_addr);
+ if (status != EFI_SUCCESS) {
+ efi_printk("Failed to alloc page for S4 key data\n");
+ goto error_s4key;
+ }
+
+ attr = 0;
+ memset((void *)s4key->skey_data_addr, 0, s4key->skey_dsize);
+ status = efi_call_phys5(sys_table->runtime->get_variable,
+ EFI_S4_SIGN_KEY_NAME, &EFI_HIBERNATE_GUID, &attr,
+ &(s4key->skey_dsize), s4key->skey_data_addr);
+ if (status) {
+ efi_printk("Couldn't get S4 key data\n");
+ goto error_gets4key;
+ }
+ if (attr & EFI_VARIABLE_RUNTIME_ACCESS) {
+ efi_printk("S4 sign key can not be a runtime variable\n");
+ memset((void *)s4key->skey_data_addr, 0, s4key->skey_dsize);
+ status = EFI_UNSUPPORTED;
+ goto error_gets4key;
+ }
+
+ return 0;
+
+error_gets4key:
+ efi_call_phys1(sys_table->boottime->free_pool, s4key->skey_data_addr);
+error_s4key:
+error_size:
+ s4key->err_status = status;
+error_setup:
+ return status;
+}
+#else
+static inline efi_status_t setup_s4_keys(struct boot_params *params)
+{
+ return 0;
+}
+#endif /* CONFIG_SNAPSHOT_VERIFICATION */
+
/*
* See if we have Graphics Output Protocol
*/
@@ -1209,6 +1299,8 @@ struct boot_params *efi_main(void *handle, efi_system_table_t *_table,
setup_efi_pci(boot_params);
+ setup_s4_keys(boot_params);
+
status = efi_call_phys3(sys_table->boottime->allocate_pool,
EFI_LOADER_DATA, sizeof(*gdt),
(void **)&gdt);
@@ -102,6 +102,15 @@ extern void efi_call_phys_epilog(void);
extern void efi_unmap_memmap(void);
extern void efi_memory_uc(u64 addr, unsigned long size);
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+struct efi_s4_key {
+ struct setup_data data;
+ unsigned long err_status;
+ unsigned long skey_dsize;
+ void *skey_data_addr;
+};
+#endif
+
#ifdef CONFIG_EFI
static inline bool efi_is_native(void)
@@ -6,6 +6,7 @@
#define SETUP_E820_EXT 1
#define SETUP_DTB 2
#define SETUP_PCI 3
+#define SETUP_S4_KEY 4
/* ram_size flags */
#define RAMDISK_IMAGE_START_MASK 0x07FF
@@ -704,6 +704,71 @@ static int __init efi_memmap_init(void)
return 0;
}
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+static unsigned long skey_dsize;
+static u64 skey_data_addr;
+static unsigned long skey_err_status;
+
+bool efi_s4_key_available(void)
+{
+ return skey_dsize && skey_data_addr && !skey_err_status;
+}
+
+unsigned long __init efi_copy_skey_data(void *page_addr)
+{
+ void *key_addr;
+
+ if (efi_s4_key_available()) {
+ key_addr = early_ioremap(skey_data_addr, skey_dsize);
+ memcpy(page_addr, key_addr, skey_dsize);
+ early_iounmap(key_addr, skey_dsize);
+ }
+
+ return skey_dsize;
+}
+
+void __init efi_erase_s4_skey_data(void)
+{
+ void *key_addr;
+
+ key_addr = early_ioremap(skey_data_addr, skey_dsize);
+ memset(key_addr, 0, skey_dsize);
+ early_iounmap(key_addr, skey_dsize);
+ memblock_free(skey_data_addr, skey_dsize);
+ skey_data_addr = 0;
+ skey_dsize = 0;
+}
+
+static void __init efi_reserve_s4_skey_data(void)
+{
+ u64 pa_data;
+ struct setup_data *data;
+ struct efi_s4_key *s4key;
+
+ skey_err_status = 0;
+ pa_data = boot_params.hdr.setup_data;
+ while (pa_data) {
+ data = early_ioremap(pa_data, sizeof(*s4key));
+ if (data->type == SETUP_S4_KEY) {
+ s4key = (struct efi_s4_key *)data;
+ if (!s4key->err_status) {
+ skey_dsize = s4key->skey_dsize;
+ skey_data_addr = (u64) s4key->skey_data_addr;
+ memblock_reserve(skey_data_addr, skey_dsize);
+ } else {
+ skey_err_status = s4key->err_status;
+ pr_err("Get S4 sign key from EFI fail: 0x%lx\n",
+ skey_err_status);
+ }
+ }
+ pa_data = data->next;
+ early_iounmap(data, sizeof(*s4key));
+ }
+}
+#else
+static inline void efi_reserve_s4_skey_data(void) {}
+#endif /* CONFIG_SNAPSHOT_VERIFICATION */
+
void __init efi_init(void)
{
efi_char16_t *c16;
@@ -729,6 +794,9 @@ void __init efi_init(void)
set_bit(EFI_SYSTEM_TABLES, &x86_efi_facility);
+ /* keep s4 key from setup_data */
+ efi_reserve_s4_skey_data();
+
/*
* Show what we know for posterity
*/
@@ -389,6 +389,18 @@ typedef efi_status_t efi_query_variable_store_t(u32 attributes, unsigned long si
#define EFI_FILE_SYSTEM_GUID \
EFI_GUID( 0x964e5b22, 0x6459, 0x11d2, 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b )
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+#define EFI_HIBERNATE_GUID \
+ EFI_GUID(0xfe141863, 0xc070, 0x478e, 0xb8, 0xa3, 0x87, 0x8a, 0x5d, 0xc9, 0xef, 0x21)
+/*
+ * The UEFI variable names of the key-pair to verify S4 snapshot image:
+ * S4SignKey-EFI_HIBERNATE_GUID: The private key is used to sign snapshot
+ * S4WakeKey-EFI_HIBERNATE_GUID: The public key is used to verify snapshot
+ */
+#define EFI_S4_SIGN_KEY_NAME ((efi_char16_t [10]) { 'S', '4', 'S', 'i', 'g', 'n', 'K', 'e', 'y', 0 })
+#define EFI_S4_WAKE_KEY_NAME ((efi_char16_t [10]) { 'S', '4', 'W', 'a', 'k', 'e', 'K', 'e', 'y', 0 })
+#endif /* CONFIG_SNAPSHOT_VERIFICATION */
+
typedef struct {
efi_guid_t guid;
u64 table;
@@ -577,6 +589,19 @@ extern void efi_enter_virtual_mode (void); /* switch EFI to virtual mode, if pos
extern void efi_late_init(void);
extern void efi_free_boot_services(void);
extern efi_status_t efi_query_variable_store(u32 attributes, unsigned long size);
+
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+struct forward_info_head {
+ bool sig_enforce;
+ int sig_check_ret;
+ unsigned long skey_dsize;
+} __attribute__((packed));
+#define SKEY_DBUF_MAX_SIZE (PAGE_SIZE - sizeof(struct forward_info_head))
+extern bool efi_s4_key_available(void);
+extern unsigned long efi_copy_skey_data(void *page_addr);
+extern void efi_erase_s4_skey_data(void);
+#endif /* CONFIG_SNAPSHOT_VERIFICATION */
+
#else
static inline void efi_late_init(void) {}
static inline void efi_free_boot_services(void) {}
@@ -66,8 +66,17 @@ config HIBERNATION
For more information take a look at <file:Documentation/power/swsusp.txt>.
-config ARCH_SAVE_PAGE_KEYS
- bool
+config SNAPSHOT_VERIFICATION
+ bool "Hibernate snapshot verification"
+ depends on HIBERNATION
+ depends on EFI_STUB
+ depends on X86
+ select PKCS8_PRIVATE_KEY_INFO_PARSER
+ help
+ This option provides support for generate anad verify the signautre by
+ RSA key-pair against hibernate snapshot image. Current mechanism
+ dependent on UEFI environment. EFI bootloader should generate the
+ key-pair.
config PM_STD_PARTITION
string "Default resume partition"
@@ -91,6 +100,9 @@ config PM_STD_PARTITION
suspended image to. It will simply pick the first available swap
device.
+config ARCH_SAVE_PAGE_KEYS
+ bool
+
config PM_SLEEP
def_bool y
depends on SUSPEND || HIBERNATE_CALLBACKS
@@ -7,6 +7,7 @@ obj-$(CONFIG_VT_CONSOLE_SLEEP) += console.o
obj-$(CONFIG_FREEZER) += process.o
obj-$(CONFIG_SUSPEND) += suspend.o
obj-$(CONFIG_PM_TEST_SUSPEND) += suspend_test.o
+obj-$(CONFIG_SNAPSHOT_VERIFICATION) += hibernate_keys.o
obj-$(CONFIG_HIBERNATION) += hibernate.o snapshot.o swap.o user.o \
block_io.o
obj-$(CONFIG_PM_AUTOSLEEP) += autosleep.o
@@ -28,6 +28,7 @@
#include <linux/syscore_ops.h>
#include <linux/ctype.h>
#include <linux/genhd.h>
+#include <linux/key.h>
#include "power.h"
@@ -680,6 +681,7 @@ int hibernate(void)
pm_restore_gfp_mask();
} else {
pr_debug("PM: Image restored successfully.\n");
+ restore_sig_forward_info();
}
Thaw:
new file mode 100644
@@ -0,0 +1,292 @@
+#include <linux/sched.h>
+#include <linux/efi.h>
+#include <linux/mpi.h>
+#include <linux/asn1.h>
+#include <crypto/public_key.h>
+#include <keys/asymmetric-type.h>
+
+#include "power.h"
+
+static unsigned char const_seq = (ASN1_SEQ | (ASN1_CONS << 5));
+
+struct forward_info {
+ struct forward_info_head head;
+ unsigned char skey_data_buf[SKEY_DBUF_MAX_SIZE];
+};
+
+static void *skey_data;
+static void *forward_info_buf;
+static unsigned long skey_dsize;
+
+bool swsusp_page_is_sign_key(struct page *page)
+{
+ unsigned long skey_data_pfn;
+ bool ret;
+
+ if (!skey_data || IS_ERR(skey_data))
+ return false;
+
+ skey_data_pfn = page_to_pfn(virt_to_page(skey_data));
+ ret = (page_to_pfn(page) == skey_data_pfn) ? true : false;
+ if (ret)
+ pr_info("PM: Avoid snapshot the page of S4 sign key.\n");
+
+ return ret;
+}
+
+unsigned long get_sig_forward_info_pfn(void)
+{
+ if (!forward_info_buf)
+ return 0;
+
+ return page_to_pfn(virt_to_page(forward_info_buf));
+}
+
+void fill_sig_forward_info(void *page, int sig_check_ret_in)
+{
+ struct forward_info *info;
+
+ if (!page)
+ return;
+
+ memset(page, 0, PAGE_SIZE);
+ info = (struct forward_info *)page;
+
+ info->head.sig_check_ret = sig_check_ret_in;
+ if (skey_data && !IS_ERR(skey_data) &&
+ skey_dsize <= SKEY_DBUF_MAX_SIZE) {
+ info->head.skey_dsize = skey_dsize;
+ memcpy(info->skey_data_buf, skey_data, skey_dsize);
+ } else
+ pr_info("PM: Fill S4 sign key fail, size: %ld\n", skey_dsize);
+
+ pr_info("PM: Filled sign information to forward buffer\n");
+}
+
+void restore_sig_forward_info(void)
+{
+ struct forward_info *info;
+ int sig_check_ret;
+
+ if (!forward_info_buf) {
+ pr_err("PM: Restore S4 sign key fail\n");
+ return;
+ }
+ info = (struct forward_info *)forward_info_buf;
+
+ sig_check_ret = info->head.sig_check_ret;
+ if (sig_check_ret)
+ pr_info("PM: Signature check fail: %d\n", sig_check_ret);
+
+ if (info->head.skey_dsize <= SKEY_DBUF_MAX_SIZE &&
+ info->skey_data_buf[0] == const_seq) {
+
+ /* restore sign key size and data from buffer */
+ skey_dsize = info->head.skey_dsize;
+ memset(skey_data, 0, PAGE_SIZE);
+ memcpy(skey_data, info->skey_data_buf, skey_dsize);
+ }
+
+ /* reset skey page buffer */
+ memset(forward_info_buf, 0, PAGE_SIZE);
+}
+
+bool skey_data_available(void)
+{
+ bool ret = false;
+
+ /* Sign key is PKCS#8 format that must be a Constructed SEQUENCE */
+ ret = skey_data && !IS_ERR(skey_data) &&
+ (skey_dsize != 0) &&
+ ((unsigned char *)skey_data)[0] == const_seq;
+
+ return ret;
+}
+
+struct key *get_sign_key(void)
+{
+ const struct cred *cred = current_cred();
+ struct key *skey;
+ int err;
+
+ if (!skey_data || IS_ERR(skey_data))
+ return ERR_PTR(-EBADMSG);
+
+ skey = key_alloc(&key_type_asymmetric, "s4_sign_key",
+ GLOBAL_ROOT_UID, GLOBAL_ROOT_GID,
+ cred, 0, KEY_ALLOC_NOT_IN_QUOTA);
+ if (IS_ERR(skey)) {
+ pr_err("PM: Allocate s4 sign key error: %ld\n", PTR_ERR(skey));
+ goto error_keyalloc;
+ }
+
+ err = key_instantiate_and_link(skey, skey_data, skey_dsize, NULL, NULL);
+ if (err < 0) {
+ pr_err("PM: S4 sign key instantiate error: %d\n", err);
+ if (skey)
+ key_put(skey);
+ skey = ERR_PTR(err);
+ goto error_keyinit;
+ }
+
+ return skey;
+
+error_keyinit:
+error_keyalloc:
+ return skey;
+}
+
+void erase_skey_data(void)
+{
+ if (!skey_data || IS_ERR(skey_data))
+ return;
+
+ memset(skey_data, 0, PAGE_SIZE);
+}
+
+void destroy_sign_key(struct key *skey)
+{
+ erase_skey_data();
+ if (skey)
+ key_put(skey);
+}
+
+static void *load_wake_key_data(unsigned long *datasize)
+{
+ struct efivar_entry *entry;
+ u32 attr;
+ void *wkey_data;
+ int ret;
+
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return ERR_PTR(-ENOMEM);
+
+ memcpy(entry->var.VariableName, EFI_S4_WAKE_KEY_NAME, sizeof(EFI_S4_WAKE_KEY_NAME));
+ memcpy(&(entry->var.VendorGuid), &EFI_HIBERNATE_GUID, sizeof(efi_guid_t));
+
+ /* obtain the size */
+ *datasize = 0;
+ ret = efivar_entry_size(entry, datasize);
+ if (ret)
+ goto error_size;
+
+ wkey_data = kzalloc(*datasize, GFP_KERNEL);
+ if (!wkey_data) {
+ ret = -ENOMEM;
+ goto error_size;
+ }
+
+ ret = efivar_entry_get(entry, &attr, datasize, wkey_data);
+ if (ret) {
+ pr_err("PM: Get wake key data error: %d\n", ret);
+ goto error_get;
+ }
+ /* check attributes */
+ if (attr & EFI_VARIABLE_NON_VOLATILE) {
+ pr_err("PM: Wake key has wrong attributes: 0x%x\n", attr);
+ goto error_get;
+ }
+
+ kfree(entry);
+
+ return wkey_data;
+
+error_get:
+ memset(wkey_data, 0, *datasize);
+ kfree(wkey_data);
+ *datasize = 0;
+error_size:
+ kfree(entry);
+
+ return ERR_PTR(ret);
+}
+
+int wkey_data_available(void)
+{
+ static int ret = 1;
+ unsigned long datasize;
+ void *wkey_data;
+
+ if (ret > 0) {
+ wkey_data = load_wake_key_data(&datasize);
+ if (wkey_data && IS_ERR(wkey_data)) {
+ ret = PTR_ERR(wkey_data);
+ goto error;
+ } else {
+ if (wkey_data) {
+ memset(wkey_data, 0, datasize);
+ kfree(wkey_data);
+ }
+ ret = 0;
+ }
+ }
+
+error:
+ return ret;
+}
+
+struct key *get_wake_key(void)
+{
+ const struct cred *cred = current_cred();
+ void *wkey_data;
+ unsigned long datasize = 0;
+ struct key *wkey;
+ int err;
+
+ wkey_data = load_wake_key_data(&datasize);
+ if (IS_ERR(wkey_data)) {
+ wkey = (struct key *)wkey_data;
+ goto error_data;
+ }
+
+ wkey = key_alloc(&key_type_asymmetric, "s4_wake_key",
+ GLOBAL_ROOT_UID, GLOBAL_ROOT_GID,
+ cred, 0, KEY_ALLOC_NOT_IN_QUOTA);
+ if (IS_ERR(wkey)) {
+ pr_err("PM: Allocate s4 wake key error: %ld\n", PTR_ERR(wkey));
+ goto error_keyalloc;
+ }
+ err = key_instantiate_and_link(wkey, wkey_data, datasize, NULL, NULL);
+ if (err < 0) {
+ pr_err("PM: S4 wake key instantiate error: %d\n", err);
+ if (wkey)
+ key_put(wkey);
+ wkey = ERR_PTR(err);
+ }
+
+error_keyalloc:
+ if (wkey_data && !IS_ERR(wkey_data))
+ kfree(wkey_data);
+error_data:
+ return wkey;
+}
+
+size_t get_key_length(const struct key *key)
+{
+ const struct public_key *pk = key->payload.data;
+ size_t len;
+
+ /* TODO: better check the RSA type */
+
+ len = mpi_get_nbits(pk->rsa.n);
+ len = (len + 7) / 8;
+
+ return len;
+}
+
+static int __init init_sign_key_data(void)
+{
+ skey_data = (void *)get_zeroed_page(GFP_KERNEL);
+ forward_info_buf = (void *)get_zeroed_page(GFP_KERNEL);
+
+ if (skey_data && efi_s4_key_available()) {
+ skey_dsize = efi_copy_skey_data(skey_data);
+ efi_erase_s4_skey_data();
+ pr_info("PM: Load s4 sign key from EFI\n");
+ }
+
+ return 0;
+}
+
+late_initcall(init_sign_key_data);
@@ -160,6 +160,36 @@ extern void swsusp_close(fmode_t);
extern int swsusp_unmark(void);
#endif
+/* kernel/power/hibernate_key.c */
+#ifdef CONFIG_SNAPSHOT_VERIFICATION
+extern bool skey_data_available(void);
+extern struct key *get_sign_key(void);
+extern void erase_skey_data(void);
+extern void destroy_sign_key(struct key *key);
+extern int wkey_data_available(void);
+extern struct key *get_wake_key(void);
+extern size_t get_key_length(const struct key *key);
+
+extern void restore_sig_forward_info(void);
+extern bool swsusp_page_is_sign_key(struct page *page);
+extern unsigned long get_sig_forward_info_pfn(void);
+extern void fill_sig_forward_info(void *page_addr, int sig_check_ret);
+#else
+static inline bool skey_data_available(void)
+{
+ return false;
+}
+static inline void restore_sig_forward_info(void) {}
+static inline bool swsusp_page_is_sign_key(struct page *page)
+{
+ return false;
+}
+static inline unsigned long get_sig_forward_info_pfn(void)
+{
+ return 0;
+}
+#endif /* !CONFIG_SNAPSHOT_VERIFICATION */
+
/* kernel/power/block_io.c */
extern struct block_device *hib_resume_bdev;