Laszlo Ersek
2021-Nov-24 10:37 UTC
[Libguestfs] [PATCH v2 0/5] work around part table type misreporting by "parted"
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1931821
v1:
https://listman.redhat.com/archives/libguestfs/2021-November/msg00211.html
In version 2, "list-filesystems" works with the bogus MBR partition
table, as long as the FAT variant in use is FAT16 or FAT32. (Parted
completely chokes on bogus MBR + FAT12, but that's arguably a rare
corner case with libguestfs.) See also the patch-level Notes sections.
Thanks,
Laszlo
Laszlo Ersek (5):
daemon/mkfs: disable creation of fake MBR partition table with
"mkfs.fat"
daemon/9p: fix wrong pathname in error message
daemon/parted: simplify print_partition_table() prototype
daemon/parted: work around part table type misreporting by "parted"
daemon/listfs: don't call "sgdisk -i" on bogus MBR partition
table
entry
daemon/9p.c | 4 ++--
daemon/listfs.ml | 5 +++++
daemon/mkfs.c | 27 +++++++++++++++++++++++++++
daemon/parted.c | 17 ++++++-----------
daemon/parted.ml | 13 ++++++++-----
daemon/utils.ml | 44 ++++++++++++++++++++++++++++++++++++++++++++
daemon/utils.mli | 10 ++++++++++
7 files changed, 102 insertions(+), 18 deletions(-)
base-commit: 9fda9110e6a7afc1d418665f8f3538d87b5c8a5b
--
2.19.1.3.g30247aa5d201
Laszlo Ersek
2021-Nov-24 10:37 UTC
[Libguestfs] [PATCH v2 1/5] daemon/mkfs: disable creation of fake MBR partition table with "mkfs.fat"
Search the usage output of "mkfs.fat" for "--mbr[="; cache
the result for
further invocations. If the option is supported, pass "--mbr=n" to
"mkfs.fat". This will prevent the creation of a bogus partition table
whose first (and only) entry describes a partition that contains the
partition table.
(Such a bogus partition table breaks "parted", which is a tool used by
libguestfs extensively, both internally and in public libguestfs APIs.)
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1931821
Signed-off-by: Laszlo Ersek <lersek at redhat.com>
Acked-by: Richard W.M. Jones <rjones at redhat.com>
---
Notes:
v2:
- no change
- pick up Rich's A-b
v1:
Testing (per
<https://bugzilla.redhat.com/show_bug.cgi?id=1931821#c0>):
$ virt-make-fs -vx --format=raw --size=200M --type=vfat --label=TEST \
website ~/test_vfat.img
> [...]
> creating vfat filesystem on /dev/sda ...
> libguestfs: trace: mkfs "vfat" "/dev/sda"
"label:TEST"
> [...]
> commandrvf: stdout=n stderr=y flags=0x0
> commandrvf: mkfs.fat
> Usage: mkfs.fat [OPTIONS] TARGET [BLOCKS]
> Create FAT filesystem in TARGET, which can be a block device or file.
Use only
> up to BLOCKS 1024 byte blocks if specified. With the -C option, file
TARGET will be
> created with a size of 1024 bytes times BLOCKS, which must be
specified.
>
> Options:
> -a Disable alignment of data structures
> -A Toggle Atari variant of the filesystem
> -b SECTOR Select SECTOR as location of the FAT32 backup boot
sector
> -c Check device for bad blocks before creating the
filesystem
> -C Create file TARGET then create filesystem in it
> -D NUMBER Write BIOS drive number NUMBER to boot sector
> -f COUNT Create COUNT file allocation tables
> -F SIZE Select FAT size SIZE (12, 16 or 32)
> -g GEOM Select disk geometry: heads/sectors_per_track
> -h NUMBER Write hidden sectors NUMBER to boot sector
> -i VOLID Set volume ID to VOLID (a 32 bit hexadecimal number)
> -I Ignore and disable safety checks
> -l FILENAME Read bad blocks list from FILENAME
> -m FILENAME Replace default error message in boot block with
contents of FILENAME
> -M TYPE Set media type in boot sector to TYPE
> --mbr[=y|n|a] Fill (fake) MBR table with one partition which spans
whole disk
> -n LABEL Set volume name to LABEL (up to 11 characters long)
> --codepage=N use DOS codepage N to encode label (default: 850)
> -r COUNT Make room for at least COUNT entries in the root
directory
> -R COUNT Set minimal number of reserved sectors to COUNT
> -s COUNT Set number of sectors per cluster to COUNT
> -S SIZE Select a sector size of SIZE (a power of two, at
least 512)
> -v Verbose execution
> --variant=TYPE Select variant TYPE of filesystem (standard or Atari)
>
> --invariant Use constants for randomly generated or time based
values
> --offset=SECTOR Write the filesystem at a specific sector into the
device file.
> --help Show this help message and exit
> [...]
> commandrvf: stdout=n stderr=y flags=0x0
> commandrvf: mkfs -t vfat -I --mbr=n -n TEST /dev/sda
$ guestfish -a ~/test_vfat.img
> ><fs> run
> ><fs> list-filesystems
> /dev/sda: vfat
daemon/mkfs.c | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/daemon/mkfs.c b/daemon/mkfs.c
index ba90cef2111c..4cb216cabe8e 100644
--- a/daemon/mkfs.c
+++ b/daemon/mkfs.c
@@ -30,6 +30,14 @@
#define MAX_ARGS 64
+enum fat_mbr_option {
+ FMO_UNCHECKED,
+ FMO_DOESNT_EXIST,
+ FMO_EXISTS,
+};
+
+static enum fat_mbr_option fat_mbr_option = FMO_UNCHECKED;
+
/* Takes optional arguments, consult optargs_bitmask. */
int
do_mkfs (const char *fstype, const char *device, int blocksize,
@@ -101,6 +109,25 @@ do_mkfs (const char *fstype, const char *device, int
blocksize,
STREQ (fstype, "msdos"))
ADD_ARG (argv, i, "-I");
+ /* Prevent mkfs.fat from creating a bogus partition table (RHBZ#1931821). */
+ if (STREQ (fstype, "fat") || STREQ (fstype, "vfat") ||
+ STREQ (fstype, "msdos")) {
+ if (fat_mbr_option == FMO_UNCHECKED) {
+ CLEANUP_FREE char *usage_err = NULL;
+
+ fat_mbr_option = FMO_DOESNT_EXIST;
+ /* Invoking either version 3 of version 4 of mkfs.fat without any options
+ * will make it (a) print a usage summary to stderr, (b) exit with status
+ * 1.
+ */
+ r = commandr (NULL, &usage_err, "mkfs.fat", (char *)NULL);
+ if (r == 1 && strstr (usage_err, "--mbr[=") != NULL)
+ fat_mbr_option = FMO_EXISTS;
+ }
+ if (fat_mbr_option == FMO_EXISTS)
+ ADD_ARG (argv, i, "--mbr=n");
+ }
+
/* Process blocksize parameter if set. */
if (optargs_bitmask & GUESTFS_MKFS_BLOCKSIZE_BITMASK) {
if (blocksize <= 0 || !is_power_of_2 (blocksize)) {
--
2.19.1.3.g30247aa5d201
Laszlo Ersek
2021-Nov-24 10:37 UTC
[Libguestfs] [PATCH v2 2/5] daemon/9p: fix wrong pathname in error message
The directory that readdir() and closedir() work on is BUS_PATH
("/sys/bus/virtio/drivers/9pnet_virtio"), not "/sys/block".
Fix the error
messages that are sent when readdir() or closedir() fails.
(The invalid "sys/block" pathname could be a leftover from when the
directory reading logic was (perhaps) copied from "daemon/sync.c".)
Fixes: 5f10c3350338bbca735a74db26f98da968957bd9
Signed-off-by: Laszlo Ersek <lersek at redhat.com>
---
Notes:
v2:
- new patch: unrelated fix for a wart I'd come across while working the
BZ
daemon/9p.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/daemon/9p.c b/daemon/9p.c
index 743a96abd449..9a3a5cfdf274 100644
--- a/daemon/9p.c
+++ b/daemon/9p.c
@@ -106,14 +106,14 @@ do_list_9p (void)
/* Check readdir didn't fail */
if (errno != 0) {
- reply_with_perror ("readdir: /sys/block");
+ reply_with_perror ("readdir: " BUS_PATH);
closedir (dir);
return NULL;
}
/* Close the directory handle */
if (closedir (dir) == -1) {
- reply_with_perror ("closedir: /sys/block");
+ reply_with_perror ("closedir: " BUS_PATH);
return NULL;
}
--
2.19.1.3.g30247aa5d201
Laszlo Ersek
2021-Nov-24 10:37 UTC
[Libguestfs] [PATCH v2 3/5] daemon/parted: simplify print_partition_table() prototype
Since commit 994ca1f8ebcc ("daemon: Reimplement
'part_get_mbr_part_type'
API in OCaml.", 2018-05-02), we've not had any calls to
print_partition_table() that would pass a "true" argument for the
"add_m_option" parameter.
Remove the parameter, and inside part_get_mbr_part_type(), remove the dead
branch.
Relatedly, update the comment on the
"print_partition_table_machine_readable" OCaml function, originally
from
commit 32e661f42169 ("daemon: Reimplement ?part_list? API in OCaml.",
2017-07-27). Because print_partition_table() now passes "-m" to
"parted"
unconditionally, and there are no use cases left that would *forbid*
"-m",
"print_partition_table_machine_readable" is almost equivalent to
print_partition_table() -- modulo the enforcement of the "BYT;"
header.
Signed-off-by: Laszlo Ersek <lersek at redhat.com>
---
Notes:
v2:
- new patch: unrelated refactoring I'd found possible while working the
BZ
daemon/parted.c | 17 ++++++-----------
daemon/parted.ml | 6 ++----
2 files changed, 8 insertions(+), 15 deletions(-)
diff --git a/daemon/parted.c b/daemon/parted.c
index ef7b90056b41..3a723762c345 100644
--- a/daemon/parted.c
+++ b/daemon/parted.c
@@ -341,7 +341,7 @@ get_table_field (const char *line, int n)
}
static char *
-print_partition_table (const char *device, bool add_m_option)
+print_partition_table (const char *device)
{
char *out;
CLEANUP_FREE char *err = NULL;
@@ -349,14 +349,9 @@ print_partition_table (const char *device, bool
add_m_option)
udev_settle ();
- if (add_m_option)
- r = command (&out, &err, "parted", "-m",
"-s", "--", device,
- "unit", "b",
- "print", NULL);
- else
- r = command (&out, &err, "parted", "-s",
"--", device,
- "unit", "b",
- "print", NULL);
+ r = command (&out, &err, "parted", "-m",
"-s", "--", device,
+ "unit", "b",
+ "print", NULL);
udev_settle ();
@@ -383,7 +378,7 @@ do_part_get_bootable (const char *device, int partnum)
return -1;
}
- CLEANUP_FREE char *out = print_partition_table (device, true);
+ CLEANUP_FREE char *out = print_partition_table (device);
if (!out)
return -1;
@@ -555,7 +550,7 @@ do_part_get_name (const char *device, int partnum)
return NULL;
if (STREQ (parttype, "gpt")) {
- CLEANUP_FREE char *out = print_partition_table (device, true);
+ CLEANUP_FREE char *out = print_partition_table (device);
if (!out)
return NULL;
diff --git a/daemon/parted.ml b/daemon/parted.ml
index 45c387987483..29b98c4806c1 100644
--- a/daemon/parted.ml
+++ b/daemon/parted.ml
@@ -57,10 +57,8 @@ let part_get_mbr_id device partnum (* It's printed in
hex, possibly with a leading space. *)
sscanf out " %x" identity
-(* This is not equivalent to print_partition_table in the C code, as
- * it only deals with the ?-m? option output, and it partially parses
- * that. If we convert other functions that don't use the ?-m? version
- * we'll have to refactor this. XXX
+(* This is almost equivalent to print_partition_table in the C code. The
+ * difference is that here we enforce the "BYT;" header internally.
*)
let print_partition_table_machine_readable device udev_settle ();
--
2.19.1.3.g30247aa5d201
Laszlo Ersek
2021-Nov-24 10:37 UTC
[Libguestfs] [PATCH v2 4/5] daemon/parted: work around part table type misreporting by "parted"
"parted" incorrectly reports "loop" rather than
"msdos" for the partition
table type, when the (fake) partition table comes from the "--mbr"
option
of "mkfs.fat" (in dosfstools-4.2+), and the FAT variant in question is
FAT16 or FAT32. (See RHBZ#2026224.) Work this around by
- parsing the partition table ourselves, and
- overriding "loop" with "msdos" when appropriate.
Note that when the FAT variant is FAT12, "parted" fails to parse the
fake
MBR partition table completely (see RHBZ#2026220), which we cannot work
around. However, FAT12 should be a rare corner case in libguestfs usage --
"mkfs.fat" auto-chooses FAT12 only below 9MB disk size, and even
"-F 12"
can only be forced up to and including 255MB disk size.
Add the helper function "has_bogus_mbr" to the Utils module; we'll
use it
elsewhere too.
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1931821
Signed-off-by: Laszlo Ersek <lersek at redhat.com>
---
Notes:
v2:
- new patch (the hard one)
daemon/parted.ml | 7 ++++++-
daemon/utils.ml | 44 ++++++++++++++++++++++++++++++++++++++++++++
daemon/utils.mli | 10 ++++++++++
3 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/daemon/parted.ml b/daemon/parted.ml
index 29b98c4806c1..53077d91afdd 100644
--- a/daemon/parted.ml
+++ b/daemon/parted.ml
@@ -118,7 +118,12 @@ let part_get_parttype device let fields = String.nsplit
":" device_line in
match fields with
| _::_::_::_::_::"loop"::_ -> (* If "loop" return an
error (RHBZ#634246). *)
- failwithf "%s: not a partitioned device" device
+ (* ... Unless parted failed to recognize the fake MBR that mkfs.fat from
+ * dosfstools-4.2+ created. In that case, return "msdos" for MBR
+ * (RHBZ#1931821).
+ *)
+ if Utils.has_bogus_mbr device then "msdos"
+ else failwithf "%s: not a partitioned device" device
| _::_::_::_::_::ret::_ -> ret
| _ ->
failwithf "%s: cannot parse the output of parted" device
diff --git a/daemon/utils.ml b/daemon/utils.ml
index b56306055634..7ef3c206b71a 100644
--- a/daemon/utils.ml
+++ b/daemon/utils.ml
@@ -187,6 +187,50 @@ and compare_device_names a b )
)
+let has_bogus_mbr device + try
+ with_openfile device [O_RDONLY; O_CLOEXEC] 0 (fun fd ->
+ let sec0size = 0x200
+ and sigofs = 0x1FE
+ and sysidofs = 0x003 and sysidsize = 0x008
+ and pte1ofs = 0x1BE
+ and parttypes = [0x01; (* FAT12 *)
+ 0x04; (* FAT16 *)
+ 0x06; (* FAT12, FAT16, FAT16B *)
+ 0x0C; (* FAT32 LBA *)
+ 0x0E (* FAT16B LBA *)] in
+ let sec0 = Bytes.create sec0size in
+ let sec0read = Unix.read fd sec0 0 sec0size in
+
+ (* sector read completely *)
+ sec0read = sec0size &&
+
+ (* boot signature present *)
+ Bytes.get_uint8 sec0 (sigofs ) = 0x55 &&
+ Bytes.get_uint8 sec0 (sigofs + 0x1) = 0xAA &&
+
+ (* mkfs.fat signature present *)
+ Bytes.sub_string sec0 sysidofs sysidsize = "mkfs.fat"
&&
+
+ (* partition bootable *)
+ Bytes.get_uint8 sec0 (pte1ofs ) = 0x80 &&
+
+ (* partition starts at C/H/S 0/0/1 *)
+ Bytes.get_uint8 sec0 (pte1ofs + 0x1) = 0x00 &&
+ Bytes.get_uint8 sec0 (pte1ofs + 0x2) = 0x01 &&
+ Bytes.get_uint8 sec0 (pte1ofs + 0x3) = 0x00 &&
+
+ (* partition type is a FAT variant that mkfs.fat is known to create *)
+ List.mem (Bytes.get_uint8 sec0 (pte1ofs + 0x4)) parttypes &&
+
+ (* partition starts at LBA 0 *)
+ Bytes.get_uint8 sec0 (pte1ofs + 0x8) = 0x00 &&
+ Bytes.get_uint8 sec0 (pte1ofs + 0x9) = 0x00 &&
+ Bytes.get_uint8 sec0 (pte1ofs + 0xA) = 0x00 &&
+ Bytes.get_uint8 sec0 (pte1ofs + 0xB) = 0x00
+ )
+ with _ -> false
+
let proc_unmangle_path path let n = String.length path in
let b = Buffer.create n in
diff --git a/daemon/utils.mli b/daemon/utils.mli
index ec1127f7211e..d7b7aeba0657 100644
--- a/daemon/utils.mli
+++ b/daemon/utils.mli
@@ -56,6 +56,16 @@ val sort_device_names : string list -> string list
This also deals with partition numbers, and works whether or not
[/dev/] is present. *)
+val has_bogus_mbr : string -> bool
+(** Check whether the first sector of the device contains a bogus MBR partition
+ table; namely one where the first partition table entry describes a
+ partition that starts at absolute sector 0, thereby overlapping the
+ partition table itself.
+
+ dosfstools-4.2+ creates bogus partition tables like this by default when
+ formatting non-removable, non-partitioned block devices. Refer to
+ RHBZ#1931821. *)
+
val proc_unmangle_path : string -> string
(** Reverse kernel path escaping done in fs/seq_file.c:mangle_path.
This is inconsistently used for /proc fields. *)
--
2.19.1.3.g30247aa5d201
Laszlo Ersek
2021-Nov-24 10:37 UTC
[Libguestfs] [PATCH v2 5/5] daemon/listfs: don't call "sgdisk -i" on bogus MBR partition table entry
The "is_partition_can_hold_filesystem" function calls
"Parted.part_get_gpt_type" on the partition if:
- the partition table type is GPT,
- or the partition table type is MBR, and the partition is primary or
logical.
The one entry in the fake MBR partition table described in the previous
patch passes the second branch of this check, therefore
"Parted.part_get_gpt_type" is reached, and it invokes "sgdisk -i
1" on the
disk.
Surprisingly (not), while "sgdisk -i" copes fine with valid MBR
partition
tables, it chokes on the fake one. The output does not contain the
"Partition GUID code" line, and so
"sgdisk_info_extract_field" throws an
exception.
Prevent calling "Parted.part_get_gpt_type" on a bogus MBR partition
table,
similarly to the "extended entry in MBR partition table" case; the
difference is that the bogus primary entry, unlike a valid extended entry,
*can* hold a filesystem.
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1931821
Signed-off-by: Laszlo Ersek <lersek at redhat.com>
---
Notes:
v2:
- new patch (sigh)
daemon/listfs.ml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/daemon/listfs.ml b/daemon/listfs.ml
index d8add5005ade..63ced260a14d 100644
--- a/daemon/listfs.ml
+++ b/daemon/listfs.ml
@@ -109,6 +109,8 @@ and is_partition_can_hold_filesystem partition if
is_gpt_or_mbr then (
if is_mbr_extended parttype device partnum then
false
+ else if is_mbr_bogus parttype device partnum then
+ true
else (
(* MBR partition id will be converted into corresponding GPT type. *)
let gpt_type = Parted.part_get_gpt_type device partnum in
@@ -130,6 +132,9 @@ and is_mbr_extended parttype device partnum parttype =
"msdos" &&
Parted.part_get_mbr_part_type device partnum = "extended"
+and is_mbr_bogus parttype device partnum + parttype = "msdos"
&& partnum = 1 && Utils.has_bogus_mbr device
+
(* Use vfs-type to check for a filesystem of some sort of [device].
* Appends (device, vfs_type) to the ret parameter (there may be
* multiple devices found in the case of btrfs).
--
2.19.1.3.g30247aa5d201