From: Tobias Waldekranz <tobias@waldekranz.com>
To: barebox@lists.infradead.org
Subject: [PATCH v2 next 2/5] dm: lvm: Add fuzz testers for metadata parser and binary headers
Date: Fri, 25 Sep 2026 10:54:24 +0000 [thread overview]
Message-ID: <20260925105510.1431927-3-tobias@waldekranz.com> (raw)
In-Reply-To: <20260925105510.1431927-1-tobias@waldekranz.com>
For the header parser, we help the fuzzer along by ensuring enough of
the basic structure is in place that allows it to reach deeper into
the code, rather than immediately bailing out on missing magic values
or incorrect CRCs.
Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
drivers/block/dm/lvm/Makefile | 1 +
drivers/block/dm/lvm/lvm-fuzz.c | 182 ++++++++++++++++++++++++++++++++
images/Makefile.sandbox | 2 +
test/testdata/fuzz/lvm-md.dict | 29 +++++
test/testdata/fuzz/lvm.dict | 30 ++++++
5 files changed, 244 insertions(+)
create mode 100644 drivers/block/dm/lvm/lvm-fuzz.c
create mode 100644 test/testdata/fuzz/lvm-md.dict
create mode 100644 test/testdata/fuzz/lvm.dict
diff --git a/drivers/block/dm/lvm/Makefile b/drivers/block/dm/lvm/Makefile
index b85f2714d1..b0be22db20 100644
--- a/drivers/block/dm/lvm/Makefile
+++ b/drivers/block/dm/lvm/Makefile
@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += lvm-core.o lvm-md.o
+obj-$(CONFIG_FUZZ) += lvm-fuzz.o
diff --git a/drivers/block/dm/lvm/lvm-fuzz.c b/drivers/block/dm/lvm/lvm-fuzz.c
new file mode 100644
index 0000000000..3eaec8c14b
--- /dev/null
+++ b/drivers/block/dm/lvm/lvm-fuzz.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-FileCopyrightText: 2026 Tobias Waldekranz <tobias@waldekranz.com>
+
+#include <block.h>
+#include <fuzz.h>
+#include <lvm.h>
+
+#include "lvm2.h"
+#include "lvm-md.h"
+
+static int fuzz_lvm_md(const char *text, size_t size)
+{
+ struct lvm_md *md;
+
+ if (!lvm_md_parse_alloc(text, size, &md))
+ lvm_md_free(md);
+
+ return 0;
+}
+fuzz_test_str("lvm-md", fuzz_lvm_md);
+
+static void lvm_fuzz_fixup_mda(u8 *img, size_t size, u64 mda_offset, u64 mda_size)
+{
+ struct lvm2_md_header *hdr;
+ struct lvm2_md_area *area;
+ u64 off, len, wrap;
+ u8 *text;
+
+ if (mda_offset >= size || mda_size < SECTOR_SIZE ||
+ mda_size > size - mda_offset)
+ return;
+
+ hdr = (void *)(img + mda_offset);
+ memcpy(hdr->magic, LVM2_MDA_MAGIC, sizeof(hdr->magic));
+ put_unaligned_le32(LVM2_MDA_VERSION, &hdr->version);
+
+ for (area = hdr->area;
+ (u8 *)(area + 1) <= (u8 *)hdr + SECTOR_SIZE; area++) {
+ off = get_unaligned_le64(&area->offset);
+ len = get_unaligned_le64(&area->size);
+
+ if (!off && !len)
+ break;
+ if (!len || off >= mda_size || len > mda_size)
+ continue;
+
+ text = malloc(len);
+ if (!text)
+ return;
+
+ if (off + len > mda_size) {
+ wrap = mda_size - off;
+
+ if (SECTOR_SIZE + (len - wrap) > mda_size)
+ goto next;
+
+ memcpy(text, img + mda_offset + off, wrap);
+ memcpy(text + wrap,
+ img + mda_offset + SECTOR_SIZE,
+ len - wrap);
+ } else {
+ memcpy(text, img + mda_offset + off, len);
+ }
+
+ put_unaligned_le32(lvm2_crc(text, len), &area->checksum);
+next:
+ free(text);
+ }
+
+ /* Last, as it covers the descriptors fixed up above. */
+ put_unaligned_le32(lvm2_crc(hdr->magic,
+ SECTOR_SIZE
+ - offsetof(struct lvm2_md_header, magic)),
+ &hdr->checksum);
+}
+
+static void lvm_fuzz_fixup_image(u8 *img, size_t size)
+{
+ struct lvm2_pv_header *pvh;
+ struct lvm2_label *label;
+ struct lvm2_area *area;
+ u8 *sector = NULL;
+ u32 pv_offset;
+ int s;
+
+ for (s = 0; s < LVM2_LABEL_SCAN_SECTORS; s++) {
+ if ((size_t)(s + 1) << SECTOR_SHIFT > size)
+ break;
+
+ label = (void *)(img + ((size_t)s << SECTOR_SHIFT));
+ if (!memcmp(label->id, LVM2_LABEL_ID, sizeof(label->id))) {
+ sector = (u8 *)label;
+ break;
+ }
+ }
+
+ /* Without a label there is nothing to find, so plant one in the
+ * sector that LVM uses by default, leaving the rest of it as it
+ * is.
+ */
+ if (!sector) {
+ if (size < 2 * SECTOR_SIZE)
+ return;
+
+ s = 1;
+ sector = img + SECTOR_SIZE;
+ label = (void *)sector;
+ memcpy(label->id, LVM2_LABEL_ID, sizeof(label->id));
+ }
+
+ memcpy(label->type, LVM2_LABEL_TYPE, sizeof(label->type));
+ put_unaligned_le64(s, &label->sector);
+
+ pv_offset = get_unaligned_le32(&label->pv_offset);
+ if (pv_offset < SECTOR_SIZE) {
+ pvh = (void *)(sector + pv_offset);
+
+ /* Same walk as lvm_pv_probe(): past the data areas, past
+ * the zero separator, then one fixup per metadata area.
+ */
+ for (area = pvh->area;
+ (u8 *)(area + 1) <= sector + SECTOR_SIZE
+ && get_unaligned_le64(&area->offset);
+ area++)
+ ;
+
+ for (area++;
+ (u8 *)(area + 1) <= sector + SECTOR_SIZE
+ && get_unaligned_le64(&area->offset);
+ area++)
+ lvm_fuzz_fixup_mda(img, size,
+ get_unaligned_le64(&area->offset),
+ get_unaligned_le64(&area->size));
+ }
+
+ put_unaligned_le32(lvm2_crc(&label->pv_offset,
+ SECTOR_SIZE - offsetof(struct lvm2_label, pv_offset)),
+ &label->crc);
+}
+
+static int fuzz_lvm(const u8 *data, size_t size)
+{
+ static struct ramdisk *ramdisk;
+ struct block_device *blk;
+ struct lvm_vg *vg;
+ char *table;
+ size_t i;
+ u8 *img;
+
+ if (size < 2 * SECTOR_SIZE)
+ return 0;
+
+ if (!ramdisk)
+ ramdisk = ramdisk_init(SECTOR_SIZE);
+ if (!ramdisk)
+ return -ENODEV;
+
+ img = xmemdup(data, size);
+
+ /* Help the fuzzer out by injecting a proper LVM label and
+ * valid CRCs, so it can reach further into the parser.
+ */
+ lvm_fuzz_fixup_image(img, size);
+
+ ramdisk_setup_rw(ramdisk, img, size);
+ blk = ramdisk_get_block_device(ramdisk);
+
+ if (!lvm_vg_alloc_by_cdev(&blk->cdev, &vg)) {
+ for (i = 0; i < vg->num_lvs; i++) {
+ table = lvm_lv_dm_ctable(vg->lvs[i]);
+ if (!IS_ERR(table))
+ free(table);
+ }
+
+ lvm_vg_free(vg);
+ }
+
+ ramdisk_setup_rw(ramdisk, NULL, 0);
+ free(img);
+ return 0;
+}
+fuzz_test("lvm", fuzz_lvm);
diff --git a/images/Makefile.sandbox b/images/Makefile.sandbox
index d0dc503fd0..0419fb8e18 100644
--- a/images/Makefile.sandbox
+++ b/images/Makefile.sandbox
@@ -16,6 +16,8 @@ fuzzer-$(CONFIG_EFI_PE_PARSER) += pe
fuzzer-$(CONFIG_TLV) += tlv
fuzzer-$(CONFIG_FS_FAT) += fat
fuzzer-$(CONFIG_STATE) += state-direct
+fuzzer-$(CONFIG_DM_LVM) += lvm
+fuzzer-$(CONFIG_DM_LVM) += lvm-md
ifeq ($(CONFIG_SANDBOX),y)
diff --git a/test/testdata/fuzz/lvm-md.dict b/test/testdata/fuzz/lvm-md.dict
new file mode 100644
index 0000000000..40b378e7f8
--- /dev/null
+++ b/test/testdata/fuzz/lvm-md.dict
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# libfuzzer dictionary for the "lvm-md" fuzzer, which exercises the
+# LVM2 metadata tokenizer. Pass it with -dict=, e.g.
+#
+# images/fuzz-lvm-md -dict=test/testdata/fuzz/lvm-md.dict
+
+# Separators
+# (libfuzzer only supports escaping backslash, quote and xAB)
+"#"
+" "
+"\x09"
+"\x0a"
+"\x0d"
+"="
+"{"
+"}"
+"["
+"]"
+","
+"\""
+
+"sec_name {"
+"prim"
+"complex+prim.0"
+"\"string literal\""
+"\"FLAG\""
+"\"unterminated"
+"unopened\""
diff --git a/test/testdata/fuzz/lvm.dict b/test/testdata/fuzz/lvm.dict
new file mode 100644
index 0000000000..7be7d04574
--- /dev/null
+++ b/test/testdata/fuzz/lvm.dict
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# libfuzzer dictionary for the "lvm" fuzzer, which exercises the LVM2
+# header parser. Pass it with -dict=, e.g.
+#
+# images/fuzz-lvm -dict=test/testdata/fuzz/lvm.dict
+
+"LABELONE"
+"LVM2 001"
+" LVM2 x[5A%r0N*>"
+
+# Small le32 constants
+"\x00\x00\x00\x00"
+"\x01\x00\x00\x00"
+"\x02\x00\x00\x00"
+"\x04\x00\x00\x00"
+"\x08\x00\x00\x00"
+"\x10\x00\x00\x00"
+"\x40\x00\x00\x00"
+"\x80\x00\x00\x00"
+
+# Small le64 constants
+"\x00\x00\x00\x00\x00\x00\x00\x00"
+"\x01\x00\x00\x00\x00\x00\x00\x00"
+"\x02\x00\x00\x00\x00\x00\x00\x00"
+"\x04\x00\x00\x00\x00\x00\x00\x00"
+"\x08\x00\x00\x00\x00\x00\x00\x00"
+"\x10\x00\x00\x00\x00\x00\x00\x00"
+"\x40\x00\x00\x00\x00\x00\x00\x00"
+"\x80\x00\x00\x00\x00\x00\x00\x00"
--
2.43.0
next prev parent reply other threads:[~2026-09-25 10:57 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-25 10:54 [PATCH v2 next 0/5] dm: lvm: Initial Logical Volume Management support Tobias Waldekranz
2026-09-25 10:54 ` [PATCH v2 next 1/5] " Tobias Waldekranz
2026-09-25 10:54 ` Tobias Waldekranz [this message]
2026-09-25 10:54 ` [PATCH v2 next 3/5] test: self: lvm: Add tests for metadata parser and binary headers Tobias Waldekranz
2026-09-25 10:54 ` [PATCH v2 next 4/5] commands: lvm: inspect VGs, activate LVs Tobias Waldekranz
2026-09-25 10:54 ` [PATCH v2 next 5/5] test: py: lvm: Add basic LV activation test Tobias Waldekranz
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260925105510.1431927-3-tobias@waldekranz.com \
--to=tobias@waldekranz.com \
--cc=barebox@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox