D. J. Bernstein
UNIX

Resource exhaustion

Introduction

Your computer's storage space is limited. You can't use more memory and disk space than you have available.

Storage space is shared among programs. One program can use all available space; then any program needing more space will fail. If you receive so much e-mail that your disk fills up, for example, you won't be able to save a new file from your word processor. This might happen by accident, or an attacker might deliberately flood you with mail.

You can partition your space to protect one program from another. For example, you can set aside one disk for receiving mail, and another disk for word processing. Under most operating systems, when you install a new disk, you can chop it into several limited partitions.

Partitioning is not free. You will be unable to receive mail when your mail disk is full, for example, even if you have tons of space available on your word-processing disk. On a multiuser machine you may want to partition the mail disk among users, allowing (say) 10MB per user---but an average user will have under 1MB of mail at any moment, so over 90% of your disk space will be unused. As a compromise you might allow each user to store 10MB on a disk with capacity for only 1MB per user; then the disk will fill up if 10% of the users are flooded with mail.

Similar comments apply to other shared resources, such as CPU time.

Managing UNIX resources

Here are some of the limited resources on a UNIX machine, and some of the tools that you can use to manage those resources. Many of the tools are system-specific; for example, BSD resource limits, aka rlimits, have been extended in many different directions by UNIX vendors.

Disk space: You can chop disks into partitions, as noted above. You can limit the disk space used by a single uid with quotas. You can limit the size of files created by a single process with the filesize rlimit.

Program memory: Process memory is divided into several segments. The text segment contains program code. Text is read-only; any number of processes running the same program will use the same text. The stack segment contains stack frames. The stack segment can grow to any size, though typical programs have bounded stacks. You can limit per-process stack size with the stacksize rlimit. The data segment contains other writable data. The data segment can grow to any size. You can limit per-process data size with the datasize rlimit.

Some programs will spawn an arbitrary number of children, using up any amount of memory even though per-process memory is limited. For example, inetd can start any number of children to handle TCP connections; its fork-per-second limit does not provide effective protection against a flood of long-term TCP connections. You can control the number of processes per uid with the maxproc rlimit, but this is useless for root daemons. Solution: replace inetd with tcpserver, which provides a concurrency limit. Similarly, sendmail can start any number of children; its load limit does not provide effective protection. Solution: run sendmail -bs under tcpserver.

CPU time: Processor time is shared among all programs. You can increase or decrease a process's share of the CPU by setting its priority. There are no tools to partition the CPU among users or subsystems: a user running more processes will get more CPU time.

Open file slots: An open file is a memory buffer for an active file on disk. You can limit per-process open files with the openfiles or descriptors rlimit.

TCP slots: Each active TCP connection uses memory for a TCP control block. There are no tools to partition the control block table.

SYN slots: Each listening TCP port uses memory for a small table of half-open connections. When this table fills up, new connections to that port will be ignored or refused. Solution: SYN cookies, available in recent versions of Linux, use cryptographic techniques to continue providing adequate service when the table is full.