| // SPDX-License-Identifier: GPL-2.0-only | |
| /* Copyright (c) 2016 Tom Herbert <tom@herbertland.com> */ | |
| #include <linux/skbuff.h> | |
| #include <linux/skbuff_ref.h> | |
| #include <linux/workqueue.h> | |
| #include <net/strparser.h> | |
| #include <net/tcp.h> | |
| #include <net/sock.h> | |
| #include <net/tls.h> | |
| #include "tls.h" | |
| static struct workqueue_struct *tls_strp_wq; | |
| void tls_strp_abort_strp(struct tls_strparser *strp, int err) | |
| { | |
| if (strp->stopped) | |
| return; | |
| strp->stopped = 1; | |
| /* Report an error on the lower socket */ | |
| WRITE_ONCE(strp->sk->sk_err, -err); | |
| /* Paired with smp_rmb() in tcp_poll() */ | |
| smp_wmb(); | |
| sk_error_report(strp->sk); | |
| } | |
| static void tls_strp_anchor_free(struct tls_strparser *strp) | |
| { | |
| struct skb_shared_info *shinfo = skb_shinfo(strp->anchor); | |
| DEBUG_NET_WARN_ON_ONCE(atomic_read(&shinfo->dataref) != 1); | |
| if (!strp->copy_mode) | |
| shinfo->frag_list = NULL; | |
| consume_skb(strp->anchor); | |
| strp->anchor = NULL; | |
| } | |
| static struct sk_buff * | |
| tls_strp_skb_copy(struct tls_strparser *strp, struct sk_buff *in_skb, | |
| int offset, int len) | |
| { | |
| struct sk_buff *skb; | |
| int i, err; | |
| skb = alloc_skb_with_frags(0, len, TLS_PAGE_ORDER, | |
| &err, strp->sk->sk_allocation); | |
| if (!skb) | |
| return NULL; | |
| for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { | |
| skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; | |
| WARN_ON_ONCE(skb_copy_bits(in_skb, offset, | |
| skb_frag_address(frag), | |
| skb_frag_size(frag))); | |
| offset += skb_frag_size(frag); | |
| } | |
| skb->len = len; | |
| skb->data_len = len; | |
| skb_copy_header(skb, in_skb); | |
| return skb; | |
| } | |
| /* Create a new skb with the contents of input copied to its page frags */ | |
| static struct sk_buff *tls_strp_msg_make_copy(struct tls_strparser *strp) | |
| { | |
| struct strp_msg *rxm; | |
| struct sk_buff *skb; | |
| skb = tls_strp_skb_copy(strp, strp->anchor, strp->stm.offset, | |
| strp->stm.full_len); | |
| if (!skb) | |
| return NULL; | |
| rxm = strp_msg(skb); | |
| rxm->offset = 0; | |
| return skb; | |
| } | |
| /* Steal the input skb, input msg is invalid after calling this function */ | |
| struct sk_buff *tls_strp_msg_detach(struct tls_sw_context_rx *ctx) | |
| { | |
| struct tls_strparser *strp = &ctx->strp; | |
| #ifdef CONFIG_TLS_DEVICE | |
| DEBUG_NET_WARN_ON_ONCE(!strp->anchor->decrypted); | |
| #else | |
| /* This function turns an input into an output, | |
| * that can only happen if we have offload. | |
| */ | |
| WARN_ON(1); | |
| #endif | |
| if (strp->copy_mode) { | |
| struct sk_buff *skb; | |
| /* Replace anchor with an empty skb, this is a little | |
| * dangerous but __tls_cur_msg() warns on empty skbs | |
| * so hopefully we'll catch abuses. | |
| */ | |
| skb = alloc_skb(0, strp->sk->sk_allocation); | |
| if (!skb) | |
| return NULL; | |
| swap(strp->anchor, skb); | |
| return skb; | |
| } | |
| return tls_strp_msg_make_copy(strp); | |
| } | |
| /* Force the input skb to be in copy mode. The data ownership remains | |
| * with the input skb itself (meaning unpause will wipe it) but it can | |
| * be modified. | |
| */ | |
| int tls_strp_msg_cow(struct tls_sw_context_rx *ctx) | |
| { | |
| struct tls_strparser *strp = &ctx->strp; | |
| struct sk_buff *skb; | |
| if (strp->copy_mode) | |
| return 0; | |
| skb = tls_strp_msg_make_copy(strp); | |
| if (!skb) | |
| return -ENOMEM; | |
| tls_strp_anchor_free(strp); | |
| strp->anchor = skb; | |
| tcp_read_done(strp->sk, strp->stm.full_len); | |
| strp->copy_mode = 1; | |
| return 0; | |
| } | |
| /* Make a clone (in the skb sense) of the input msg to keep a reference | |
| * to the underlying data. The reference-holding skbs get placed on | |
| * @dst. | |
| */ | |
| int tls_strp_msg_hold(struct tls_strparser *strp, struct sk_buff_head *dst) | |
| { | |
| struct skb_shared_info *shinfo = skb_shinfo(strp->anchor); | |
| if (strp->copy_mode) { | |
| struct sk_buff *skb; | |
| WARN_ON_ONCE(!shinfo->nr_frags); | |
| /* We can't skb_clone() the anchor, it gets wiped by unpause */ | |
| skb = alloc_skb(0, strp->sk->sk_allocation); | |
| if (!skb) | |
| return -ENOMEM; | |
| __skb_queue_tail(dst, strp->anchor); | |
| strp->anchor = skb; | |
| } else { | |
| struct sk_buff *iter, *clone; | |
| int chunk, len, offset; | |
| offset = strp->stm.offset; | |
| len = strp->stm.full_len; | |
| iter = shinfo->frag_list; | |
| while (len > 0) { | |
| if (iter->len <= offset) { | |
| offset -= iter->len; | |
| goto next; | |
| } | |
| chunk = iter->len - offset; | |
| offset = 0; | |
| clone = skb_clone(iter, strp->sk->sk_allocation); | |
| if (!clone) | |
| return -ENOMEM; | |
| __skb_queue_tail(dst, clone); | |
| len -= chunk; | |
| next: | |
| iter = iter->next; | |
| } | |
| } | |
| return 0; | |
| } | |
| static void tls_strp_flush_anchor_copy(struct tls_strparser *strp) | |
| { | |
| struct skb_shared_info *shinfo = skb_shinfo(strp->anchor); | |
| int i; | |
| DEBUG_NET_WARN_ON_ONCE(atomic_read(&shinfo->dataref) != 1); | |
| for (i = 0; i < shinfo->nr_frags; i++) | |
| skb_frag_unref(strp->anchor, i); | |
| shinfo->nr_frags = 0; | |
| if (strp->copy_mode) { | |
| kfree_skb_list(shinfo->frag_list); | |
| shinfo->frag_list = NULL; | |
| } | |
| strp->copy_mode = 0; | |
| strp->mixed_decrypted = 0; | |
| } | |
| static int tls_strp_copyin_frag(struct tls_strparser *strp, struct sk_buff *skb, | |
| struct sk_buff *in_skb, unsigned int offset, | |
| size_t in_len) | |
| { | |
| unsigned int nfrag = skb->len / PAGE_SIZE; | |
| size_t len, chunk; | |
| skb_frag_t *frag; | |
| int sz; | |
| if (unlikely(nfrag >= skb_shinfo(skb)->nr_frags)) { | |
| DEBUG_NET_WARN_ON_ONCE(1); | |
| return -EMSGSIZE; | |
| } | |
| frag = &skb_shinfo(skb)->frags[nfrag]; | |
| len = in_len; | |
| /* First make sure we got the header */ | |
| if (!strp->stm.full_len) { | |
| /* Assume one page is more than enough for headers */ | |
| chunk = min_t(size_t, len, PAGE_SIZE - skb_frag_size(frag)); | |
| WARN_ON_ONCE(skb_copy_bits(in_skb, offset, | |
| skb_frag_address(frag) + | |
| skb_frag_size(frag), | |
| chunk)); | |
| skb->len += chunk; | |
| skb->data_len += chunk; | |
| skb_frag_size_add(frag, chunk); | |
| sz = tls_rx_msg_size(strp, skb); | |
| if (sz < 0) | |
| return sz; | |
| /* We may have over-read, sz == 0 is guaranteed under-read */ | |
| if (unlikely(sz && sz < skb->len)) { | |
| int over = skb->len - sz; | |
| WARN_ON_ONCE(over > chunk); | |
| skb->len -= over; | |
| skb->data_len -= over; | |
| skb_frag_size_add(frag, -over); | |
| chunk -= over; | |
| } | |
| frag++; | |
| len -= chunk; | |
| offset += chunk; | |
| strp->stm.full_len = sz; | |
| if (!strp->stm.full_len) | |
| goto read_done; | |
| } | |
| /* Load up more data */ | |
| while (len && strp->stm.full_len > skb->len) { | |
| chunk = min_t(size_t, len, strp->stm.full_len - skb->len); | |
| chunk = min_t(size_t, chunk, PAGE_SIZE - skb_frag_size(frag)); | |
| WARN_ON_ONCE(skb_copy_bits(in_skb, offset, | |
| skb_frag_address(frag) + | |
| skb_frag_size(frag), | |
| chunk)); | |
| skb->len += chunk; | |
| skb->data_len += chunk; | |
| skb_frag_size_add(frag, chunk); | |
| frag++; | |
| len -= chunk; | |
| offset += chunk; | |
| } | |
| read_done: | |
| return in_len - len; | |
| } | |
| static int tls_strp_copyin_skb(struct tls_strparser *strp, struct sk_buff *skb, | |
| struct sk_buff *in_skb, unsigned int offset, | |
| size_t in_len) | |
| { | |
| struct sk_buff *nskb, *first, *last; | |
| struct skb_shared_info *shinfo; | |
| size_t chunk; | |
| int sz; | |
| if (strp->stm.full_len) | |
| chunk = strp->stm.full_len - skb->len; | |
| else | |
| chunk = TLS_MAX_PAYLOAD_SIZE + PAGE_SIZE; | |
| chunk = min(chunk, in_len); | |
| nskb = tls_strp_skb_copy(strp, in_skb, offset, chunk); | |
| if (!nskb) | |
| return -ENOMEM; | |
| shinfo = skb_shinfo(skb); | |
| if (!shinfo->frag_list) { | |
| shinfo->frag_list = nskb; | |
| nskb->prev = nskb; | |
| } else { | |
| first = shinfo->frag_list; | |
| last = first->prev; | |
| last->next = nskb; | |
| first->prev = nskb; | |
| } | |
| skb->len += chunk; | |
| skb->data_len += chunk; | |
| if (!strp->stm.full_len) { | |
| sz = tls_rx_msg_size(strp, skb); | |
| if (sz < 0) | |
| return sz; | |
| /* We may have over-read, sz == 0 is guaranteed under-read */ | |
| if (unlikely(sz && sz < skb->len)) { | |
| int over = skb->len - sz; | |
| WARN_ON_ONCE(over > chunk); | |
| skb->len -= over; | |
| skb->data_len -= over; | |
| __pskb_trim(nskb, nskb->len - over); | |
| chunk -= over; | |
| } | |
| strp->stm.full_len = sz; | |
| } | |
| return chunk; | |
| } | |
| static int tls_strp_copyin(read_descriptor_t *desc, struct sk_buff *in_skb, | |
| unsigned int offset, size_t in_len) | |
| { | |
| struct tls_strparser *strp = (struct tls_strparser *)desc->arg.data; | |
| struct sk_buff *skb; | |
| int ret; | |
| if (strp->msg_ready) | |
| return 0; | |
| skb = strp->anchor; | |
| if (!skb->len) | |
| skb_copy_decrypted(skb, in_skb); | |
| else | |
| strp->mixed_decrypted |= !!skb_cmp_decrypted(skb, in_skb); | |
| if (IS_ENABLED(CONFIG_TLS_DEVICE) && strp->mixed_decrypted) | |
| ret = tls_strp_copyin_skb(strp, skb, in_skb, offset, in_len); | |
| else | |
| ret = tls_strp_copyin_frag(strp, skb, in_skb, offset, in_len); | |
| if (ret < 0) { | |
| desc->error = ret; | |
| ret = 0; | |
| } | |
| if (strp->stm.full_len && strp->stm.full_len == skb->len) { | |
| desc->count = 0; | |
| WRITE_ONCE(strp->msg_ready, 1); | |
| } | |
| return ret; | |
| } | |
| static int tls_strp_read_copyin(struct tls_strparser *strp) | |
| { | |
| read_descriptor_t desc; | |
| desc.arg.data = strp; | |
| desc.error = 0; | |
| desc.count = 1; /* give more than one skb per call */ | |
| /* sk should be locked here, so okay to do read_sock */ | |
| tcp_read_sock(strp->sk, &desc, tls_strp_copyin); | |
| return desc.error; | |
| } | |
| static int tls_strp_read_copy(struct tls_strparser *strp, bool qshort) | |
| { | |
| struct skb_shared_info *shinfo; | |
| struct page *page; | |
| int need_spc, len; | |
| /* If the rbuf is small or rcv window has collapsed to 0 we need | |
| * to read the data out. Otherwise the connection will stall. | |
| * Without pressure threshold of INT_MAX will never be ready. | |
| */ | |
| if (likely(qshort && !tcp_epollin_ready(strp->sk, INT_MAX))) | |
| return 0; | |
| shinfo = skb_shinfo(strp->anchor); | |
| /* If we don't know the length go max plus page for cipher overhead */ | |
| need_spc = strp->stm.full_len ?: TLS_MAX_PAYLOAD_SIZE + PAGE_SIZE; | |
| for (len = need_spc; len > 0; len -= PAGE_SIZE) { | |
| page = alloc_page(strp->sk->sk_allocation); | |
| if (!page) { | |
| tls_strp_flush_anchor_copy(strp); | |
| return -ENOMEM; | |
| } | |
| skb_fill_page_desc(strp->anchor, shinfo->nr_frags++, | |
| page, 0, 0); | |
| } | |
| shinfo->frag_list = NULL; | |
| strp->copy_mode = 1; | |
| strp->stm.offset = 0; | |
| strp->anchor->len = 0; | |
| strp->anchor->data_len = 0; | |
| strp->anchor->truesize = round_up(need_spc, PAGE_SIZE); | |
| tls_strp_read_copyin(strp); | |
| return 0; | |
| } | |
| static bool tls_strp_check_queue_ok(struct tls_strparser *strp, | |
| unsigned int len) | |
| { | |
| unsigned int remaining = strp->stm.offset + len; | |
| struct sk_buff *first, *skb; | |
| u32 seq; | |
| first = skb_shinfo(strp->anchor)->frag_list; | |
| skb = first; | |
| seq = TCP_SKB_CB(first)->seq; | |
| /* Make sure there's no duplicate data in the queue, | |
| * and the decrypted status matches. | |
| */ | |
| while (skb->len < remaining) { | |
| seq += skb->len; | |
| remaining -= skb->len; | |
| skb = skb->next; | |
| if (TCP_SKB_CB(skb)->seq != seq) | |
| return false; | |
| if (skb_cmp_decrypted(first, skb)) | |
| return false; | |
| } | |
| return true; | |
| } | |
| static void tls_strp_load_anchor_with_queue(struct tls_strparser *strp, int len) | |
| { | |
| struct tcp_sock *tp = tcp_sk(strp->sk); | |
| struct sk_buff *first; | |
| u32 offset; | |
| first = tcp_recv_skb(strp->sk, tp->copied_seq, &offset); | |
| if (WARN_ON_ONCE(!first)) | |
| return; | |
| /* Bestow the state onto the anchor */ | |
| strp->anchor->len = offset + len; | |
| strp->anchor->data_len = offset + len; | |
| strp->anchor->truesize = offset + len; | |
| skb_shinfo(strp->anchor)->frag_list = first; | |
| skb_copy_header(strp->anchor, first); | |
| strp->anchor->destructor = NULL; | |
| strp->stm.offset = offset; | |
| } | |
| bool tls_strp_msg_load(struct tls_strparser *strp, bool force_refresh) | |
| { | |
| struct strp_msg *rxm; | |
| struct tls_msg *tlm; | |
| DEBUG_NET_WARN_ON_ONCE(!strp->msg_ready); | |
| DEBUG_NET_WARN_ON_ONCE(!strp->stm.full_len); | |
| if (!strp->copy_mode && force_refresh) { | |
| if (unlikely(tcp_inq(strp->sk) < strp->stm.full_len)) { | |
| WRITE_ONCE(strp->msg_ready, 0); | |
| strp->msg_announced = 0; | |
| memset(&strp->stm, 0, sizeof(strp->stm)); | |
| return false; | |
| } | |
| tls_strp_load_anchor_with_queue(strp, strp->stm.full_len); | |
| } | |
| rxm = strp_msg(strp->anchor); | |
| rxm->full_len = strp->stm.full_len; | |
| rxm->offset = strp->stm.offset; | |
| tlm = tls_msg(strp->anchor); | |
| tlm->control = strp->mark; | |
| return true; | |
| } | |
| /* Called with lock held on lower socket */ | |
| static int tls_strp_read_sock(struct tls_strparser *strp) | |
| { | |
| int sz, inq; | |
| inq = tcp_inq(strp->sk); | |
| if (inq < 1) | |
| return 0; | |
| if (unlikely(strp->copy_mode)) | |
| return tls_strp_read_copyin(strp); | |
| if (inq < strp->stm.full_len) | |
| return tls_strp_read_copy(strp, true); | |
| tls_strp_load_anchor_with_queue(strp, inq); | |
| if (!strp->stm.full_len) { | |
| if (inq < TLS_HEADER_SIZE) | |
| return tls_strp_read_copy(strp, true); | |
| if (!tls_strp_check_queue_ok(strp, TLS_HEADER_SIZE)) | |
| return tls_strp_read_copy(strp, false); | |
| sz = tls_rx_msg_size(strp, strp->anchor); | |
| if (sz < 0) | |
| return sz; | |
| strp->stm.full_len = sz; | |
| if (!strp->stm.full_len || inq < strp->stm.full_len) | |
| return tls_strp_read_copy(strp, true); | |
| } | |
| if (!tls_strp_check_queue_ok(strp, strp->stm.full_len)) | |
| return tls_strp_read_copy(strp, false); | |
| WRITE_ONCE(strp->msg_ready, 1); | |
| return 0; | |
| } | |
| /* Parse queued data. When @announce is true and parsing produces a | |
| * newly-ready record, fire the consumer notification. Callers that | |
| * need to notify a waiter about a record parsed by another path | |
| * should invoke tls_rx_msg_maybe_announce() directly. | |
| */ | |
| void tls_strp_check_rcv(struct tls_strparser *strp, bool announce) | |
| { | |
| if (unlikely(strp->stopped) || strp->msg_ready) | |
| return; | |
| if (tls_strp_read_sock(strp) == -ENOMEM) | |
| queue_work(tls_strp_wq, &strp->work); | |
| else if (announce && strp->msg_ready) | |
| tls_rx_msg_maybe_announce(strp); | |
| } | |
| /* Lower sock lock held */ | |
| void tls_strp_data_ready(struct tls_strparser *strp) | |
| { | |
| /* This check is needed to synchronize with do_tls_strp_work. | |
| * do_tls_strp_work acquires a process lock (lock_sock) whereas | |
| * the lock held here is bh_lock_sock. The two locks can be | |
| * held by different threads at the same time, but bh_lock_sock | |
| * allows a thread in BH context to safely check if the process | |
| * lock is held. In this case, if the lock is held, queue work. | |
| */ | |
| if (sock_owned_by_user_nocheck(strp->sk)) { | |
| queue_work(tls_strp_wq, &strp->work); | |
| return; | |
| } | |
| tls_strp_check_rcv(strp, true); | |
| } | |
| static void tls_strp_work(struct work_struct *w) | |
| { | |
| struct tls_strparser *strp = | |
| container_of(w, struct tls_strparser, work); | |
| lock_sock(strp->sk); | |
| tls_strp_check_rcv(strp, true); | |
| release_sock(strp->sk); | |
| } | |
| /* Release the current record without triggering a check for the | |
| * next record. Callers must invoke tls_strp_check_rcv() before | |
| * releasing the socket lock, or queued data will stall until the | |
| * next tls_strp_data_ready() event. | |
| */ | |
| void tls_strp_msg_consume(struct tls_strparser *strp) | |
| { | |
| WARN_ON(!strp->stm.full_len); | |
| if (likely(!strp->copy_mode)) | |
| tcp_read_done(strp->sk, strp->stm.full_len); | |
| else | |
| tls_strp_flush_anchor_copy(strp); | |
| WRITE_ONCE(strp->msg_ready, 0); | |
| strp->msg_announced = 0; | |
| memset(&strp->stm, 0, sizeof(strp->stm)); | |
| } | |
| void tls_strp_stop(struct tls_strparser *strp) | |
| { | |
| strp->stopped = 1; | |
| } | |
| int tls_strp_init(struct tls_strparser *strp, struct sock *sk) | |
| { | |
| memset(strp, 0, sizeof(*strp)); | |
| strp->sk = sk; | |
| strp->anchor = alloc_skb(0, GFP_KERNEL); | |
| if (!strp->anchor) | |
| return -ENOMEM; | |
| INIT_WORK(&strp->work, tls_strp_work); | |
| return 0; | |
| } | |
| /* strp must already be stopped so that tls_strp_recv will no longer be called. | |
| * Note that tls_strp_done is not called with the lower socket held. | |
| */ | |
| void tls_strp_done(struct tls_strparser *strp) | |
| { | |
| WARN_ON(!strp->stopped); | |
| cancel_work_sync(&strp->work); | |
| __tls_strp_done(strp); | |
| } | |
| /* For setup error paths where the strparser was initialized but never armed. */ | |
| void __tls_strp_done(struct tls_strparser *strp) | |
| { | |
| tls_strp_anchor_free(strp); | |
| } | |
| int __init tls_strp_dev_init(void) | |
| { | |
| tls_strp_wq = create_workqueue("tls-strp"); | |
| if (unlikely(!tls_strp_wq)) | |
| return -ENOMEM; | |
| return 0; | |
| } | |
| void tls_strp_dev_exit(void) | |
| { | |
| destroy_workqueue(tls_strp_wq); | |
| } |