fs/xfs: Add several fixes/improvements to xfs fs from upstream

Resolves: #2247926
Signed-off-by: Nicolas Frayer <nfrayer@redhat.com>
This commit is contained in:
Nicolas Frayer 2023-12-01 11:22:36 +01:00
parent c27e33d757
commit 6b9f32bc1e
8 changed files with 2175 additions and 1 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,47 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "t.feng" <fengtao40@huawei.com>
Date: Tue, 29 Nov 2022 17:14:15 +0800
Subject: [PATCH] fs/xfs: Fix memory leaks in XFS module
Signed-off-by: t.feng <fengtao40@huawei.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
grub-core/fs/xfs.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/grub-core/fs/xfs.c b/grub-core/fs/xfs.c
index d6de7f1a2dd2..b67407690c1a 100644
--- a/grub-core/fs/xfs.c
+++ b/grub-core/fs/xfs.c
@@ -585,7 +585,10 @@ grub_xfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
if (grub_disk_read (node->data->disk,
GRUB_XFS_FSB_TO_BLOCK (node->data, get_fsb (keys, i - 1 + recoffset)) << (node->data->sblock.log2_bsize - GRUB_DISK_SECTOR_BITS),
0, node->data->bsize, leaf))
- return 0;
+ {
+ grub_free (leaf);
+ return 0;
+ }
if ((!node->data->hascrc &&
grub_strncmp ((char *) leaf->magic, "BMAP", 4)) ||
@@ -751,6 +754,7 @@ static int iterate_dir_call_hook (grub_uint64_t ino, const char *filename,
if (err)
{
grub_print_error ();
+ grub_free (fdiro);
return 0;
}
@@ -861,7 +865,10 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir,
blk << dirblk_log2,
dirblk_size, dirblock, 0);
if (numread != dirblk_size)
- return 0;
+ {
+ grub_free (dirblock);
+ return 0;
+ }
entries = (grub_be_to_cpu32 (tail->leaf_count)
- grub_be_to_cpu32 (tail->leaf_stale));

View file

@ -0,0 +1,106 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Darren Kenny <darren.kenny@oracle.com>
Date: Fri, 2 Jun 2023 18:08:44 +0000
Subject: [PATCH] fs/xfs: Fix issues found while fuzzing the XFS filesystem
While performing fuzz testing with XFS filesystem images with ASAN
enabled, several issues were found where the memory accesses are made
beyond the data that is allocated into the struct grub_xfs_data
structure's data field.
The existing structure didn't store the size of the memory allocated into
the buffer in the data field and had no way to check it. To resolve these
issues, the data size is stored to enable checks into the data buffer.
With these checks in place, the fuzzing corpus no longer cause any crashes.
Signed-off-by: Darren Kenny <darren.kenny@oracle.com>
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
Signed-off-by: Marta Lewandowska <mlewando@redhat.com>
Signed-off-by: Lidong Chen <lidong.chen@oracle.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
---
grub-core/fs/xfs.c | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/grub-core/fs/xfs.c b/grub-core/fs/xfs.c
index b67407690c1a..b91cd32b49ab 100644
--- a/grub-core/fs/xfs.c
+++ b/grub-core/fs/xfs.c
@@ -239,6 +239,7 @@ struct grub_fshelp_node
struct grub_xfs_data
{
+ grub_size_t data_size;
struct grub_xfs_sblock sblock;
grub_disk_t disk;
int pos;
@@ -611,8 +612,20 @@ grub_xfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
}
else if (node->inode.format == XFS_INODE_FORMAT_EXT)
{
+ grub_addr_t exts_end = 0;
+ grub_addr_t data_end = 0;
+
nrec = grub_be_to_cpu32 (node->inode.nextents);
exts = (struct grub_xfs_extent *) grub_xfs_inode_data(&node->inode);
+
+ if (grub_mul (sizeof (struct grub_xfs_extent), nrec, &exts_end) ||
+ grub_add ((grub_addr_t) node->data, exts_end, &exts_end) ||
+ grub_add ((grub_addr_t) node->data, node->data->data_size, &data_end) ||
+ exts_end > data_end)
+ {
+ grub_error (GRUB_ERR_BAD_FS, "invalid number of XFS extents");
+ return 0;
+ }
}
else
{
@@ -803,6 +816,9 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir,
grub_uint8_t *inopos = grub_xfs_inline_de_inopos(dir->data, de);
grub_uint8_t c;
+ if ((inopos + (smallino ? 4 : 8)) > (grub_uint8_t *) dir + grub_xfs_fshelp_size (dir->data))
+ return grub_error (GRUB_ERR_BAD_FS, "not a correct XFS inode");
+
/* inopos might be unaligned. */
if (smallino)
ino = (((grub_uint32_t) inopos[0]) << 24)
@@ -829,6 +845,10 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir,
de->name[de->len] = c;
de = grub_xfs_inline_next_de(dir->data, head, de);
+
+ if ((grub_uint8_t *) de >= (grub_uint8_t *) dir + grub_xfs_fshelp_size (dir->data))
+ return grub_error (GRUB_ERR_BAD_FS, "invalid XFS directory entry");
+
}
break;
}
@@ -897,6 +917,9 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir,
}
filename = (char *)(direntry + 1);
+ if (filename + direntry->len - 1 > (char *) tail)
+ return grub_error (GRUB_ERR_BAD_FS, "invalid XFS directory entry");
+
/* The byte after the filename is for the filetype, padding, or
tag, which is not used by GRUB. So it can be overwritten. */
filename[direntry->len] = '\0';
@@ -941,6 +964,8 @@ grub_xfs_mount (grub_disk_t disk)
if (!data)
return 0;
+ data->data_size = sizeof (struct grub_xfs_data);
+
grub_dprintf("xfs", "Reading sb\n");
/* Read the superblock. */
if (grub_disk_read (disk, 0, 0,
@@ -962,6 +987,7 @@ grub_xfs_mount (grub_disk_t disk)
if (! data)
goto fail;
+ data->data_size = sz;
data->diropen.data = data;
data->diropen.ino = grub_be_to_cpu64(data->sblock.rootino);
data->diropen.inode_read = 1;

View file

@ -0,0 +1,47 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Lidong Chen <lidong.chen@oracle.com>
Date: Thu, 28 Sep 2023 22:33:44 +0000
Subject: [PATCH] fs/xfs: Incorrect short form directory data boundary check
After parsing of the current entry, the entry pointer is advanced
to the next entry at the end of the "for" loop. In case where the
last entry is at the end of the data boundary, the advanced entry
pointer can point off the data boundary. The subsequent boundary
check for the advanced entry pointer can cause a failure.
The fix is to include the boundary check into the "for" loop
condition.
Signed-off-by: Lidong Chen <lidong.chen@oracle.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Tested-by: Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
Tested-by: Marta Lewandowska <mlewando@redhat.com>
---
grub-core/fs/xfs.c | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/grub-core/fs/xfs.c b/grub-core/fs/xfs.c
index b91cd32b49ab..ebf962793fa7 100644
--- a/grub-core/fs/xfs.c
+++ b/grub-core/fs/xfs.c
@@ -810,7 +810,8 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir,
if (iterate_dir_call_hook (parent, "..", &ctx))
return 1;
- for (i = 0; i < head->count; i++)
+ for (i = 0; i < head->count &&
+ (grub_uint8_t *) de < ((grub_uint8_t *) dir + grub_xfs_fshelp_size (dir->data)); i++)
{
grub_uint64_t ino;
grub_uint8_t *inopos = grub_xfs_inline_de_inopos(dir->data, de);
@@ -845,10 +846,6 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir,
de->name[de->len] = c;
de = grub_xfs_inline_next_de(dir->data, head, de);
-
- if ((grub_uint8_t *) de >= (grub_uint8_t *) dir + grub_xfs_fshelp_size (dir->data))
- return grub_error (GRUB_ERR_BAD_FS, "invalid XFS directory entry");
-
}
break;
}

View file

@ -0,0 +1,168 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jon DeVree <nuxi@vault24.org>
Date: Tue, 17 Oct 2023 23:03:47 -0400
Subject: [PATCH] fs/xfs: Fix XFS directory extent parsing
The XFS directory entry parsing code has never been completely correct
for extent based directories. The parser correctly handles the case
where the directory is contained in a single extent, but then mistakenly
assumes the data blocks for the multiple extent case are each identical
to the single extent case. The difference in the format of the data
blocks between the two cases is tiny enough that its gone unnoticed for
a very long time.
A recent change introduced some additional bounds checking into the XFS
parser. Like GRUB's existing parser, it is correct for the single extent
case but incorrect for the multiple extent case. When parsing a directory
with multiple extents, this new bounds checking is sometimes (but not
always) tripped and triggers an "invalid XFS directory entry" error. This
probably would have continued to go unnoticed but the /boot/grub/<arch>
directory is large enough that it often has multiple extents.
The difference between the two cases is that when there are multiple
extents, the data blocks do not contain a trailer nor do they contain
any leaf information. That information is stored in a separate set of
extents dedicated to just the leaf information. These extents come after
the directory entry extents and are not included in the inode size. So
the existing parser already ignores the leaf extents.
The only reason to read the trailer/leaf information at all is so that
the parser can avoid misinterpreting that data as directory entries. So
this updates the parser as follows:
For the single extent case the parser doesn't change much:
1. Read the size of the leaf information from the trailer
2. Set the end pointer for the parser to the start of the leaf
information. (The previous bounds checking set the end pointer to the
start of the trailer, so this is actually a small improvement.)
3. Set the entries variable to the expected number of directory entries.
For the multiple extent case:
1. Set the end pointer to the end of the block.
2. Do not set up the entries variable. Figuring out how many entries are
in each individual block is complex and does not seem worth it when
it appears to be safe to just iterate over the entire block.
The bounds check itself was also dependent upon the faulty XFS parser
because it accidentally used "filename + length - 1". Presumably this
was able to pass the fuzzer because in the old parser there was always
8 bytes of slack space between the tail pointer and the actual end of
the block. Since this is no longer the case the bounds check needs to be
updated to "filename + length + 1" in order to prevent a regression in
the handling of corrupt fliesystems.
Notes:
* When there is only one extent there will only ever be one block. If
more than one block is required then XFS will always switch to holding
leaf information in a separate extent.
* B-tree based directories seems to be parsed properly by the same code
that handles multiple extents. This is unlikely to ever occur within
/boot though because its only used when there are an extremely large
number of directory entries.
Fixes: ef7850c75 (fs/xfs: Fix issues found while fuzzing the XFS filesystem)
Fixes: b2499b29c (Adds support for the XFS filesystem.)
Fixes: https://savannah.gnu.org/bugs/?64376
Signed-off-by: Jon DeVree <nuxi@vault24.org>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Tested-by: Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
Tested-by: Marta Lewandowska <mlewando@redhat.com>
---
grub-core/fs/xfs.c | 52 ++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 38 insertions(+), 14 deletions(-)
diff --git a/grub-core/fs/xfs.c b/grub-core/fs/xfs.c
index ebf962793fa7..18edfcff486c 100644
--- a/grub-core/fs/xfs.c
+++ b/grub-core/fs/xfs.c
@@ -223,6 +223,12 @@ struct grub_xfs_inode
/* Size of struct grub_xfs_inode v2, up to unused4 member included. */
#define XFS_V2_INODE_SIZE (XFS_V3_INODE_SIZE - 76)
+struct grub_xfs_dir_leaf_entry
+{
+ grub_uint32_t hashval;
+ grub_uint32_t address;
+} GRUB_PACKED;
+
struct grub_xfs_dirblock_tail
{
grub_uint32_t leaf_count;
@@ -874,9 +880,8 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir,
{
struct grub_xfs_dir2_entry *direntry =
grub_xfs_first_de(dir->data, dirblock);
- int entries;
- struct grub_xfs_dirblock_tail *tail =
- grub_xfs_dir_tail(dir->data, dirblock);
+ int entries = -1;
+ char *end = dirblock + dirblk_size;
numread = grub_xfs_read_file (dir, 0, 0,
blk << dirblk_log2,
@@ -887,14 +892,27 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir,
return 0;
}
- entries = (grub_be_to_cpu32 (tail->leaf_count)
- - grub_be_to_cpu32 (tail->leaf_stale));
+ /*
+ * Leaf and tail information are only in the data block if the number
+ * of extents is 1.
+ */
+ if (dir->inode.nextents == grub_cpu_to_be32_compile_time (1))
+ {
+ struct grub_xfs_dirblock_tail *tail = grub_xfs_dir_tail (dir->data, dirblock);
- if (!entries)
- continue;
+ end = (char *) tail;
+
+ /* Subtract the space used by leaf nodes. */
+ end -= grub_be_to_cpu32 (tail->leaf_count) * sizeof (struct grub_xfs_dir_leaf_entry);
+
+ entries = grub_be_to_cpu32 (tail->leaf_count) - grub_be_to_cpu32 (tail->leaf_stale);
+
+ if (!entries)
+ continue;
+ }
/* Iterate over all entries within this block. */
- while ((char *)direntry < (char *)tail)
+ while ((char *) direntry < (char *) end)
{
grub_uint8_t *freetag;
char *filename;
@@ -914,7 +932,7 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir,
}
filename = (char *)(direntry + 1);
- if (filename + direntry->len - 1 > (char *) tail)
+ if (filename + direntry->len + 1 > (char *) end)
return grub_error (GRUB_ERR_BAD_FS, "invalid XFS directory entry");
/* The byte after the filename is for the filetype, padding, or
@@ -928,11 +946,17 @@ grub_xfs_iterate_dir (grub_fshelp_node_t dir,
return 1;
}
- /* Check if last direntry in this block is
- reached. */
- entries--;
- if (!entries)
- break;
+ /*
+ * The expected number of directory entries is only tracked for the
+ * single extent case.
+ */
+ if (dir->inode.nextents == grub_cpu_to_be32_compile_time (1))
+ {
+ /* Check if last direntry in this block is reached. */
+ entries--;
+ if (!entries)
+ break;
+ }
/* Select the next directory entry. */
direntry = grub_xfs_next_de(dir->data, direntry);

View file

@ -0,0 +1,115 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Anthony Iliopoulos <ailiop@suse.com>
Date: Thu, 26 Oct 2023 11:53:39 +0200
Subject: [PATCH] fs/xfs: Add large extent counters incompat feature support
XFS introduced 64-bit extent counters for inodes via a series of
upstream commits and the feature was marked as stable in v6.5 via
commit 61d7e8274cd8 (xfs: drop EXPERIMENTAL tag for large extent
counts).
Further, xfsprogs release v6.5.0 switched this feature on by default
in mkfs.xfs via commit e5b18d7d1d96 (mkfs: enable large extent counts
by default).
Filesystems formatted with large extent count support, nrext64=1, are
thus currently not recognizable by GRUB, since this is an incompat
feature. Add the required support so that those filesystems and inodes
with large extent counters can be read by GRUB.
Signed-off-by: Anthony Iliopoulos <ailiop@suse.com>
Reviewed-by: Andrey Albershteyn <aalbersh@redhat.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
Tested-by: Marta Lewandowska <mlewando@redhat.com>
Tested-by: Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
---
grub-core/fs/xfs.c | 30 +++++++++++++++++++++++++-----
1 file changed, 25 insertions(+), 5 deletions(-)
diff --git a/grub-core/fs/xfs.c b/grub-core/fs/xfs.c
index 18edfcff486c..bc2224dbb463 100644
--- a/grub-core/fs/xfs.c
+++ b/grub-core/fs/xfs.c
@@ -79,6 +79,8 @@ GRUB_MOD_LICENSE ("GPLv3+");
/* Inode flags2 flags */
#define XFS_DIFLAG2_BIGTIME_BIT 3
#define XFS_DIFLAG2_BIGTIME (1 << XFS_DIFLAG2_BIGTIME_BIT)
+#define XFS_DIFLAG2_NREXT64_BIT 4
+#define XFS_DIFLAG2_NREXT64 (1 << XFS_DIFLAG2_NREXT64_BIT)
/* incompat feature flags */
#define XFS_SB_FEAT_INCOMPAT_FTYPE (1 << 0) /* filetype in dirent */
@@ -86,6 +88,7 @@ GRUB_MOD_LICENSE ("GPLv3+");
#define XFS_SB_FEAT_INCOMPAT_META_UUID (1 << 2) /* metadata UUID */
#define XFS_SB_FEAT_INCOMPAT_BIGTIME (1 << 3) /* large timestamps */
#define XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR (1 << 4) /* needs xfs_repair */
+#define XFS_SB_FEAT_INCOMPAT_NREXT64 (1 << 5) /* large extent counters */
/*
* Directory entries with ftype are explicitly handled by GRUB code.
@@ -101,7 +104,8 @@ GRUB_MOD_LICENSE ("GPLv3+");
XFS_SB_FEAT_INCOMPAT_SPINODES | \
XFS_SB_FEAT_INCOMPAT_META_UUID | \
XFS_SB_FEAT_INCOMPAT_BIGTIME | \
- XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR)
+ XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR | \
+ XFS_SB_FEAT_INCOMPAT_NREXT64)
struct grub_xfs_sblock
{
@@ -203,7 +207,8 @@ struct grub_xfs_inode
grub_uint16_t mode;
grub_uint8_t version;
grub_uint8_t format;
- grub_uint8_t unused2[26];
+ grub_uint8_t unused2[18];
+ grub_uint64_t nextents_big;
grub_uint64_t atime;
grub_uint64_t mtime;
grub_uint64_t ctime;
@@ -545,11 +550,26 @@ get_fsb (const void *keys, int idx)
return grub_be_to_cpu64 (grub_get_unaligned64 (p));
}
+static int
+grub_xfs_inode_has_large_extent_counts (const struct grub_xfs_inode *inode)
+{
+ return inode->version >= 3 &&
+ (inode->flags2 & grub_cpu_to_be64_compile_time (XFS_DIFLAG2_NREXT64));
+}
+
+static grub_uint64_t
+grub_xfs_get_inode_nextents (struct grub_xfs_inode *inode)
+{
+ return (grub_xfs_inode_has_large_extent_counts (inode)) ?
+ grub_be_to_cpu64 (inode->nextents_big) :
+ grub_be_to_cpu32 (inode->nextents);
+}
+
static grub_disk_addr_t
grub_xfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
{
struct grub_xfs_btree_node *leaf = 0;
- int ex, nrec;
+ grub_uint64_t ex, nrec;
struct grub_xfs_extent *exts;
grub_uint64_t ret = 0;
@@ -574,7 +594,7 @@ grub_xfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
/ (2 * sizeof (grub_uint64_t));
do
{
- int i;
+ grub_uint64_t i;
for (i = 0; i < nrec; i++)
{
@@ -621,7 +641,7 @@ grub_xfs_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock)
grub_addr_t exts_end = 0;
grub_addr_t data_end = 0;
- nrec = grub_be_to_cpu32 (node->inode.nextents);
+ nrec = grub_xfs_get_inode_nextents (&node->inode);
exts = (struct grub_xfs_extent *) grub_xfs_inode_data(&node->inode);
if (grub_mul (sizeof (struct grub_xfs_extent), nrec, &exts_end) ||

View file

@ -338,3 +338,9 @@ Patch0337: 0337-Fix-missing-include-in-ofdisk.c.patch
Patch0338: 0338-kern-ieee1275-init-ppc64-Restrict-high-memory-in-pre.patch
Patch0339: 0339-grub-install-on-EFI-if-forced.patch
Patch0340: 0340-Remove-Install-section-from-aux-systemd-units.patch
Patch0341: 0341-fs-Remove-trailing-whitespaces.patch
Patch0342: 0342-fs-xfs-Fix-memory-leaks-in-XFS-module.patch
Patch0343: 0343-fs-xfs-Fix-issues-found-while-fuzzing-the-XFS-filesy.patch
Patch0344: 0344-fs-xfs-Incorrect-short-form-directory-data-boundary-.patch
Patch0345: 0345-fs-xfs-Fix-XFS-directory-extent-parsing.patch
Patch0346: 0346-fs-xfs-Add-large-extent-counters-incompat-feature-su.patch

View file

@ -17,7 +17,7 @@
Name: grub2
Epoch: 1
Version: 2.06
Release: 109%{?dist}
Release: 110%{?dist}
Summary: Bootloader with support for Linux, Multiboot and more
License: GPLv3+
URL: http://www.gnu.org/software/grub/
@ -555,6 +555,10 @@ mv ${EFI_HOME}/grub.cfg.stb ${EFI_HOME}/grub.cfg
%endif
%changelog
* Fri Dec 1 2023 Nicolas Frayer <nfrayer@redhat.com> - 2.06-110
- fs/xfs: Add several fixes/improvements to xfs fs from upstream
- Resolves: #2247926
* Wed Nov 15 2023 Nicolas Frayer <nfrayer@redhat.com> - 2.06-109
- Linker: added --no-warn-rwx-segments as build will fail after
ld.bfd default options have been changed.