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.
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.