The CSE students of IIT Guwahati frequently need to bring the system (our respective servers) down. We need to do it for:
- Saving ourselves from being forced to show the assignments (“Sir, I have done the assignment and it’s there on the server. But the system…”),
- Demonstrating our technical superiority to our peers, and by implication, their technical inferiority to us (“You suckers! If I’m not showing the assignment, neither of you will. As a bonus, you won’t check your mails. Let’s play Quake!”),
- Testing our hunches on ‘What brings the server down?’ (“…and this simply brings the server down. Never thought that the system and the SysAd were SO dumb.”), and most importantly,
- For the thrill and sheer pleasure in the process and the results (“I did it. I did it again!”).
No matter what the reason, we are all IITians and whenever we put in some effort, we manage to bring the system down. We use wide range of techniques to achieve our goal. However, we prefer generic methods over those based on specific bugs (which would anyway be patched in a short while).
The first generations of our attacks were very simple; all of them were essentially fork-bombs:
while(1) fork();
This would result in exponential growth of the number of processes and since there is a finite limit to the number of processes in a system, soon the system won’t be able to create new processes resulting in quite a useless system.
While fork-bombs are elegant, the OS can easily avoid their system-wide effect. It can simply limit the maximum number of processes per user so that a fork-bomb will only be able to jam the account of the user fork-bombing. Of course, this is not what we want. We want all users affected; we want the system itself affected.
Another old approach is to hog the file-system by creating recursive directories in the file-system until all the inodes (or space) in the partition is consumed (whichever happens first). As a result, users will not be able to create new files/directories in the particular file-system. While this would render the system (mostly) unusable to some users, the system is still up and running. In fact, most users are unaffected. Further, this can easily be avoided by limiting the disk-space/inodes allocated per user. This attack is not at all in the league of perfect attacks.
Other variation of above approach targets specifically the /tmp partition (or directory) by consuming all the inodes and/or disk-space. Though this is lot more effective than the previous method, it still is no more perfect attack than the previous one.
One more approach tries to hog all of the system memory by calling malloc() (or other variants) repeatedly, decreasing memory request on every malloc() failure until a minimum (predefined) value is reached. The aim is to take away as much memory from the system as it can grant (to the last page) so that it has no more memory left to grant to other processes, resulting in a useless system. While theoretically sound, this method is not at all effective in practice. It does increase the system overhead, but the system stays up and running. The kernel shrewdly reserves a lot of space for its own operation and in desperate needs, it eventually kills our memory hogging process.
And here, finally, is my newest technique. The idea is to fool the system into granting much more virtual memory than it can physically provide (in RAM and/or in swap-space) followed to an active claim to the memory (virtually) granted by the system. This is achieved as follows:
- Create a process (our root process) and repeatedly call
malloc()to get as much memory as the system is willing to grant (to the last page). - Multiply by repeatedly calling
fork()until the system refuses to create more copies. - From each copy, write one byte (or more) in each page forcing the system to translate all the virtual memory which was granted to our root process (multiplied by number all the copies) into physical memory.
The system will not be able to translate all of the virtual memory, but since it has already committed to the root process, and thus, to all of its copies, it has to provide all of them all the memory they are requesting now. Soon the system will run out of memory, resulting in a frozen system.
There is one caveat though. When a typical Linux based system is out of memory, it will try to eliminate our memory hogging processes (read: OOM Killer). We can increase the chances of survival of our processes by:
- Creating less number of child processes.
- Consuming less memory per process.
- Pretending to do some useful raw I/O. (Maybe even network access).
- NOT using
niceandnohup. (I know, it’s lot to ask, but so is life.) - Making our processes go slowly about their work.
So that’s all for now. Enjoy bringing your server down!