MOUNT Protocol¶
The MOUNT protocol converts an export path into a file handle. It is the gateway to NFS. Before a client can read a file, list a directory, or do anything over NFSv2 or NFSv3, it must first call the MOUNT daemon to obtain a root file handle for the target export. That handle is the key to everything that follows.
MOUNT is a separate RPC program (100005) from NFS (100003). It runs as its own daemon (rpc.mountd) on a dynamic port, registered with portmapper. This separation exists because MOUNT was designed to handle the authentication and authorization question ("is this client allowed to access this export?") while NFS was designed to handle file operations without re-asking that question on every call. The result is a one-time gate: MOUNT checks your credentials once, hands you a handle, and NFS trusts whoever holds the handle from that point forward.
Why MOUNT is separate from NFS¶
RFC 1094 Appendix A.1 explains the rationale:
The mount protocol is kept separate from the NFS protocol to make it easy to plug in new access checking and validation methods without changing the NFS server protocol.
The intent was modularity. MOUNT would evolve its access control independently, without touching NFS wire formats. In practice, this separation created a security gap that has never been closed: MOUNT authorizes export access at mount time, but NFS never re-checks that authorization. The two protocols share a file handle and nothing else. There is no callback, no session token, no lease that ties them together.
This architectural split means:
- MOUNT is stateful (it tracks which clients are mounted) but its state is advisory and unenforceable
- NFS is stateless (in v2/v3) and treats every request independently, using only the file handle and AUTH_SYS credentials
- Revoking access requires restarting nfsd, because there is no mechanism to invalidate handles already issued
NFSv4 eliminates MOUNT
NFSv4 (RFC 7530) merges MOUNT's functionality into the NFS protocol itself, using PUTROOTFH to obtain root handles and SECINFO for per-path security negotiation. But any server that also exposes v2 or v3 still runs a MOUNT daemon, and the handles it issues work across all versions.
The MOUNT-to-NFS flow¶
A typical NFSv3 session requires three protocols cooperating. The client queries portmapper for the MOUNT daemon's port, calls MOUNT to get a root handle, then uses that handle for all NFS operations:
sequenceDiagram
participant C as Client
participant P as Portmapper<br>:111
participant M as mountd<br>:dynamic
participant N as nfsd<br>:2049
Note over C,P: Phase 1: Service discovery
C->>P: GETPORT(program=100005, version=3)
P-->>C: port 36801
Note over C,M: Phase 2: Export access
C->>M: MNT("/srv/nfs/data")
M-->>C: root file handle + auth flavors [1, 6]
Note over C,N: Phase 3: File operations (handle is the key)
C->>N: GETATTR(handle)
N-->>C: file attributes
C->>N: READDIRPLUS(handle)
N-->>C: directory entries + child handles
C->>N: READ(child_handle)
N-->>C: file data
Note over C,M: Later: client unmounts
C->>M: UMNT("/srv/nfs/data")
M-->>C: OK (mount list entry removed)
Note over C,N: But the handle still works
C->>N: READ(handle)
N-->>C: file data (handle is still valid)
The final exchange is the critical security observation. After UMNT, the client is "unmounted" according to MOUNT's bookkeeping, but the NFS daemon has no knowledge of this. The handle continues to work indefinitely.
MOUNT v3 procedures¶
MOUNT v3 (RFC 1813 Appendix I) defines six procedures on RPC program 100005, version 3:
| # | Procedure | Input | Output | Auth required |
|---|---|---|---|---|
| 0 | NULL |
void | void | None |
| 1 | MNT |
dirpath | fhandle3 + auth_flavors | AUTH_UNIX or better |
| 2 | DUMP |
void | mountlist | None |
| 3 | UMNT |
dirpath | void | AUTH_UNIX or better |
| 4 | UMNTALL |
void | void | AUTH_UNIX or better |
| 5 | EXPORT |
void | exports | None |
Procedure 0: NULL -- Do nothing¶
Standard RPC liveness check. Takes no arguments, returns nothing. Useful for confirming the MOUNT daemon is responding without triggering any access control. The spec states that NULL "should never require any authentication" (RFC 1813 Appendix I, Section 5.2.0), though implementations may diverge.
Procedure 1: MNT -- Get root handle¶
MNT is the central procedure. It takes an export path (e.g., /srv/nfs/data) and returns the root file handle for that directory plus a list of authentication flavors the export accepts.
Input: dirpath -- the export path (string, max 1024 bytes)
Output: mountres3 -- discriminated union:
MNT3_OK:
fhandle3 -- variable-length handle (up to 64 bytes)
auth_flavors -- array of integers (1=AUTH_SYS, 6=RPCSEC_GSS, etc.)
Error:
mountstat3 -- error code (PERM, NOENT, ACCES, etc.)
The auth_flavors list is a security indicator. nfswolf's analyzer uses it to detect:
- AUTH_SYS (1) only -- the export has no cryptographic protection (F-1.1)
- AUTH_SYS alongside RPCSEC_GSS -- Kerberos is available but optional, enabling flavor downgrade (F-1.7)
- RPCSEC_GSS only -- the export requires Kerberos; AUTH_SYS attempts will get AUTH_TOOWEAK (F-1.8)
- AUTH_DH (3) -- the export accepts the cryptographically broken DES-based authentication (F-3.7)
- AUTH_NONE (0) -- the export accepts completely unauthenticated requests (F-5.8)
MNT returns a bearer token
The file handle returned by MNT is a bearer token (RFC 2623 Section 2.6). Once issued, it works for any client, with any credentials, from any IP address, indefinitely. The server never checks whether the presenter was the original recipient. If the export ACL is changed to remove the client's IP after MNT succeeds, the handle remains valid until the server restarts or the underlying inode is deleted. This is finding F-2.1.
Procedure 2: DUMP -- List active mounts¶
DUMP returns all currently mounted clients with their export paths. Each entry contains a hostname (or IP) and the directory path they mounted.
Output: mountlist -- linked list of:
ml_hostname -- client name (string, max 255 bytes)
ml_directory -- mounted export path (string, max 1024 bytes)
RFC 1094 Appendix A describes the mount list as "intended for advisory use only." No authentication is required to call DUMP.
Information leak
DUMP reveals which machines are actively using NFS: hostnames, IP addresses, and the specific exports they have mounted. This identifies potential targets for credential theft or lateral movement. A client mounting /home is a higher-value target than one mounting /usr/share/docs. nfswolf retrieves DUMP output from both MOUNT v3 and v1 via dump_clients and dump_clients_v1 in src/proto/mount.rs.
Procedure 3: UMNT -- Remove mount entry¶
UMNT removes a single entry from the server's mount list for the calling client.
UMNT modifies only MOUNT's internal bookkeeping. It does not invalidate the file handle. It does not notify the NFS daemon. It does not revoke access. The handle obtained from MNT continues to work after UMNT completes. RFC 1094 Appendix A.1 is explicit: "The mount list information is not critical for the correct functioning of either the client or the server." See finding F-2.5.
Procedure 4: UMNTALL -- Remove all mount entries¶
UMNTALL removes all mount list entries for the calling client in one call, across all exports. Designed for client crash recovery: the client broadcasts UMNTALL to all known servers on startup to clean up stale mount entries.
Like UMNT, this is purely cosmetic. No handles are invalidated.
Procedure 5: EXPORT -- List all exports¶
EXPORT returns every exported filesystem on the server along with the hosts or networks allowed to mount each one.
Output: exports -- linked list of:
ex_dir -- export path (string)
ex_groups -- linked list of allowed host/network names
No authentication is required. Any host that can reach the MOUNT daemon gets the complete export topology. This is the primary reconnaissance procedure for NFS attacks (F-5.1).
- Every exported path (e.g.,
/,/home,/srv/nfs/data) - The allowed hosts or networks for each export (e.g.,
*.example.com,10.0.0.0/24,*) - Wildcard exports accessible from any IP (F-7.1)
- Subdirectory exports on the same filesystem, vulnerable to lateral access (F-2.8)
- The overall NFS topology of the target
- Export options (
ro,root_squash,no_subtree_check,sec=) - Whether the export is on a separate filesystem or a bind mount
- The filesystem type (ext4, XFS, BTRFS, etc.)
MOUNT v1 vs v3 differences¶
MOUNT v1 (RFC 1094 Appendix A) was designed alongside NFSv2. MOUNT v3 (RFC 1813 Appendix I) was introduced with NFSv3. The wire format differences have direct security implications:
| Feature | MOUNT v1 | MOUNT v3 |
|---|---|---|
| Handle format | Fixed 32 bytes (fhandle) |
Variable, up to 64 bytes (fhandle3) |
| Handle return type | fhstatus (handle or error) |
mountres3 (handle + auth_flavors, or error) |
| Auth flavor list | Not returned | Returned with every MNT |
| Error codes | UNIX errno values | Dedicated mountstat3 enum |
| EXPORT wire format | Identical | Identical |
| DUMP wire format | Identical | Identical |
| RPC program / port | 100005 / dynamic | 100005 / dynamic |
| Paired NFS version | NFSv2 | NFSv3 |
Why the differences matter for attacks¶
Handle size constrains escape. MOUNT v1's 32-byte handles fit fewer fsid bytes, so the kernel uses compact fsid_types (0, 3, 4) instead of the full-UUID types (6, 7). Escape handles must also fit in 32 bytes, limiting which fileid_types are usable. See File Handles for the complete layout.
No auth flavor list enables blind downgrade. MOUNT v1 MNT returns only a handle and a status code, with no indication of what authentication the export requires. A client that mounts via v1 has no way to discover that the export expects Kerberos. On Linux knfsd, MOUNT v1 issues the handle even on sec=krb5 exports; subsequent NFSv2 operations fail with AUTH_TOOWEAK, but the handle has already been leaked (F-1.6).
Error code granularity. MOUNT v1 returns raw UNIX errno values (e.g., 13 for EACCES). MOUNT v3 defines a dedicated mountstat3 enum with NFS-specific error codes (MNT3ERR_ACCES, MNT3ERR_NOTSUPP, MNT3ERR_SERVERFAULT). The v3 errors are more precise for automated tooling.
nfswolf's escape pipeline uses both versions
The escape pipeline gathers seed handles from MOUNT v3, MOUNT v1, and NFSv4 LOOKUP. The v1 path is especially valuable against krb5-protected exports where v1 leaks the handle without enforcing the auth flavor requirement.
Security implications¶
The MOUNT-NFS disconnect¶
The fundamental weakness of the MOUNT protocol is that it authorizes export access exactly once, and then NFS operates without any reference to that authorization. There is no session, no lease, no token; just an opaque byte sequence that the NFS daemon trusts at face value.
This disconnect manifests in several concrete ways:
- MOUNT checks the client's IP against the export ACL. NFS never checks it again. If the ACL changes after MNT, the handle remains valid.
- MOUNT records the mount in its list. NFS has no access to this list and never queries it.
- UMNT removes the list entry. NFS is not notified and the handle keeps working.
- MOUNT can require AUTH_UNIX or better. NFS enforces AUTH_SYS on a per-call basis but does not verify that a valid MOUNT preceded the NFS call.
Concrete attack: access after ACL removal
- Attacker mounts
/srv/nfs/datafrom 10.0.0.50 via MOUNT MNT. Gets handle0x010007.... - Administrator removes 10.0.0.50 from the export ACL and runs
exportfs -rto reload. - New MOUNT MNT attempts from 10.0.0.50 are denied (MNT3ERR_ACCES).
- Attacker continues using the existing handle for NFS READ/WRITE operations. The NFS daemon accepts every request because it has no concept of export ACL changes.
- The handle remains valid until the server is fully restarted or the underlying filesystem is unmounted.
Export ACLs are IP-based¶
MOUNT's access control for MNT is based on the client's source IP address, matched against the export's allowed-hosts list. This has two weaknesses:
- IP spoofing. On L2-adjacent networks, source IP can be spoofed. MOUNT's UDP listener is particularly vulnerable because UDP has no handshake. TCP-based spoofing is harder but not impossible in certain network positions.
- IP is not identity. On shared infrastructure, multiple users or containers share an IP. The export ACL cannot distinguish between them. Any process on an allowed IP can mount any export that IP is authorized for.
The export ACL is enforced only at MNT time. The handle returned by MNT carries no record of which IP obtained it and works from any address.
MOUNT leaks the full export topology¶
Both EXPORT and DUMP require no authentication. Any client that can reach the MOUNT daemon's port gets:
- From EXPORT: every exported path and its allowed networks (the complete NFS topology)
- From DUMP: every currently connected client hostname and the export they mounted
This is the primary reconnaissance step in an NFS attack. The export list identifies wildcard exports, root-exported filesystems, subdirectory exports on shared partitions, and the general security posture of the server. The DUMP list identifies active NFS clients worth targeting for credential theft or impersonation.
Auth flavor enumeration reveals security posture¶
MOUNT v3 MNT returns the list of authentication flavors each export accepts. This is not sensitive by itself, but it tells the attacker exactly what security mechanisms are in play before a single NFS call is made.
| Flavor list | What it means |
|---|---|
[1] (AUTH_SYS only) |
No cryptographic protection. Full UID/GID spoofing possible. |
[1, 6] (AUTH_SYS + RPCSEC_GSS) |
Kerberos available but optional. Downgrade to AUTH_SYS trivial. |
[6] (RPCSEC_GSS only) |
Kerberos enforced. AUTH_SYS rejected with AUTH_TOOWEAK. |
[1, 3] (AUTH_SYS + AUTH_DH) |
DES-based auth available. Cryptographically broken. |
[0, 1] (AUTH_NONE + AUTH_SYS) |
Accepts completely unauthenticated requests. |
UMNT does not revoke access¶
RFC 1094 Appendix A.1 is unambiguous:
The mount list information is not critical for the correct functioning of either the client or the server. It is intended for advisory use only.
UMNT and UMNTALL modify this advisory list and nothing else. An administrator who removes a client from the mount list (via umount on the client, exportfs -u on the server, or UMNT/UMNTALL calls) has not revoked that client's access. The file handle continues to work until the NFS server process is restarted or the underlying filesystem is unmounted. This is a common operational misconception and a reliable attack persistence mechanism (F-2.5).
nfswolf implementation¶
The nfs-mount crate implements MOUNT v1 and v3 as standalone protocol clients, separated from nfswolf's policy layer.
| Component | What it does |
|---|---|
crates/nfs-mount/src/ |
Wire types and MountClient (v3) / MountV1Client (v1): NULL, MNT, UMNT, DUMP, UMNTALL, EXPORT |
src/proto/mount.rs |
NfsMountClient: policy layer wrapping nfs-mount, with dump_clients, dump_clients_v1, unmount_v1, auth-flavor extraction |
src/cli/escape.rs |
Escape pipeline Phase 1 gathers seed handles from MOUNT v3 MNT, MOUNT v1 MNT, and NFSv4 LOOKUP |
src/engine/scanner.rs |
Scanner calls MOUNT EXPORT and DUMP during Phase 3 (data collection) |
src/engine/analyzer.rs |
Analyzer checks auth flavors returned by MNT for security findings |
use nfs_mount::MountV1Client;
// MOUNT v1: get a 32-byte NFSv2 handle
let mut mount = MountV1Client::new(transport);
let fhstatus = mount.mnt("/export").await?;
// MOUNT v3: get a variable handle + auth flavors
use nfs_v3::MountClient;
let mount = MountClient::new(transport);
let res = mount.mnt(dirpath(Opaque::borrowed(b"/export"))).await?;
// res.fhandle -> root handle (up to 64 bytes)
// res.auth_flavors -> vec of accepted auth mechanisms
For the complete list of MOUNT-related security findings, see MOUNT Findings.