Here, line is a 512-byte array allocated automatically on the stack. When a user provides more
input than that to the finger daemon, the gets() routine will keep putting it on the stack. Most
architectures are vulnerable to overwriting an existing entry in the middle of the stack with something
bigger, that also overwrites neighboring entries. The cost of checking each stack access for size and
permission would be prohibitive in software. A knowledgeable malefactor can amend the return
address in the procedure activation record on the stack by stashing the right binary patterns in the
argument string. This will divert the flow of execution not back to where it came from, but to a special
instruction sequence (also carefully deposited on the stack) that calls execv() to replace the
running image with a shell. Voilà, you are now talking to a shell on a remote machine instead of the
finger daemon, and you can issue commands to drag across a copy of the virus to another machine.
Repeat until sent to prison. Figure 2-2 shows the process.
Figure 2-2. How the Internet Worm Gained Remote Execution Privileges
Ironically, the gets() routine is an obsolete function that provided compatibility with the very first
version of the portable I/O library, and was replaced by standard I/O more than a decade ago. The
manpage even strongly recommends that fgets() always be used instead. The fgets() routine
sets a limit on the number of characters read, so it won't exceed the size of the buffer. The finger
daemon was made secure with a two-line fix that replaced:
gets(line);
by the lines:
if (fgets(line, sizeof(line), stdin) == NULL)
exit(1);
This swallows a limited amount of input, and thus can't be manipulated into overwriting important
locations by someone running the program. However, the ANSI C Standard did not remove gets()
from the language. Thus, while this particular program was made secure, the underlying defect in the
C standard library was not removed.