Skip to content

3_1 Linux

Michael Eder edited this page Dec 29, 2024 · 1 revision

This page describes how file handles are structured so you can understand attacks that can be performed on misconfigured servers.

File handle structure

A Linux file handle consists of three parts.

The first part is always 4 bytes long and contains information on how to parse the other two parts. The second part is the file system identifier, referred to as fsid which has a variable length. It is used to identify the file system and the export containing a file. The third part is the fileid which also has a variable length and identifies a file or directory within an export. Its structure depends on how the underlying file system identifies files.

As an example, we will look at the following file handle which has been generated by a Linux server:

  meta                        fsid                                    fileid
|======||================================================||==============================|
0x0100070201fd05000000000065e9e814e14742e0b4b70279933adc3778e006009b9d890402fd0500706bf7e7

It is interpreted in the following way.

First part: 4 byte metadata

Data Len Meaning
0x01 1 Version, always 0x01
0x00 1 Authentication, always 0x00
0x07 1 fsid type, specifies which type of identifier is used to identify the file system and export
0x02 1 fileid type, specifies how files are identified within the export

Second part: File system ID (fsid)

Since the third byte specifies that the file system id has type 7, it consists of the following fields:

Data Len Meaning
0x01fd050000000000 8 inode of export root directory
0x65e9e814e14742e0b4b70279933adc37 16 uuid of the file system

Third part: File ID (fileid)

As specified in the fourth byte, the fileid has type 2 and can be broken up into the following parts.

Data Len Meaning
0x78e00600 4 inode
0x9b9d8904 4 generation
0x02fd0500 4 inode of parent directory
0x706bf7e7 4 generation of parent directory

This fileid structure is used to identify files and directories in exports on ext4 and xfs partitions. In some cases, fileid type 1 which does not contain information about the parent is used to refer to directories.

The inode and generation number are stored in little endian. Since file handles are not supposed to be interpreted by clients, these fields are not converted to network byte order.

For more information on the file handle structure, see fs/nfsd/nfsfh.h and include/linux/exportfs.h.

subtree_check

As we could see, a file handle contains an identifier for the export and one for the file within the export. In our previous example, the export was identified by the file system uuid and the inode of the export directory.

There is a special case when the export is not the root directory of the file system. By default Linux does not check if the file a handle refers to is inside the exported directory. A client could request an operation on a file handle that refers to any other file on the same file system. The check to protect against this has to be explicitly enabled by setting the subtree_check option on the export. This means that by default it is possible to read other files that are outside the export directory but on the same file system.

The check works by starting at the requested file and then walking up the file system tree until either the export directory or the system root is reached or there is a permission error. If the export directory is reached, the check is successful. The implementation and the acceptance conditions of subtree_check can be found in nfsd_acceptable in fs/nfsd/nfsfh.c Since one file can be in multiple directories, the file handle contains the inode and generation of the parent directory in order to identify which hardlink it refers to.

A file handle should remain valid and point to the same file even after it has been moved to a different directory. When subtree_check is enabled, this property is not fulfilled because the information about the parent directory in the file handle is not correct anymore. This will cause operations on files that have been moved to fail. For this reason it is recommended to always use the root directory of a file system as an export. If you are sure that this access pattern does not occur, e.g. in a readonly export, you can use subtree_check.

Auditing, logging and tracing

A common way to log file accesses on Linux is auditd. However, auditd cannot be used to log file accesses performed by NFS clients because the NFS server runs in the kernel and bypasses the auditing. There are some tracepoints in the NFS request handlers which could in theory be used to monitor accesses, however there does not seem to be a tool for this.

Misconfiguration examples

This section shows a few things that can go wrong when configuring NFS exports on a Linux server. On Linux exports are configured in the /etc/exports file where each line has the following format:

/PATH/TO/EXPORT     HOST1(OPTIONS1) HOST2(OPTIONS2) ...

Line starting with # are comments

subtree_check problem

Let's look at a configuration that is vulnerable to the attack described previously. Consider the following configuration where /mnt/storage is the root directory of a file system.

# Give write access to the private directory to the specified subnet using Kerberos authentication with encryption
/mnt/storage/private 192.168.2.0/24(rw,subtree_check,sec=krb5p)

#Gives read access to the public directory to everyone, no_subtree_check and sec=sys by default
/mnt/storage/public *(ro)

In this case anyone can read files from the private directory because the export for public does not have the option subtree_check set.

nested exports

A similar problem occurs with the following configuration. It is documented in the man page of exports.

/mnt/storage          192.168.2.0/24(rw,subtree_check)
/mnt/storage/private  192.168.2.0/24(rw,subtree_check,sec=krb5p)

While the export /mnt/storage/private can only be mounted with Kerberos enabled, it is still possible to mount /mnt/storage with Unix authentication and access files in the private directory. It is also possible to mount /mnt/storage and replace the private directory with a symlink to any other directory on the server. When the NFS service restarts, it will follow the symlink and export the directory it points to. The subtree_check option does not prevent these two attacks.

bind mounts

The problem can also occur when bind mounts are used, therefore it is important to note (despite others claiming something different on the internet) that bind mounts do not provide isolation. Consider the following fstab.

/dev/sda2                 /                  ext4
/dev/sdb1                 /mnt/storage       ext4
/mnt/storage/public       /srv/nfs/public    bind

The /etc/exports files is defined as follows.

/srv/nfs          *(rw,fsid=root)
/srv/nfs/public   *(rw)

The first export /srv/nfs is a subdirectory of the filesystem on /dev/sda2, therefore all files on the operating system partition are accessible. The other export /srv/nfs/public is a bind mount to /mnt/storage/public which is a subdirectory of /dev/sdb1. This means that all files in /mnt/storage are accessible. Both exports are subdirectory exports and should use the subtree_check option.

The fsid=root will cause the NFS file handles to refer to the file system using the user-defined identifier 0 instead of the volume uuid but it doesn't prevent clients from accessing files outside the exported subdirectory.

space between host and options

Another potential error is mentioned in the Red Had Documentation

/mnt/storage 192.168.2.123 (rw)

This configuration gives everyone write access and 192.168.2.123 read access to the export. Because of the space between the IP address and the parentheses, this is interpreted as two different entries. One for 192.168.2.123 with default settings because there are no parentheses and one for everyone with option rw.

Identifying misconfigurations in server logs

After making changes to /etc/exports and restarting nfs-server.service to apply the changes, it can be helpful to run journalctl -u nfs-server.service and look at the output in order to check for errors. In some cases the NFS server may appear to behave normally even if there are errors present in the exports file.

If the output contains the following line:

exportfs: No options for /EXPORT/PATH : suggest (sync) to avoid warning

This means that there might be an export with space between a host and the options on that export.

If the output contains the line:

Neither 'subree_check' or 'no_subtree_check' specified for export "CLIENT:/EXPORT/PATH"

This means that for some export neither subtree_check nor no_subtree_check is set and that by default no_subtree_check is set. If the export is the root of a file system, it is safe to add the no_subtree_check option.