| 1 | // SPDX-License-Identifier: GPL-2.0-only | 
|---|
| 2 | /* | 
|---|
| 3 | * linux/net/sunrpc/svcauth.c | 
|---|
| 4 | * | 
|---|
| 5 | * The generic interface for RPC authentication on the server side. | 
|---|
| 6 | * | 
|---|
| 7 | * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> | 
|---|
| 8 | * | 
|---|
| 9 | * CHANGES | 
|---|
| 10 | * 19-Apr-2000 Chris Evans      - Security fix | 
|---|
| 11 | */ | 
|---|
| 12 |  | 
|---|
| 13 | #include <linux/types.h> | 
|---|
| 14 | #include <linux/module.h> | 
|---|
| 15 | #include <linux/sunrpc/types.h> | 
|---|
| 16 | #include <linux/sunrpc/xdr.h> | 
|---|
| 17 | #include <linux/sunrpc/svcsock.h> | 
|---|
| 18 | #include <linux/sunrpc/svcauth.h> | 
|---|
| 19 | #include <linux/err.h> | 
|---|
| 20 | #include <linux/hash.h> | 
|---|
| 21 | #include <linux/user_namespace.h> | 
|---|
| 22 |  | 
|---|
| 23 | #include <trace/events/sunrpc.h> | 
|---|
| 24 |  | 
|---|
| 25 | #include "sunrpc.h" | 
|---|
| 26 |  | 
|---|
| 27 | #define RPCDBG_FACILITY	RPCDBG_AUTH | 
|---|
| 28 |  | 
|---|
| 29 |  | 
|---|
| 30 | /* | 
|---|
| 31 | * Table of authenticators | 
|---|
| 32 | */ | 
|---|
| 33 | extern struct auth_ops svcauth_null; | 
|---|
| 34 | extern struct auth_ops svcauth_unix; | 
|---|
| 35 | extern struct auth_ops svcauth_tls; | 
|---|
| 36 |  | 
|---|
| 37 | static struct auth_ops __rcu *authtab[RPC_AUTH_MAXFLAVOR] = { | 
|---|
| 38 | [RPC_AUTH_NULL] = (struct auth_ops __force __rcu *)&svcauth_null, | 
|---|
| 39 | [RPC_AUTH_UNIX] = (struct auth_ops __force __rcu *)&svcauth_unix, | 
|---|
| 40 | [RPC_AUTH_TLS]  = (struct auth_ops __force __rcu *)&svcauth_tls, | 
|---|
| 41 | }; | 
|---|
| 42 |  | 
|---|
| 43 | static struct auth_ops * | 
|---|
| 44 | svc_get_auth_ops(rpc_authflavor_t flavor) | 
|---|
| 45 | { | 
|---|
| 46 | struct auth_ops		*aops; | 
|---|
| 47 |  | 
|---|
| 48 | if (flavor >= RPC_AUTH_MAXFLAVOR) | 
|---|
| 49 | return NULL; | 
|---|
| 50 | rcu_read_lock(); | 
|---|
| 51 | aops = rcu_dereference(authtab[flavor]); | 
|---|
| 52 | if (aops != NULL && !try_module_get(module: aops->owner)) | 
|---|
| 53 | aops = NULL; | 
|---|
| 54 | rcu_read_unlock(); | 
|---|
| 55 | return aops; | 
|---|
| 56 | } | 
|---|
| 57 |  | 
|---|
| 58 | static void | 
|---|
| 59 | svc_put_auth_ops(struct auth_ops *aops) | 
|---|
| 60 | { | 
|---|
| 61 | module_put(module: aops->owner); | 
|---|
| 62 | } | 
|---|
| 63 |  | 
|---|
| 64 | /** | 
|---|
| 65 | * svc_authenticate - Initialize an outgoing credential | 
|---|
| 66 | * @rqstp: RPC execution context | 
|---|
| 67 | * | 
|---|
| 68 | * Return values: | 
|---|
| 69 | *   %SVC_OK: XDR encoding of the result can begin | 
|---|
| 70 | *   %SVC_DENIED: Credential or verifier is not valid | 
|---|
| 71 | *   %SVC_GARBAGE: Failed to decode credential or verifier | 
|---|
| 72 | *   %SVC_COMPLETE: GSS context lifetime event; no further action | 
|---|
| 73 | *   %SVC_DROP: Drop this request; no further action | 
|---|
| 74 | *   %SVC_CLOSE: Like drop, but also close transport connection | 
|---|
| 75 | */ | 
|---|
| 76 | enum svc_auth_status svc_authenticate(struct svc_rqst *rqstp) | 
|---|
| 77 | { | 
|---|
| 78 | struct auth_ops *aops; | 
|---|
| 79 | u32 flavor; | 
|---|
| 80 |  | 
|---|
| 81 | rqstp->rq_auth_stat = rpc_auth_ok; | 
|---|
| 82 |  | 
|---|
| 83 | /* | 
|---|
| 84 | * Decode the Call credential's flavor field. The credential's | 
|---|
| 85 | * body field is decoded in the chosen ->accept method below. | 
|---|
| 86 | */ | 
|---|
| 87 | if (xdr_stream_decode_u32(xdr: &rqstp->rq_arg_stream, ptr: &flavor) < 0) | 
|---|
| 88 | return SVC_GARBAGE; | 
|---|
| 89 |  | 
|---|
| 90 | aops = svc_get_auth_ops(flavor); | 
|---|
| 91 | if (aops == NULL) { | 
|---|
| 92 | rqstp->rq_auth_stat = rpc_autherr_badcred; | 
|---|
| 93 | return SVC_DENIED; | 
|---|
| 94 | } | 
|---|
| 95 |  | 
|---|
| 96 | rqstp->rq_auth_slack = 0; | 
|---|
| 97 | init_svc_cred(cred: &rqstp->rq_cred); | 
|---|
| 98 |  | 
|---|
| 99 | rqstp->rq_authop = aops; | 
|---|
| 100 | return aops->accept(rqstp); | 
|---|
| 101 | } | 
|---|
| 102 |  | 
|---|
| 103 | /** | 
|---|
| 104 | * svc_set_client - Assign an appropriate 'auth_domain' as the client | 
|---|
| 105 | * @rqstp: RPC execution context | 
|---|
| 106 | * | 
|---|
| 107 | * Return values: | 
|---|
| 108 | *   %SVC_OK: Client was found and assigned | 
|---|
| 109 | *   %SVC_DENY: Client was explicitly denied | 
|---|
| 110 | *   %SVC_DROP: Ignore this request | 
|---|
| 111 | *   %SVC_CLOSE: Ignore this request and close the connection | 
|---|
| 112 | */ | 
|---|
| 113 | enum svc_auth_status svc_set_client(struct svc_rqst *rqstp) | 
|---|
| 114 | { | 
|---|
| 115 | rqstp->rq_client = NULL; | 
|---|
| 116 | return rqstp->rq_authop->set_client(rqstp); | 
|---|
| 117 | } | 
|---|
| 118 | EXPORT_SYMBOL_GPL(svc_set_client); | 
|---|
| 119 |  | 
|---|
| 120 | /** | 
|---|
| 121 | * svc_authorise - Finalize credentials/verifier and release resources | 
|---|
| 122 | * @rqstp: RPC execution context | 
|---|
| 123 | * | 
|---|
| 124 | * Returns zero on success, or a negative errno. | 
|---|
| 125 | */ | 
|---|
| 126 | int svc_authorise(struct svc_rqst *rqstp) | 
|---|
| 127 | { | 
|---|
| 128 | struct auth_ops *aops = rqstp->rq_authop; | 
|---|
| 129 | int rv = 0; | 
|---|
| 130 |  | 
|---|
| 131 | rqstp->rq_authop = NULL; | 
|---|
| 132 |  | 
|---|
| 133 | if (aops) { | 
|---|
| 134 | rv = aops->release(rqstp); | 
|---|
| 135 | svc_put_auth_ops(aops); | 
|---|
| 136 | } | 
|---|
| 137 | return rv; | 
|---|
| 138 | } | 
|---|
| 139 |  | 
|---|
| 140 | int | 
|---|
| 141 | svc_auth_register(rpc_authflavor_t flavor, struct auth_ops *aops) | 
|---|
| 142 | { | 
|---|
| 143 | struct auth_ops *old; | 
|---|
| 144 | int rv = -EINVAL; | 
|---|
| 145 |  | 
|---|
| 146 | if (flavor < RPC_AUTH_MAXFLAVOR) { | 
|---|
| 147 | old = cmpxchg((struct auth_ops ** __force)&authtab[flavor], NULL, aops); | 
|---|
| 148 | if (old == NULL || old == aops) | 
|---|
| 149 | rv = 0; | 
|---|
| 150 | } | 
|---|
| 151 | return rv; | 
|---|
| 152 | } | 
|---|
| 153 | EXPORT_SYMBOL_GPL(svc_auth_register); | 
|---|
| 154 |  | 
|---|
| 155 | void | 
|---|
| 156 | svc_auth_unregister(rpc_authflavor_t flavor) | 
|---|
| 157 | { | 
|---|
| 158 | if (flavor < RPC_AUTH_MAXFLAVOR) | 
|---|
| 159 | rcu_assign_pointer(authtab[flavor], NULL); | 
|---|
| 160 | } | 
|---|
| 161 | EXPORT_SYMBOL_GPL(svc_auth_unregister); | 
|---|
| 162 |  | 
|---|
| 163 | /** | 
|---|
| 164 | * svc_auth_flavor - return RPC transaction's RPC_AUTH flavor | 
|---|
| 165 | * @rqstp: RPC transaction context | 
|---|
| 166 | * | 
|---|
| 167 | * Returns an RPC flavor or GSS pseudoflavor. | 
|---|
| 168 | */ | 
|---|
| 169 | rpc_authflavor_t svc_auth_flavor(struct svc_rqst *rqstp) | 
|---|
| 170 | { | 
|---|
| 171 | struct auth_ops *aops = rqstp->rq_authop; | 
|---|
| 172 |  | 
|---|
| 173 | if (!aops->pseudoflavor) | 
|---|
| 174 | return aops->flavour; | 
|---|
| 175 | return aops->pseudoflavor(rqstp); | 
|---|
| 176 | } | 
|---|
| 177 | EXPORT_SYMBOL_GPL(svc_auth_flavor); | 
|---|
| 178 |  | 
|---|
| 179 | /** | 
|---|
| 180 | * svcauth_map_clnt_to_svc_cred_local - maps a generic cred | 
|---|
| 181 | * to a svc_cred suitable for use in nfsd. | 
|---|
| 182 | * @clnt: rpc_clnt associated with nfs client | 
|---|
| 183 | * @cred: generic cred associated with nfs client | 
|---|
| 184 | * @svc: returned svc_cred that is suitable for use in nfsd | 
|---|
| 185 | */ | 
|---|
| 186 | void svcauth_map_clnt_to_svc_cred_local(struct rpc_clnt *clnt, | 
|---|
| 187 | const struct cred *cred, | 
|---|
| 188 | struct svc_cred *svc) | 
|---|
| 189 | { | 
|---|
| 190 | struct user_namespace *userns = clnt->cl_cred ? | 
|---|
| 191 | clnt->cl_cred->user_ns : &init_user_ns; | 
|---|
| 192 |  | 
|---|
| 193 | memset(s: svc, c: 0, n: sizeof(struct svc_cred)); | 
|---|
| 194 |  | 
|---|
| 195 | svc->cr_uid = KUIDT_INIT(from_kuid_munged(userns, cred->fsuid)); | 
|---|
| 196 | svc->cr_gid = KGIDT_INIT(from_kgid_munged(userns, cred->fsgid)); | 
|---|
| 197 | svc->cr_flavor = clnt->cl_auth->au_flavor; | 
|---|
| 198 | if (cred->group_info) | 
|---|
| 199 | svc->cr_group_info = get_group_info(gi: cred->group_info); | 
|---|
| 200 | /* These aren't relevant for local (network is bypassed) */ | 
|---|
| 201 | svc->cr_principal = NULL; | 
|---|
| 202 | svc->cr_gss_mech = NULL; | 
|---|
| 203 | } | 
|---|
| 204 | EXPORT_SYMBOL_GPL(svcauth_map_clnt_to_svc_cred_local); | 
|---|
| 205 |  | 
|---|
| 206 | /************************************************** | 
|---|
| 207 | * 'auth_domains' are stored in a hash table indexed by name. | 
|---|
| 208 | * When the last reference to an 'auth_domain' is dropped, | 
|---|
| 209 | * the object is unhashed and freed. | 
|---|
| 210 | * If auth_domain_lookup fails to find an entry, it will return | 
|---|
| 211 | * it's second argument 'new'.  If this is non-null, it will | 
|---|
| 212 | * have been atomically linked into the table. | 
|---|
| 213 | */ | 
|---|
| 214 |  | 
|---|
| 215 | #define	DN_HASHBITS	6 | 
|---|
| 216 | #define	DN_HASHMAX	(1<<DN_HASHBITS) | 
|---|
| 217 |  | 
|---|
| 218 | static struct hlist_head	auth_domain_table[DN_HASHMAX]; | 
|---|
| 219 | static DEFINE_SPINLOCK(auth_domain_lock); | 
|---|
| 220 |  | 
|---|
| 221 | static void auth_domain_release(struct kref *kref) | 
|---|
| 222 | __releases(&auth_domain_lock) | 
|---|
| 223 | { | 
|---|
| 224 | struct auth_domain *dom = container_of(kref, struct auth_domain, ref); | 
|---|
| 225 |  | 
|---|
| 226 | hlist_del_rcu(n: &dom->hash); | 
|---|
| 227 | dom->flavour->domain_release(dom); | 
|---|
| 228 | spin_unlock(lock: &auth_domain_lock); | 
|---|
| 229 | } | 
|---|
| 230 |  | 
|---|
| 231 | void auth_domain_put(struct auth_domain *dom) | 
|---|
| 232 | { | 
|---|
| 233 | kref_put_lock(kref: &dom->ref, release: auth_domain_release, lock: &auth_domain_lock); | 
|---|
| 234 | } | 
|---|
| 235 | EXPORT_SYMBOL_GPL(auth_domain_put); | 
|---|
| 236 |  | 
|---|
| 237 | struct auth_domain * | 
|---|
| 238 | auth_domain_lookup(char *name, struct auth_domain *new) | 
|---|
| 239 | { | 
|---|
| 240 | struct auth_domain *hp; | 
|---|
| 241 | struct hlist_head *head; | 
|---|
| 242 |  | 
|---|
| 243 | head = &auth_domain_table[hash_str(name, DN_HASHBITS)]; | 
|---|
| 244 |  | 
|---|
| 245 | spin_lock(lock: &auth_domain_lock); | 
|---|
| 246 |  | 
|---|
| 247 | hlist_for_each_entry(hp, head, hash) { | 
|---|
| 248 | if (strcmp(hp->name, name)==0) { | 
|---|
| 249 | kref_get(kref: &hp->ref); | 
|---|
| 250 | spin_unlock(lock: &auth_domain_lock); | 
|---|
| 251 | return hp; | 
|---|
| 252 | } | 
|---|
| 253 | } | 
|---|
| 254 | if (new) | 
|---|
| 255 | hlist_add_head_rcu(n: &new->hash, h: head); | 
|---|
| 256 | spin_unlock(lock: &auth_domain_lock); | 
|---|
| 257 | return new; | 
|---|
| 258 | } | 
|---|
| 259 | EXPORT_SYMBOL_GPL(auth_domain_lookup); | 
|---|
| 260 |  | 
|---|
| 261 | struct auth_domain *auth_domain_find(char *name) | 
|---|
| 262 | { | 
|---|
| 263 | struct auth_domain *hp; | 
|---|
| 264 | struct hlist_head *head; | 
|---|
| 265 |  | 
|---|
| 266 | head = &auth_domain_table[hash_str(name, DN_HASHBITS)]; | 
|---|
| 267 |  | 
|---|
| 268 | rcu_read_lock(); | 
|---|
| 269 | hlist_for_each_entry_rcu(hp, head, hash) { | 
|---|
| 270 | if (strcmp(hp->name, name)==0) { | 
|---|
| 271 | if (!kref_get_unless_zero(kref: &hp->ref)) | 
|---|
| 272 | hp = NULL; | 
|---|
| 273 | rcu_read_unlock(); | 
|---|
| 274 | return hp; | 
|---|
| 275 | } | 
|---|
| 276 | } | 
|---|
| 277 | rcu_read_unlock(); | 
|---|
| 278 | return NULL; | 
|---|
| 279 | } | 
|---|
| 280 | EXPORT_SYMBOL_GPL(auth_domain_find); | 
|---|
| 281 |  | 
|---|
| 282 | /** | 
|---|
| 283 | * auth_domain_cleanup - check that the auth_domain table is empty | 
|---|
| 284 | * | 
|---|
| 285 | * On module unload the auth_domain_table must be empty.  To make it | 
|---|
| 286 | * easier to catch bugs which don't clean up domains properly, we | 
|---|
| 287 | * warn if anything remains in the table at cleanup time. | 
|---|
| 288 | * | 
|---|
| 289 | * Note that we cannot proactively remove the domains at this stage. | 
|---|
| 290 | * The ->release() function might be in a module that has already been | 
|---|
| 291 | * unloaded. | 
|---|
| 292 | */ | 
|---|
| 293 |  | 
|---|
| 294 | void auth_domain_cleanup(void) | 
|---|
| 295 | { | 
|---|
| 296 | int h; | 
|---|
| 297 | struct auth_domain *hp; | 
|---|
| 298 |  | 
|---|
| 299 | for (h = 0; h < DN_HASHMAX; h++) | 
|---|
| 300 | hlist_for_each_entry(hp, &auth_domain_table[h], hash) | 
|---|
| 301 | pr_warn( "svc: domain %s still present at module unload.\n", | 
|---|
| 302 | hp->name); | 
|---|
| 303 | } | 
|---|
| 304 |  | 
|---|