Contents:
Understanding NFS
Server-Side NFS Security
Client-Side NFS Security
Improving NFS Security
Some Last Comments
In many environments, we want to share files and programs among many workstations in a local area network. Doing so requires programs that let us share the files, create new files, do file locking, and manage ownership correctly. Over the last dozen years there have been a number of network-capable filesystems developed by commercial firms and research groups. These have included Apollo Domain, the Andrew Filesystem (AFS), the AT&T Remote Filesystem (RFS), and Sun Microsystems' Network Filesystem (NFS). Each of these has had beneficial features and limiting drawbacks.
Of all the network filesystems, NFS is probably the most widely used. NFS is available on almost all versions of UNIX, as well as on Apple Macintosh systems, MS-DOS, Windows, OS/2, and VMS. NFS has continued to mature, and we expect that Version 3 of NFS will help to perpetuate and expand its reach. For this reason, we will focus in this book on the security implications of running NFS on your UNIX systems. If you use one of the other forms of network filesystems, there are associated security considerations, many of which are similar to the ones we present here: be sure to consult your vendor documentation.
Using NFS, clients can mount partitions of a server as if they were physically connected to the client. In addition to simply allowing remote access to files over the network, NFS allows many (relatively) low-cost computer systems to share the same high-capacity disk drive at the same time. NFS server programs have been written for many different operating systems, which let users on UNIX workstations have remote access to files stored on a variety of different platforms. NFS clients have been written for microcomputers such as the IBM/PC and Apple Macintosh, giving PC users much of the same flexibility enjoyed by their UNIX coworkers, as well as a relatively easy method of data interchange.
NFS is nearly transparent. In practice, a workstation user simply logs into the workstation and begins working, accessing it as if the files were locally stored. In many environments, workstations are set up to mount the disks on the server automatically at boot time or when files on the disk are first referenced. NFS also has a network mounting program that can be configured to mount the NFS disk automatically when an attempt is made to access files stored on remote disks.
There are several basic security problems with NFS:
NFS is built on top of Sun's RPC (Remote Procedure Call), and in most cases uses RPC for user authentication. Unless a secure form of RPC is used, NFS can be easily spoofed.
Even when Secure RPC is used, information sent by NFS over the network is not encrypted, and is thus subject to monitoring and eavesdropping. As we mention elsewhere, the data can be intercepted and replaced (thereby corrupting or Trojaning files being imported via NFS).
NFS uses the standard UNIX filesystem for access control, opening the networked filesystem to many of the same problems as a local filesystem.
One of the key design features behind NFS is the concept of server statelessness. Unlike several other systems, there is no "state" kept on a server to indicate that a client is performing a remote file operation. Thus, if the client crashes and is rebooted, there is no state in the server that needs to be recovered. Alternatively, if the server crashes and is rebooted, the client can continue operating on the remote file as if nothing really happened - there is no server-side state to recreate.[1] We'll discuss this concept further in later sections.
[1] Actual implementations are not completely stateless, however, as we will see later in this chapter.
NFS was developed inside Sun Microsystems in the early 1980s. Since that time, NFS has undergone three major revisions:
NFS Version 1 was Sun's prototype network filesystem. This version was never released to the outside world.
NFS Version 2 was first distributed with Sun's SunOS 2 operating system in 1985. Version 2 was widely licensed to numerous UNIX workstation vendors. A freely distributable, compatible version was developed in the late 1980s at the University of California at Berkeley.
During its 10-year life, many subtle, undocumented changes were made to the NFS version 2 specification. Some vendors allowed NFS version 2 to read or write more than 4K bytes at a time; others increased the number of groups provided as part of the RPC authentication from 8 to 16. Although these minor changes created occasional incompatibilities between different NFS implementations, NFS version 2 provided a remarkable degree of compatibility between systems made by different vendors.
The NFS Version 3 specification was developed during a series of meetings in Boston in July, 1992.[2] Working code for NFS Version 3 was introduced by some vendors in 1995, and is expected to be widely available in 1996. Version 3 incorporates many performance improvements over Version 2, but does not significantly change the way that NFS works or the security model used by the network filesystem.
[2] Pawlowski, Juszczak, Staubach, Smith, Lebel and Hitz, "NFS Version 3 Design and Implementation," USENIX Summer 1994 conference. The standard was later codified as RFC 1813. A copy of the NFS Version 3 paper can be obtained from http://www.netapp.com/Docs/TechnicalDocs/nfs_version_3.html. The RFC can be downloaded from http://ds.internic.net/rfc/rfc1813.txt.
NFS is based on two similar but distinct protocols: MOUNT and NFS. Both make use of a data object known as a file handle. There is also a distributed protocol for file locking, which is not technically part of NFS, and which does not have any obvious security ramifications (other than those related to potential denial of service attacks for its users), so we won't describe the file locking protocol here.
Each object on the NFS-mounted filesystem is referenced by a unique object called a file handle. A file handle is viewed by the client as being opaque - the client cannot interpret the contents. However, to the server, the contents have considerable meaning. The file handles uniquely identify every file and directory on the server computer.
Under UNIX, a file handle consists of at least three important elements: the filesystem identifier, the file identifier, and a generation count. The file identifier can be something as simple as an inode number to refer to a particular item on a partition. The filesystem identifier refers to the partition containing the file (inode numbers are unique per partition, but not per system). The file handle doesn't include a pathname; a pathname is not necessary and is in fact subject to change while a file is being accessed.
The generation count is a number that is incremented each time a file is unlinked and recreated. The generation count ensures that when a client references a file on the server, that file is in fact the same file that the server thinks it is. Without a generation count, two clients accessing the same file on the server could produce erroneous results if one client deleted the file and created a new file with the same inode number. The generation count prevents such situations from occurring: when the file is recreated, the generation number is incremented, and the second client gets an error message when it attempts to access the older, now nonexistent, file.
NOTE: Some NFS servers ignore the generation count in the file handle. These versions of NFS are considerably less secure, as they enable an attacker to easily create valid file handles for directories on the server.
The MOUNT protocol is used for the initial negotiation between the NFS client and the NFS server. Using MOUNT, a client can determine which filesystems are available for mounting and can obtain a token (the file handle) which is used to access the root directory of a particular filesystem. After that file handle is returned, it can thereafter be used to retrieve file handles for other directories and files on the server.
Another benefit of the MOUNT protocol is that you can export only a portion of a local partition to a remote client. By specifying that the root is a directory on the partition, the MOUNT service will return its file handle to the client. To the client, this file handle behaves exactly as one for the root of a partition: reads, writes, and directory lookups all behave the same way.
MOUNT is an RPC service. The service is provided by the mountd or rpc.mountd daemon, which is started automatically at boot. (On Solaris 2.x systems, for example, mountd is located in /usr/lib/nfs/mountd, and is started by the startup script /etc/rc3.d/S15nfs.server.) MOUNT is often given the RPC program number 100,005. The standard mountd can respond to seven different requests:
NULL | Does nothing. |
MNT | Returns a file handle for a filesystem. Advises the mount daemon that a client has mounted the filesystem. |
DUMP | Returns the list of mounted filesystems. |
UMNT | Removes the mount entry for this client for a particular filesystem. |
UMNTALL | Removes all mount entries for this client. |
EXPORT | Returns the server's export list to the client. |
Although the MOUNT protocol provides useful information within an organization, the information that it provides could be used by those outside an organization to launch an attack. For this reason, you should prevent people outside your organization from accessing your computer's mount daemon. Two ways of providing this protection are via the portmapper wrapper, and via an organizational firewall. See Chapter 22, Wrappers and Proxies, and Chapter 21, Firewalls, for further information.
The MOUNT protocol is based on Sun Microsystem's Remote Procedure Call (RPC) and External Data Representation (XDR) protocols. For a complete description of the MOUNT protocol see RFC 1094.
The NFS protocol takes over where the MOUNT protocol leaves off. With the NFS protocol, a client can list the contents of an exported filesystem's directories; obtain file handles for other directories and files; and even create, read, or modify files (as permitted by UNIX permissions.)
Here is a list of the RPC functions that perform operations on directories:
CREATE | Creates (or truncates) a file in the directory |
LINK | Creates a hard link |
LOOKUP | Looks up a file in the directory |
MKDIR | Makes a directory |
READADDR | Reads the contents of a directory |
REMOVE | Removes a file in the directory |
RENAME | Renames a file in the directory |
RMDIR | Removes a directory |
SYMLINK | Creates a symbolic link |
These RPC functions can be used with files:
GETATTR | Gets a file's attributes (owner, length, etc.) |
SETATTR | Sets some of a file's attributes |
READLINK | Reads a symbolic link's path |
READ | Reads from a file. |
WRITE | Writes to a file. |
NFS version 3 adds a number of additional RPC functions. With the exception of MKNOD3, these new functions simply allow improved performance:
ACCESS | Determines if a user has the permission to access a particular file or directory. |
FSINFO | Returns static information about a filesystem. |
FSSTAT | Returns dynamic information about a filesystem. |
MKNOD | Creates a device or special file on the remote filesystem. |
READDIRPLUS | Reads a directory and returns the file attributes for each entry in the directory. |
PATHCONF | Returns the attributes of a file specified by pathname. |
COMMIT | Commits the NFS write cache to disk. |
All communication between the NFS client and the NFS server is based upon Sun's RPC system, which lets programs running on one computer call subroutines that are executed on another. RPC uses Sun's XDR system to allow the exchange of information between different kinds of computers. For speed and simplicity, Sun built NFS upon the Internet User Datagram Protocol (UDP); however, NFS version 3 allows the use of TCP, which actually improves performance over low-bandwidth, high-latency links such as modem-based PPP connections.
UDP is fast but only best-effort: "Best effort" means that the protocol does not guarantee that UDP packets transmitted will ever be delivered, or that they will be delivered in order. NFS works around this problem by requiring the NFS server to acknowledge every RPC command with a result code that indicates whether the command was successfully completed or not. If the NFS client does not get an acknowledgment within a certain amount of time, it retransmits the original command.
If the NFS client does not receive an acknowledgment, then UDP lost either the original RPC command or the RPC acknowledgment. If the original RPC command was lost, there is no problem - the server sees it for the first time when it is retransmitted. But if the acknowledgment was lost, the server will actually get the same NFS command twice.
For most NFS commands, this duplication of requests presents no problem. With READ, for example, the same block of data can be read once or a dozen times, without consequence. Even with the WRITE command, the same block of data can be written twice to the same point in the file, without consequence.[3]
[3] This is precisely the reason that NFS does not have an atomic command for appending information to the end of a file.
Other commands, however, cannot be executed twice in a row. MKDIR, for example, will fail the second time that it is executed because the requested directory will already exist. For commands that cannot be repeated, some NFS servers maintain a cache of the last few commands that were executed. When the server receives a MKDIR request, it first checks the cache to see if it has already received the MKDIR request. If so, the server merely retransmits the acknowledgment (which must have been lost).
If the NFS client still receives no acknowledgment, it will retransmit the request again and again, each time doubling the time that it waits between retries. If the network filesystem was mounted with the soft option, the request will eventually time out. If the network filesystem is mounted with the hard option, the client continues sending the request until the client is rebooted or gets an acknowledgment. BSDI and OSF/1 also have a spongy option that is similar to hard, except that the stat, lookup, fsstat, readlink, and readdir operations behave like a soft MOUNT.
NFS uses the mount command to specify if a filesystem is mounted with the hard or soft option. To mount a filesystem soft, specify the soft option. For example:
/etc/mount -o soft zeus:/big /zbig
This command mounts the directory /big stored on the server called zeus locally in the directory /zbig. The option -o soft tells the mount program that you wish the filesystem mounted soft.
To mount a filesystem hard, do not specify the soft option:
/etc/mount zeus:/big /zbig
Deciding whether to mount a filesystem hard or soft can be difficult, because there are advantages and disadvantages to each option. Diskless workstations often hard-mount the directories that they use to keep system programs; if a server crashes, the workstations wait until the server is rebooted, then continue file access with no problem. Filesystems containing home directories are usually hard mounted, so that all disk writes to those filesystems will be correctly performed.
On the other hand, if you mount many filesystems with the hard option, you will discover that your workstation may stop working every time any server crashes until it reboots. If there are many libraries and archives that you keep mounted on your system, but which are not critical, you may wish to mount them soft. You may also wish to specify the intr option, which is like the hard option except that the user can interrupt it by typing the kill character (usually control-C).
As a general rule of thumb, read-only filesystems can be mounted soft without any chance of accidental loss of data. But you will have problems if you try to run programs off partitions that are soft-mounted, because when you get errors, the program that you are running will crash.
An alternative to using soft mounts is to mount everything hard (or spongy, when available), but to avoid mounting your nonessential NFS partitions directly in the root directory. This practice will prevent the UNIX getpwd() function from hanging when a server is down.[4]
[4] Hal Stern, in Managing NFS and NIS, says that any filesystem that is read-write or on which you are mounting executables should be mounted hard to avoid corruption. His analogy with a dodgy NFS server is that hard mount behaves like a slow drive, while soft mount behaves like a broken drive!
As we've mentioned, NFS servers are stateless by design. Stateless means that all of the information that the client needs to mount a remote filesystem is kept on the client, instead of having additional information with the mount stored on the server. After a file handle is issued for a file, that file handle will remain good even if the server is shut down and rebooted, as long as the file continues to exist and as long as no major changes are made to the configuration of the server that would change the values (e.g., a file system rebuild or restore from tape).
Early NFS servers were also connectionless. Connectionless means that the server program does not keep track of every client that has remotely mounted the filesystem.[5] When offering NFS over a TCP connection, however, NFS is not connectionless: there is one TCP connection for each mounted filesystem.
[5] An NFS server computer does keep track of clients that mount their filesystems remotely. The /usr/etc/rpc.mountd program maintains this database; however, a computer that is not in this database can still access the server's filesystem even if it is not registered in the rpc.mountd database.
The advantage of a stateless, connectionless system is that such systems are easier to write and debug. The programmer does not need to write any code for reestablishing connections after the network server crashes and restarts, because there is no connection that must be reestablished. If a client should crash (or if the network should become disconnected), valuable resources are not tied up on the server maintaining a connection and state for that client.
A second advantage of this approach is that it scales. That is, a connectionless, stateless NFS server works equally well if ten clients are using a filesystem or if ten thousand are using it. Although system performance suffers under extremely heavy use, every file request made by a client using NFS will eventually be satisfied, and there is absolutely no performance penalty if a client mounts a filesystem but never uses it.
Because the superuser can do so much damage on the typical UNIX system, NFS takes special precautions in the way that it handles the superuser running on client computers.
Instead of giving the client superuser unlimited privileges on the NFS server, NFS gives the superuser on the clients virtually no privileges: the superuser gets mapped to the UID of the nobody user - usually a UID of 32767 or 60001 (although occasionally -1 or -2 on pre-POSIX systems).[6] Some versions of NFS allow you to specify the UID to which to map root's accesses, with the UID of the nobody user as the default.
[6] The UNIX kernel maps accesses from client superusers to the kernel variable nobody, which is set to different values on different systems. Historically, the value of nobody was -1, although Solaris defines nobody to be 60001. You can change this value to 0 through the use of adb, making all superuser requests automatically be treated as superuser on the NFS server. In the immortal words of Ian D. Horswill, "The Sun kernel has a user-patchable cosmology. It contains a polytheism bit called `nobody.'...The default corresponds to a basically Greek pantheon in which there are many Gods and they're all trying to screw each other (both literally and figuratively in the Greek case). However, by using adb to set the kernel variable nobody to 0 in the divine boot image, you can move to a Ba'hai cosmology in which all Gods are really manifestations of the One Root God, Zero, thus inventing monotheism." (The UNIX-Haters Handbook, Garfinkel et al. IDG Books, 1994. p. 291)
Thus, superusers on NFS client machines actually have fewer privileges (with respect to the NFS server) than ordinary users. However, this lack of privilege isn't usually much of a problem for would-be attackers who have root access, because the superuser can simply su to a different UID such as bin or sys. On the other hand, treating the superuser in this way can protect other files on the NFS server.
NFS does no remapping of any other UID, nor does it do any remapping of any GID values. Thus, if a server exports any file or directory with access permissions for some user or group, the superuser on a client machine can take on an identity to access that information. This rule implies that the exported file can be read or copied by someone remote, or worse, modified without authorization.
During the ten years of the life of NFS Version 2, a number of problems were discovered with it. These problems included:
NFS was originally based on AUTH_UNIX RPC security. As such, it provided almost no protection against spoofing. AUTH_UNIX simply used the stated UID and GID of the client user to determine access.
The packets transmitted by NFS were not encrypted, and were thus open to eavesdropping, alteration, or forging on a network.
NFS had no provisions for files larger than 4GB. This was not a problem in 1985, but many UNIX users now have bigger disks and bigger files.
NFS suffered serious performance problems on high-speed networks because of the maximum 8K data-size limitation on READ and WRITE procedures, and because of the need to separately request the file attributes on each file when a directory was read.
NFS 3 is the first major revision to NFS since the protocol was commercially released. As such, NFS 3 was designed to correct many of the problems that had been experienced with NFS. But NFS 3 is not a total rewrite. According to Pawlowski et al., there were three guiding principles in designing NFS 3:
Keep it simple.
Get it done in a year.
Avoid anything controversial.
Thus, while NFS 3 allows for improved performance and access to files larger than 4GB, it does not make any fundamental changes to the overall NFS architecture.
As a result of the design criteria, there are relatively few changes between the NFS 2 and 3 protocols:
File-handle size has been increased from a fixed-length 32-byte block of data to a variable-length array with a maximum length of 64 bytes.
The maximum size of data that can be transferred using READ and WRITE procedures is now determined dynamically by the values returned by the FSINFO function. The maximum lengths for filenames and pathnames are now similarly specified.
File lengths and offsets have been extended from four bytes to eight bytes.[7]
[7] Future versions of NFS - or any other filesystem - will not likely need to use more than eight bytes to represent the size of a file: eight bytes can represent more than 1.7 x 1013MB of storage.
RPC errors can now return data (such as file attributes) in addition to return codes.
Additional file types are now supported for character- and block-device files, sockets, and FIFOS.
An ACCESS procedure has been added to allow an NFS client to explicitly check to see if a particular user can or cannot access a file.
Because RPC allows a server to respond to more than one version of a protocol at the same time, NFS 3 servers will be able to support the NFS 2 and 3 protocols simultaneously, so that they can serve older NFS 2 clients while allowing easy upgradability to NFS 3. Likewise, most NFS 3 clients will continue to support the NFS 2 protocol as well, so that they can speak with old servers and new ones.
This need for backward compatibility effectively prevented the NFS 3 designers from adding new security features to the protocols. If NFS 3 had more security features, an attacker could avoid them by resorting to NFS 2. On the other hand, by changing a site from unsecure RPC to secure RPC, a site can achieve secure NFS for all of its NFS clients and servers, whether they are running NFS 2 or NFS 3.
NOTE: If and when your system supports NFS over TCP links, you should configure it to use TCP and not UDP unless there are significant performance reasons for not doing so. TCP-based service is more immune to denial of service problems, spoofed requests, and several other potential problems inherent in the current use of UDP packets.