CS 111 Scribe Notes for 11/30/2010
NFS and Security (Lecture 22)
Written by Michael Sechooler based on a lecture by Professor Paul Eggert.
Files reside on server computer, not on a localhost.
Figure 1:
Network layout for an NFS system.
|
There are three ways to extend programs to utilize NFS filesystems:
- Modify all applications to use special syscalls to open NFS files:
nfs_open("name", ...) -> nfsfd_t
However, this requires modifying all existing applications.
- Make available the same syscalls and extend libraries
(e.g., C/C++ standard libraries with stdio and iostream) to
seamlessly and automatically use them when working with NFS shares. To
an application using these libraries, nothing would change. However,
too many applications directly use syscalls for I/O, so this approach
would not work for a wide set of programs.
- Change the syscall implementations themselves. Make the kernel support it
natively, seamlessly, and automatically. In Linux (and using similar
concepts in others), all filesystems sit below the VFS layer. High-level
kernel code works with the VFS abstraction, so it does not need to be
modified for each filesystem. Only the low-level portion that maps VFS
functions and concepts to the filesystem-specific ones must be changed.
See Figure 2 for more on this mapping.
Figure 2:
VFS and filesystem-specific map.
|
Since the third option does not require changing any previous application code,
NFS uses it. It is its own filesystem (under VFS) and uses RPC, as opposed to local disk access. To the user, however, it looks like ordinary files and directories. An NFS filesystem is typically mounted into the filesytem as a subdirectory (or, possibly, as the root one).
With NFS, the syscalls actually become message passing calls. Even the protocol used to pass these message across a network resembles the I/O syscalls with which we are already familiar because it was designed for UNIX systems. See Figure 3 for some example messages. Note that the RPCs closely match syscalls, with the exception of open(). open() for NFS is requires opening (LOOKUPing) all parent directories to get their directory file handles (dirfh).
Figure 3:
Example NFS messages.
|
The file handle (fh) is perhaps the most important component of NFS. It uniquely identifies a file, somewhat like a file descriptor, but is persistent across server crashes and reboots -- essentially for the life of the file. Typically, the easiest way to provide such a number is to combine the inode of the file (which is constant for its life) and the device/filesystem number (since inodes are specific to one filesystem). However, the operating system provides no easy way to access files based on these two numbers, since doing so would enable bypassing directory permissions by going straight to the file. Therefore, most implementations require some kernel-space code; however, it is possible to grant the ability to access files in this manner to root only and then to run the server as root in user-space.
However, NFS has one key difference between local disk filesystems: having a file handle does not guarantee that the file will continue to exist. For an example, considering the following interactions between two clients:
- Client A opens (LOOKUP) and continually reads (READ) a NFS file.
- Client B, while A is reading, removes (REMOVE) the file. The server promptly deletes the file.
Client A still has a file handle; however it has become stale since the file it points to has been removed. The read() syscall would return ESTALE, an error code created especially for this problem. Normally, the operating system does not actually delete a file until all processes have released it. However, the server cannot know when a client is finished. We could implement a CLOSE message, but a client could crash without closing a file; the file would then remain unlinked but otherwise immortal. Since NFS is supposed to be robust, this would not be acceptable, so it breaks this promise.
An NFS server is a stateless, meaning that no data is kept in RAM; everything is written to disk immediately. This is to ensure that the server does not, for example, return success for a write but then promptly lose it when it crashes. However, the lack of state also prevents locking.
/Open Consistency
However, NFS can provide close/open consistency. Consider the following interactions between two clients:
- Client A opens a file, changes it, and closes it.
- Client B opens file after A finishes and sees the changes.
Because a close command flushes the buffers and an open command disregards prior buffering, NFS can guarantee that a client will see changes of files closed before it opened them.
A lot of individual calls takes a lot of time.
NFS clients typically read-ahead (see Figure 4), cache read blocks on client, and dally for writes (in which it returns immediately but sends later).
Figure 4:
Reading normally (left) vs. with reads-ahead (right).
|
These cheats can harm reliability. Caching ignores intermediary changes. Dallying could prevent other clients from receiving updates in a timely fashion or, in the event the writer crashes before actually updating the server, ever. These methods give up transaction consistency for performance.
Server caches writes to RAM. Not allowed for consistency unless the server has reliable RAM (battery-backed, for example, as found in high-end servers).
This benchmark is taken from spec.org, which hosts various benchmark data.
The HP BL860c i2 4-node HA-NFS (Highly Available: tolerates single component failure with speed penalty -- Fault Tolerant does so without the penalty) has backup systems for everything, for a total of:
- 4 servers
- 8 controllers
- 16 disk arrays with dual controllers (2 GiB cache each)
- 1472 disk drives (72 GiB; 15000 RPM)
With a cost of hundreds of thousands, although it has a performance sweet spot of 2ms response time at 300k OPS (operations per second). In fact, the speed is significantly faster than any local hard disk.
- Authentication
- Traditionally, it is assumed the server is on a trusted network.
- UID
- UIDs may not match on different machines. Traditionally, a standard passwd file is copied among files so they all share the same account information.
The new solution is to use the Kerberos authentication system with UID remapping. However, it has a performance hit, since each RPC request must be authenticated.
In the real world, must attacks are by force or fraud.
OS security deals mainly with countering fraud. Most forms of fraud are against:
- Privacy
- Unauthorized access to data.
- Integrity
- Tampering with data.
- Service
- Denial of service.
There are two goals in defense:
- Disallow unauthorized access, solving privacy and integrity concerns. (Negative goal.)
- Allow authorized access. (Positive goal.)
The latter gets more tested in practice, because users complain if they cannot use their software. A is often poorly tested, sometimes even outsourced. (Tip: put deliberate errors in code to judge the testers' ability to find them.)
Typically, the first system in securing a system is to build a threat model, like the following:
- Network attacks
- Denial of service attacks from outside network
- Exploit a bug a Linux to break in (buffer overrun, etc.)
- Packet sniffing for passwords, etc.
- Brute forcing passwords
- Viruses via e-mail
- Local attacks
- USB sticks, CD-ROMs with viruses or other malware
- Social attacks
- Insiders
- Social engineering