Subject: bug#22266: [libparted] jump instruction and boot code is corrupted with random bytes after fat is resized

From: Curtis Gedak <[email-masked]>
Date: Sun, 10 Apr 2016 14:00:33 -0600

Attached is a patch to fix the issue with jump instruction and boot code
is corrupted with random bytes after fat is resized.

Thanks goes to Tom Yan, Dirk Eberhardt, and others who reported the
problem and provided additional details.  This detailed information
helped with my understanding of the issue.

The portion of this email after my signature delves further into the
problem investigation and solution.

Regards,
Curtis Gedak

PROBLEM
-------

Windows does not recognize FAT32 after resizing with utilities (for
example GParted) that use parted-3.2 libraries.

CONFIRMATION
------------

Steps to confirm are:

1) Create a single FAT32 partition (e.g. 500 MiB) on a device such as
   a USB drive.

   When I created FAT32 partition using GParted the boot sector
   started with:

   $ sudo hexdump -n 7k -C /dev/sda1
   00000000  eb 58 90 6d 6b 66 73                              |.X.mkfs|
   00000007
   $

2) Use GParted linked with parted-3.2 libraries to shrink the FAT32
   partition (e.g. 450 MiB).

   After FAT32 resizing using GParted the boot sector started with:

   $ sudo hexdump -n 7k -C /dev/sda1
   00000000  d0 02 30 4d 53 57 49                              |..0MSWI|
   00000007
   $

   The important distinction is that the first three hexadecimal
   values were randomly changed.  These values represent the "boot
   jump" code and Windows requires these values to be correctly set.

3) Try to use the FAT32 partition with Windows (e.g. insert USB drive
   in Windows computer).  The FAT32 file system is not recognized.

   Regarding boot_jump (and system_id) see:
   http://git.savannah.gnu.org/cgit/parted.git/tree/doc/FAT?id=v3.2#n176

If the libraries from parted 3.1, or versions 2.4 and earlier are
used, then the resized FAT32 partition is recognized by Windows.

INVESTIGATION
-------------

The problem with Windows FAT32 recognition was introduced with:

  Fix filesystem detection on non 512 byte sectors
  80678bdd957cf49a9ccfc8b88ba3fb8b4c63fc12

The cause of the problem is that when the parted library is resizing
FAT32, there is one too many boot_sector memory allocations.

The calling structure is as follows:

fat_resize (...)
{
  ...
  ctx = create_resize_context (...);  # Allocates new boot_sector
                                      #   memory and copies content
                                      #   from old boot sector
  ...
  fat_boot_sector_generate (...)      # Allocates new boot_sector
                                      #   memory AGAIN!
                                      #   Copied content lost!
  fat_boot_sector_write (...)
  ...
}

SOLUTION
--------

Remove new boot sector memory allocation from
fat_boot_sector_generate(...).

The function fat_boot_sector_generate(...) is also called from
fat_create(...).

  Note that access to fat_create(...) via ped_file_system_create(...)
  is no longer possible since parted-3.0 because all file system
  operations were removed.  See:
  http://git.savannah.gnu.org/cgit/parted.git/tree/NEWS?id=v3.0

  In parted-3.1, access to fat_create(...) via
  ped_file_system_create(...) was not re-introduced.  See:
  http://git.savannah.gnu.org/cgit/parted.git/tree/NEWS?id=v3.1

Although fat_create(...) in inaccessible, I thought it best to move
the new boot_sector memory allocation to where it is needed in
fat_boot_sector_set_boot_code(...).  That way the code should still
work if it is ever re-instated.

REFERENCES
----------

GNU bug report logs - #22266:
[libparted] jump instruction and boot code is corrupted with random
bytes after fat is resized
http://debbugs.gnu.org/cgi/bugreport.cgi?bug=22266

GNU bug report logs - #22710:
libparted 3.2 fat32 bootsector incompatible w. windows
http://debbugs.gnu.org/cgi/bugreport.cgi?bug=22710

GParted Bug Report:
Bug 759916 - fat(32) resizing generates boot sector(s) with invalid
             jump instruction and pseudo-random boot code
https://bugzilla.gnome.org/show_bug.cgi?id=759916

GParted Forum:
Vista/XP don't accept a shrinked Fat32 partition (USB-stick)
http://gparted-forum.surf4.info/viewtopic.php?id=17318
>From d9a424a6fd922f5275183ea96e911af9abb9b745 Mon Sep 17 00:00:00 2001
From: Curtis Gedak <[email-masked]>
Date: Sun, 10 Apr 2016 11:38:41 -0600
Subject: [PATCH] lib-fs-resize: Fix recognition of FAT file system after
 resizing

When resizing a FAT partition, an extra boot_sector memory allocation
was causing the original boot_sector information to be lost.  The
resulting FAT file system was still recognized by GNU/Linux, but not
recognized by the proprietary Windows operating system.

The problem with Windows FAT32 recognition was introduced with:

  Fix filesystem detection on non 512 byte sectors
  80678bdd957cf49a9ccfc8b88ba3fb8b4c63fc12

Fix by removing the extra boot_sector memory allocation.

Note that since parted-3.0 another code path to the extra memory
allocation of fat_create(...) via ped_file_system_create(...) is
inaccessible.  In an effort to maintain the ability to re-instate the
code, add a new boot_sector memory allocation where it is needed in
the alternate code path.

GNU bug report logs - #22266
[libparted] jump instruction and boot code is corrupted with random
bytes after fat is resized
http://debbugs.gnu.org/cgi/bugreport.cgi?bug=22266
---
 libparted/fs/r/fat/bootsector.c | 9 +++++++--
 libparted/fs/r/fat/bootsector.h | 2 +-
 libparted/fs/r/fat/fat.c        | 2 +-
 3 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/libparted/fs/r/fat/bootsector.c b/libparted/fs/r/fat/bootsector.c
index 1d2b601..99d788d 100644
--- a/libparted/fs/r/fat/bootsector.c
+++ b/libparted/fs/r/fat/bootsector.c
@@ -281,8 +281,13 @@ fat_boot_sector_analyse (FatBootSector* bs, PedFileSystem* fs)
 
 #ifndef DISCOVER_ONLY
 int
-fat_boot_sector_set_boot_code (FatBootSector* bs)
+fat_boot_sector_set_boot_code (FatBootSector** bsp, const PedFileSystem* fs)
 {
+	FatSpecific*	fs_info = FAT_SPECIFIC (fs);
+
+	PED_ASSERT (bsp != NULL);
+	*bsp = ped_malloc (fs->geom->dev->sector_size);
+	FatBootSector *bs = *bsp;
 	PED_ASSERT (bs != NULL);
 
 	memset (bs, 0, 512);
@@ -297,8 +302,8 @@ fat_boot_sector_generate (FatBootSector** bsp, const PedFileSystem* fs)
 	FatSpecific*	fs_info = FAT_SPECIFIC (fs);
 
 	PED_ASSERT (bsp != NULL);
-	*bsp = ped_malloc (fs->geom->dev->sector_size);
 	FatBootSector *bs = *bsp;
+	PED_ASSERT (bs != NULL);
 
 	memcpy (bs->system_id, "MSWIN4.1", 8);
 	bs->sector_size = PED_CPU_TO_LE16 (fs_info->logical_sector_size * 512);
diff --git a/libparted/fs/r/fat/bootsector.h b/libparted/fs/r/fat/bootsector.h
index e92842c..3f84d7f 100644
--- a/libparted/fs/r/fat/bootsector.h
+++ b/libparted/fs/r/fat/bootsector.h
@@ -120,7 +120,7 @@ int fat_boot_sector_read (FatBootSector** bs, const PedGeometry* geom);
 FatType fat_boot_sector_probe_type (const FatBootSector* bs,
 				    const PedGeometry* geom);
 int fat_boot_sector_analyse (FatBootSector* bs, PedFileSystem* fs);
-int fat_boot_sector_set_boot_code (FatBootSector* bs);
+int fat_boot_sector_set_boot_code (FatBootSector** bs, const PedFileSystem* fs);
 int fat_boot_sector_generate (FatBootSector** bs, const PedFileSystem* fs);
 int fat_boot_sector_write (const FatBootSector* bs, PedFileSystem* fs);
 
diff --git a/libparted/fs/r/fat/fat.c b/libparted/fs/r/fat/fat.c
index 4ecf5c5..444668d 100644
--- a/libparted/fs/r/fat/fat.c
+++ b/libparted/fs/r/fat/fat.c
@@ -310,7 +310,7 @@ fat_create (PedGeometry* geom, FatType fat_type, PedTimer* timer)
 
 	fs_info->serial_number = generate_random_uint32 ();
 
-	if (!fat_boot_sector_set_boot_code (fs_info->boot_sector))
+	if (!fat_boot_sector_set_boot_code (&fs_info->boot_sector, fs))
 		goto error_free_buffers;
 	if (!fat_boot_sector_generate (&fs_info->boot_sector, fs))
 		goto error_free_buffers;
-- 
1.9.1