diff mbox series

libblkid: avoid undefined shifts and an unaligned read in the probes

Message ID 20260921162945.32486-1-kokokoala4211@gmail.com
State New
Headers show
Series libblkid: avoid undefined shifts and an unaligned read in the probes | expand

Commit Message

Akira Patafio Sept. 21, 2026, 4:29 p.m. UTC
Three sites in lib/blkid/probe.c operate on values taken straight from a
superblock, which on any system that probes removable media is
attacker-supplied.

probe_jfs() uses js_l2bsize and js_l2pbsize as shift exponents on a
32-bit 1U.  Both are 16-bit on-disk fields, so any value of 32 or more
makes the shift undefined.  No such value can describe a valid 32-bit
block size, so reject the superblock rather than shift by it.

exfat_next_cluster() casts the result of get_buffer() to uint32_t * and
dereferences it.  get_buffer() returns a pointer into a byte buffer at an
offset computed from fat_block_start, so it carries no alignment
guarantee and the offset is under the volume's control; the dereference
is undefined when that offset is not 4-byte aligned.  Copy the bytes out
with memcpy() instead, which the compiler turns back into a load where
unaligned access is permitted.

Neither has a memory-safety consequence: the shifts only produce a wrong
comparison, and get_buffer() already bounds-checks the read it returns.
This is a correctness and undefined-behaviour fix.

Found with libFuzzer and UBSan (clang 18, aarch64) over a corpus of
NTFS/exFAT/JFS superblocks.  util-linux made equivalent changes to its
fork of this code in 585815c1f8f7 ("libblkid: exfat - avoid undefined
shift") and 9c82a8ca123a ("libblkid: ntfs: avoid UB in signed shift").

Signed-off-by: Akira Patafio <kokokoala4211@gmail.com>
---
 lib/blkid/probe.c | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)
diff mbox series

Patch

diff --git a/lib/blkid/probe.c b/lib/blkid/probe.c
index 6a3bb24..689ee6d 100644
--- a/lib/blkid/probe.c
+++ b/lib/blkid/probe.c
@@ -854,13 +854,16 @@  static int probe_jfs(struct blkid_probe *probe,
 {
 	struct jfs_super_block *js;
 	const char *label = 0;
+	uint16_t l2bsize, l2pbsize;
 
 	js = (struct jfs_super_block *)buf;
 
-	if (blkid_le32(js->js_bsize) != (1U << blkid_le16(js->js_l2bsize)))
+	l2bsize = blkid_le16(js->js_l2bsize);
+	if (l2bsize >= 32 || blkid_le32(js->js_bsize) != (1U << l2bsize))
 		return 1;
 
-	if (blkid_le32(js->js_pbsize) != (1U << blkid_le16(js->js_l2pbsize)))
+	l2pbsize = blkid_le16(js->js_l2pbsize);
+	if (l2pbsize >= 32 || blkid_le32(js->js_pbsize) != (1U << l2pbsize))
 		return 1;
 
 	if ((blkid_le16(js->js_l2bsize) - blkid_le16(js->js_l2pbsize)) !=
@@ -1461,14 +1464,19 @@  static uint32_t exfat_next_cluster(struct blkid_probe *probe,
                                    const struct exfat_super_block *sb,
                                    uint32_t cluster)
 {
-    uint32_t *next;
+    uint32_t next;
     uint64_t offset;
+    unsigned char *buf;
 
     offset = exfat_block_to_offset(sb, sb->fat_block_start)
             + (uint64_t) cluster * sizeof (cluster);
-    next = (uint32_t *)get_buffer(probe, offset, sizeof (uint32_t));
+    buf = get_buffer(probe, offset, sizeof (uint32_t));
+    if (!buf)
+        return 0;
 
-    return next ? *next : 0;
+    memcpy(&next, buf, sizeof(next));
+
+    return next;
 }
 
 static struct exfat_entry_label *find_exfat_entry_label(