2015年10月12日 星期一

How to taint a hibernation snapshot image in a qemu swap disk file

For testing hibernation signature verification, I need try to taint the hibernation snapshot image in swap partition. Using qemu to test to taint snapshot image is more convenient than the real swap partition.

First, I created a qemu raw disk file to be the /dev/sdb swap partition to guest OS:

  qemu-img create ./swap.image 1G

Please be sure the format of file is raw but not qcow2, otherwise the swap header will not in the first page of swap file. I didn't investigate the detail of qcow2 format, but looks the magic string, SWAPSPACE2, is in the position after 5M offset but not the first page:

00050ff0  53 50 41 43 45 32 53 57  41 50 53 50 41 43 45 32  |SPACE2SWAPSPACE2|

The position of magic string in raw format file is in the first page:

00000fe0  01 00 00 00 00 00 00 00  03 00 00 00 53 57 41 50  |............SWAP|
00000ff0  53 50 41 43 45 32 53 57  41 50 53 50 41 43 45 32  |SPACE2SWAPSPACE2|

My whole investigation is base on the raw format disk file.

After create a disk file, then add it to guest OS to be the swap partition. My guest OS is openSUSE:

a. Add the new disk file to be the hdb of guest OS in qemu command:
  -hdb /home/joeyli/qemu-vm/swap.image

b. Launch guest OS, format the sdb disk to be swap:
  mkswap /dev/sdb

c. Add to /etc/fstab
  /dev/sdb swap swap defaults 0 0

d. Add kernel parameter to indicate sdb to be the resume partition:
  resume=/dev/sdb

e. Add the following kernel parameter to grub2 conf for testing hibernation:
earlyprintk=ttyS0,115200 console=tty0 console=ttyS0,115200 debug no_console_suspend=1 loglevel=9 nomodeset earlyprintk=efi hibernate=nocompress

Using hibernate=nocompress to avoid crc32 check, anyway, it's just for testing hibernation verification mechanism. We will taint a snapshot image in swap, then CRC checking maybe failed before hibernation verification.

After /dev/sdb show in guest OS, then trying hibernation. Before hibernation, you should find the magic string of swap parition:

00000fe0  01 00 00 00 00 00 00 00  03 00 00 00 53 57 41 50  |............SWAP|
00000ff0  53 50 41 43 45 32 53 57  41 50 53 50 41 43 45 32  |SPACE2SWAPSPACE2|

The declaration of swap header in kernel source is the swap_header union in swap.h header:

include/linux/swap.h
/*
 * Magic header for a swap area. The first part of the union is
 * what the swap magic looks like for the old (limited to 128MB)
 * swap area format, the second part of the union adds - in the
 * old reserved area - some extra information. Note that the first
 * kilobyte is reserved for boot loader or disk label stuff...
 *
 * Having the magic at the end of the PAGE_SIZE makes detecting swap
 * areas somewhat tricky on machines that support multiple page sizes.
 * For 2.5 we'll probably want to move the magic to just beyond the
 * bootbits...
 */
union swap_header {
        struct {
                char reserved[PAGE_SIZE - 10];
                char magic[10];                 /* SWAP-SPACE or SWAPSPACE2 */
        } magic;
        struct {
                char            bootbits[1024]; /* Space for disklabel etc. */
                __u32           version;
                __u32           last_page;
                __u32           nr_badpages;
                unsigned char   sws_uuid[16];
                unsigned char   sws_volume[16];
                __u32           padding[117];
                __u32           badpages[1];
        } info;
};

So, the magic string is in the end of the first page of partition. On the other hand, other meta-datas are written to the first page from top-down.

After launched hibernation, the magic string will be changed to S1SUSPEND:

00000fe0  01 00 00 00 00 00 00 00  05 00 00 00 53 57 41 50  |............SWAP|
00000ff0  53 50 41 43 45 32 53 31  53 55 53 50 45 4e 44 00  |SPACE2S1SUSPEND.|

After hibernation resuming, either success or fail, the magic string will be changed back to SWAPSPACE2.

The hibernation verification mechanism is calculating the signature of data pages in snapshot image. To test the mechanism is to taint the content in data pages:


Find out the position of data pages is the most important thing to taint data pages in snapshot image. The snapshot image header is swsusp_info, it's in the first page of snapshot. The hibernation mechanism uses the reserved space from bottom-up of the first page of swap partition:

kernel/power/swap.c
struct swsusp_header {      /* 1 page, 4096 bytes */
char reserved[PAGE_SIZE - 20 - sizeof(sector_t) - sizeof(int) -
sizeof(u32)]; /* 4096 - 20 - 8 - 4 - 4 = 4060 */
u32     crc32;             /* 4 bytes */
sector_t image;          /* sector_t of snapshot image, unsigned long 8 bytes */
unsigned int flags;      /* unsigned int, 4 bytes */
char    orig_sig[10];   /* 10 bytes */
char    sig[10];           /* 10 bytes *//* SWAPSPACE2 or S1SUSPEND */
} __attribute__((packed));

The swsusp_header actually is also mapping to the first page of swap, in the same space of swap_header:
The main field of swsusp_header to find the position snapshot image is "image", it's a number of sector_t, the size the same with page on swap. For example, if image = 1, then means the offset of snapshot image in swap is:
    offest = (swap header + image sector) * PAGE_SIZE = (1 + 1) * 4096

Then follow this offset, we can find out the snapshoti image header, swsusp_info:

kernel/power/power.h
struct swsusp_info {
        struct new_utsname      uts;     /* on x86, struct restore_data_record, 24 bytes */
                                                         /* 0x0123456789ABCDEFUL */
        u32                     version_code;
        unsigned long     num_physpages;
        int                       cpus;
        unsigned long      image_pages;    /* data pages number */
        unsigned long      pages;               /* total pages number that are in snapshot */
        unsigned long      size;
} __aligned(PAGE_SIZE);

I want to taint the content of data pages to emulate someone modified the image data. There have some important field in swsusp_info structure, pages and image_pages. So the offset of the beginning of data pages area is: 
    data pages offset = (swap header + image sector) * PAGE_SIZE + (pages - image_pages) * PAGE_SIZE

Then, using printf and dd command to modify the content of data pages. For example, this is the command to modify one byte in 5242848 offset:
    printf '\x32' | dd conv=notrunc of=./swap.image bs=1 seek=5242848

Here is a simple C program to check the swap header and swsusp header in qemu swap raw file, and taint 3 bytes in 3 different position for testing.