[PATCH] encrypt suspend data for easy wiping
authorAndreas Steinmetz <ast@domdv.de>
Sat, 3 Sep 2005 22:56:59 +0000 (15:56 -0700)
committerLinus Torvalds <torvalds@evo.osdl.org>
Mon, 5 Sep 2005 07:06:16 +0000 (00:06 -0700)
The patch protects from leaking sensitive data after resume from suspend.
During suspend a temporary key is created and this key is used to encrypt the
data written to disk.  When, during resume, the data was read back into memory
the temporary key is destroyed which simply means that all data written to
disk during suspend are then inaccessible so they can't be stolen lateron.

Think of the following: you suspend while an application is running that keeps
sensitive data in memory.  The application itself prevents the data from being
swapped out.  Suspend, however, must write these data to swap to be able to
resume lateron.  Without suspend encryption your sensitive data are then
stored in plaintext on disk.  This means that after resume your sensitive data
are accessible to all applications having direct access to the swap device
which was used for suspend.  If you don't need swap after resume these data
can remain on disk virtually forever.  Thus it can happen that your system
gets broken in weeks later and sensitive data which you thought were encrypted
and protected are retrieved and stolen from the swap device.

Signed-off-by: Andreas Steinmetz <ast@domdv.de>
Acked-by: Pavel Machek <pavel@suse.cz>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
kernel/power/Kconfig
kernel/power/swsusp.c

index 2c7121d..917066a 100644 (file)
@@ -72,6 +72,18 @@ config PM_STD_PARTITION
          suspended image to. It will simply pick the first available swap 
          device.
 
+config SWSUSP_ENCRYPT
+       bool "Encrypt suspend image"
+       depends on SOFTWARE_SUSPEND && CRYPTO=y && (CRYPTO_AES=y || CRYPTO_AES_586=y || CRYPTO_AES_X86_64=y)
+       default ""
+       ---help---
+         To prevent data gathering from swap after resume you can encrypt
+         the suspend image with a temporary key that is deleted on
+         resume.
+
+         Note that the temporary key is stored unencrypted on disk while the
+         system is suspended.
+
 config SUSPEND_SMP
        bool
        depends on HOTPLUG_CPU && X86 && PM
index 975b164..b041cea 100644 (file)
@@ -31,6 +31,9 @@
  * Alex Badea <vampire@go.ro>:
  * Fixed runaway init
  *
+ * Andreas Steinmetz <ast@domdv.de>:
+ * Added encrypted suspend option
+ *
  * More state savers are welcome. Especially for the scsi layer...
  *
  * For TODOs,FIXMEs also look in Documentation/power/swsusp.txt
 #include <asm/tlbflush.h>
 #include <asm/io.h>
 
+#include <linux/random.h>
+#include <linux/crypto.h>
+#include <asm/scatterlist.h>
+
 #include "power.h"
 
+#define CIPHER "aes"
+#define MAXKEY 32
+#define MAXIV  32
+
 /* References to section boundaries */
 extern const void __nosave_begin, __nosave_end;
 
@@ -103,7 +114,8 @@ static suspend_pagedir_t *pagedir_save;
 #define SWSUSP_SIG     "S1SUSPEND"
 
 static struct swsusp_header {
-       char reserved[PAGE_SIZE - 20 - sizeof(swp_entry_t)];
+       char reserved[PAGE_SIZE - 20 - MAXKEY - MAXIV - sizeof(swp_entry_t)];
+       u8 key_iv[MAXKEY+MAXIV];
        swp_entry_t swsusp_info;
        char    orig_sig[10];
        char    sig[10];
@@ -129,6 +141,131 @@ static struct swsusp_info swsusp_info;
 static unsigned short swapfile_used[MAX_SWAPFILES];
 static unsigned short root_swap;
 
+static int write_page(unsigned long addr, swp_entry_t * loc);
+static int bio_read_page(pgoff_t page_off, void * page);
+
+static u8 key_iv[MAXKEY+MAXIV];
+
+#ifdef CONFIG_SWSUSP_ENCRYPT
+
+static int crypto_init(int mode, void **mem)
+{
+       int error = 0;
+       int len;
+       char *modemsg;
+       struct crypto_tfm *tfm;
+
+       modemsg = mode ? "suspend not possible" : "resume not possible";
+
+       tfm = crypto_alloc_tfm(CIPHER, CRYPTO_TFM_MODE_CBC);
+       if(!tfm) {
+               printk(KERN_ERR "swsusp: no tfm, %s\n", modemsg);
+               error = -EINVAL;
+               goto out;
+       }
+
+       if(MAXKEY < crypto_tfm_alg_min_keysize(tfm)) {
+               printk(KERN_ERR "swsusp: key buffer too small, %s\n", modemsg);
+               error = -ENOKEY;
+               goto fail;
+       }
+
+       if (mode)
+               get_random_bytes(key_iv, MAXKEY+MAXIV);
+
+       len = crypto_tfm_alg_max_keysize(tfm);
+       if (len > MAXKEY)
+               len = MAXKEY;
+
+       if (crypto_cipher_setkey(tfm, key_iv, len)) {
+               printk(KERN_ERR "swsusp: key setup failure, %s\n", modemsg);
+               error = -EKEYREJECTED;
+               goto fail;
+       }
+
+       len = crypto_tfm_alg_ivsize(tfm);
+
+       if (MAXIV < len) {
+               printk(KERN_ERR "swsusp: iv buffer too small, %s\n", modemsg);
+               error = -EOVERFLOW;
+               goto fail;
+       }
+
+       crypto_cipher_set_iv(tfm, key_iv+MAXKEY, len);
+
+       *mem=(void *)tfm;
+
+       goto out;
+
+fail:  crypto_free_tfm(tfm);
+out:   return error;
+}
+
+static __inline__ void crypto_exit(void *mem)
+{
+       crypto_free_tfm((struct crypto_tfm *)mem);
+}
+
+static __inline__ int crypto_write(struct pbe *p, void *mem)
+{
+       int error = 0;
+       struct scatterlist src, dst;
+
+       src.page   = virt_to_page(p->address);
+       src.offset = 0;
+       src.length = PAGE_SIZE;
+       dst.page   = virt_to_page((void *)&swsusp_header);
+       dst.offset = 0;
+       dst.length = PAGE_SIZE;
+
+       error = crypto_cipher_encrypt((struct crypto_tfm *)mem, &dst, &src,
+                                       PAGE_SIZE);
+
+       if (!error)
+               error = write_page((unsigned long)&swsusp_header,
+                               &(p->swap_address));
+       return error;
+}
+
+static __inline__ int crypto_read(struct pbe *p, void *mem)
+{
+       int error = 0;
+       struct scatterlist src, dst;
+
+       error = bio_read_page(swp_offset(p->swap_address), (void *)p->address);
+       if (!error) {
+               src.offset = 0;
+               src.length = PAGE_SIZE;
+               dst.offset = 0;
+               dst.length = PAGE_SIZE;
+               src.page = dst.page = virt_to_page((void *)p->address);
+
+               error = crypto_cipher_decrypt((struct crypto_tfm *)mem, &dst,
+                                               &src, PAGE_SIZE);
+       }
+       return error;
+}
+#else
+static __inline__ int crypto_init(int mode, void *mem)
+{
+       return 0;
+}
+
+static __inline__ void crypto_exit(void *mem)
+{
+}
+
+static __inline__ int crypto_write(struct pbe *p, void *mem)
+{
+       return write_page(p->address, &(p->swap_address));
+}
+
+static __inline__ int crypto_read(struct pbe *p, void *mem)
+{
+       return bio_read_page(swp_offset(p->swap_address), (void *)p->address);
+}
+#endif
+
 static int mark_swapfiles(swp_entry_t prev)
 {
        int error;
@@ -140,6 +277,7 @@ static int mark_swapfiles(swp_entry_t prev)
            !memcmp("SWAPSPACE2",swsusp_header.sig, 10)) {
                memcpy(swsusp_header.orig_sig,swsusp_header.sig, 10);
                memcpy(swsusp_header.sig,SWSUSP_SIG, 10);
+               memcpy(swsusp_header.key_iv, key_iv, MAXKEY+MAXIV);
                swsusp_header.swsusp_info = prev;
                error = rw_swap_page_sync(WRITE,
                                          swp_entry(root_swap, 0),
@@ -286,6 +424,10 @@ static int data_write(void)
        int error = 0, i = 0;
        unsigned int mod = nr_copy_pages / 100;
        struct pbe *p;
+       void *tfm;
+
+       if ((error = crypto_init(1, &tfm)))
+               return error;
 
        if (!mod)
                mod = 1;
@@ -294,11 +436,14 @@ static int data_write(void)
        for_each_pbe (p, pagedir_nosave) {
                if (!(i%mod))
                        printk( "\b\b\b\b%3d%%", i / mod );
-               if ((error = write_page(p->address, &(p->swap_address))))
+               if ((error = crypto_write(p, tfm))) {
+                       crypto_exit(tfm);
                        return error;
+               }
                i++;
        }
        printk("\b\b\b\bdone\n");
+       crypto_exit(tfm);
        return error;
 }
 
@@ -400,6 +545,7 @@ static int write_suspend_image(void)
        if ((error = close_swap()))
                goto FreePagedir;
  Done:
+       memset(key_iv, 0, MAXKEY+MAXIV);
        return error;
  FreePagedir:
        free_pagedir_entries();
@@ -1212,6 +1358,8 @@ static int check_sig(void)
                return error;
        if (!memcmp(SWSUSP_SIG, swsusp_header.sig, 10)) {
                memcpy(swsusp_header.sig, swsusp_header.orig_sig, 10);
+               memcpy(key_iv, swsusp_header.key_iv, MAXKEY+MAXIV);
+               memset(swsusp_header.key_iv, 0, MAXKEY+MAXIV);
 
                /*
                 * Reset swap signature now.
@@ -1239,6 +1387,10 @@ static int data_read(struct pbe *pblist)
        int error = 0;
        int i = 0;
        int mod = swsusp_info.image_pages / 100;
+       void *tfm;
+
+       if ((error = crypto_init(0, &tfm)))
+               return error;
 
        if (!mod)
                mod = 1;
@@ -1250,14 +1402,15 @@ static int data_read(struct pbe *pblist)
                if (!(i % mod))
                        printk("\b\b\b\b%3d%%", i / mod);
 
-               error = bio_read_page(swp_offset(p->swap_address),
-                                 (void *)p->address);
-               if (error)
+               if ((error = crypto_read(p, tfm))) {
+                       crypto_exit(tfm);
                        return error;
+               }
 
                i++;
        }
        printk("\b\b\b\bdone\n");
+       crypto_exit(tfm);
        return error;
 }
 
@@ -1385,6 +1538,7 @@ int swsusp_read(void)
 
        error = read_suspend_image();
        blkdev_put(resume_bdev);
+       memset(key_iv, 0, MAXKEY+MAXIV);
 
        if (!error)
                pr_debug("swsusp: Reading resume file was successful\n");