Hi Andrew,

Happy to. Short version: ast2600-evb can't hit the SMP timing window,
so I reproduce each missing piece deliberately. The driver code under
test is unmodified -- only the stimulus and the post-race state are
injected. Stock qemu-system-arm (Debian 8.2.2), no QEMU changes.

Three obstacles, and what I did about each:

1. No BIOS to emit POST codes -- an injection module stages bytes into
   the snoop registers via the LPC syscon regmap (SNPWDR + the HICR6
   data-ready bit).

2. QEMU doesn't raise the snoop IRQ for those writes -- after staging,
   the module dispatches it in software with
   generic_handle_irq_safe(sdev->irq), which runs the driver's real
   aspeed_lpc_snoop_irq() -> put_fifo_with_discard() path.

3. The SMP race won't trigger under TCG -- so I reconstruct its outcome
   instead: force the channel-0 kfifo to in=4097, out=1, i.e.
   (in - out) = 4096 > the 2048-byte ring, the exact state a reader
   observes inside the window.

One caveat so it isn't misread: step 3 writes in/out directly, so it
bypasses the new lock. The patched run therefore shows the read path no
longer turns a corrupt (in - out) into a usercopy overflow; the lock's
job of preventing that state is the SPSC argument from the commit, which
TCG can't exercise.

== Tree / config ==

  base:  v7.1-rc7
  clang: 22.1.7 (LLVM=-22; needed for the context-analysis check)
  ARM multi_v7_defconfig + CONFIG_CC_IS_CLANG, WARN_CONTEXT_ANALYSIS,
  SMP, ASPEED_LPC_SNOOP, HARDENED_USERCOPY, PROVE_LOCKING,
  DEBUG_ATOMIC_SLEEP.

  make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- LLVM=-22 O=build \
       -j$(nproc) zImage

== Run ==

The injection module (full source below the sign-off) creates two
write-only sysfs knobs under /sys/kernel/snoop_test/. The init loads it,
then:

  echo 4096 > /sys/kernel/snoop_test/generate    # fill via the real
                                                  # IRQ path 
  echo 1    > /sys/kernel/snoop_test/adjust_ptrs  # force in=4097/out=1
                                                  
  read(fd, buf, 4096) from /dev/aspeed-lpc-snoop0 # the overflowing read

Build it against the same tree (-DSNOOP_PATCHED for v6, which mirrors the
spinlock the fix adds ahead of @fifo; omit it for the unpatched build).

  qemu-system-arm -M ast2600-evb -smp 2 \
    -kernel build/arch/arm/boot/zImage \
    -dtb build/arch/arm/boot/dts/aspeed/aspeed-ast2600-evb.dtb \
    -initrd repro.cpio.gz \
    -append "console=ttyS4,115200 panic=-1" -nographic -no-reboot

== Result ==

Unpatched, read(4096) with in=4097/out=1:

  usercopy: Kernel memory exposure attempt detected from SLUB object
  'kmalloc-2k' (offset 0, size 2049)!
   kfifo_copy_to_user / __kfifo_to_user / snoop_file_read / vfs_read
  Kernel panic - not syncing: Fatal exception

Patched: read() returns 2048, no panic; no lockdep or atomic-sleep
splats.

The init is just: mount proc/sysfs/devtmpfs, finit_module() the .ko,
write the two knobs above, then read(4096) from the char device. Full
injection module follows.

Thanks,
Karthikeyan

------ snoop_test_inject.c (build as an out-of-tree module) ------

// SPDX-License-Identifier: GPL-2.0
/*
 * Reproduce the aspeed-lpc-snoop kfifo SPSC-violation post-race state
 * deterministically under QEMU. Two write-only sysfs knobs under
 * /sys/kernel/snoop_test/:
 *
 *   generate <count>  Push <count> POST-code bytes through the *real*
 *                     driver IRQ path: write SNPWDR + HICR6 via the LPC
 *                     syscon regmap, then dispatch the snoop IRQ so
 *                     aspeed_lpc_snoop_irq() -> put_fifo_with_discard()
 *                     runs.
 *
 *   adjust_ptrs <1>   Force the channel-0 kfifo into the state a reader
 *                     observes inside the race window: in = 4097,
 *                     out = 1, so (in - out) = 4096 > the 2048-byte ring.
 *
 * The driver's private channel is reached through a mirror struct whose
 * layout must match drivers/soc/aspeed/aspeed-lpc-snoop.c. The v6 fix
 * inserts a spinlock_t ahead of @fifo -- build with -DSNOOP_PATCHED to
 * mirror that, otherwise the &fifo offset is wrong.
 */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/kfifo.h>
#include <linux/miscdevice.h>
#include <linux/regmap.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdesc.h>
#include <linux/device.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/wait.h>
#include <linux/spinlock.h>
#include <linux/bitops.h>

#define HICR6                   0x84
#define HICR6_STR_SNP0W         BIT(0)
#define SNPWDR                  0x94
#define SNOOP_DEV               "/dev/aspeed-lpc-snoop0"
#define RACE_OUT                1u
#define RACE_IN                 4097u

struct snoop_chan_mirror {
        const void              *cfg;
        bool                    enabled;
#ifdef SNOOP_PATCHED
        spinlock_t              lock;           /* added by the fix */
#endif
        struct kfifo            fifo;
        wait_queue_head_t       wq;
        struct miscdevice       miscdev;
};

struct snoop_dev_mirror {
        struct regmap           *regmap;
        int                     irq;
        /* struct clk *clk; struct aspeed_lpc_snoop_channel chan[2]; follow */
};

static struct file *snoop_open(struct snoop_chan_mirror **chan_out)
{
        struct file *filp;
        struct miscdevice *md;

        filp = filp_open(SNOOP_DEV, O_RDONLY | O_NONBLOCK, 0);
        if (IS_ERR(filp))
                return filp;

        md = filp->private_data;
        if (!md) {
                filp_close(filp, NULL);
                return ERR_PTR(-ENODEV);
        }

        *chan_out = container_of(md, struct snoop_chan_mirror, miscdev);
        return filp;
}

static ssize_t generate_store(struct kobject *kobj, struct kobj_attribute *attr,
                              const char *buf, size_t len)
{
        struct snoop_chan_mirror *chan;
        struct snoop_dev_mirror *sdev;
        struct file *filp;
        unsigned int count, i;
        int rc;

        rc = kstrtouint(buf, 0, &count);
        if (rc)
                return rc;

        filp = snoop_open(&chan);
        if (IS_ERR(filp))
                return PTR_ERR(filp);

        sdev = dev_get_drvdata(chan->miscdev.parent);
        if (!sdev || !sdev->regmap || sdev->irq <= 0) {
                filp_close(filp, NULL);
                return -ENODEV;
        }

        for (i = 0; i < count; i++) {
                /* Stage the snoop'ed byte and the data-ready status bit. */
                regmap_write(sdev->regmap, SNPWDR, (u32)(i & 0xff));
                regmap_write(sdev->regmap, HICR6, HICR6_STR_SNP0W);

                /*
                 * Dispatch IRQ -> aspeed_lpc_snoop_irq() ->
                 * put_fifo_with_discard(). generic_handle_irq_safe() copes
                 * with the GIC requiring the handler to run with IRQs off.
                 */
                generic_handle_irq_safe(sdev->irq);
        }

        filp_close(filp, NULL);
        return len;
}

static ssize_t adjust_ptrs_store(struct kobject *kobj, struct kobj_attribute 
*attr,
                                 const char *buf, size_t len)
{
        struct snoop_chan_mirror *chan;
        struct file *filp;
        struct __kfifo *kf;
        unsigned int val;
        int rc;

        rc = kstrtouint(buf, 0, &val);
        if (rc)
                return rc;
        if (val != 1)
                return -EINVAL;

        filp = snoop_open(&chan);
        if (IS_ERR(filp))
                return PTR_ERR(filp);

        kf = &chan->fifo.kfifo;
        /* Reproduce the race outcome: fresh 'in', stale 'out'. */
        WRITE_ONCE(kf->out, RACE_OUT);
        WRITE_ONCE(kf->in, RACE_IN);
        pr_info("snoop_test: in=%u out=%u (in-out=%u, size=%u)\n",
                kf->in, kf->out, kf->in - kf->out, kf->mask + 1);

        filp_close(filp, NULL);
        return len;
}

static struct kobj_attribute generate_attr =
        __ATTR(generate, 0220, NULL, generate_store);
static struct kobj_attribute adjust_ptrs_attr =
        __ATTR(adjust_ptrs, 0220, NULL, adjust_ptrs_store);

static struct attribute *snoop_attrs[] = {
        &generate_attr.attr,
        &adjust_ptrs_attr.attr,
        NULL,
};
static const struct attribute_group snoop_group = { .attrs = snoop_attrs };
static struct kobject *snoop_kobj;

static int __init snoop_test_init(void)
{
        int rc;

        snoop_kobj = kobject_create_and_add("snoop_test", kernel_kobj);
        if (!snoop_kobj)
                return -ENOMEM;

        rc = sysfs_create_group(snoop_kobj, &snoop_group);
        if (rc) {
                kobject_put(snoop_kobj);
                return rc;
        }
        return 0;
}

static void __exit snoop_test_exit(void)
{
        sysfs_remove_group(snoop_kobj, &snoop_group);
        kobject_put(snoop_kobj);
}

module_init(snoop_test_init);
module_exit(snoop_test_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("aspeed-lpc-snoop kfifo race post-state reproducer");

Reply via email to