โฌข DragonFlyBSD Kernel Audit
DF-3957 / trigger_df3957.c
โ† back to finding โ†“ download raw
/*
 * DF-3957 โ€” radeon atombios_encoders.c unguarded enc_priv derefs for
 *           LCD-supporting digital encoders (NULL-page read AND write)
 * =====================================================================
 * Target: sys/dev/drm/radeon/atombios_encoders.c (pass 2; pass 1: clean)
 *
 * Root cause: radeon_add_atom_encoder() (:2834-2838, :2868-2872) assigns
 *   enc_priv = radeon_atombios_get_lvds_info() for LCD-supporting encoders.
 *   That function (radeon_atombios.c:1617-1755) returns NULL when the VBIOS
 *   LVDS_Info DATA table is missing (atom_parse_data_header fails) or kzalloc
 *   fails โ€” while the connector table still advertised ATOM_DEVICE_LCD support
 *   (crafted/marginal VBIOS, or OOM at boot). Contrast the guards that DO
 *   exist: :100 (set_backlight_level), :240 (backlight_init), :629
 *   (digital_setup), :2551 (prepare).
 *
 * Unguarded sinks (radeon_encoder->enc_priv dereferenced with no NULL check):
 *   :742-744  atombios_get_encoder_mode: dig_enc->active_mst_links (READ)
 *   :1085     atombios_dig_transmitter_setup2: dig->dig_encoder (READ;
 *             reached at BOOT from radeon_atom_encoder_init :2314)
 *   :1695-97  dpms_avivo: dig->backlight_level (READ, arg evaluation)
 *   :1744     dpms_dig: dig->panel_mode = ... (WRITE)
 *   :1783     dpms_dig: dig->backlight_level (READ)
 *   :1796     dpms_dig: dig->active_mst_links (READ)
 *   :2006-07  atombios_set_encoder_crtc_source: dig->dig_encoder (READ)
 *   :2270-72  radeon_atom_pick_dig_encoder: dig->dig_encoder (READ)
 *   :2661-63  radeon_atom_encoder_disable: dig->dig_encoder (READ)
 *             then dig->dig_encoder = -1 (WRITE at NULL+offset)
 *
 * All faults land in the NULL page (offsets < ~0x60) => kernel page fault =>
 * panic. Upstream Linux radeon carries the same unguarded paths (inherited);
 * trigger is hardware+VBIOS-gated (no unprivileged local vector).
 *
 * This harness replicates struct offsets and executes the verbatim
 * NULL+offset read and WRITE in a child; arithmetic demonstration, NOT a
 * kernel reproduction.
 *
 * Build: cc -O2 -Wall -o trigger_df3957 trigger_df3957.c
 * Run:   ./trigger_df3957 / ./trigger_df3957 crash
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

/* struct radeon_encoder_atom_dig (radeon.h) โ€” only the leading fields matter */
struct radeon_encoder_atom_dig {
	int	dig_encoder;		/* first member, offset 0 */
	int	linkb;
	int	active_mst_links;
	int	panel_mode;
	int	backlight_level;
};

int main(int argc, char **argv)
{
	int crash = (argc > 1 && !strcmp(argv[1], "crash"));

	printf("DF-3957 radeon atombios_encoders enc_priv==NULL family (simulation)\n");
	printf("enc_priv = radeon_atombios_get_lvds_info() -> NULL (missing LVDS_Info table / OOM)\n");
	printf(":742  dig_enc->active_mst_links  READ  at NULL+%zu\n",
	       offsetof(struct radeon_encoder_atom_dig, active_mst_links));
	printf(":1085 dig->dig_encoder           READ  at NULL+%zu\n",
	       offsetof(struct radeon_encoder_atom_dig, dig_encoder));
	printf(":1744 dig->panel_mode = ...      WRITE at NULL+%zu\n",
	       offsetof(struct radeon_encoder_atom_dig, panel_mode));
	printf(":2663 dig->dig_encoder = -1      WRITE at NULL+%zu\n",
	       offsetof(struct radeon_encoder_atom_dig, dig_encoder));
	printf("kernel effect: NULL-page read/write => page fault => panic\n");

	if (!crash) {
		printf("NON-CRASH MODE: analysis only. Re-run with 'crash' to execute them.\n");
		return 0;
	}

	struct radeon_encoder_atom_dig *dig = NULL;	/* enc_priv == NULL */

	pid_t p1 = fork();
	if (p1 == 0) {		/* radeon_atom_encoder_disable (:2661-2663) */
		printf("child1: :2661-2663 read dig->dig_encoder, then WRITE -1 ...\n");
		fflush(stdout);
		int v = dig->dig_encoder;	/* READ */
		dig->dig_encoder = -1;		/* WRITE */
		printf("child1: NOT REACHED (v=%d)\n", v);
		_exit(0);
	}
	pid_t p2 = fork();
	if (p2 == 0) {		/* radeon_atom_encoder_dpms_dig (:1744) */
		printf("child2: :1744 WRITE dig->panel_mode ...\n");
		fflush(stdout);
		dig->panel_mode = 1;
		printf("child2: NOT REACHED\n");
		_exit(0);
	}

	int st, rc = 0;
	waitpid(p1, &st, 0);
	printf("child1 (disable :2661-2663): %s", WIFSIGNALED(st) ? "SIGNALLED" : "exited");
	if (WIFSIGNALED(st)) printf(" sig=%d", WTERMSIG(st));
	printf("\n");
	if (!(WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV)) rc |= 1;
	waitpid(p2, &st, 0);
	printf("child2 (dpms_dig :1744): %s", WIFSIGNALED(st) ? "SIGNALLED" : "exited");
	if (WIFSIGNALED(st)) printf(" sig=%d", WTERMSIG(st));
	printf("\n");
	if (!(WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV)) rc |= 2;
	printf("RESULT: %s (rc=%d)\n", rc ? "DEVIATION" : "NULL-PAGE FAULTS AS PREDICTED", rc);
	return rc;
}