[filedisk] Require Microsoft VHD footers for booted disk images
authorShao Miller <Shao.Miller@yrdsb.edu.on.ca>
Thu, 3 Jun 2010 20:52:44 +0000 (16:52 -0400)
committerShao Miller <Shao.Miller@yrdsb.edu.on.ca>
Thu, 3 Jun 2010 20:52:44 +0000 (16:52 -0400)
The previous check for the short MBR magic was really cheap and
could fairly easily result in false positives.  We now require
a Microsoft .VHD "footer" to be within the last 512 bytes of
the disk image.  This works nicely for HDD images right now.

[Un]Fortunately, testing the .ISO case revealed that the code
which iterates through disks in an attempt to find the right
backing disk is running at an IRQL which is incompatible with
IoGetDeviceInterfaces(), which wishes to allocate to pageable
memory.  It would seem best to enqueue this activity, but
neither threaded filedisks nor the GRUB4DOS threaded filedisk
actually implement a new "class" of device, so there's not a
place to throw the previous disk__io routine while we hook a
read/write operation in order to allow for delayed backing
disk discovery.

WinVBlock.dev
src/include/msvhd.h [new file with mode: 0644]
src/winvblock/filedisk/grub4dos.c

index 5f391df..196837b 100644 (file)
@@ -1,7 +1,7 @@
 [Project]\r
 FileName=WinVBlock.dev\r
 Name=WinVBlock\r
-UnitCount=57\r
+UnitCount=58\r
 PchHead=-1\r
 PchSource=-1\r
 Ver=3\r
@@ -651,3 +651,13 @@ Priority=1000
 OverrideBuildCmd=0\r
 BuildCmd=\r
 \r
+[Unit58]\r
+FileName=src\include\msvhd.h\r
+CompileCpp=1\r
+Folder=Include\r
+Compile=1\r
+Link=1\r
+Priority=1000\r
+OverrideBuildCmd=0\r
+BuildCmd=\r
+\r
diff --git a/src/include/msvhd.h b/src/include/msvhd.h
new file mode 100644 (file)
index 0000000..dda70f2
--- /dev/null
@@ -0,0 +1,83 @@
+/**
+ * Copyright (C) 2010, Shao Miller <shao.miller@yrdsb.edu.on.ca>.
+ *
+ * This file is part of WinVBlock, derived from WinAoE.
+ *
+ * WinVBlock is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * WinVBlock is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with WinVBlock.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef _msvhd_h
+#  define _msvhd_h
+
+/**
+ * @file
+ *
+ * Microsoft .VHD disk image format specifics.
+ * Structure details from the VHD Image Format Specification Document[1],
+ * kindly provided by Microsoft's Open Source Promise[2].
+ *
+ * [1] http://technet.microsoft.com/en-us/virtualserver/bb676673.aspx
+ * [2] http://www.microsoft.com/interop/osp/default.mspx
+ *
+ */
+
+/* The .VHD disk image footer format */
+#  ifdef _MSC_VER
+#    pragma pack(1)
+#  endif
+winvblock__def_struct ( msvhd__footer )
+{
+  char cookie[8];
+  byte__array_union ( winvblock__uint32, features );
+  byte__array_union ( winvblock__uint32, file_ver );
+  byte__array_union ( ULONGLONG, data_offset );
+  byte__array_union ( winvblock__uint32, timestamp );
+  char creator_app[4];
+  byte__array_union ( winvblock__uint32, creator_ver );
+  byte__array_union ( winvblock__uint32, creator_os );
+  byte__array_union ( ULONGLONG, orig_size );
+  byte__array_union ( ULONGLONG, cur_size );
+  byte__array_union ( winvblock__uint16, geom_cyls );
+  winvblock__uint8 geom_heads;
+  winvblock__uint8 geom_sects_per_track;
+  byte__array_union ( winvblock__uint32, type );
+  byte__array_union ( winvblock__uint32, checksum );
+  char uid[16];
+  char saved_state;
+  char reserved[427];
+} __attribute__ ( ( __packed__ ) );
+
+#  ifdef _MSC_VER
+#    pragma pack()
+#  endif
+
+/* Function body in header so user-land utility links without WinVBlock */
+static void STDCALL
+msvhd__footer_swap_endian (
+  msvhd__footer_ptr footer_ptr
+ )
+{
+  byte__rev_array_union ( footer_ptr->features );
+  byte__rev_array_union ( footer_ptr->file_ver );
+  byte__rev_array_union ( footer_ptr->data_offset );
+  byte__rev_array_union ( footer_ptr->timestamp );
+  byte__rev_array_union ( footer_ptr->creator_ver );
+  byte__rev_array_union ( footer_ptr->creator_os );
+  byte__rev_array_union ( footer_ptr->orig_size );
+  byte__rev_array_union ( footer_ptr->cur_size );
+  byte__rev_array_union ( footer_ptr->geom_cyls );
+  byte__rev_array_union ( footer_ptr->type );
+  byte__rev_array_union ( footer_ptr->checksum );
+}
+
+#endif                         /* _msvhd_h */
index 9758d34..a814027 100644 (file)
@@ -39,6 +39,8 @@
 #include "debug.h"
 #include "probe.h"
 #include "grub4dos.h"
+#include "byte.h"
+#include "msvhd.h"
 
 static disk__io_routine threaded_io;
 
@@ -55,30 +57,58 @@ check_disk_match (
   IN filedisk__type_ptr filedisk_ptr
  )
 {
-  mbr_ptr buf;
+  msvhd__footer_ptr buf;
   NTSTATUS status;
   IO_STATUS_BLOCK io_status;
-  winvblock__bool pass = FALSE;
+  LARGE_INTEGER end_sect;
+  winvblock__bool pass = TRUE;
 
   /*
-   * Allocate a buffer for testing for an MBR
+   * Allocate a buffer for testing for a MS .VHD footer
    */
-  buf = ExAllocatePool ( NonPagedPool, sizeof ( mbr ) );
+  buf = ExAllocatePool ( NonPagedPool, sizeof ( *buf ) );
   if ( buf == NULL )
     return STATUS_INSUFFICIENT_RESOURCES;
   /*
    * Read in the buffer
    */
+  end_sect.QuadPart =
+    filedisk_ptr->offset.QuadPart +
+    ( filedisk_ptr->disk->LBADiskSize * filedisk_ptr->disk->SectorSize ) -
+    sizeof ( *buf );
   status =
-    ZwReadFile ( file, NULL, NULL, NULL, &io_status, buf, sizeof ( mbr ),
-                &filedisk_ptr->offset, NULL );
+    ZwReadFile ( file, NULL, NULL, NULL, &io_status, buf, sizeof ( *buf ),
+                &end_sect, NULL );
   if ( !NT_SUCCESS ( status ) )
     return status;
   /*
-   * Check for an MBR signature
+   * Adjust the footer's byte ordering
    */
-  if ( buf->mbr_sig == 0xaa55 )
-    pass = TRUE;
+  msvhd__footer_swap_endian ( buf );
+  /*
+   * Examine .VHD fields for validity
+   */
+  if ( RtlCompareMemory ( &buf->cookie, "conectix", sizeof ( buf->cookie ) ) !=
+       sizeof ( buf->cookie ) )
+    pass = FALSE;
+  if ( buf->file_ver.val != 0x10000 )
+    pass = FALSE;
+  if ( buf->data_offset.val != 0xffffffff )
+    pass = FALSE;
+  if ( buf->orig_size.val != buf->cur_size.val )
+    pass = FALSE;
+  if ( buf->type.val != 2 )
+    pass = FALSE;
+  /*
+   * Match against our expected disk size, within an OD sector's worth
+   */
+  if ( buf->orig_size.val <
+       filedisk_ptr->disk->LBADiskSize * filedisk_ptr->disk->SectorSize )
+    pass = FALSE;
+  if ( buf->orig_size.val -
+       filedisk_ptr->disk->LBADiskSize * filedisk_ptr->disk->SectorSize >
+       2048 )
+    pass = FALSE;
   /*
    * Free buffer and return status
    */
@@ -98,25 +128,26 @@ disk__io_decl (
  )
 {
   filedisk__type_ptr filedisk_ptr;
+  NTSTATUS status;
   GUID disk_guid = GUID_DEVINTERFACE_DISK;
   PWSTR sym_links;
   PWCHAR pos;
   HANDLE file;
-  NTSTATUS status;
 
   filedisk_ptr = filedisk__get_ptr ( dev_ptr );
   /*
    * Find the backing disk and use it.  We walk a list
    * of unicode disk device names and check each one
    */
-  IoGetDeviceInterfaces ( &disk_guid, NULL, 0, &sym_links );
+  status = IoGetDeviceInterfaces ( &disk_guid, NULL, 0, &sym_links );
+  if ( !NT_SUCCESS ( status ) )
+    goto dud;
   pos = sym_links;
   while ( *pos != UNICODE_NULL )
     {
       UNICODE_STRING path;
       OBJECT_ATTRIBUTES obj_attrs;
       IO_STATUS_BLOCK io_status;
-
       RtlInitUnicodeString ( &path, pos );
       InitializeObjectAttributes ( &obj_attrs, &path,
                                   OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
@@ -156,12 +187,7 @@ disk__io_decl (
    * If we did not find the backing disk, we are a dud
    */
   if ( !NT_SUCCESS ( status ) )
-    {
-      irp->IoStatus.Information = 0;
-      irp->IoStatus.Status = STATUS_NO_MEDIA_IN_DEVICE;
-      IoCompleteRequest ( irp, IO_NO_INCREMENT );
-      return STATUS_NO_MEDIA_IN_DEVICE;
-    }
+    goto dud;
   /*
    *  Use the backing disk and restore the original read/write routine
    */
@@ -172,6 +198,12 @@ disk__io_decl (
    */
   return threaded_io ( dev_ptr, mode, start_sector, sector_count, buffer,
                       irp );
+
+dud:
+  irp->IoStatus.Information = 0;
+  irp->IoStatus.Status = STATUS_NO_MEDIA_IN_DEVICE;
+  IoCompleteRequest ( irp, IO_NO_INCREMENT );
+  return STATUS_NO_MEDIA_IN_DEVICE;
 }
 
 void
@@ -188,7 +220,6 @@ filedisk_grub4dos__find (
   winvblock__uint32 i = 8;
   winvblock__bool FoundGrub4DosMapping = FALSE;
   filedisk__type_ptr filedisk_ptr;
-
   /*
    * Find a GRUB4DOS sector-mapped disk.  Start by looking at the
    * real-mode IDT and following the "SafeMBRHook" INT 0x13 hook
@@ -292,7 +323,6 @@ filedisk_grub4dos__find (
          filedisk_ptr->disk->Cylinders =
            filedisk_ptr->disk->LBADiskSize / ( filedisk_ptr->disk->Heads *
                                                filedisk_ptr->disk->Sectors );
-
          /*
           * Set a filedisk "hash" and mark the drive as a boot-drive.
           * The "hash" is 'G4DX', where X is the GRUB4DOS INT 13h