summaryrefslogtreecommitdiffstats
path: root/net/strparser/strparser.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
commit034dd340b08be1f2f0477ad16131d609f9dbd53c (patch)
tree2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /net/strparser/strparser.c
downloadlinux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz
linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure
Diffstat (limited to 'net/strparser/strparser.c')
-rw-r--r--net/strparser/strparser.c547
1 files changed, 547 insertions, 0 deletions
diff --git a/net/strparser/strparser.c b/net/strparser/strparser.c
new file mode 100644
index 000000000..a23f4b4df
--- /dev/null
+++ b/net/strparser/strparser.c
@@ -0,0 +1,547 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Stream Parser
+ *
+ * Copyright (c) 2016 Tom Herbert <tom@herbertland.com>
+ */
+
+#include <linux/bpf.h>
+#include <linux/errno.h>
+#include <linux/errqueue.h>
+#include <linux/file.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/poll.h>
+#include <linux/rculist.h>
+#include <linux/skbuff.h>
+#include <linux/socket.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+#include <net/strparser.h>
+#include <net/netns/generic.h>
+#include <net/sock.h>
+
+static struct workqueue_struct *strp_wq;
+
+static inline struct _strp_msg *_strp_msg(struct sk_buff *skb)
+{
+ return (struct _strp_msg *)((void *)skb->cb +
+ offsetof(struct sk_skb_cb, strp));
+}
+
+/* Lower lock held */
+static void strp_abort_strp(struct strparser *strp, int err)
+{
+ /* Unrecoverable error in receive */
+
+ cancel_delayed_work(&strp->msg_timer_work);
+
+ if (strp->stopped)
+ return;
+
+ strp->stopped = 1;
+
+ if (strp->skb_head) {
+ kfree_skb(strp->skb_head);
+ strp->skb_head = NULL;
+ }
+
+ strp->skb_nextp = NULL;
+ strp->need_bytes = 0;
+
+ if (strp->sk) {
+ struct sock *sk = strp->sk;
+
+ /* Report an error on the lower socket */
+ sk->sk_err = -err;
+ sk_error_report(sk);
+ }
+}
+
+static void strp_start_timer(struct strparser *strp, long timeo)
+{
+ if (timeo && timeo != LONG_MAX)
+ mod_delayed_work(strp_wq, &strp->msg_timer_work, timeo);
+}
+
+/* Lower lock held */
+static void strp_parser_err(struct strparser *strp, int err,
+ read_descriptor_t *desc)
+{
+ desc->error = err;
+ kfree_skb(strp->skb_head);
+ strp->skb_head = NULL;
+ strp->cb.abort_parser(strp, err);
+}
+
+static inline int strp_peek_len(struct strparser *strp)
+{
+ if (strp->sk) {
+ struct socket *sock = strp->sk->sk_socket;
+
+ return sock->ops->peek_len(sock);
+ }
+
+ /* If we don't have an associated socket there's nothing to peek.
+ * Return int max to avoid stopping the strparser.
+ */
+
+ return INT_MAX;
+}
+
+/* Lower socket lock held */
+static int __strp_recv(read_descriptor_t *desc, struct sk_buff *orig_skb,
+ unsigned int orig_offset, size_t orig_len,
+ size_t max_msg_size, long timeo)
+{
+ struct strparser *strp = (struct strparser *)desc->arg.data;
+ struct _strp_msg *stm;
+ struct sk_buff *head, *skb;
+ size_t eaten = 0, cand_len;
+ ssize_t extra;
+ int err;
+ bool cloned_orig = false;
+
+ if (strp->paused)
+ return 0;
+
+ head = strp->skb_head;
+ if (head) {
+ /* Message already in progress */
+ if (unlikely(orig_offset)) {
+ /* Getting data with a non-zero offset when a message is
+ * in progress is not expected. If it does happen, we
+ * need to clone and pull since we can't deal with
+ * offsets in the skbs for a message expect in the head.
+ */
+ orig_skb = skb_clone(orig_skb, GFP_ATOMIC);
+ if (!orig_skb) {
+ STRP_STATS_INCR(strp->stats.mem_fail);
+ desc->error = -ENOMEM;
+ return 0;
+ }
+ if (!pskb_pull(orig_skb, orig_offset)) {
+ STRP_STATS_INCR(strp->stats.mem_fail);
+ kfree_skb(orig_skb);
+ desc->error = -ENOMEM;
+ return 0;
+ }
+ cloned_orig = true;
+ orig_offset = 0;
+ }
+
+ if (!strp->skb_nextp) {
+ /* We are going to append to the frag_list of head.
+ * Need to unshare the frag_list.
+ */
+ err = skb_unclone(head, GFP_ATOMIC);
+ if (err) {
+ STRP_STATS_INCR(strp->stats.mem_fail);
+ desc->error = err;
+ return 0;
+ }
+
+ if (unlikely(skb_shinfo(head)->frag_list)) {
+ /* We can't append to an sk_buff that already
+ * has a frag_list. We create a new head, point
+ * the frag_list of that to the old head, and
+ * then are able to use the old head->next for
+ * appending to the message.
+ */
+ if (WARN_ON(head->next)) {
+ desc->error = -EINVAL;
+ return 0;
+ }
+
+ skb = alloc_skb_for_msg(head);
+ if (!skb) {
+ STRP_STATS_INCR(strp->stats.mem_fail);
+ desc->error = -ENOMEM;
+ return 0;
+ }
+
+ strp->skb_nextp = &head->next;
+ strp->skb_head = skb;
+ head = skb;
+ } else {
+ strp->skb_nextp =
+ &skb_shinfo(head)->frag_list;
+ }
+ }
+ }
+
+ while (eaten < orig_len) {
+ /* Always clone since we will consume something */
+ skb = skb_clone(orig_skb, GFP_ATOMIC);
+ if (!skb) {
+ STRP_STATS_INCR(strp->stats.mem_fail);
+ desc->error = -ENOMEM;
+ break;
+ }
+
+ cand_len = orig_len - eaten;
+
+ head = strp->skb_head;
+ if (!head) {
+ head = skb;
+ strp->skb_head = head;
+ /* Will set skb_nextp on next packet if needed */
+ strp->skb_nextp = NULL;
+ stm = _strp_msg(head);
+ memset(stm, 0, sizeof(*stm));
+ stm->strp.offset = orig_offset + eaten;
+ } else {
+ /* Unclone if we are appending to an skb that we
+ * already share a frag_list with.
+ */
+ if (skb_has_frag_list(skb)) {
+ err = skb_unclone(skb, GFP_ATOMIC);
+ if (err) {
+ STRP_STATS_INCR(strp->stats.mem_fail);
+ desc->error = err;
+ break;
+ }
+ }
+
+ stm = _strp_msg(head);
+ *strp->skb_nextp = skb;
+ strp->skb_nextp = &skb->next;
+ head->data_len += skb->len;
+ head->len += skb->len;
+ head->truesize += skb->truesize;
+ }
+
+ if (!stm->strp.full_len) {
+ ssize_t len;
+
+ len = (*strp->cb.parse_msg)(strp, head);
+
+ if (!len) {
+ /* Need more header to determine length */
+ if (!stm->accum_len) {
+ /* Start RX timer for new message */
+ strp_start_timer(strp, timeo);
+ }
+ stm->accum_len += cand_len;
+ eaten += cand_len;
+ STRP_STATS_INCR(strp->stats.need_more_hdr);
+ WARN_ON(eaten != orig_len);
+ break;
+ } else if (len < 0) {
+ if (len == -ESTRPIPE && stm->accum_len) {
+ len = -ENODATA;
+ strp->unrecov_intr = 1;
+ } else {
+ strp->interrupted = 1;
+ }
+ strp_parser_err(strp, len, desc);
+ break;
+ } else if (len > max_msg_size) {
+ /* Message length exceeds maximum allowed */
+ STRP_STATS_INCR(strp->stats.msg_too_big);
+ strp_parser_err(strp, -EMSGSIZE, desc);
+ break;
+ } else if (len <= (ssize_t)head->len -
+ (ssize_t)skb->len - stm->strp.offset) {
+ /* Length must be into new skb (and also
+ * greater than zero)
+ */
+ STRP_STATS_INCR(strp->stats.bad_hdr_len);
+ strp_parser_err(strp, -EPROTO, desc);
+ break;
+ }
+
+ stm->strp.full_len = len;
+ }
+
+ extra = (ssize_t)(stm->accum_len + cand_len) -
+ stm->strp.full_len;
+
+ if (extra < 0) {
+ /* Message not complete yet. */
+ if (stm->strp.full_len - stm->accum_len >
+ strp_peek_len(strp)) {
+ /* Don't have the whole message in the socket
+ * buffer. Set strp->need_bytes to wait for
+ * the rest of the message. Also, set "early
+ * eaten" since we've already buffered the skb
+ * but don't consume yet per strp_read_sock.
+ */
+
+ if (!stm->accum_len) {
+ /* Start RX timer for new message */
+ strp_start_timer(strp, timeo);
+ }
+
+ stm->accum_len += cand_len;
+ eaten += cand_len;
+ strp->need_bytes = stm->strp.full_len -
+ stm->accum_len;
+ STRP_STATS_ADD(strp->stats.bytes, cand_len);
+ desc->count = 0; /* Stop reading socket */
+ break;
+ }
+ stm->accum_len += cand_len;
+ eaten += cand_len;
+ WARN_ON(eaten != orig_len);
+ break;
+ }
+
+ /* Positive extra indicates more bytes than needed for the
+ * message
+ */
+
+ WARN_ON(extra > cand_len);
+
+ eaten += (cand_len - extra);
+
+ /* Hurray, we have a new message! */
+ cancel_delayed_work(&strp->msg_timer_work);
+ strp->skb_head = NULL;
+ strp->need_bytes = 0;
+ STRP_STATS_INCR(strp->stats.msgs);
+
+ /* Give skb to upper layer */
+ strp->cb.rcv_msg(strp, head);
+
+ if (unlikely(strp->paused)) {
+ /* Upper layer paused strp */
+ break;
+ }
+ }
+
+ if (cloned_orig)
+ kfree_skb(orig_skb);
+
+ STRP_STATS_ADD(strp->stats.bytes, eaten);
+
+ return eaten;
+}
+
+int strp_process(struct strparser *strp, struct sk_buff *orig_skb,
+ unsigned int orig_offset, size_t orig_len,
+ size_t max_msg_size, long timeo)
+{
+ read_descriptor_t desc; /* Dummy arg to strp_recv */
+
+ desc.arg.data = strp;
+
+ return __strp_recv(&desc, orig_skb, orig_offset, orig_len,
+ max_msg_size, timeo);
+}
+EXPORT_SYMBOL_GPL(strp_process);
+
+static int strp_recv(read_descriptor_t *desc, struct sk_buff *orig_skb,
+ unsigned int orig_offset, size_t orig_len)
+{
+ struct strparser *strp = (struct strparser *)desc->arg.data;
+
+ return __strp_recv(desc, orig_skb, orig_offset, orig_len,
+ strp->sk->sk_rcvbuf, READ_ONCE(strp->sk->sk_rcvtimeo));
+}
+
+static int default_read_sock_done(struct strparser *strp, int err)
+{
+ return err;
+}
+
+/* Called with lock held on lower socket */
+static int strp_read_sock(struct strparser *strp)
+{
+ struct socket *sock = strp->sk->sk_socket;
+ read_descriptor_t desc;
+
+ if (unlikely(!sock || !sock->ops))
+ return -EBUSY;
+
+ if (unlikely(!strp->cb.read_sock && !sock->ops->read_sock))
+ return -EBUSY;
+
+ 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 */
+ if (strp->cb.read_sock)
+ strp->cb.read_sock(strp, &desc, strp_recv);
+ else
+ sock->ops->read_sock(strp->sk, &desc, strp_recv);
+
+ desc.error = strp->cb.read_sock_done(strp, desc.error);
+
+ return desc.error;
+}
+
+/* Lower sock lock held */
+void strp_data_ready(struct strparser *strp)
+{
+ if (unlikely(strp->stopped) || strp->paused)
+ return;
+
+ /* This check is needed to synchronize with do_strp_work.
+ * do_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(strp_wq, &strp->work);
+ return;
+ }
+
+ if (strp->need_bytes) {
+ if (strp_peek_len(strp) < strp->need_bytes)
+ return;
+ }
+
+ if (strp_read_sock(strp) == -ENOMEM)
+ queue_work(strp_wq, &strp->work);
+}
+EXPORT_SYMBOL_GPL(strp_data_ready);
+
+static void do_strp_work(struct strparser *strp)
+{
+ /* We need the read lock to synchronize with strp_data_ready. We
+ * need the socket lock for calling strp_read_sock.
+ */
+ strp->cb.lock(strp);
+
+ if (unlikely(strp->stopped))
+ goto out;
+
+ if (strp->paused)
+ goto out;
+
+ if (strp_read_sock(strp) == -ENOMEM)
+ queue_work(strp_wq, &strp->work);
+
+out:
+ strp->cb.unlock(strp);
+}
+
+static void strp_work(struct work_struct *w)
+{
+ do_strp_work(container_of(w, struct strparser, work));
+}
+
+static void strp_msg_timeout(struct work_struct *w)
+{
+ struct strparser *strp = container_of(w, struct strparser,
+ msg_timer_work.work);
+
+ /* Message assembly timed out */
+ STRP_STATS_INCR(strp->stats.msg_timeouts);
+ strp->cb.lock(strp);
+ strp->cb.abort_parser(strp, -ETIMEDOUT);
+ strp->cb.unlock(strp);
+}
+
+static void strp_sock_lock(struct strparser *strp)
+{
+ lock_sock(strp->sk);
+}
+
+static void strp_sock_unlock(struct strparser *strp)
+{
+ release_sock(strp->sk);
+}
+
+int strp_init(struct strparser *strp, struct sock *sk,
+ const struct strp_callbacks *cb)
+{
+
+ if (!cb || !cb->rcv_msg || !cb->parse_msg)
+ return -EINVAL;
+
+ /* The sk (sock) arg determines the mode of the stream parser.
+ *
+ * If the sock is set then the strparser is in receive callback mode.
+ * The upper layer calls strp_data_ready to kick receive processing
+ * and strparser calls the read_sock function on the socket to
+ * get packets.
+ *
+ * If the sock is not set then the strparser is in general mode.
+ * The upper layer calls strp_process for each skb to be parsed.
+ */
+
+ if (!sk) {
+ if (!cb->lock || !cb->unlock)
+ return -EINVAL;
+ }
+
+ memset(strp, 0, sizeof(*strp));
+
+ strp->sk = sk;
+
+ strp->cb.lock = cb->lock ? : strp_sock_lock;
+ strp->cb.unlock = cb->unlock ? : strp_sock_unlock;
+ strp->cb.rcv_msg = cb->rcv_msg;
+ strp->cb.parse_msg = cb->parse_msg;
+ strp->cb.read_sock = cb->read_sock;
+ strp->cb.read_sock_done = cb->read_sock_done ? : default_read_sock_done;
+ strp->cb.abort_parser = cb->abort_parser ? : strp_abort_strp;
+
+ INIT_DELAYED_WORK(&strp->msg_timer_work, strp_msg_timeout);
+ INIT_WORK(&strp->work, strp_work);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(strp_init);
+
+void strp_unpause(struct strparser *strp)
+{
+ strp->paused = 0;
+
+ /* Sync setting paused with RX work */
+ smp_mb();
+
+ queue_work(strp_wq, &strp->work);
+}
+EXPORT_SYMBOL_GPL(strp_unpause);
+
+/* strp must already be stopped so that strp_recv will no longer be called.
+ * Note that strp_done is not called with the lower socket held.
+ */
+void strp_done(struct strparser *strp)
+{
+ WARN_ON(!strp->stopped);
+
+ cancel_delayed_work_sync(&strp->msg_timer_work);
+ cancel_work_sync(&strp->work);
+
+ if (strp->skb_head) {
+ kfree_skb(strp->skb_head);
+ strp->skb_head = NULL;
+ }
+}
+EXPORT_SYMBOL_GPL(strp_done);
+
+void strp_stop(struct strparser *strp)
+{
+ strp->stopped = 1;
+}
+EXPORT_SYMBOL_GPL(strp_stop);
+
+void strp_check_rcv(struct strparser *strp)
+{
+ queue_work(strp_wq, &strp->work);
+}
+EXPORT_SYMBOL_GPL(strp_check_rcv);
+
+static int __init strp_dev_init(void)
+{
+ BUILD_BUG_ON(sizeof(struct sk_skb_cb) >
+ sizeof_field(struct sk_buff, cb));
+
+ strp_wq = create_singlethread_workqueue("kstrp");
+ if (unlikely(!strp_wq))
+ return -ENOMEM;
+
+ return 0;
+}
+device_initcall(strp_dev_init);