| 1 | |
| 2 | |
| 3 | * Handshake request lifetime events
|
| 4 | *
|
| 5 | * Author: Chuck Lever <[email protected]>
|
| 6 | *
|
| 7 | * Copyright (c) 2023, Oracle and/or its affiliates.
|
| 8 |
|
| 9 |
|
| 10 | #include <linux/types.h>
|
| 11 | #include <linux/socket.h>
|
| 12 | #include <linux/kernel.h>
|
| 13 | #include <linux/module.h>
|
| 14 | #include <linux/skbuff.h>
|
| 15 | #include <linux/inet.h>
|
| 16 | #include <linux/file.h>
|
| 17 | #include <linux/rhashtable.h>
|
| 18 |
|
| 19 | #include <net/sock.h>
|
| 20 | #include <net/genetlink.h>
|
| 21 | #include <net/netns/generic.h>
|
| 22 |
|
| 23 | #include <kunit/visibility.h>
|
| 24 |
|
| 25 | #include <uapi/linux/handshake.h>
|
| 26 | #include "handshake.h"
|
| 27 |
|
| 28 | #include <trace/events/handshake.h>
|
| 29 |
|
| 30 | |
| 31 | * We need both a handshake_req -> sock mapping, and a sock ->
|
| 32 | * handshake_req mapping. Both are one-to-one.
|
| 33 | *
|
| 34 | * To avoid adding another pointer field to struct sock, net/handshake
|
| 35 | * maintains a hash table, indexed by the memory address of @sock, to
|
| 36 | * find the struct handshake_req outstanding for that socket. The
|
| 37 | * reverse direction uses a simple pointer field in the handshake_req
|
| 38 | * struct.
|
| 39 |
|
| 40 |
|
| 41 | static struct rhashtable handshake_rhashtbl ____cacheline_aligned_in_smp;
|
| 42 |
|
| 43 | static const struct rhashtable_params handshake_rhash_params = {
|
| 44 | .key_len = sizeof_field(struct handshake_req, hr_sk),
|
| 45 | .key_offset = offsetof(struct handshake_req, hr_sk),
|
| 46 | .head_offset = offsetof(struct handshake_req, hr_rhash),
|
| 47 | .automatic_shrinking = true,
|
| 48 | };
|
| 49 |
|
| 50 | int handshake_req_hash_init(void)
|
| 51 | {
|
| 52 | return rhashtable_init(&handshake_rhashtbl, &handshake_rhash_params);
|
| 53 | }
|
| 54 |
|
| 55 | void handshake_req_hash_destroy(void)
|
| 56 | {
|
| 57 | rhashtable_destroy(&handshake_rhashtbl);
|
| 58 | }
|
| 59 |
|
| 60 | struct handshake_req *handshake_req_hash_lookup(struct sock *sk)
|
| 61 | {
|
| 62 | return rhashtable_lookup_fast(&handshake_rhashtbl, &sk,
|
| 63 | handshake_rhash_params);
|
| 64 | }
|
| 65 | EXPORT_SYMBOL_IF_KUNIT(handshake_req_hash_lookup);
|
| 66 |
|
| 67 | static bool handshake_req_hash_add(struct handshake_req *req)
|
| 68 | {
|
| 69 | int ret;
|
| 70 |
|
| 71 | ret = rhashtable_lookup_insert_fast(&handshake_rhashtbl,
|
| 72 | &req->hr_rhash,
|
| 73 | handshake_rhash_params);
|
| 74 | return ret == 0;
|
| 75 | }
|
| 76 |
|
| 77 | static void handshake_req_destroy(struct handshake_req *req)
|
| 78 | {
|
| 79 | if (req->hr_proto->hp_destroy)
|
| 80 | req->hr_proto->hp_destroy(req);
|
| 81 | rhashtable_remove_fast(&handshake_rhashtbl, &req->hr_rhash,
|
| 82 | handshake_rhash_params);
|
| 83 | kfree(req);
|
| 84 | }
|
| 85 |
|
| 86 | static void handshake_sk_destruct(struct sock *sk)
|
| 87 | {
|
| 88 | void (*sk_destruct)(struct sock *sk);
|
| 89 | struct handshake_req *req;
|
| 90 |
|
| 91 | req = handshake_req_hash_lookup(sk);
|
| 92 | if (!req)
|
| 93 | return;
|
| 94 |
|
| 95 | trace_handshake_destruct(sock_net(sk), req, sk);
|
| 96 | sk_destruct = req->hr_odestruct;
|
| 97 | handshake_req_destroy(req);
|
| 98 | if (sk_destruct)
|
| 99 | sk_destruct(sk);
|
| 100 | }
|
| 101 |
|
| 102 | |
| 103 | * handshake_req_alloc - Allocate a handshake request
|
| 104 | * @proto: security protocol
|
| 105 | * @flags: memory allocation flags
|
| 106 | *
|
| 107 | * Returns an initialized handshake_req or NULL.
|
| 108 |
|
| 109 | struct handshake_req *handshake_req_alloc(const struct handshake_proto *proto,
|
| 110 | gfp_t flags)
|
| 111 | {
|
| 112 | struct handshake_req *req;
|
| 113 |
|
| 114 | if (!proto)
|
| 115 | return NULL;
|
| 116 | if (proto->hp_handler_class <= HANDSHAKE_HANDLER_CLASS_NONE)
|
| 117 | return NULL;
|
| 118 | if (proto->hp_handler_class >= HANDSHAKE_HANDLER_CLASS_MAX)
|
| 119 | return NULL;
|
| 120 | if (!proto->hp_accept || !proto->hp_done)
|
| 121 | return NULL;
|
| 122 |
|
| 123 | req = kzalloc_flex(*req, hr_priv, proto->hp_privsize, flags);
|
| 124 | if (!req)
|
| 125 | return NULL;
|
| 126 |
|
| 127 | INIT_LIST_HEAD(&req->hr_list);
|
| 128 | req->hr_proto = proto;
|
| 129 | return req;
|
| 130 | }
|
| 131 | EXPORT_SYMBOL(handshake_req_alloc);
|
| 132 |
|
| 133 | |
| 134 | * handshake_req_private - Get per-handshake private data
|
| 135 | * @req: handshake arguments
|
| 136 | *
|
| 137 |
|
| 138 | void *handshake_req_private(struct handshake_req *req)
|
| 139 | {
|
| 140 | return (void *)&req->hr_priv;
|
| 141 | }
|
| 142 | EXPORT_SYMBOL(handshake_req_private);
|
| 143 |
|
| 144 | static bool __add_pending_locked(struct handshake_net *hn,
|
| 145 | struct handshake_req *req)
|
| 146 | {
|
| 147 | if (WARN_ON_ONCE(!list_empty(&req->hr_list)))
|
| 148 | return false;
|
| 149 | hn->hn_pending++;
|
| 150 | list_add_tail(&req->hr_list, &hn->hn_requests);
|
| 151 | return true;
|
| 152 | }
|
| 153 |
|
| 154 | static void __remove_pending_locked(struct handshake_net *hn,
|
| 155 | struct handshake_req *req)
|
| 156 | {
|
| 157 | hn->hn_pending--;
|
| 158 | list_del_init(&req->hr_list);
|
| 159 | }
|
| 160 |
|
| 161 | |
| 162 | * Returns %true if the request was found on @net's pending list,
|
| 163 | * otherwise %false.
|
| 164 | *
|
| 165 | * If @req was on a pending list, it has not yet been accepted.
|
| 166 | * Returns %false when the net namespace is draining; the drain
|
| 167 | * loop has taken ownership of the pending list.
|
| 168 |
|
| 169 | static bool remove_pending(struct handshake_net *hn, struct handshake_req *req)
|
| 170 | {
|
| 171 | bool ret = false;
|
| 172 |
|
| 173 | spin_lock_bh(&hn->hn_lock);
|
| 174 | if (!test_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags) &&
|
| 175 | !list_empty(&req->hr_list)) {
|
| 176 | __remove_pending_locked(hn, req);
|
| 177 | ret = true;
|
| 178 | }
|
| 179 | spin_unlock_bh(&hn->hn_lock);
|
| 180 |
|
| 181 | return ret;
|
| 182 | }
|
| 183 |
|
| 184 | |
| 185 | * handshake_req_next - Return the next queued handshake request
|
| 186 | * @hn: per-net handshake state
|
| 187 | * @class: handler class to match
|
| 188 | *
|
| 189 | * On a non-NULL return, the caller owns an extra reference
|
| 190 | * on @req->hr_file. FD_PREPARE() consumes it on success; on
|
| 191 | * the FD_PREPARE() failure path the caller must fput() it.
|
| 192 | *
|
| 193 | * Return: pointer to a removed handshake_req, or NULL.
|
| 194 |
|
| 195 | struct handshake_req *handshake_req_next(struct handshake_net *hn, int class)
|
| 196 | {
|
| 197 | struct handshake_req *req, *pos;
|
| 198 |
|
| 199 | req = NULL;
|
| 200 | spin_lock_bh(&hn->hn_lock);
|
| 201 | list_for_each_entry(pos, &hn->hn_requests, hr_list) {
|
| 202 | if (pos->hr_proto->hp_handler_class != class)
|
| 203 | continue;
|
| 204 | __remove_pending_locked(hn, pos);
|
| 205 | |
| 206 | * hn_lock. A concurrent handshake_req_cancel() can drop
|
| 207 | * hr_file before accept reaches FD_PREPARE(); this extra
|
| 208 | * reference keeps the file alive until FD_PREPARE() takes
|
| 209 | * ownership.
|
| 210 |
|
| 211 | get_file(pos->hr_file);
|
| 212 | req = pos;
|
| 213 | break;
|
| 214 | }
|
| 215 | spin_unlock_bh(&hn->hn_lock);
|
| 216 |
|
| 217 | return req;
|
| 218 | }
|
| 219 | EXPORT_SYMBOL_IF_KUNIT(handshake_req_next);
|
| 220 |
|
| 221 | |
| 222 | * handshake_req_submit - Submit a handshake request
|
| 223 | * @sock: open socket on which to perform the handshake
|
| 224 | * @req: handshake arguments
|
| 225 | * @flags: memory allocation flags
|
| 226 | *
|
| 227 | * Return values:
|
| 228 | * %0: Request queued
|
| 229 | * %-EINVAL: Invalid argument
|
| 230 | * %-EBUSY: A handshake is already under way for this socket
|
| 231 | * %-ESRCH: No handshake agent is available
|
| 232 | * %-EAGAIN: Too many pending handshake requests
|
| 233 | * %-ENOMEM: Failed to allocate memory
|
| 234 | * %-EMSGSIZE: Failed to construct notification message
|
| 235 | * %-EOPNOTSUPP: Handshake module not initialized
|
| 236 | *
|
| 237 | * A zero return value from handshake_req_submit() means that
|
| 238 | * exactly one subsequent completion callback is guaranteed.
|
| 239 | *
|
| 240 | * A negative return value from handshake_req_submit() guarantees that
|
| 241 | * no completion callback will occur and that @req is no longer owned by
|
| 242 | * the caller. If cancellation wins the completion race after the request
|
| 243 | * has been published, final destruction is deferred until socket teardown.
|
| 244 | *
|
| 245 | * The caller must hold a reference on @sock->file for the duration
|
| 246 | * of this call. Once the request is published to the accept side, a
|
| 247 | * concurrent completion or cancellation may release the request's pin on
|
| 248 | * @sock->file; the caller's reference is what keeps @sock->sk valid until
|
| 249 | * handshake_req_submit() returns.
|
| 250 |
|
| 251 | int handshake_req_submit(struct socket *sock, struct handshake_req *req,
|
| 252 | gfp_t flags)
|
| 253 | {
|
| 254 | struct handshake_net *hn;
|
| 255 | struct net *net;
|
| 256 | int ret;
|
| 257 |
|
| 258 | if (!sock || !req || !sock->file) {
|
| 259 | kfree(req);
|
| 260 | return -EINVAL;
|
| 261 | }
|
| 262 |
|
| 263 | req->hr_sk = sock->sk;
|
| 264 | if (!req->hr_sk) {
|
| 265 | kfree(req);
|
| 266 | return -EINVAL;
|
| 267 | }
|
| 268 |
|
| 269 | |
| 270 | * Pin sock->file for the lifetime of the request so the
|
| 271 | * accept side does not race a consumer that releases the
|
| 272 | * socket while a handshake is pending.
|
| 273 |
|
| 274 | req->hr_file = get_file(sock->file);
|
| 275 |
|
| 276 | req->hr_odestruct = req->hr_sk->sk_destruct;
|
| 277 | req->hr_sk->sk_destruct = handshake_sk_destruct;
|
| 278 |
|
| 279 | ret = -EOPNOTSUPP;
|
| 280 | net = sock_net(req->hr_sk);
|
| 281 | hn = handshake_pernet(net);
|
| 282 | if (!hn)
|
| 283 | goto out_err;
|
| 284 |
|
| 285 | ret = -EAGAIN;
|
| 286 | if (READ_ONCE(hn->hn_pending) >= hn->hn_pending_max)
|
| 287 | goto out_err;
|
| 288 |
|
| 289 | spin_lock_bh(&hn->hn_lock);
|
| 290 | ret = -EOPNOTSUPP;
|
| 291 | if (test_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags))
|
| 292 | goto out_unlock;
|
| 293 | ret = -EBUSY;
|
| 294 | if (!handshake_req_hash_add(req))
|
| 295 | goto out_unlock;
|
| 296 | if (!__add_pending_locked(hn, req))
|
| 297 | goto out_unlock;
|
| 298 | spin_unlock_bh(&hn->hn_lock);
|
| 299 |
|
| 300 | ret = handshake_genl_notify(net, req->hr_proto, flags);
|
| 301 | if (ret) {
|
| 302 | trace_handshake_notify_err(net, req, req->hr_sk, ret);
|
| 303 | if (remove_pending(hn, req))
|
| 304 | goto out_err;
|
| 305 | }
|
| 306 |
|
| 307 | trace_handshake_submit(net, req, req->hr_sk);
|
| 308 | return 0;
|
| 309 |
|
| 310 | out_unlock:
|
| 311 | spin_unlock_bh(&hn->hn_lock);
|
| 312 | out_err:
|
| 313 | trace_handshake_submit_err(net, req, req->hr_sk, ret);
|
| 314 | if (!test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) {
|
| 315 |
|
| 316 | req->hr_sk->sk_destruct = req->hr_odestruct;
|
| 317 | fput(req->hr_file);
|
| 318 | handshake_req_destroy(req);
|
| 319 | }
|
| 320 | return ret;
|
| 321 | }
|
| 322 | EXPORT_SYMBOL(handshake_req_submit);
|
| 323 |
|
| 324 | void handshake_complete(struct handshake_req *req, int status,
|
| 325 | struct genl_info *info)
|
| 326 | {
|
| 327 | struct sock *sk = req->hr_sk;
|
| 328 | struct net *net = sock_net(sk);
|
| 329 |
|
| 330 | if (!test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) {
|
| 331 | struct file *file = req->hr_file;
|
| 332 |
|
| 333 | trace_handshake_complete(net, req, sk, status);
|
| 334 | req->hr_proto->hp_done(req, status, info);
|
| 335 |
|
| 336 | fput(file);
|
| 337 | }
|
| 338 | }
|
| 339 | EXPORT_SYMBOL_IF_KUNIT(handshake_complete);
|
| 340 |
|
| 341 | |
| 342 | * handshake_req_cancel - Cancel an in-progress handshake
|
| 343 | * @sk: socket on which there is an ongoing handshake
|
| 344 | *
|
| 345 | * Request cancellation races with request completion. To determine
|
| 346 | * who won, callers examine the return value from this function.
|
| 347 | *
|
| 348 | * Return values:
|
| 349 | * %true - Uncompleted handshake request was canceled
|
| 350 | * %false - Handshake request already completed or not found
|
| 351 |
|
| 352 | bool handshake_req_cancel(struct sock *sk)
|
| 353 | {
|
| 354 | struct handshake_req *req;
|
| 355 | struct handshake_net *hn;
|
| 356 | struct net *net;
|
| 357 |
|
| 358 | net = sock_net(sk);
|
| 359 | req = handshake_req_hash_lookup(sk);
|
| 360 | if (!req) {
|
| 361 | trace_handshake_cancel_none(net, req, sk);
|
| 362 | return false;
|
| 363 | }
|
| 364 |
|
| 365 | hn = handshake_pernet(net);
|
| 366 | if (hn && remove_pending(hn, req)) {
|
| 367 |
|
| 368 | if (test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) {
|
| 369 | trace_handshake_cancel_busy(net, req, sk);
|
| 370 | return false;
|
| 371 | }
|
| 372 | goto out_true;
|
| 373 | }
|
| 374 | if (test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) {
|
| 375 |
|
| 376 | trace_handshake_cancel_busy(net, req, sk);
|
| 377 | return false;
|
| 378 | }
|
| 379 |
|
| 380 | out_true:
|
| 381 | trace_handshake_cancel(net, req, sk);
|
| 382 |
|
| 383 | fput(req->hr_file);
|
| 384 | return true;
|
| 385 | }
|
| 386 | EXPORT_SYMBOL(handshake_req_cancel);
|