⬢ DragonFlyBSD Kernel Audit
DF-4366 / amdgpu_parser_replica.c
← back to finding ↓ download raw
/*
 * amdgpu_parser_replica.c - replica of the token-walk in
 * amdgpu_set_pp_power_profile_mode() (sys/dev/drm/amd/amdgpu/amdgpu_pm.c:916-975).
 *
 * The sysfs store handler for pp_power_profile_mode parses a user write
 * ("5 <params...>" selects the CUSTOM profile).  Its loop is:
 *
 *   amdgpu_pm.c:944  memcpy(buf_cpy, buf, count-i);   <-- NO NUL terminator
 *   amdgpu_pm.c:946  tmp_str = buf_cpy;
 *   amdgpu_pm.c:947  while (tmp_str[0]) {
 *   amdgpu_pm.c:948      sub_str = strsep(&tmp_str, delimiter);
 *   amdgpu_pm.c:949      ret = kstrtol(sub_str, 0, &parameter[parameter_size]);
 *   ...                  parameter_size++;
 *   amdgpu_pm.c:955      while (isspace(*tmp_str))   <-- strsep returned NULL
 *                            tmp_str++;                  when the input did
 *                                                 not end with a delimiter
 *
 * DragonFly strsep (sys/libkern/strsep.c:63-65) sets *stringp = NULL when
 * it consumes the remainder without finding a delimiter.  A write that
 * does NOT end in ' ' or '\n' (e.g. `printf '5 1 2 3' > pp_power_profile_mode`,
 * i.e. no echo-added newline) therefore makes tmp_str NULL and the very
 * next `isspace(*tmp_str)` dereferences NULL (sys/sys/ctype.h:43 expands
 * isspace(c) to ((c) == ' ' || ...) with c = *tmp_str) -> kernel panic.
 *
 * Secondary defect in the same loop: buf_cpy[128] is filled with
 * count-i <= 127 bytes and NEVER NUL-terminated, so on inputs that DO end
 * with a delimiter the walk continues into uninitialized kernel stack
 * until it happens to hit a zero byte.
 *
 * This module reproduces the exact walk (same strsep/isspace sequence,
 * same 3-delimiter set {' ', '\n', '\0'}) on a static kernel buffer.
 * kstrtol() is stood in for by a digit-accepting check so the module does
 * not depend on the DRM compat headers; the crash is in the delimiter
 * walk, not in the numeric conversion.
 *
 * Sysctl (root):
 *   kern.amdgparun = 1   run the amdgpu_pm.c:946-957 walk on "5 1 2 3"
 *                        (no trailing delimiter) -> NULL deref panic.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/libkern.h>
#include <sys/ctype.h>

static const char delimiter[3] = { ' ', '\n', '\0' };

static int
fake_kstrtol_ok(const char *s)
{
	/* stands in for kstrtol(sub_str, 0, ...) == 0 on numeric tokens */
	if (*s == '-' || *s == '+')
		s++;
	return (*s >= '0' && *s <= '9');
}

static int
run_parse(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;
	long parameter[64];
	char *sub_str, *tmp_str;
	uint32_t parameter_size = 0, i = 0;
	/* what the sysfs layer hands the handler for `printf '5 1 2 3' >` */
	static char sysfs_buf[16] = "5 1 2 3";
	char *buf = sysfs_buf;
	size_t count = 7;			/* "5 1 2 3", no trailing \n */
	char buf_cpy[128];

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error || req->newptr == NULL || v == 0)
		return (error);

	/* ---- amdgpu_pm.c:938-944 (profile_mode==CUSTOM path) ---- */
	/* while (isspace(*++buf)) i++;  -- buf[1] is ' ', buf[2] is '1' */
	while (isspace(*++buf))
		i++;
	memcpy(buf_cpy, buf, count - i);	/* <-- NO NUL terminator */

	/* ---- amdgpu_pm.c:946-957: the fatal token walk ---- */
	tmp_str = buf_cpy;
	kprintf("amdgparun: walking %zu-byte content, last byte 0x%02x\n",
	    count - i, (unsigned char)buf_cpy[count - i - 1]);
	while (tmp_str[0]) {
		sub_str = strsep(&tmp_str, delimiter);
		if (!fake_kstrtol_ok(sub_str)) {
			kprintf("amdgparun: kstrtol fail -> -EINVAL (safe)\n");
			return (0);		/* handler would goto fail */
		}
		parameter[parameter_size] = 1;	/* stand-in store */
		parameter_size++;
		/*
		 * amdgpu_pm.c:955-956: after the final token ("3") strsep
		 * found NO delimiter -> tmp_str == NULL -> *tmp_str is a
		 * NULL pointer dereference -> panic.
		 */
		while (isspace(*tmp_str))
			tmp_str++;
	}
	parameter[parameter_size] = 5;		/* profile_mode = CUSTOM */

	kprintf("amdgparun: walk completed without crash (input must have "
	    "ended in a delimiter + NUL)\n");
	return (0);
}

static int
amdgparun_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		sysctl_add_oid(NULL, SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "amdgparun", CTLTYPE_INT|CTLFLAG_RW,
		    NULL, 0, run_parse, "I",
		    "run the amdgpu_pm.c:946-957 token walk (panics)");
		kprintf("amdgparun: loaded (pp_power_profile_mode parser "
		    "replica)\n");
		return (0);
	case MOD_UNLOAD:
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t amdgparun_mod = { "amdgparun", amdgparun_load, NULL };
DECLARE_MODULE(amdgparun, amdgparun_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);