]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.6-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 3 Jan 2024 10:19:18 +0000 (11:19 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 3 Jan 2024 10:19:18 +0000 (11:19 +0100)
added patches:
ftrace-fix-modification-of-direct_function-hash-while-in-use.patch
ring-buffer-fix-wake-ups-when-buffer_percent-is-set-to-100.patch
tracing-fix-blocked-reader-of-snapshot-buffer.patch
wifi-cfg80211-fix-cqm-for-non-range-use.patch
wifi-nl80211-fix-deadlock-in-nl80211_set_cqm_rssi-6.6.x.patch

queue-6.6/ftrace-fix-modification-of-direct_function-hash-while-in-use.patch [new file with mode: 0644]
queue-6.6/ring-buffer-fix-wake-ups-when-buffer_percent-is-set-to-100.patch [new file with mode: 0644]
queue-6.6/series
queue-6.6/tracing-fix-blocked-reader-of-snapshot-buffer.patch [new file with mode: 0644]
queue-6.6/wifi-cfg80211-fix-cqm-for-non-range-use.patch [new file with mode: 0644]
queue-6.6/wifi-nl80211-fix-deadlock-in-nl80211_set_cqm_rssi-6.6.x.patch [new file with mode: 0644]

diff --git a/queue-6.6/ftrace-fix-modification-of-direct_function-hash-while-in-use.patch b/queue-6.6/ftrace-fix-modification-of-direct_function-hash-while-in-use.patch
new file mode 100644 (file)
index 0000000..1b0dcae
--- /dev/null
@@ -0,0 +1,305 @@
+From d05cb470663a2a1879277e544f69e660208f08f2 Mon Sep 17 00:00:00 2001
+From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
+Date: Fri, 29 Dec 2023 11:51:34 -0500
+Subject: ftrace: Fix modification of direct_function hash while in use
+
+From: Steven Rostedt (Google) <rostedt@goodmis.org>
+
+commit d05cb470663a2a1879277e544f69e660208f08f2 upstream.
+
+Masami Hiramatsu reported a memory leak in register_ftrace_direct() where
+if the number of new entries are added is large enough to cause two
+allocations in the loop:
+
+        for (i = 0; i < size; i++) {
+                hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
+                        new = ftrace_add_rec_direct(entry->ip, addr, &free_hash);
+                        if (!new)
+                                goto out_remove;
+                        entry->direct = addr;
+                }
+        }
+
+Where ftrace_add_rec_direct() has:
+
+        if (ftrace_hash_empty(direct_functions) ||
+            direct_functions->count > 2 * (1 << direct_functions->size_bits)) {
+                struct ftrace_hash *new_hash;
+                int size = ftrace_hash_empty(direct_functions) ? 0 :
+                        direct_functions->count + 1;
+
+                if (size < 32)
+                        size = 32;
+
+                new_hash = dup_hash(direct_functions, size);
+                if (!new_hash)
+                        return NULL;
+
+                *free_hash = direct_functions;
+                direct_functions = new_hash;
+        }
+
+The "*free_hash = direct_functions;" can happen twice, losing the previous
+allocation of direct_functions.
+
+But this also exposed a more serious bug.
+
+The modification of direct_functions above is not safe. As
+direct_functions can be referenced at any time to find what direct caller
+it should call, the time between:
+
+                new_hash = dup_hash(direct_functions, size);
+ and
+                direct_functions = new_hash;
+
+can have a race with another CPU (or even this one if it gets interrupted),
+and the entries being moved to the new hash are not referenced.
+
+That's because the "dup_hash()" is really misnamed and is really a
+"move_hash()". It moves the entries from the old hash to the new one.
+
+Now even if that was changed, this code is not proper as direct_functions
+should not be updated until the end. That is the best way to handle
+function reference changes, and is the way other parts of ftrace handles
+this.
+
+The following is done:
+
+ 1. Change add_hash_entry() to return the entry it created and inserted
+    into the hash, and not just return success or not.
+
+ 2. Replace ftrace_add_rec_direct() with add_hash_entry(), and remove
+    the former.
+
+ 3. Allocate a "new_hash" at the start that is made for holding both the
+    new hash entries as well as the existing entries in direct_functions.
+
+ 4. Copy (not move) the direct_function entries over to the new_hash.
+
+ 5. Copy the entries of the added hash to the new_hash.
+
+ 6. If everything succeeds, then use rcu_pointer_assign() to update the
+    direct_functions with the new_hash.
+
+This simplifies the code and fixes both the memory leak as well as the
+race condition mentioned above.
+
+Link: https://lore.kernel.org/all/170368070504.42064.8960569647118388081.stgit@devnote2/
+Link: https://lore.kernel.org/linux-trace-kernel/20231229115134.08dd5174@gandalf.local.home
+
+Cc: stable@vger.kernel.org
+Cc: Mark Rutland <mark.rutland@arm.com>
+Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+Cc: Jiri Olsa <jolsa@kernel.org>
+Cc: Alexei Starovoitov <ast@kernel.org>
+Cc: Daniel Borkmann <daniel@iogearbox.net>
+Acked-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Fixes: 763e34e74bb7d ("ftrace: Add register_ftrace_direct()")
+Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/trace/ftrace.c |  100 +++++++++++++++++++++++---------------------------
+ 1 file changed, 47 insertions(+), 53 deletions(-)
+
+--- a/kernel/trace/ftrace.c
++++ b/kernel/trace/ftrace.c
+@@ -1183,18 +1183,19 @@ static void __add_hash_entry(struct ftra
+       hash->count++;
+ }
+-static int add_hash_entry(struct ftrace_hash *hash, unsigned long ip)
++static struct ftrace_func_entry *
++add_hash_entry(struct ftrace_hash *hash, unsigned long ip)
+ {
+       struct ftrace_func_entry *entry;
+       entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+       if (!entry)
+-              return -ENOMEM;
++              return NULL;
+       entry->ip = ip;
+       __add_hash_entry(hash, entry);
+-      return 0;
++      return entry;
+ }
+ static void
+@@ -1349,7 +1350,6 @@ alloc_and_copy_ftrace_hash(int size_bits
+       struct ftrace_func_entry *entry;
+       struct ftrace_hash *new_hash;
+       int size;
+-      int ret;
+       int i;
+       new_hash = alloc_ftrace_hash(size_bits);
+@@ -1366,8 +1366,7 @@ alloc_and_copy_ftrace_hash(int size_bits
+       size = 1 << hash->size_bits;
+       for (i = 0; i < size; i++) {
+               hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
+-                      ret = add_hash_entry(new_hash, entry->ip);
+-                      if (ret < 0)
++                      if (add_hash_entry(new_hash, entry->ip) == NULL)
+                               goto free_hash;
+               }
+       }
+@@ -2536,7 +2535,7 @@ ftrace_find_unique_ops(struct dyn_ftrace
+ #ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
+ /* Protected by rcu_tasks for reading, and direct_mutex for writing */
+-static struct ftrace_hash *direct_functions = EMPTY_HASH;
++static struct ftrace_hash __rcu *direct_functions = EMPTY_HASH;
+ static DEFINE_MUTEX(direct_mutex);
+ int ftrace_direct_func_count;
+@@ -2555,39 +2554,6 @@ unsigned long ftrace_find_rec_direct(uns
+       return entry->direct;
+ }
+-static struct ftrace_func_entry*
+-ftrace_add_rec_direct(unsigned long ip, unsigned long addr,
+-                    struct ftrace_hash **free_hash)
+-{
+-      struct ftrace_func_entry *entry;
+-
+-      if (ftrace_hash_empty(direct_functions) ||
+-          direct_functions->count > 2 * (1 << direct_functions->size_bits)) {
+-              struct ftrace_hash *new_hash;
+-              int size = ftrace_hash_empty(direct_functions) ? 0 :
+-                      direct_functions->count + 1;
+-
+-              if (size < 32)
+-                      size = 32;
+-
+-              new_hash = dup_hash(direct_functions, size);
+-              if (!new_hash)
+-                      return NULL;
+-
+-              *free_hash = direct_functions;
+-              direct_functions = new_hash;
+-      }
+-
+-      entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+-      if (!entry)
+-              return NULL;
+-
+-      entry->ip = ip;
+-      entry->direct = addr;
+-      __add_hash_entry(direct_functions, entry);
+-      return entry;
+-}
+-
+ static void call_direct_funcs(unsigned long ip, unsigned long pip,
+                             struct ftrace_ops *ops, struct ftrace_regs *fregs)
+ {
+@@ -4223,8 +4189,8 @@ enter_record(struct ftrace_hash *hash, s
+               /* Do nothing if it exists */
+               if (entry)
+                       return 0;
+-
+-              ret = add_hash_entry(hash, rec->ip);
++              if (add_hash_entry(hash, rec->ip) == NULL)
++                      ret = -ENOMEM;
+       }
+       return ret;
+ }
+@@ -5266,7 +5232,8 @@ __ftrace_match_addr(struct ftrace_hash *
+               return 0;
+       }
+-      return add_hash_entry(hash, ip);
++      entry = add_hash_entry(hash, ip);
++      return entry ? 0 :  -ENOMEM;
+ }
+ static int
+@@ -5410,7 +5377,7 @@ static void remove_direct_functions_hash
+  */
+ int register_ftrace_direct(struct ftrace_ops *ops, unsigned long addr)
+ {
+-      struct ftrace_hash *hash, *free_hash = NULL;
++      struct ftrace_hash *hash, *new_hash = NULL, *free_hash = NULL;
+       struct ftrace_func_entry *entry, *new;
+       int err = -EBUSY, size, i;
+@@ -5436,17 +5403,44 @@ int register_ftrace_direct(struct ftrace
+               }
+       }
+-      /* ... and insert them to direct_functions hash. */
+       err = -ENOMEM;
++
++      /* Make a copy hash to place the new and the old entries in */
++      size = hash->count + direct_functions->count;
++      if (size > 32)
++              size = 32;
++      new_hash = alloc_ftrace_hash(fls(size));
++      if (!new_hash)
++              goto out_unlock;
++
++      /* Now copy over the existing direct entries */
++      size = 1 << direct_functions->size_bits;
++      for (i = 0; i < size; i++) {
++              hlist_for_each_entry(entry, &direct_functions->buckets[i], hlist) {
++                      new = add_hash_entry(new_hash, entry->ip);
++                      if (!new)
++                              goto out_unlock;
++                      new->direct = entry->direct;
++              }
++      }
++
++      /* ... and add the new entries */
++      size = 1 << hash->size_bits;
+       for (i = 0; i < size; i++) {
+               hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
+-                      new = ftrace_add_rec_direct(entry->ip, addr, &free_hash);
++                      new = add_hash_entry(new_hash, entry->ip);
+                       if (!new)
+-                              goto out_remove;
++                              goto out_unlock;
++                      /* Update both the copy and the hash entry */
++                      new->direct = addr;
+                       entry->direct = addr;
+               }
+       }
++      free_hash = direct_functions;
++      rcu_assign_pointer(direct_functions, new_hash);
++      new_hash = NULL;
++
+       ops->func = call_direct_funcs;
+       ops->flags = MULTI_FLAGS;
+       ops->trampoline = FTRACE_REGS_ADDR;
+@@ -5454,17 +5448,17 @@ int register_ftrace_direct(struct ftrace
+       err = register_ftrace_function_nolock(ops);
+- out_remove:
+-      if (err)
+-              remove_direct_functions_hash(hash, addr);
+-
+  out_unlock:
+       mutex_unlock(&direct_mutex);
+-      if (free_hash) {
++      if (free_hash && free_hash != EMPTY_HASH) {
+               synchronize_rcu_tasks();
+               free_ftrace_hash(free_hash);
+       }
++
++      if (new_hash)
++              free_ftrace_hash(new_hash);
++
+       return err;
+ }
+ EXPORT_SYMBOL_GPL(register_ftrace_direct);
+@@ -6309,7 +6303,7 @@ ftrace_graph_set_hash(struct ftrace_hash
+                               if (entry)
+                                       continue;
+-                              if (add_hash_entry(hash, rec->ip) < 0)
++                              if (add_hash_entry(hash, rec->ip) == NULL)
+                                       goto out;
+                       } else {
+                               if (entry) {
diff --git a/queue-6.6/ring-buffer-fix-wake-ups-when-buffer_percent-is-set-to-100.patch b/queue-6.6/ring-buffer-fix-wake-ups-when-buffer_percent-is-set-to-100.patch
new file mode 100644 (file)
index 0000000..f995d26
--- /dev/null
@@ -0,0 +1,73 @@
+From 623b1f896fa8a669a277ee5a258307a16c7377a3 Mon Sep 17 00:00:00 2001
+From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
+Date: Tue, 26 Dec 2023 12:59:02 -0500
+Subject: ring-buffer: Fix wake ups when buffer_percent is set to 100
+
+From: Steven Rostedt (Google) <rostedt@goodmis.org>
+
+commit 623b1f896fa8a669a277ee5a258307a16c7377a3 upstream.
+
+The tracefs file "buffer_percent" is to allow user space to set a
+water-mark on how much of the tracing ring buffer needs to be filled in
+order to wake up a blocked reader.
+
+ 0 - is to wait until any data is in the buffer
+ 1 - is to wait for 1% of the sub buffers to be filled
+ 50 - would be half of the sub buffers are filled with data
+ 100 - is not to wake the waiter until the ring buffer is completely full
+
+Unfortunately the test for being full was:
+
+       dirty = ring_buffer_nr_dirty_pages(buffer, cpu);
+       return (dirty * 100) > (full * nr_pages);
+
+Where "full" is the value for "buffer_percent".
+
+There is two issues with the above when full == 100.
+
+1. dirty * 100 > 100 * nr_pages will never be true
+   That is, the above is basically saying that if the user sets
+   buffer_percent to 100, more pages need to be dirty than exist in the
+   ring buffer!
+
+2. The page that the writer is on is never considered dirty, as dirty
+   pages are only those that are full. When the writer goes to a new
+   sub-buffer, it clears the contents of that sub-buffer.
+
+That is, even if the check was ">=" it would still not be equal as the
+most pages that can be considered "dirty" is nr_pages - 1.
+
+To fix this, add one to dirty and use ">=" in the compare.
+
+Link: https://lore.kernel.org/linux-trace-kernel/20231226125902.4a057f1d@gandalf.local.home
+
+Cc: stable@vger.kernel.org
+Cc: Mark Rutland <mark.rutland@arm.com>
+Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+Acked-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Fixes: 03329f9939781 ("tracing: Add tracefs file buffer_percentage")
+Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/trace/ring_buffer.c |    9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+--- a/kernel/trace/ring_buffer.c
++++ b/kernel/trace/ring_buffer.c
+@@ -881,9 +881,14 @@ static __always_inline bool full_hit(str
+       if (!nr_pages || !full)
+               return true;
+-      dirty = ring_buffer_nr_dirty_pages(buffer, cpu);
++      /*
++       * Add one as dirty will never equal nr_pages, as the sub-buffer
++       * that the writer is on is not counted as dirty.
++       * This is needed if "buffer_percent" is set to 100.
++       */
++      dirty = ring_buffer_nr_dirty_pages(buffer, cpu) + 1;
+-      return (dirty * 100) > (full * nr_pages);
++      return (dirty * 100) >= (full * nr_pages);
+ }
+ /*
index da29cdeb8577f1eec4b776c8d1d1d15410dff1ac..fce2f08781d5d0f2fe0354191a5164c559f621d5 100644 (file)
@@ -41,3 +41,8 @@ mm-migrate-high-order-folios-in-swap-cache-correctly.patch
 mm-memory-failure-cast-index-to-loff_t-before-shifting-it.patch
 mm-memory-failure-check-the-mapcount-of-the-precise-page.patch
 revert-nvme-fc-fix-race-between-error-recovery-and-creating-association.patch
+ring-buffer-fix-wake-ups-when-buffer_percent-is-set-to-100.patch
+ftrace-fix-modification-of-direct_function-hash-while-in-use.patch
+tracing-fix-blocked-reader-of-snapshot-buffer.patch
+wifi-cfg80211-fix-cqm-for-non-range-use.patch
+wifi-nl80211-fix-deadlock-in-nl80211_set_cqm_rssi-6.6.x.patch
diff --git a/queue-6.6/tracing-fix-blocked-reader-of-snapshot-buffer.patch b/queue-6.6/tracing-fix-blocked-reader-of-snapshot-buffer.patch
new file mode 100644 (file)
index 0000000..2e4e512
--- /dev/null
@@ -0,0 +1,105 @@
+From 39a7dc23a1ed0fe81141792a09449d124c5953bd Mon Sep 17 00:00:00 2001
+From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
+Date: Thu, 28 Dec 2023 09:51:49 -0500
+Subject: tracing: Fix blocked reader of snapshot buffer
+
+From: Steven Rostedt (Google) <rostedt@goodmis.org>
+
+commit 39a7dc23a1ed0fe81141792a09449d124c5953bd upstream.
+
+If an application blocks on the snapshot or snapshot_raw files, expecting
+to be woken up when a snapshot occurs, it will not happen. Or it may
+happen with an unexpected result.
+
+That result is that the application will be reading the main buffer
+instead of the snapshot buffer. That is because when the snapshot occurs,
+the main and snapshot buffers are swapped. But the reader has a descriptor
+still pointing to the buffer that it originally connected to.
+
+This is fine for the main buffer readers, as they may be blocked waiting
+for a watermark to be hit, and when a snapshot occurs, the data that the
+main readers want is now on the snapshot buffer.
+
+But for waiters of the snapshot buffer, they are waiting for an event to
+occur that will trigger the snapshot and they can then consume it quickly
+to save the snapshot before the next snapshot occurs. But to do this, they
+need to read the new snapshot buffer, not the old one that is now
+receiving new data.
+
+Also, it does not make sense to have a watermark "buffer_percent" on the
+snapshot buffer, as the snapshot buffer is static and does not receive new
+data except all at once.
+
+Link: https://lore.kernel.org/linux-trace-kernel/20231228095149.77f5b45d@gandalf.local.home
+
+Cc: stable@vger.kernel.org
+Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+Cc: Mark Rutland <mark.rutland@arm.com>
+Acked-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Fixes: debdd57f5145f ("tracing: Make a snapshot feature available from userspace")
+Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/trace/ring_buffer.c |    3 ++-
+ kernel/trace/trace.c       |   20 +++++++++++++++++---
+ 2 files changed, 19 insertions(+), 4 deletions(-)
+
+--- a/kernel/trace/ring_buffer.c
++++ b/kernel/trace/ring_buffer.c
+@@ -949,7 +949,8 @@ void ring_buffer_wake_waiters(struct tra
+       /* make sure the waiters see the new index */
+       smp_wmb();
+-      rb_wake_up_waiters(&rbwork->work);
++      /* This can be called in any context */
++      irq_work_queue(&rbwork->work);
+ }
+ /**
+--- a/kernel/trace/trace.c
++++ b/kernel/trace/trace.c
+@@ -1893,6 +1893,9 @@ update_max_tr(struct trace_array *tr, st
+       __update_max_tr(tr, tsk, cpu);
+       arch_spin_unlock(&tr->max_lock);
++
++      /* Any waiters on the old snapshot buffer need to wake up */
++      ring_buffer_wake_waiters(tr->array_buffer.buffer, RING_BUFFER_ALL_CPUS);
+ }
+ /**
+@@ -1944,12 +1947,23 @@ update_max_tr_single(struct trace_array
+ static int wait_on_pipe(struct trace_iterator *iter, int full)
+ {
++      int ret;
++
+       /* Iterators are static, they should be filled or empty */
+       if (trace_buffer_iter(iter, iter->cpu_file))
+               return 0;
+-      return ring_buffer_wait(iter->array_buffer->buffer, iter->cpu_file,
+-                              full);
++      ret = ring_buffer_wait(iter->array_buffer->buffer, iter->cpu_file, full);
++
++#ifdef CONFIG_TRACER_MAX_TRACE
++      /*
++       * Make sure this is still the snapshot buffer, as if a snapshot were
++       * to happen, this would now be the main buffer.
++       */
++      if (iter->snapshot)
++              iter->array_buffer = &iter->tr->max_buffer;
++#endif
++      return ret;
+ }
+ #ifdef CONFIG_FTRACE_STARTUP_TEST
+@@ -8514,7 +8528,7 @@ tracing_buffers_splice_read(struct file
+               wait_index = READ_ONCE(iter->wait_index);
+-              ret = wait_on_pipe(iter, iter->tr->buffer_percent);
++              ret = wait_on_pipe(iter, iter->snapshot ? 0 : iter->tr->buffer_percent);
+               if (ret)
+                       goto out;
diff --git a/queue-6.6/wifi-cfg80211-fix-cqm-for-non-range-use.patch b/queue-6.6/wifi-cfg80211-fix-cqm-for-non-range-use.patch
new file mode 100644 (file)
index 0000000..4b6d1c4
--- /dev/null
@@ -0,0 +1,151 @@
+From leo@leolam.fr Sat Dec 16 06:48:17 2023
+From: "Léo Lam" <leo@leolam.fr>
+Date: Sat, 16 Dec 2023 05:47:15 +0000
+Subject: wifi: cfg80211: fix CQM for non-range use
+To: stable@vger.kernel.org
+Cc: "Johannes Berg" <johannes.berg@intel.com>, "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>, "Léo Lam" <leo@leolam.fr>
+Message-ID: <20231216054715.7729-2-leo@leolam.fr>
+
+From: Johannes Berg <johannes.berg@intel.com>
+
+commit 7e7efdda6adb385fbdfd6f819d76bc68c923c394 upstream.
+
+[note: this is commit 4a7e92551618f3737b305f62451353ee05662f57 reapplied;
+that commit had been reverted in 6.6.6 because it caused regressions, see
+https://lore.kernel.org/stable/2023121450-habitual-transpose-68a1@gregkh/
+for details]
+
+My prior race fix here broke CQM when ranges aren't used, as
+the reporting worker now requires the cqm_config to be set in
+the wdev, but isn't set when there's no range configured.
+
+Rather than continuing to special-case the range version, set
+the cqm_config always and configure accordingly, also tracking
+if range was used or not to be able to clear the configuration
+appropriately with the same API, which was actually not right
+if both were implemented by a driver for some reason, as is
+the case with mac80211 (though there the implementations are
+equivalent so it doesn't matter.)
+
+Also, the original multiple-RSSI commit lost checking for the
+callback, so might have potentially crashed if a driver had
+neither implementation, and userspace tried to use it despite
+not being advertised as supported.
+
+Cc: stable@vger.kernel.org
+Fixes: 4a4b8169501b ("cfg80211: Accept multiple RSSI thresholds for CQM")
+Fixes: 37c20b2effe9 ("wifi: cfg80211: fix cqm_config access race")
+Signed-off-by: Johannes Berg <johannes.berg@intel.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: "Léo Lam" <leo@leolam.fr>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/wireless/core.h    |    1 
+ net/wireless/nl80211.c |   50 ++++++++++++++++++++++++++++++-------------------
+ 2 files changed, 32 insertions(+), 19 deletions(-)
+
+--- a/net/wireless/core.h
++++ b/net/wireless/core.h
+@@ -299,6 +299,7 @@ struct cfg80211_cqm_config {
+       u32 rssi_hyst;
+       s32 last_rssi_event_value;
+       enum nl80211_cqm_rssi_threshold_event last_rssi_event_type;
++      bool use_range_api;
+       int n_rssi_thresholds;
+       s32 rssi_thresholds[] __counted_by(n_rssi_thresholds);
+ };
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -12824,10 +12824,6 @@ static int cfg80211_cqm_rssi_update(stru
+       int i, n, low_index;
+       int err;
+-      /* RSSI reporting disabled? */
+-      if (!cqm_config)
+-              return rdev_set_cqm_rssi_range_config(rdev, dev, 0, 0);
+-
+       /*
+        * Obtain current RSSI value if possible, if not and no RSSI threshold
+        * event has been received yet, we should receive an event after a
+@@ -12902,18 +12898,6 @@ static int nl80211_set_cqm_rssi(struct g
+           wdev->iftype != NL80211_IFTYPE_P2P_CLIENT)
+               return -EOPNOTSUPP;
+-      if (n_thresholds <= 1 && rdev->ops->set_cqm_rssi_config) {
+-              if (n_thresholds == 0 || thresholds[0] == 0) /* Disabling */
+-                      return rdev_set_cqm_rssi_config(rdev, dev, 0, 0);
+-
+-              return rdev_set_cqm_rssi_config(rdev, dev,
+-                                              thresholds[0], hysteresis);
+-      }
+-
+-      if (!wiphy_ext_feature_isset(&rdev->wiphy,
+-                                   NL80211_EXT_FEATURE_CQM_RSSI_LIST))
+-              return -EOPNOTSUPP;
+-
+       if (n_thresholds == 1 && thresholds[0] == 0) /* Disabling */
+               n_thresholds = 0;
+@@ -12921,6 +12905,20 @@ static int nl80211_set_cqm_rssi(struct g
+       old = rcu_dereference_protected(wdev->cqm_config,
+                                       lockdep_is_held(&wdev->mtx));
++      /* if already disabled just succeed */
++      if (!n_thresholds && !old)
++              return 0;
++
++      if (n_thresholds > 1) {
++              if (!wiphy_ext_feature_isset(&rdev->wiphy,
++                                           NL80211_EXT_FEATURE_CQM_RSSI_LIST) ||
++                  !rdev->ops->set_cqm_rssi_range_config)
++                      return -EOPNOTSUPP;
++      } else {
++              if (!rdev->ops->set_cqm_rssi_config)
++                      return -EOPNOTSUPP;
++      }
++
+       if (n_thresholds) {
+               cqm_config = kzalloc(struct_size(cqm_config, rssi_thresholds,
+                                                n_thresholds),
+@@ -12935,13 +12933,26 @@ static int nl80211_set_cqm_rssi(struct g
+               memcpy(cqm_config->rssi_thresholds, thresholds,
+                      flex_array_size(cqm_config, rssi_thresholds,
+                                      n_thresholds));
++              cqm_config->use_range_api = n_thresholds > 1 ||
++                                          !rdev->ops->set_cqm_rssi_config;
+               rcu_assign_pointer(wdev->cqm_config, cqm_config);
++
++              if (cqm_config->use_range_api)
++                      err = cfg80211_cqm_rssi_update(rdev, dev, cqm_config);
++              else
++                      err = rdev_set_cqm_rssi_config(rdev, dev,
++                                                     thresholds[0],
++                                                     hysteresis);
+       } else {
+               RCU_INIT_POINTER(wdev->cqm_config, NULL);
++              /* if enabled as range also disable via range */
++              if (old->use_range_api)
++                      err = rdev_set_cqm_rssi_range_config(rdev, dev, 0, 0);
++              else
++                      err = rdev_set_cqm_rssi_config(rdev, dev, 0, 0);
+       }
+-      err = cfg80211_cqm_rssi_update(rdev, dev, cqm_config);
+       if (err) {
+               rcu_assign_pointer(wdev->cqm_config, old);
+               kfree_rcu(cqm_config, rcu_head);
+@@ -19131,10 +19142,11 @@ void cfg80211_cqm_rssi_notify_work(struc
+       wdev_lock(wdev);
+       cqm_config = rcu_dereference_protected(wdev->cqm_config,
+                                              lockdep_is_held(&wdev->mtx));
+-      if (!wdev->cqm_config)
++      if (!cqm_config)
+               goto unlock;
+-      cfg80211_cqm_rssi_update(rdev, wdev->netdev, cqm_config);
++      if (cqm_config->use_range_api)
++              cfg80211_cqm_rssi_update(rdev, wdev->netdev, cqm_config);
+       rssi_level = cqm_config->last_rssi_event_value;
+       rssi_event = cqm_config->last_rssi_event_type;
diff --git a/queue-6.6/wifi-nl80211-fix-deadlock-in-nl80211_set_cqm_rssi-6.6.x.patch b/queue-6.6/wifi-nl80211-fix-deadlock-in-nl80211_set_cqm_rssi-6.6.x.patch
new file mode 100644 (file)
index 0000000..1e4ebe8
--- /dev/null
@@ -0,0 +1,80 @@
+From stable+bounces-6868-greg=kroah.com@vger.kernel.org Sat Dec 16 06:57:02 2023
+From: "Léo Lam" <leo@leolam.fr>
+Date: Sat, 16 Dec 2023 05:47:17 +0000
+Subject: wifi: nl80211: fix deadlock in nl80211_set_cqm_rssi (6.6.x)
+To: stable@vger.kernel.org
+Cc: "Léo Lam" <leo@leolam.fr>, "Philip Müller" <philm@manjaro.org>, "Johannes Berg" <johannes.berg@intel.com>
+Message-ID: <20231216054715.7729-4-leo@leolam.fr>
+
+From: "Léo Lam" <leo@leolam.fr>
+
+Commit 008afb9f3d57 ("wifi: cfg80211: fix CQM for non-range use"
+backported to 6.6.x) causes nl80211_set_cqm_rssi not to release the
+wdev lock in some of the error paths.
+
+Of course, the ensuing deadlock causes userland network managers to
+break pretty badly, and on typical systems this also causes lockups on
+on suspend, poweroff and reboot. See [1], [2], [3] for example reports.
+
+The upstream commit 7e7efdda6adb ("wifi: cfg80211: fix CQM for non-range
+use"), committed in November 2023, is completely fine because there was
+another commit in August 2023 that removed the wdev lock:
+see commit 076fc8775daf ("wifi: cfg80211: remove wdev mutex").
+
+The reason things broke in 6.6.5 is that commit 4338058f6009 was applied
+without also applying 076fc8775daf.
+
+Commit 076fc8775daf ("wifi: cfg80211: remove wdev mutex") is a rather
+large commit; adjusting the error handling (which is what this commit does)
+yields a much simpler patch and was tested to work properly.
+
+Fix the deadlock by releasing the lock before returning.
+
+[1] https://bugzilla.kernel.org/show_bug.cgi?id=218247
+[2] https://bbs.archlinux.org/viewtopic.php?id=290976
+[3] https://lore.kernel.org/all/87sf4belmm.fsf@turtle.gmx.de/
+
+Link: https://lore.kernel.org/stable/e374bb16-5b13-44cc-b11a-2f4eefb1ecf5@manjaro.org/
+Fixes: 008afb9f3d57 ("wifi: cfg80211: fix CQM for non-range use")
+Tested-by: "Léo Lam" <leo@leolam.fr>
+Tested-by: Philip Müller <philm@manjaro.org>
+Cc: stable@vger.kernel.org
+Cc: Johannes Berg <johannes.berg@intel.com>
+Signed-off-by: "Léo Lam" <leo@leolam.fr>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/wireless/nl80211.c |   18 ++++++++++++------
+ 1 file changed, 12 insertions(+), 6 deletions(-)
+
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -12906,17 +12906,23 @@ static int nl80211_set_cqm_rssi(struct g
+                                       lockdep_is_held(&wdev->mtx));
+       /* if already disabled just succeed */
+-      if (!n_thresholds && !old)
+-              return 0;
++      if (!n_thresholds && !old) {
++              err = 0;
++              goto unlock;
++      }
+       if (n_thresholds > 1) {
+               if (!wiphy_ext_feature_isset(&rdev->wiphy,
+                                            NL80211_EXT_FEATURE_CQM_RSSI_LIST) ||
+-                  !rdev->ops->set_cqm_rssi_range_config)
+-                      return -EOPNOTSUPP;
++                  !rdev->ops->set_cqm_rssi_range_config) {
++                      err = -EOPNOTSUPP;
++                      goto unlock;
++              }
+       } else {
+-              if (!rdev->ops->set_cqm_rssi_config)
+-                      return -EOPNOTSUPP;
++              if (!rdev->ops->set_cqm_rssi_config) {
++                      err = -EOPNOTSUPP;
++                      goto unlock;
++              }
+       }
+       if (n_thresholds) {