The NFS Protocol Stack¶
NFS is not one protocol. It is a stack of five or more distinct protocols, each with its own RPC program number, wire format, and security properties (or lack thereof).
Every NFS operation (reading a file, listing a directory, creating a symlink) passes through multiple protocol layers before reaching the wire. Understanding this stack is essential for security work because vulnerabilities exist at every layer, and because the layers interact in ways that undermine each other's security guarantees. A Kerberos-secured NFS session still relies on plaintext portmapper queries. A root_squash export still hands out bearer-token file handles to anonymous clients.
This page walks through the stack from the bottom (serialization) to the top (file operations), explains what each layer does and what it fails to protect, and maps every layer to its governing RFC and its role in nfswolf's attack chain.
The full stack¶
graph TB
subgraph Application Layer
NFS["NFS<br>Program 100003<br>v2 / v3 / v4<br>Port 2049"]
MOUNT["MOUNT<br>Program 100005<br>v1 / v3<br>Dynamic port"]
SIDEBAND["Sideband RPC<br>NFS_ACL (100227)<br>RQUOTA (100011)<br>NLM (100021)"]
end
subgraph Service Discovery
PORTMAP["Portmapper / rpcbind<br>Program 100000<br>v2 / v3 / v4<br>Port 111"]
end
subgraph RPC Framework
ONCRPC["ONC RPC v2<br>RFC 5531<br>Call/reply framing,<br>authentication slots"]
end
subgraph Serialization
XDR["XDR<br>RFC 4506<br>Binary encoding"]
end
subgraph Transport
TCP["TCP"]
UDP["UDP"]
end
NFS --> ONCRPC
MOUNT --> ONCRPC
SIDEBAND --> ONCRPC
PORTMAP --> ONCRPC
ONCRPC --> XDR
XDR --> TCP
XDR --> UDP
style NFS fill:#1a1a2e,stroke:#e94560,color:#fff
style MOUNT fill:#1a1a2e,stroke:#e94560,color:#fff
style SIDEBAND fill:#1a1a2e,stroke:#0f3460,color:#fff
style PORTMAP fill:#1a1a2e,stroke:#0f3460,color:#fff
style ONCRPC fill:#1a1a2e,stroke:#16213e,color:#fff
style XDR fill:#1a1a2e,stroke:#16213e,color:#fff
style TCP fill:#1a1a2e,stroke:#533483,color:#fff
style UDP fill:#1a1a2e,stroke:#533483,color:#fff
Layer-by-layer breakdown¶
Layer 1: XDR -- External Data Representation¶
XDR (RFC 4506) is the serialization format used by every protocol in the stack. It encodes integers, strings, arrays, discriminated unions, and opaque byte sequences into a platform-independent binary format. All multi-byte values are big-endian. All values are padded to 4-byte boundaries.
XDR is a pure encoding layer: it has no concept of security, authentication, or integrity. It faithfully serializes whatever the caller provides, including forged credentials, crafted file handles, and malformed data. The serialization layer is not where attacks happen, but it is worth understanding because every field in every protocol above is XDR-encoded.
nfswolf implementation
The onc-xdr crate implements XDR encoding and decoding with the Pack and Unpack traits. The onc-xdr-derive crate provides #[derive(XdrCodec)] for automatic code generation from Rust structs. See ONC XDR for the full protocol reference.
Security properties: None. XDR is a data format, not a security mechanism.
Layer 2: ONC RPC -- Open Network Computing Remote Procedure Call¶
ONC RPC v2 (RFC 5531, originally RFC 1831) provides the call/reply framing that every service in the NFS stack uses. Each RPC message contains:
- A program number identifying the service (e.g., 100003 for NFS, 100005 for MOUNT)
- A version number (e.g., 2, 3, or 4 for NFS)
- A procedure number (e.g., 6 for READ in NFSv3)
- A credential and verifier pair for authentication
- The procedure arguments or reply data
The RPC layer carries authentication but does not enforce it. The credential and verifier are opaque byte sequences that the RPC layer passes through to the service implementation. The service decides whether to trust them.
AUTH_SYS: the default authentication flavor
The dominant credential type is AUTH_SYS (flavor 1), which carries plaintext UID/GID integers with no cryptographic verification. The server trusts whatever the client sends. See Authentication Model for the full AUTH_SYS and RPCSEC_GSS breakdown, XDR structures, and attack implications.
Security properties: ONC RPC defines authentication slots but provides no built-in security. AUTH_SYS is plaintext and trivially forged; RPCSEC_GSS (RFC 2203) adds Kerberos but is optional and can be downgraded (F-1.7).
nfswolf implementation
The onc-rpc-client crate implements the RPC call/reply framing, AuthSys credential construction, and the RpcTransport trait that separates wire I/O from protocol logic. See ONC RPC for the full protocol reference.
Layer 3: Portmapper / rpcbind -- service discovery¶
Portmapper (RFC 1057 Appendix A) and its successor rpcbind (RFC 1833) run on port 111 and map RPC program numbers to network ports. When an NFS client starts, it queries portmapper to learn where the MOUNT daemon is listening. Without portmapper, the client must know the MOUNT port in advance.
The service discovery flow for NFSv2/v3 looks like this:
sequenceDiagram
participant C as Client
participant P as Portmapper<br>:111
participant M as mountd<br>:dynamic
participant N as nfsd<br>:2049
C->>P: GETPORT(program=100005, version=3)
P-->>C: port 36801
C->>M: MNT("/export")
M-->>C: file handle + auth flavors
C->>N: GETATTR(handle)
N-->>C: file attributes
Portmapper is itself an RPC service (program 100000) with no authentication. Any host that can reach port 111 can query the full list of registered services, including their program numbers, versions, protocols, and ports. This is finding F-5.4: the portmapper acts as a free reconnaissance oracle.
Portmapper has no authentication
Any host that can reach port 111 can enumerate all registered RPC services, including over UDP where source IPs can be spoofed. See Portmapper for details.
Security properties: None. Portmapper is unauthenticated, unencrypted, and reveals the full RPC service map to any querier.
nfswolf implementation
The onc-rpcbind crate implements both portmapper v2 (GETPORT, DUMP) and rpcbind v3/v4 (GETTIME, GETSTAT). The scanner uses portmapper as the first phase of service discovery. See Portmapper and rpcbind for the full protocol reference.
Layer 4: MOUNT -- export access¶
The MOUNT protocol (v1: RFC 1094 Appendix A; v3: RFC 1813 Appendix I) converts a filesystem path (e.g., /export/data) into an opaque file handle. This handle is the key to everything -- every subsequent NFS operation takes a file handle as input. MOUNT is the only place in the v2/v3 protocol stack where human-readable path names appear.
MOUNT runs as a separate daemon (rpc.mountd) on a dynamic port, registered with portmapper. It provides:
- MNT -- Convert an export path to a root file handle
- EXPORT -- List all exported paths and their allowed hosts
- DUMP -- List all active mount entries (which clients hold which exports)
- UMNT / UMNTALL -- Bookkeeping for unmounting (advisory only, not enforced)
MOUNT grants bearer tokens
The file handle returned by MNT is a bearer token -- it works for any client, with any credentials, indefinitely (F-2.1). See File Handles for the full bearer-token analysis and MOUNT protocol for the wire-level reference.
MOUNT v3 returns an additional piece of information that v1 does not: the list of authentication flavors the export accepts. This is how nfswolf's analyzer determines whether an export requires Kerberos or accepts AUTH_SYS without attempting any file operations.
Security properties: MOUNT can enforce IP-based access control on MNT requests, but the resulting handles have no access control of their own. The EXPORT procedure reveals all export paths and ACLs to any querier. MOUNT v1 has no auth-flavor negotiation.
nfswolf implementation
The nfs-mount crate implements MOUNT v1 and v3. The escape pipeline gathers seed handles from both MOUNT versions. See MOUNT for the full protocol reference.
Layer 5: NFS -- file operations¶
The NFS protocol itself is the top of the stack. It provides the file operations: read, write, create, remove, rename, symlink, readdir, getattr, setattr, and more. Every operation takes a file handle as its primary input and returns data, attributes, or a new file handle.
18 procedures, fixed 32-byte handles, 2 GB file size limit, synchronous writes only, no auth-flavor negotiation. The simplest version and the one most likely to have weaker security enforcement due to its age and lack of security negotiation (RFC 2623 Section 2.7).
22 procedures, variable-length handles (up to 64 bytes), 64-bit file sizes, async writes, READDIRPLUS (handles + attrs in one call), ACCESS (permission probing), and the STALE/BADHANDLE oracle that makes handle brute-forcing efficient.
A fundamentally different architecture: single port (2049), no MOUNT, no portmapper, COMPOUND batching of operations, stateful sessions (SETCLIENTID, OPEN/CLOSE, LOCK), pseudo-filesystem for namespace traversal, in-band SECINFO for per-path security negotiation, and LOOKUPP for parent directory traversal (enabling export escape without handle manipulation).
The NFS layer enforces access control based on the AUTH_SYS credentials in the RPC header. On Linux knfsd, the server maps the client-supplied UID/GID to a local identity and checks standard Unix permission bits. This is the only access control in the entire stack, and it relies entirely on trusting the client's self-reported identity.
Security properties: Access control is enforced here, but depends on AUTH_SYS credentials that are trivially forged. File handles are bearer tokens that bypass path-based export boundaries.
nfswolf implementation
The nfs-v2, nfs-v3, and nfs-v4 crates implement the full wire protocol for each version. See NFSv2, NFSv3, and NFSv4 for the full protocol references.
Sideband RPC services¶
Beyond the core NFS/MOUNT/portmapper stack, several auxiliary RPC services extend NFS functionality. These run as separate daemons on dynamic ports and are discoverable through portmapper:
| Service | Program | Purpose | Security relevance |
|---|---|---|---|
| NFS_ACL | 100227 | POSIX ACL retrieval | Reveals fine-grained access control entries that may grant unexpected access |
| RQUOTA | 100011 | Disk quota queries | UID existence oracle (valid UIDs return quota data, invalid return errors) and filesystem block-size fingerprinting |
| NLM | 100021 | Network Lock Manager | Lock-based denial of service (out of scope in nfswolf) |
| NSM | 100024 | Network Status Monitor | Crash recovery notifications for NLM |
| NFSSTAT | 100249 | Server statistics | Information disclosure (operation counts, error rates) |
nfswolf implementation
nfswolf implements NFS_ACL and RQUOTA clients for POSIX ACL enumeration and UID oracle attacks. NLM and NSM were removed in v0.2.0. See NFS_ACL and RQUOTA for details.
Protocol-to-RFC mapping¶
| Layer | Protocol | RFC | Port | Auth | nfswolf crate |
|---|---|---|---|---|---|
| Serialization | XDR | RFC 4506 | -- | None | onc-xdr |
| RPC framework | ONC RPC v2 | RFC 5531 | -- | AUTH_SYS / RPCSEC_GSS | onc-rpc-client |
| Service discovery | Portmapper v2 | RFC 1057 App. A | 111 | None | onc-rpcbind |
| Service discovery | rpcbind v3/v4 | RFC 1833 | 111 | None | onc-rpcbind |
| Export access | MOUNT v1 | RFC 1094 App. A | Dynamic | IP-based ACL | nfs-mount |
| Export access | MOUNT v3 | RFC 1813 App. I | Dynamic | IP-based ACL | nfs-mount |
| File operations | NFSv2 | RFC 1094 | 2049 | AUTH_SYS | nfs-v2 |
| File operations | NFSv3 | RFC 1813 | 2049 | AUTH_SYS / RPCSEC_GSS | nfs-v3 |
| File operations | NFSv4.0 | RFC 7530 | 2049 | AUTH_SYS / RPCSEC_GSS | nfs-v4 |
| POSIX ACLs | NFS_ACL | Linux-specific | Dynamic | AUTH_SYS | src/proto/nfs_acl.rs |
| Quotas | RQUOTA | Sun RPC | Dynamic | AUTH_SYS | src/proto/rquota.rs |
How the layers fail together¶
The most important thing about the NFS protocol stack from a security perspective is that the layers do not compose securely. Each layer has its own security model (or lack thereof), and weaknesses at one layer undermine protections at another.
Cross-layer security failures
- Portmapper reveals what MOUNT tries to hide. DUMP exposes the full service map to any querier, even when MNT enforces IP ACLs.
- MOUNT grants what NFS cannot revoke. MNT returns bearer-token handles that work forever, regardless of subsequent ACL changes.
- NFS trusts what RPC cannot verify. Access control depends entirely on AUTH_SYS credentials that any network client can forge.
- File handles bypass what export paths enforce. A crafted handle resolves to any inode on the filesystem; see export escape.
- NFSv4 eliminates MOUNT but inherits AUTH_SYS. Fewer services to probe, but AUTH_SYS is still accepted by default.
NFSv4: a different architecture¶
NFSv4 deserves special mention because it restructures the stack. Instead of three separate services on multiple ports, NFSv4 collapses everything into a single TCP connection on port 2049:
graph LR
subgraph "NFSv2/v3 (3 services, 2-3 ports)"
PM2["Portmapper<br>:111"] --> MT2["MOUNT<br>:dynamic"]
MT2 --> NF2["NFS<br>:2049"]
end
subgraph "NFSv4 (1 service, 1 port)"
NF4["NFS v4<br>:2049<br>PUTROOTFH + COMPOUND"]
end
style PM2 fill:#1a1a2e,stroke:#0f3460,color:#fff
style MT2 fill:#1a1a2e,stroke:#0f3460,color:#fff
style NF2 fill:#1a1a2e,stroke:#e94560,color:#fff
style NF4 fill:#1a1a2e,stroke:#e94560,color:#fff
NFSv4 replaces MOUNT with PUTROOTFH (get the root handle directly), replaces portmapper with a fixed port, replaces EXPORT listing with pseudo-filesystem traversal, and adds in-band security negotiation via SECINFO. This reduces the attack surface in some ways (fewer services to probe, no unauthenticated portmapper) but retains the fundamental AUTH_SYS weakness.
For the full NFSv4 protocol reference, see NFSv4.
nfswolf's relationship to the stack¶
nfswolf implements every layer of the NFS protocol stack in pure Rust, with no C dependencies and no kernel NFS client. This gives it complete control over every field in every protocol message, including the fields that legitimate NFS clients never let users touch: AUTH_SYS credentials, file handle bytes, and RPC stamps.
The implementation is split into 8 workspace crates, one per protocol layer, plus the nfswolf binary that wires them together with connection pooling, circuit breaking, stealth pacing, and credential escalation. The crate boundary enforces a strict separation: protocol crates encode and decode wire formats; the binary adds security-testing policy.
For details on how nfswolf uses each protocol layer offensively, see the NFSWolf tab and the Security tab.