mail archive of the barebox mailing list
 help / color / mirror / Atom feed
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




  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