⬢ DragonFlyBSD Kernel Audit
DF-4961 / agi2c_probe.c
← back to finding ↓ download raw
/*
 * DF-4961 probe KLD - amdgpu_i2c.c bit-bang adapters have NULL
 * adapter->lock_ops, so every i2c_transfer() on them calls
 * adapter->lock_ops->lock_bus() through a NULL pointer -> kernel panic.
 *
 * This module compiles the REAL, UNMODIFIED audited files
 *   sys/dev/drm/amd/amdgpu/amdgpu_i2c.c   (the audited file)
 *   sys/dev/drm/linux_i2c.c               (the DragonFly linux-compat i2c
 *                                          core where i2c_transfer() does
 *                                          adapter->lock_ops->lock_bus())
 * from the guest's own /usr/src/sys (sha256-verified identical to the audit
 * tree by build.sh) and drives the real production chain:
 *
 *   amdgpu_i2c_create()                       amdgpu_i2c.c:160-219
 *     rec->hw_capable == false -> bit branch  amdgpu_i2c.c:193-212
 *       -> i2c_bit_add_bus()                  linux_i2c.c:128-134
 *          (sets adapter->algo = &i2c_bit_algo; DOES NOT set lock_ops)
 *   amdgpu_i2c_router_select_ddc_port()       amdgpu_i2c.c:341-366
 *     -> amdgpu_i2c_get_byte()                amdgpu_i2c.c:284-316
 *       -> i2c_transfer()                     linux_i2c.c:92-104
 *         -> adapter->lock_ops->lock_bus()    linux_i2c.c:99  <- NULL fp
 *
 * The NULL dereference happens BEFORE any hardware access (lock_bus precedes
 * master_xfer), so no AMD GPU is required. Production reachability of the
 * exact same i2c_transfer() call: drm_get_edid(&ddc_bus->adapter)
 * (amdgpu_connectors.c:310-313) -> drm_do_probe_ddc_edid (drm_edid.c:1552),
 * plus this file's own router_select entry points
 * (amdgpu_display.c:463, amdgpu_connectors.c:292, dce_v8_0.c:3229,
 * dce_v10_0.c:3341, dce_v11_0.c:3467).
 *
 * Reference proof that the compat layer REQUIRES i2c_add_adapter() for
 * lock_ops: radeon's identical bit branch carries an explicit
 * "#ifdef __DragonFly__ / i2c_add_adapter(&i2c->adapter); / #endif"
 * with the comment "setup lock_ops" (radeon_i2c.c:970-972), and the DP
 * aux core sets its own lock_ops (drm_dp_helper.c:1124) - only
 * amdgpu_i2c.c's bit branch omits it.
 *
 * Stubbed (linkage only, never executed on the audited bit path):
 *   - the ATOM BIOS hw i2c algo (only reachable when rec->hw_capable &&
 *     amdgpu_hw_i2c, which we do not take)
 *   - amdgpu_atombios_i2c_init (driver-init helper)
 *   - drm_dbg/drm_err (logging); stubs ALWAYS print so the fixed build's
 *     "read failed" path is visible in the log (this also live-demonstrates
 *     the uninitialized *val read at amdgpu_i2c.c:313-314 - DF-4962)
 * amdgpu_hw_i2c is defined to its production default value 1
 * (amdgpu_drv.c:88) - !hw_capable bus records take the bit branch
 * regardless of this flag.
 */
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/i2c.h>

#include <drm/drmP.h>
#include "amdgpu.h"
#include "amdgpu_i2c.h"
#include "amdgpu_mode.h"
#include "amdgpu_atombios.h"
#include "atombios_i2c.h"

/* ------------------------------------------------------------------ */
/* Linkage stubs                                                       */
/* ------------------------------------------------------------------ */

int amdgpu_hw_i2c = 1;		/* production default: amdgpu_drv.c:88 */

int amdgpu_atombios_i2c_xfer(struct i2c_adapter *i2c_adap,
			     struct i2c_msg *msgs, int num)
{
	printk(KERN_INFO "agi2c:   [stub] amdgpu_atombios_i2c_xfer - NOT REACHED\n");
	return (-EIO);
}

u32 amdgpu_atombios_i2c_func(struct i2c_adapter *adap)
{
	printk(KERN_INFO "agi2c:   [stub] amdgpu_atombios_i2c_func - NOT REACHED\n");
	return (I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL);
}

void amdgpu_atombios_i2c_init(struct amdgpu_device *adev)
{
	printk(KERN_INFO "agi2c:   [stub] amdgpu_atombios_i2c_init - NOT REACHED\n");
}

/*
 * amdgpu_i2c.c's pre_xfer, post_xfer, get_data, get_clock, set_data and
 * set_clock expand RREG32/WREG32 to amdgpu_mm_rreg() and amdgpu_mm_wreg()
 * (amdgpu.h:1066-1069).  Those callbacks are only ever dispatched by a
 * real bit-algo engine; DF's i2c_bit_algo master_xfer is the bit_xfer
 * stub (linux_i2c.c:106-111) and never calls them - the symbols only
 * need to RESOLVE.  Never executed here.
 */
uint32_t amdgpu_mm_rreg(struct amdgpu_device *adev, uint32_t reg,
			uint32_t acc_flags)
{
	printk(KERN_INFO "agi2c:   [stub] amdgpu_mm_rreg(0x%x) - NOT REACHED\n", reg);
	return (0);
}

void amdgpu_mm_wreg(struct amdgpu_device *adev, uint32_t reg, uint32_t v,
		    uint32_t acc_flags)
{
	printk(KERN_INFO "agi2c:   [stub] amdgpu_mm_wreg(0x%x, 0x%x) - NOT REACHED\n",
	    reg, v);
}

static void
agi2c_vprint(const char *tag, const char *func, const char *format, va_list ap)
{
	char buf[160];

	kvsnprintf(buf, sizeof(buf), format, ap);
	printk(KERN_INFO "agi2c: [%s %s] %s\n", tag, func, buf);
}

void drm_dbg(const char *func, unsigned int category, const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	agi2c_vprint("drm_dbg stub", func, format, ap);
	va_end(ap);
}

void drm_err(const char *func, const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	agi2c_vprint("drm_err stub", func, format, ap);
	va_end(ap);
}

/* ------------------------------------------------------------------ */
/* Faithful fake objects (BSS, like driver-allocated memory).          */
/* amdgpu_i2c_create() only computes &dev->pdev->dev (an ADDRESS);     */
/* nothing on the audited bit path dereferences the fake drm_device.   */
/* ------------------------------------------------------------------ */

static struct pci_dev fpdev;
static struct drm_device fdrm;
static struct amdgpu_i2c_bus_rec frec;
static struct amdgpu_connector fconn;
static struct amdgpu_i2c_chan *probe_chan;

static int __init agi2c_init(void)
{
	struct amdgpu_i2c_chan *chan;

	fdrm.pdev = &fpdev;

	/*
	 * GPIO-only DDC bus record, as parsed from the VBIOS GPIO_I2C table
	 * for legacy connectors (no I2C_HW_CAP): valid=1, hw_capable=0,
	 * mm_i2c=0.  amdgpu_i2c_create() line 184 checks
	 * "rec->hw_capable && amdgpu_hw_i2c" -> FALSE -> bit branch
	 * (amdgpu_i2c.c:193-212), regardless of the amdgpu_hw_i2c default.
	 */
	memset(&frec, 0, sizeof(frec));
	frec.valid = true;
	frec.i2c_id = 0;
	frec.hw_capable = false;
	frec.mm_i2c = false;

	printk(KERN_INFO "agi2c: calling REAL amdgpu_i2c_create(dev, rec, \"DF4961-probe\")\n");
	chan = amdgpu_i2c_create(&fdrm, &frec, "DF4961-probe");
	if (chan == NULL) {
		printk(KERN_INFO "agi2c: amdgpu_i2c_create returned NULL - abort\n");
		return (0);
	}
	probe_chan = chan;

	printk(KERN_INFO "agi2c: chan=%p adapter.name='%s'\n",
	    chan, chan->adapter.name);
	printk(KERN_INFO "agi2c: adapter.algo=%p (== &i2c_bit_algo: %s)"
	    " master_xfer=%p\n",
	    chan->adapter.algo,
	    (chan->adapter.algo == &i2c_bit_algo) ? "YES - bit bus" : "no",
	    chan->adapter.algo->master_xfer);
	printk(KERN_INFO "agi2c: adapter.lock_ops=%p  <- i2c_transfer() needs this non-NULL\n",
	    chan->adapter.lock_ops);

	if (chan->adapter.lock_ops != NULL) {
		printk(KERN_INFO "agi2c: lock_ops set - fix applied, firing anyway (no panic expected)\n");
	} else {
		printk(KERN_INFO "agi2c: lock_ops == NULL: i2c_transfer() will call"
		    " (NULL)->lock_bus() -> NULL function pointer -> PANIC\n");
	}

	/*
	 * Real exported entry point of the audited file, reached in
	 * production from amdgpu_display.c:463 / amdgpu_connectors.c:292
	 * (DDC router) with exactly this shape of connector.
	 */
	memset(&fconn, 0, sizeof(fconn));
	fconn.router_bus = chan;
	fconn.router.ddc_valid = true;
	fconn.router.i2c_addr = 0x70 >> 1;		/* typical i2c mux */
	fconn.router.ddc_mux_control_pin = (1 << 4);
	fconn.router.ddc_mux_state = 0;

	printk(KERN_INFO "agi2c: FIRING - REAL amdgpu_i2c_router_select_ddc_port(&fconn)...\n");
	amdgpu_i2c_router_select_ddc_port(&fconn);

	printk(KERN_INFO "agi2c: survived amdgpu_i2c_router_select_ddc_port"
	    " (unfixed build panics inside i2c_transfer before this line)\n");
	return (0);
}

static void __exit agi2c_exit(void)
{
	if (probe_chan != NULL)
		amdgpu_i2c_destroy(probe_chan);
	printk(KERN_INFO "agi2c: unloaded\n");
}

module_init(agi2c_init);
module_exit(agi2c_exit);

MODULE_DESCRIPTION("DF-4961: amdgpu bit-bang i2c adapters have NULL adapter->lock_ops -> i2c_transfer() NULL function-pointer deref -> panic");