Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ed941643 authored by Andrew Elble's avatar Andrew Elble Committed by J. Bruce Fields
Browse files

nfsd: implement machine credential support for some operations



This addresses the conundrum referenced in RFC5661 18.35.3,
and will allow clients to return state to the server using the
machine credentials.

The biggest part of the problem is that we need to allow the client
to send a compound op with integrity/privacy on mounts that don't
have it enabled.

Add server support for properly decoding and using spo_must_enforce
and spo_must_allow bits. Add support for machine credentials to be
used for CLOSE, OPEN_DOWNGRADE, LOCKU, DELEGRETURN,
and TEST/FREE STATEID.
Implement a check so as to not throw WRONGSEC errors when these
operations are used if integrity/privacy isn't turned on.

Without this, Linux clients with credentials that expired while holding
delegations were getting stuck in an endless loop.

Signed-off-by: default avatarAndrew Elble <aweits@rit.edu>
Reviewed-by: default avatarJeff Layton <jlayton@redhat.com>
Signed-off-by: default avatarJ. Bruce Fields <bfields@redhat.com>
parent dedeb13f
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -954,6 +954,16 @@ __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)
		    rqstp->rq_cred.cr_flavor == RPC_AUTH_UNIX)
			return 0;
	}

	/* If the compound op contains a spo_must_allowed op,
	 * it will be sent with integrity/protection which
	 * will have to be expressly allowed on mounts that
	 * don't support it
	 */

	if (nfsd4_spo_must_allow(rqstp))
		return 0;

	return nfserr_wrongsec;
}

+39 −0
Original line number Diff line number Diff line
@@ -2335,6 +2335,45 @@ static struct nfsd4_operation nfsd4_ops[] = {
	},
};

/**
 * nfsd4_spo_must_allow - Determine if the compound op contains an
 * operation that is allowed to be sent with machine credentials
 *
 * @rqstp: a pointer to the struct svc_rqst
 *
 * Checks to see if the compound contains a spo_must_allow op
 * and confirms that it was sent with the proper machine creds.
 */

bool nfsd4_spo_must_allow(struct svc_rqst *rqstp)
{
	struct nfsd4_compoundres *resp = rqstp->rq_resp;
	struct nfsd4_compoundargs *argp = rqstp->rq_argp;
	struct nfsd4_op *this = &argp->ops[resp->opcnt - 1];
	struct nfsd4_compound_state *cstate = &resp->cstate;
	struct nfs4_op_map *allow = &cstate->clp->cl_spo_must_allow;
	u32 opiter;

	if (!cstate->minorversion)
		return false;

	if (cstate->spo_must_allowed == true)
		return true;

	opiter = resp->opcnt;
	while (opiter < argp->opcnt) {
		this = &argp->ops[opiter++];
		if (test_bit(this->opnum, allow->u.longs) &&
			cstate->clp->cl_mach_cred &&
			nfsd4_mach_creds_match(cstate->clp, rqstp)) {
			cstate->spo_must_allowed = true;
			return true;
		}
	}
	cstate->spo_must_allowed = false;
	return false;
}

int nfsd4_max_reply(struct svc_rqst *rqstp, struct nfsd4_op *op)
{
	struct nfsd4_operation *opdesc;
+18 −0
Original line number Diff line number Diff line
@@ -2388,6 +2388,22 @@ nfsd4_exchange_id(struct svc_rqst *rqstp,

	switch (exid->spa_how) {
	case SP4_MACH_CRED:
		exid->spo_must_enforce[0] = 0;
		exid->spo_must_enforce[1] = (
			1 << (OP_BIND_CONN_TO_SESSION - 32) |
			1 << (OP_EXCHANGE_ID - 32) |
			1 << (OP_CREATE_SESSION - 32) |
			1 << (OP_DESTROY_SESSION - 32) |
			1 << (OP_DESTROY_CLIENTID - 32));

		exid->spo_must_allow[0] &= (1 << (OP_CLOSE) |
					1 << (OP_OPEN_DOWNGRADE) |
					1 << (OP_LOCKU) |
					1 << (OP_DELEGRETURN));

		exid->spo_must_allow[1] &= (
					1 << (OP_TEST_STATEID - 32) |
					1 << (OP_FREE_STATEID - 32));
		if (!svc_rqst_integrity_protected(rqstp)) {
			status = nfserr_inval;
			goto out_nolock;
@@ -2473,6 +2489,8 @@ nfsd4_exchange_id(struct svc_rqst *rqstp,
			goto out;
	}
	new->cl_minorversion = cstate->minorversion;
	new->cl_spo_must_allow.u.words[0] = exid->spo_must_allow[0];
	new->cl_spo_must_allow.u.words[1] = exid->spo_must_allow[1];

	gen_clid(new, nn);
	add_to_unconfirmed(new);
+23 −28
Original line number Diff line number Diff line
@@ -1299,16 +1299,14 @@ nfsd4_decode_exchange_id(struct nfsd4_compoundargs *argp,
		break;
	case SP4_MACH_CRED:
		/* spo_must_enforce */
		READ_BUF(4);
		dummy = be32_to_cpup(p++);
		READ_BUF(dummy * 4);
		p += dummy;

		status = nfsd4_decode_bitmap(argp,
					exid->spo_must_enforce);
		if (status)
			goto out;
		/* spo_must_allow */
		READ_BUF(4);
		dummy = be32_to_cpup(p++);
		READ_BUF(dummy * 4);
		p += dummy;
		status = nfsd4_decode_bitmap(argp, exid->spo_must_allow);
		if (status)
			goto out;
		break;
	case SP4_SSV:
		/* ssp_ops */
@@ -3867,14 +3865,6 @@ nfsd4_encode_write(struct nfsd4_compoundres *resp, __be32 nfserr, struct nfsd4_w
	return nfserr;
}

static const u32 nfs4_minimal_spo_must_enforce[2] = {
	[1] = 1 << (OP_BIND_CONN_TO_SESSION - 32) |
	      1 << (OP_EXCHANGE_ID - 32) |
	      1 << (OP_CREATE_SESSION - 32) |
	      1 << (OP_DESTROY_SESSION - 32) |
	      1 << (OP_DESTROY_CLIENTID - 32)
};

static __be32
nfsd4_encode_exchange_id(struct nfsd4_compoundres *resp, __be32 nfserr,
			 struct nfsd4_exchange_id *exid)
@@ -3885,6 +3875,7 @@ nfsd4_encode_exchange_id(struct nfsd4_compoundres *resp, __be32 nfserr,
	char *server_scope;
	int major_id_sz;
	int server_scope_sz;
	int status = 0;
	uint64_t minor_id = 0;

	if (nfserr)
@@ -3913,18 +3904,20 @@ nfsd4_encode_exchange_id(struct nfsd4_compoundres *resp, __be32 nfserr,
	case SP4_NONE:
		break;
	case SP4_MACH_CRED:
		/* spo_must_enforce, spo_must_allow */
		p = xdr_reserve_space(xdr, 16);
		if (!p)
			return nfserr_resource;

		/* spo_must_enforce bitmap: */
		*p++ = cpu_to_be32(2);
		*p++ = cpu_to_be32(nfs4_minimal_spo_must_enforce[0]);
		*p++ = cpu_to_be32(nfs4_minimal_spo_must_enforce[1]);
		/* empty spo_must_allow bitmap: */
		*p++ = cpu_to_be32(0);

		status = nfsd4_encode_bitmap(xdr,
					exid->spo_must_enforce[0],
					exid->spo_must_enforce[1],
					exid->spo_must_enforce[2]);
		if (status)
			goto out;
		/* spo_must_allow bitmap: */
		status = nfsd4_encode_bitmap(xdr,
					exid->spo_must_allow[0],
					exid->spo_must_allow[1],
					exid->spo_must_allow[2]);
		if (status)
			goto out;
		break;
	default:
		WARN_ON_ONCE(1);
@@ -3951,6 +3944,8 @@ nfsd4_encode_exchange_id(struct nfsd4_compoundres *resp, __be32 nfserr,
	/* Implementation id */
	*p++ = cpu_to_be32(0);	/* zero length nfs_impl_id4 array */
	return 0;
out:
	return status;
}

static __be32
+5 −0
Original line number Diff line number Diff line
@@ -124,6 +124,7 @@ void nfs4_state_shutdown_net(struct net *net);
void nfs4_reset_lease(time_t leasetime);
int nfs4_reset_recoverydir(char *recdir);
char * nfs4_recoverydir(void);
bool nfsd4_spo_must_allow(struct svc_rqst *rqstp);
#else
static inline int nfsd4_init_slabs(void) { return 0; }
static inline void nfsd4_free_slabs(void) { }
@@ -134,6 +135,10 @@ static inline void nfs4_state_shutdown_net(struct net *net) { }
static inline void nfs4_reset_lease(time_t leasetime) { }
static inline int nfs4_reset_recoverydir(char *recdir) { return 0; }
static inline char * nfs4_recoverydir(void) {return NULL; }
static inline bool nfsd4_spo_must_allow(struct svc_rqst *rqstp)
{
	return false;
}
#endif

/*
Loading