Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

NFSD: Sign filehandles

NFS clients may bypass restrictive directory permissions by using
open_by_handle() (or other available OS system call) to guess the
filehandles for files below that directory.

In order to harden knfsd servers against this attack, create a method to
sign and verify filehandles using SipHash-2-4 as a MAC (Message
Authentication Code). According to
https://cr.yp.to/siphash/siphash-20120918.pdf, SipHash can be used as a
MAC, and our use of SipHash-2-4 provides a low 1 in 2^64 chance of forgery.

Filehandles that have been signed cannot be tampered with, nor can
clients reasonably guess correct filehandles and hashes that may exist in
parts of the filesystem they cannot access due to directory permissions.

Append the 8 byte SipHash to encoded filehandles for exports that have set
the "sign_fh" export option. Filehandles received from clients are
verified by comparing the appended hash to the expected hash. If the MAC
does not match the server responds with NFS error _STALE. If unsigned
filehandles are received for an export with "sign_fh" they are rejected
with NFS error _STALE.

Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>

authored by

Benjamin Coddington and committed by
Chuck Lever
2a83ffc5 a002ad8a

+157 -5
+85
Documentation/filesystems/nfs/exporting.rst
··· 206 206 all of an inode's dirty data on last close. Exports that behave this 207 207 way should set EXPORT_OP_FLUSH_ON_CLOSE so that NFSD knows to skip 208 208 waiting for writeback when closing such files. 209 + 210 + Signed Filehandles 211 + ------------------ 212 + 213 + To protect against filehandle guessing attacks, the Linux NFS server can be 214 + configured to sign filehandles with a Message Authentication Code (MAC). 215 + 216 + Standard NFS filehandles are often predictable. If an attacker can guess 217 + a valid filehandle for a file they do not have permission to access via 218 + directory traversal, they may be able to bypass path-based permissions 219 + (though they still remain subject to inode-level permissions). 220 + 221 + Signed filehandles prevent this by appending a MAC to the filehandle 222 + before it is sent to the client. Upon receiving a filehandle back from a 223 + client, the server re-calculates the MAC using its internal key and 224 + verifies it against the one provided. If the signatures do not match, 225 + the server treats the filehandle as invalid (returning NFS[34]ERR_STALE). 226 + 227 + Note that signing filehandles provides integrity and authenticity but 228 + not confidentiality. The contents of the filehandle remain visible to 229 + the client; they simply cannot be forged or modified. 230 + 231 + Configuration 232 + ~~~~~~~~~~~~~ 233 + 234 + To enable signed filehandles, the administrator must provide a signing 235 + key to the kernel and enable the "sign_fh" export option. 236 + 237 + 1. Providing a Key 238 + The signing key is managed via the nfsd netlink interface. This key 239 + is per-network-namespace and must be set before any exports using 240 + "sign_fh" become active. 241 + 242 + 2. Export Options 243 + The feature is controlled on a per-export basis in /etc/exports: 244 + 245 + sign_fh 246 + Enables signing for all filehandles generated under this export. 247 + 248 + no_sign_fh 249 + (Default) Disables signing. 250 + 251 + Key Management and Rotation 252 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 253 + 254 + The security of this mechanism relies entirely on the secrecy of the 255 + signing key. 256 + 257 + Initial Setup: 258 + The key should be generated using a high-quality random source and 259 + loaded early in the boot process or during the nfs-server startup 260 + sequence. 261 + 262 + Changing Keys: 263 + If a key is changed while clients have active mounts, existing 264 + filehandles held by those clients will become invalid, resulting in 265 + "Stale file handle" errors on the client side. 266 + 267 + Safe Rotation: 268 + Currently, there is no mechanism for "graceful" key rotation 269 + (maintaining multiple valid keys). Changing the key is an atomic 270 + operation that immediately invalidates all previous signatures. 271 + 272 + Transitioning Exports 273 + ~~~~~~~~~~~~~~~~~~~~~ 274 + 275 + When adding or removing the "sign_fh" flag from an active export, the 276 + following behaviors should be expected: 277 + 278 + +-------------------+---------------------------------------------------+ 279 + | Change | Result for Existing Clients | 280 + +===================+===================================================+ 281 + | Adding sign_fh | Clients holding unsigned filehandles will find | 282 + | | them rejected, as the server now expects a | 283 + | | signature. | 284 + +-------------------+---------------------------------------------------+ 285 + | Removing sign_fh | Clients holding signed filehandles will find them | 286 + | | rejected, as the server now expects the | 287 + | | filehandle to end at its traditional boundary | 288 + | | without a MAC. | 289 + +-------------------+---------------------------------------------------+ 290 + 291 + Because filehandles are often cached persistently by clients, adding or 292 + removing this option should generally be done during a scheduled maintenance 293 + window involving a NFS client unmount/remount.
+1 -1
fs/nfsd/Kconfig
··· 7 7 select CRC32 8 8 select CRYPTO_LIB_MD5 if NFSD_LEGACY_CLIENT_TRACKING 9 9 select CRYPTO_LIB_SHA256 if NFSD_V4 10 + select CRYPTO # required by RPCSEC_GSS_KRB5 and signed filehandles 10 11 select LOCKD 11 12 select SUNRPC 12 13 select EXPORTFS ··· 79 78 depends on NFSD && PROC_FS 80 79 select FS_POSIX_ACL 81 80 select RPCSEC_GSS_KRB5 82 - select CRYPTO # required by RPCSEC_GSS_KRB5 83 81 select GRACE_PERIOD 84 82 select NFS_V4_2_SSC_HELPER if NFS_V4_2 85 83 help
+70 -4
fs/nfsd/nfsfh.c
··· 11 11 #include <linux/exportfs.h> 12 12 13 13 #include <linux/sunrpc/svcauth_gss.h> 14 + #include <crypto/utils.h> 14 15 #include "nfsd.h" 15 16 #include "vfs.h" 16 17 #include "auth.h" ··· 141 140 return nfs_ok; 142 141 } 143 142 143 + /* Size of a file handle MAC, in 4-octet words */ 144 + #define FH_MAC_WORDS (sizeof(__le64) / 4) 145 + 146 + static bool fh_append_mac(struct svc_fh *fhp, struct net *net) 147 + { 148 + struct nfsd_net *nn = net_generic(net, nfsd_net_id); 149 + struct knfsd_fh *fh = &fhp->fh_handle; 150 + siphash_key_t *fh_key = nn->fh_key; 151 + __le64 hash; 152 + 153 + if (!fh_key) 154 + goto out_no_key; 155 + if (fh->fh_size + sizeof(hash) > fhp->fh_maxsize) 156 + goto out_no_space; 157 + 158 + hash = cpu_to_le64(siphash(&fh->fh_raw, fh->fh_size, fh_key)); 159 + memcpy(&fh->fh_raw[fh->fh_size], &hash, sizeof(hash)); 160 + fh->fh_size += sizeof(hash); 161 + return true; 162 + 163 + out_no_key: 164 + pr_warn_ratelimited("NFSD: unable to sign filehandles, fh_key not set.\n"); 165 + return false; 166 + 167 + out_no_space: 168 + pr_warn_ratelimited("NFSD: unable to sign filehandles, fh_size %zu would be greater than fh_maxsize %d.\n", 169 + fh->fh_size + sizeof(hash), fhp->fh_maxsize); 170 + return false; 171 + } 172 + 173 + /* 174 + * Verify that the filehandle's MAC was hashed from this filehandle 175 + * given the server's fh_key: 176 + */ 177 + static bool fh_verify_mac(struct svc_fh *fhp, struct net *net) 178 + { 179 + struct nfsd_net *nn = net_generic(net, nfsd_net_id); 180 + struct knfsd_fh *fh = &fhp->fh_handle; 181 + siphash_key_t *fh_key = nn->fh_key; 182 + __le64 hash; 183 + 184 + if (!fh_key) { 185 + pr_warn_ratelimited("NFSD: unable to verify signed filehandles, fh_key not set.\n"); 186 + return false; 187 + } 188 + 189 + hash = cpu_to_le64(siphash(&fh->fh_raw, fh->fh_size - sizeof(hash), fh_key)); 190 + return crypto_memneq(&fh->fh_raw[fh->fh_size - sizeof(hash)], 191 + &hash, sizeof(hash)) == 0; 192 + } 193 + 144 194 /* 145 195 * Use the given filehandle to look up the corresponding export and 146 196 * dentry. On success, the results are used to set fh_export and ··· 288 236 /* 289 237 * Look up the dentry using the NFS file handle. 290 238 */ 291 - error = nfserr_badhandle; 292 - 293 239 fileid_type = fh->fh_fileid_type; 240 + error = nfserr_stale; 294 241 295 - if (fileid_type == FILEID_ROOT) 242 + if (fileid_type == FILEID_ROOT) { 243 + /* We don't sign or verify the root, no per-file identity */ 296 244 dentry = dget(exp->ex_path.dentry); 297 - else { 245 + } else { 246 + if (exp->ex_flags & NFSEXP_SIGN_FH) { 247 + if (!fh_verify_mac(fhp, net)) { 248 + trace_nfsd_set_fh_dentry_badmac(rqstp, fhp, -ESTALE); 249 + goto out; 250 + } 251 + data_left -= FH_MAC_WORDS; 252 + } 253 + 298 254 dentry = exportfs_decode_fh_raw(exp->ex_path.mnt, fid, 299 255 data_left, fileid_type, 0, 300 256 nfsd_acceptable, exp); ··· 318 258 } 319 259 } 320 260 } 261 + 262 + error = nfserr_badhandle; 321 263 if (dentry == NULL) 322 264 goto out; 323 265 if (IS_ERR(dentry)) { ··· 560 498 fhp->fh_handle.fh_fileid_type = 561 499 fileid_type > 0 ? fileid_type : FILEID_INVALID; 562 500 fhp->fh_handle.fh_size += maxsize * 4; 501 + 502 + if (exp->ex_flags & NFSEXP_SIGN_FH) 503 + if (!fh_append_mac(fhp, exp->cd->net)) 504 + fhp->fh_handle.fh_fileid_type = FILEID_INVALID; 563 505 } else { 564 506 fhp->fh_handle.fh_fileid_type = FILEID_ROOT; 565 507 }
+1
fs/nfsd/trace.h
··· 373 373 374 374 DEFINE_NFSD_FH_ERR_EVENT(set_fh_dentry_badexport); 375 375 DEFINE_NFSD_FH_ERR_EVENT(set_fh_dentry_badhandle); 376 + DEFINE_NFSD_FH_ERR_EVENT(set_fh_dentry_badmac); 376 377 377 378 TRACE_EVENT(nfsd_exp_find_key, 378 379 TP_PROTO(const struct svc_expkey *key,