]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
5.15-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 23 Dec 2024 11:32:11 +0000 (12:32 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 23 Dec 2024 11:32:11 +0000 (12:32 +0100)
added patches:
btrfs-tree-checker-reject-inline-extent-items-with-0-ref-count.patch
drivers-hv-util-avoid-accessing-a-ringbuffer-not-initialized-yet.patch
kvm-x86-play-nice-with-protected-guests-in-complete_hypercall_exit.patch
tracing-add-missing-helper-functions-in-event-pointer-dereference-check.patch
tracing-add-s-check-in-test_event_printk.patch
tracing-fix-test_event_printk-to-process-entire-print-argument.patch

queue-5.15/btrfs-tree-checker-reject-inline-extent-items-with-0-ref-count.patch [new file with mode: 0644]
queue-5.15/drivers-hv-util-avoid-accessing-a-ringbuffer-not-initialized-yet.patch [new file with mode: 0644]
queue-5.15/kvm-x86-play-nice-with-protected-guests-in-complete_hypercall_exit.patch [new file with mode: 0644]
queue-5.15/series
queue-5.15/tracing-add-missing-helper-functions-in-event-pointer-dereference-check.patch [new file with mode: 0644]
queue-5.15/tracing-add-s-check-in-test_event_printk.patch [new file with mode: 0644]
queue-5.15/tracing-fix-test_event_printk-to-process-entire-print-argument.patch [new file with mode: 0644]

diff --git a/queue-5.15/btrfs-tree-checker-reject-inline-extent-items-with-0-ref-count.patch b/queue-5.15/btrfs-tree-checker-reject-inline-extent-items-with-0-ref-count.patch
new file mode 100644 (file)
index 0000000..5b5514b
--- /dev/null
@@ -0,0 +1,104 @@
+From dfb92681a19e1d5172420baa242806414b3eff6f Mon Sep 17 00:00:00 2001
+From: Qu Wenruo <wqu@suse.com>
+Date: Wed, 4 Dec 2024 13:30:46 +1030
+Subject: btrfs: tree-checker: reject inline extent items with 0 ref count
+
+From: Qu Wenruo <wqu@suse.com>
+
+commit dfb92681a19e1d5172420baa242806414b3eff6f upstream.
+
+[BUG]
+There is a bug report in the mailing list where btrfs_run_delayed_refs()
+failed to drop the ref count for logical 25870311358464 num_bytes
+2113536.
+
+The involved leaf dump looks like this:
+
+  item 166 key (25870311358464 168 2113536) itemoff 10091 itemsize 50
+    extent refs 1 gen 84178 flags 1
+    ref#0: shared data backref parent 32399126528000 count 0 <<<
+    ref#1: shared data backref parent 31808973717504 count 1
+
+Notice the count number is 0.
+
+[CAUSE]
+There is no concrete evidence yet, but considering 0 -> 1 is also a
+single bit flipped, it's possible that hardware memory bitflip is
+involved, causing the on-disk extent tree to be corrupted.
+
+[FIX]
+To prevent us reading such corrupted extent item, or writing such
+damaged extent item back to disk, enhance the handling of
+BTRFS_EXTENT_DATA_REF_KEY and BTRFS_SHARED_DATA_REF_KEY keys for both
+inlined and key items, to detect such 0 ref count and reject them.
+
+CC: stable@vger.kernel.org # 5.4+
+Link: https://lore.kernel.org/linux-btrfs/7c69dd49-c346-4806-86e7-e6f863a66f48@app.fastmail.com/
+Reported-by: Frankie Fisher <frankie@terrorise.me.uk>
+Reviewed-by: Filipe Manana <fdmanana@suse.com>
+Signed-off-by: Qu Wenruo <wqu@suse.com>
+Reviewed-by: David Sterba <dsterba@suse.com>
+Signed-off-by: David Sterba <dsterba@suse.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/btrfs/tree-checker.c |   27 ++++++++++++++++++++++++++-
+ 1 file changed, 26 insertions(+), 1 deletion(-)
+
+--- a/fs/btrfs/tree-checker.c
++++ b/fs/btrfs/tree-checker.c
+@@ -1416,6 +1416,11 @@ static int check_extent_item(struct exte
+                                          dref_offset, fs_info->sectorsize);
+                               return -EUCLEAN;
+                       }
++                      if (unlikely(btrfs_extent_data_ref_count(leaf, dref) == 0)) {
++                              extent_err(leaf, slot,
++                      "invalid data ref count, should have non-zero value");
++                              return -EUCLEAN;
++                      }
+                       inline_refs += btrfs_extent_data_ref_count(leaf, dref);
+                       break;
+               /* Contains parent bytenr and ref count */
+@@ -1428,6 +1433,11 @@ static int check_extent_item(struct exte
+                                          inline_offset, fs_info->sectorsize);
+                               return -EUCLEAN;
+                       }
++                      if (unlikely(btrfs_shared_data_ref_count(leaf, sref) == 0)) {
++                              extent_err(leaf, slot,
++                      "invalid shared data ref count, should have non-zero value");
++                              return -EUCLEAN;
++                      }
+                       inline_refs += btrfs_shared_data_ref_count(leaf, sref);
+                       break;
+               default:
+@@ -1479,8 +1489,18 @@ static int check_simple_keyed_refs(struc
+ {
+       u32 expect_item_size = 0;
+-      if (key->type == BTRFS_SHARED_DATA_REF_KEY)
++      if (key->type == BTRFS_SHARED_DATA_REF_KEY) {
++              struct btrfs_shared_data_ref *sref;
++
++              sref = btrfs_item_ptr(leaf, slot, struct btrfs_shared_data_ref);
++              if (unlikely(btrfs_shared_data_ref_count(leaf, sref) == 0)) {
++                      extent_err(leaf, slot,
++              "invalid shared data backref count, should have non-zero value");
++                      return -EUCLEAN;
++              }
++
+               expect_item_size = sizeof(struct btrfs_shared_data_ref);
++      }
+       if (unlikely(btrfs_item_size_nr(leaf, slot) != expect_item_size)) {
+               generic_err(leaf, slot,
+@@ -1540,6 +1560,11 @@ static int check_extent_data_ref(struct
+                                  offset, leaf->fs_info->sectorsize);
+                       return -EUCLEAN;
+               }
++              if (unlikely(btrfs_extent_data_ref_count(leaf, dref) == 0)) {
++                      extent_err(leaf, slot,
++      "invalid extent data backref count, should have non-zero value");
++                      return -EUCLEAN;
++              }
+       }
+       return 0;
+ }
diff --git a/queue-5.15/drivers-hv-util-avoid-accessing-a-ringbuffer-not-initialized-yet.patch b/queue-5.15/drivers-hv-util-avoid-accessing-a-ringbuffer-not-initialized-yet.patch
new file mode 100644 (file)
index 0000000..40df3e8
--- /dev/null
@@ -0,0 +1,169 @@
+From 07a756a49f4b4290b49ea46e089cbe6f79ff8d26 Mon Sep 17 00:00:00 2001
+From: Michael Kelley <mhklinux@outlook.com>
+Date: Wed, 6 Nov 2024 07:42:47 -0800
+Subject: Drivers: hv: util: Avoid accessing a ringbuffer not initialized yet
+
+From: Michael Kelley <mhklinux@outlook.com>
+
+commit 07a756a49f4b4290b49ea46e089cbe6f79ff8d26 upstream.
+
+If the KVP (or VSS) daemon starts before the VMBus channel's ringbuffer is
+fully initialized, we can hit the panic below:
+
+hv_utils: Registering HyperV Utility Driver
+hv_vmbus: registering driver hv_utils
+...
+BUG: kernel NULL pointer dereference, address: 0000000000000000
+CPU: 44 UID: 0 PID: 2552 Comm: hv_kvp_daemon Tainted: G E 6.11.0-rc3+ #1
+RIP: 0010:hv_pkt_iter_first+0x12/0xd0
+Call Trace:
+...
+ vmbus_recvpacket
+ hv_kvp_onchannelcallback
+ vmbus_on_event
+ tasklet_action_common
+ tasklet_action
+ handle_softirqs
+ irq_exit_rcu
+ sysvec_hyperv_stimer0
+ </IRQ>
+ <TASK>
+ asm_sysvec_hyperv_stimer0
+...
+ kvp_register_done
+ hvt_op_read
+ vfs_read
+ ksys_read
+ __x64_sys_read
+
+This can happen because the KVP/VSS channel callback can be invoked
+even before the channel is fully opened:
+1) as soon as hv_kvp_init() -> hvutil_transport_init() creates
+/dev/vmbus/hv_kvp, the kvp daemon can open the device file immediately and
+register itself to the driver by writing a message KVP_OP_REGISTER1 to the
+file (which is handled by kvp_on_msg() ->kvp_handle_handshake()) and
+reading the file for the driver's response, which is handled by
+hvt_op_read(), which calls hvt->on_read(), i.e. kvp_register_done().
+
+2) the problem with kvp_register_done() is that it can cause the
+channel callback to be called even before the channel is fully opened,
+and when the channel callback is starting to run, util_probe()->
+vmbus_open() may have not initialized the ringbuffer yet, so the
+callback can hit the panic of NULL pointer dereference.
+
+To reproduce the panic consistently, we can add a "ssleep(10)" for KVP in
+__vmbus_open(), just before the first hv_ringbuffer_init(), and then we
+unload and reload the driver hv_utils, and run the daemon manually within
+the 10 seconds.
+
+Fix the panic by reordering the steps in util_probe() so the char dev
+entry used by the KVP or VSS daemon is not created until after
+vmbus_open() has completed. This reordering prevents the race condition
+from happening.
+
+Reported-by: Dexuan Cui <decui@microsoft.com>
+Fixes: e0fa3e5e7df6 ("Drivers: hv: utils: fix a race on userspace daemons registration")
+Cc: stable@vger.kernel.org
+Signed-off-by: Michael Kelley <mhklinux@outlook.com>
+Acked-by: Wei Liu <wei.liu@kernel.org>
+Link: https://lore.kernel.org/r/20241106154247.2271-3-mhklinux@outlook.com
+Signed-off-by: Wei Liu <wei.liu@kernel.org>
+Message-ID: <20241106154247.2271-3-mhklinux@outlook.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/hv/hv_kvp.c       |    6 ++++++
+ drivers/hv/hv_snapshot.c  |    6 ++++++
+ drivers/hv/hv_util.c      |    9 +++++++++
+ drivers/hv/hyperv_vmbus.h |    2 ++
+ include/linux/hyperv.h    |    1 +
+ 5 files changed, 24 insertions(+)
+
+--- a/drivers/hv/hv_kvp.c
++++ b/drivers/hv/hv_kvp.c
+@@ -767,6 +767,12 @@ hv_kvp_init(struct hv_util_service *srv)
+        */
+       kvp_transaction.state = HVUTIL_DEVICE_INIT;
++      return 0;
++}
++
++int
++hv_kvp_init_transport(void)
++{
+       hvt = hvutil_transport_init(kvp_devname, CN_KVP_IDX, CN_KVP_VAL,
+                                   kvp_on_msg, kvp_on_reset);
+       if (!hvt)
+--- a/drivers/hv/hv_snapshot.c
++++ b/drivers/hv/hv_snapshot.c
+@@ -385,6 +385,12 @@ hv_vss_init(struct hv_util_service *srv)
+        */
+       vss_transaction.state = HVUTIL_DEVICE_INIT;
++      return 0;
++}
++
++int
++hv_vss_init_transport(void)
++{
+       hvt = hvutil_transport_init(vss_devname, CN_VSS_IDX, CN_VSS_VAL,
+                                   vss_on_msg, vss_on_reset);
+       if (!hvt) {
+--- a/drivers/hv/hv_util.c
++++ b/drivers/hv/hv_util.c
+@@ -141,6 +141,7 @@ static struct hv_util_service util_heart
+ static struct hv_util_service util_kvp = {
+       .util_cb = hv_kvp_onchannelcallback,
+       .util_init = hv_kvp_init,
++      .util_init_transport = hv_kvp_init_transport,
+       .util_pre_suspend = hv_kvp_pre_suspend,
+       .util_pre_resume = hv_kvp_pre_resume,
+       .util_deinit = hv_kvp_deinit,
+@@ -149,6 +150,7 @@ static struct hv_util_service util_kvp =
+ static struct hv_util_service util_vss = {
+       .util_cb = hv_vss_onchannelcallback,
+       .util_init = hv_vss_init,
++      .util_init_transport = hv_vss_init_transport,
+       .util_pre_suspend = hv_vss_pre_suspend,
+       .util_pre_resume = hv_vss_pre_resume,
+       .util_deinit = hv_vss_deinit,
+@@ -592,6 +594,13 @@ static int util_probe(struct hv_device *
+       if (ret)
+               goto error;
++      if (srv->util_init_transport) {
++              ret = srv->util_init_transport();
++              if (ret) {
++                      vmbus_close(dev->channel);
++                      goto error;
++              }
++      }
+       return 0;
+ error:
+--- a/drivers/hv/hyperv_vmbus.h
++++ b/drivers/hv/hyperv_vmbus.h
+@@ -356,12 +356,14 @@ void vmbus_on_event(unsigned long data);
+ void vmbus_on_msg_dpc(unsigned long data);
+ int hv_kvp_init(struct hv_util_service *srv);
++int hv_kvp_init_transport(void);
+ void hv_kvp_deinit(void);
+ int hv_kvp_pre_suspend(void);
+ int hv_kvp_pre_resume(void);
+ void hv_kvp_onchannelcallback(void *context);
+ int hv_vss_init(struct hv_util_service *srv);
++int hv_vss_init_transport(void);
+ void hv_vss_deinit(void);
+ int hv_vss_pre_suspend(void);
+ int hv_vss_pre_resume(void);
+--- a/include/linux/hyperv.h
++++ b/include/linux/hyperv.h
+@@ -1526,6 +1526,7 @@ struct hv_util_service {
+       void *channel;
+       void (*util_cb)(void *);
+       int (*util_init)(struct hv_util_service *);
++      int (*util_init_transport)(void);
+       void (*util_deinit)(void);
+       int (*util_pre_suspend)(void);
+       int (*util_pre_resume)(void);
diff --git a/queue-5.15/kvm-x86-play-nice-with-protected-guests-in-complete_hypercall_exit.patch b/queue-5.15/kvm-x86-play-nice-with-protected-guests-in-complete_hypercall_exit.patch
new file mode 100644 (file)
index 0000000..4dfbcc4
--- /dev/null
@@ -0,0 +1,59 @@
+From 9b42d1e8e4fe9dc631162c04caa69b0d1860b0f0 Mon Sep 17 00:00:00 2001
+From: Sean Christopherson <seanjc@google.com>
+Date: Wed, 27 Nov 2024 16:43:39 -0800
+Subject: KVM: x86: Play nice with protected guests in complete_hypercall_exit()
+
+From: Sean Christopherson <seanjc@google.com>
+
+commit 9b42d1e8e4fe9dc631162c04caa69b0d1860b0f0 upstream.
+
+Use is_64_bit_hypercall() instead of is_64_bit_mode() to detect a 64-bit
+hypercall when completing said hypercall.  For guests with protected state,
+e.g. SEV-ES and SEV-SNP, KVM must assume the hypercall was made in 64-bit
+mode as the vCPU state needed to detect 64-bit mode is unavailable.
+
+Hacking the sev_smoke_test selftest to generate a KVM_HC_MAP_GPA_RANGE
+hypercall via VMGEXIT trips the WARN:
+
+  ------------[ cut here ]------------
+  WARNING: CPU: 273 PID: 326626 at arch/x86/kvm/x86.h:180 complete_hypercall_exit+0x44/0xe0 [kvm]
+  Modules linked in: kvm_amd kvm ... [last unloaded: kvm]
+  CPU: 273 UID: 0 PID: 326626 Comm: sev_smoke_test Not tainted 6.12.0-smp--392e932fa0f3-feat #470
+  Hardware name: Google Astoria/astoria, BIOS 0.20240617.0-0 06/17/2024
+  RIP: 0010:complete_hypercall_exit+0x44/0xe0 [kvm]
+  Call Trace:
+   <TASK>
+   kvm_arch_vcpu_ioctl_run+0x2400/0x2720 [kvm]
+   kvm_vcpu_ioctl+0x54f/0x630 [kvm]
+   __se_sys_ioctl+0x6b/0xc0
+   do_syscall_64+0x83/0x160
+   entry_SYSCALL_64_after_hwframe+0x76/0x7e
+   </TASK>
+  ---[ end trace 0000000000000000 ]---
+
+Fixes: b5aead0064f3 ("KVM: x86: Assume a 64-bit hypercall for guests with protected state")
+Cc: stable@vger.kernel.org
+Cc: Tom Lendacky <thomas.lendacky@amd.com>
+Reviewed-by: Xiaoyao Li <xiaoyao.li@intel.com>
+Reviewed-by: Nikunj A Dadhania <nikunj@amd.com>
+Reviewed-by: Tom Lendacky <thomas.lendacky@amd.com>
+Reviewed-by: Binbin Wu <binbin.wu@linux.intel.com>
+Reviewed-by: Kai Huang <kai.huang@intel.com>
+Link: https://lore.kernel.org/r/20241128004344.4072099-2-seanjc@google.com
+Signed-off-by: Sean Christopherson <seanjc@google.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/x86/kvm/x86.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/x86/kvm/x86.c
++++ b/arch/x86/kvm/x86.c
+@@ -8946,7 +8946,7 @@ static int complete_hypercall_exit(struc
+ {
+       u64 ret = vcpu->run->hypercall.ret;
+-      if (!is_64_bit_mode(vcpu))
++      if (!is_64_bit_hypercall(vcpu))
+               ret = (u32)ret;
+       kvm_rax_write(vcpu, ret);
+       ++vcpu->stat.hypercalls;
index 00df4d086530b34daa8c0cfb7f04095d0a6742a2..4d185080f0cd485c609da7ba945a9cc646213ab3 100644 (file)
@@ -39,3 +39,9 @@ hwmon-tmp513-fix-current-register-value-interpretati.patch
 hwmon-tmp513-fix-interpretation-of-values-of-tempera.patch
 sh-clk-fix-clk_enable-to-return-0-on-null-clk.patch
 zram-refuse-to-use-zero-sized-block-device-as-backing-device.patch
+btrfs-tree-checker-reject-inline-extent-items-with-0-ref-count.patch
+drivers-hv-util-avoid-accessing-a-ringbuffer-not-initialized-yet.patch
+kvm-x86-play-nice-with-protected-guests-in-complete_hypercall_exit.patch
+tracing-fix-test_event_printk-to-process-entire-print-argument.patch
+tracing-add-missing-helper-functions-in-event-pointer-dereference-check.patch
+tracing-add-s-check-in-test_event_printk.patch
diff --git a/queue-5.15/tracing-add-missing-helper-functions-in-event-pointer-dereference-check.patch b/queue-5.15/tracing-add-missing-helper-functions-in-event-pointer-dereference-check.patch
new file mode 100644 (file)
index 0000000..0c77395
--- /dev/null
@@ -0,0 +1,78 @@
+From 917110481f6bc1c96b1e54b62bb114137fbc6d17 Mon Sep 17 00:00:00 2001
+From: Steven Rostedt <rostedt@goodmis.org>
+Date: Mon, 16 Dec 2024 21:41:20 -0500
+Subject: tracing: Add missing helper functions in event pointer dereference check
+
+From: Steven Rostedt <rostedt@goodmis.org>
+
+commit 917110481f6bc1c96b1e54b62bb114137fbc6d17 upstream.
+
+The process_pointer() helper function looks to see if various trace event
+macros are used. These macros are for storing data in the event. This
+makes it safe to dereference as the dereference will then point into the
+event on the ring buffer where the content of the data stays with the
+event itself.
+
+A few helper functions were missing. Those were:
+
+  __get_rel_dynamic_array()
+  __get_dynamic_array_len()
+  __get_rel_dynamic_array_len()
+  __get_rel_sockaddr()
+
+Also add a helper function find_print_string() to not need to use a middle
+man variable to test if the string exists.
+
+Cc: stable@vger.kernel.org
+Cc: Masami Hiramatsu <mhiramat@kernel.org>
+Cc: Mark Rutland <mark.rutland@arm.com>
+Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+Cc: Andrew Morton <akpm@linux-foundation.org>
+Cc: Al Viro <viro@ZenIV.linux.org.uk>
+Cc: Linus Torvalds <torvalds@linux-foundation.org>
+Link: https://lore.kernel.org/20241217024720.521836792@goodmis.org
+Fixes: 5013f454a352c ("tracing: Add check of trace event print fmts for dereferencing pointers")
+Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/trace/trace_events.c |   21 +++++++++++++++++++--
+ 1 file changed, 19 insertions(+), 2 deletions(-)
+
+--- a/kernel/trace/trace_events.c
++++ b/kernel/trace/trace_events.c
+@@ -257,6 +257,15 @@ static bool test_field(const char *fmt,
+       return false;
+ }
++/* Look for a string within an argument */
++static bool find_print_string(const char *arg, const char *str, const char *end)
++{
++      const char *r;
++
++      r = strstr(arg, str);
++      return r && r < end;
++}
++
+ /* Return true if the argument pointer is safe */
+ static bool process_pointer(const char *fmt, int len, struct trace_event_call *call)
+ {
+@@ -275,9 +284,17 @@ static bool process_pointer(const char *
+               a = strchr(fmt, '&');
+               if ((a && (a < r)) || test_field(r, call))
+                       return true;
+-      } else if ((r = strstr(fmt, "__get_dynamic_array(")) && r < e) {
++      } else if (find_print_string(fmt, "__get_dynamic_array(", e)) {
++              return true;
++      } else if (find_print_string(fmt, "__get_rel_dynamic_array(", e)) {
++              return true;
++      } else if (find_print_string(fmt, "__get_dynamic_array_len(", e)) {
++              return true;
++      } else if (find_print_string(fmt, "__get_rel_dynamic_array_len(", e)) {
++              return true;
++      } else if (find_print_string(fmt, "__get_sockaddr(", e)) {
+               return true;
+-      } else if ((r = strstr(fmt, "__get_sockaddr(")) && r < e) {
++      } else if (find_print_string(fmt, "__get_rel_sockaddr(", e)) {
+               return true;
+       }
+       return false;
diff --git a/queue-5.15/tracing-add-s-check-in-test_event_printk.patch b/queue-5.15/tracing-add-s-check-in-test_event_printk.patch
new file mode 100644 (file)
index 0000000..9d01995
--- /dev/null
@@ -0,0 +1,206 @@
+From 65a25d9f7ac02e0cf361356e834d1c71d36acca9 Mon Sep 17 00:00:00 2001
+From: Steven Rostedt <rostedt@goodmis.org>
+Date: Mon, 16 Dec 2024 21:41:21 -0500
+Subject: tracing: Add "%s" check in test_event_printk()
+
+From: Steven Rostedt <rostedt@goodmis.org>
+
+commit 65a25d9f7ac02e0cf361356e834d1c71d36acca9 upstream.
+
+The test_event_printk() code makes sure that when a trace event is
+registered, any dereferenced pointers in from the event's TP_printk() are
+pointing to content in the ring buffer. But currently it does not handle
+"%s", as there's cases where the string pointer saved in the ring buffer
+points to a static string in the kernel that will never be freed. As that
+is a valid case, the pointer needs to be checked at runtime.
+
+Currently the runtime check is done via trace_check_vprintf(), but to not
+have to replicate everything in vsnprintf() it does some logic with the
+va_list that may not be reliable across architectures. In order to get rid
+of that logic, more work in the test_event_printk() needs to be done. Some
+of the strings can be validated at this time when it is obvious the string
+is valid because the string will be saved in the ring buffer content.
+
+Do all the validation of strings in the ring buffer at boot in
+test_event_printk(), and make sure that the field of the strings that
+point into the kernel are accessible. This will allow adding checks at
+runtime that will validate the fields themselves and not rely on paring
+the TP_printk() format at runtime.
+
+Cc: stable@vger.kernel.org
+Cc: Masami Hiramatsu <mhiramat@kernel.org>
+Cc: Mark Rutland <mark.rutland@arm.com>
+Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+Cc: Andrew Morton <akpm@linux-foundation.org>
+Cc: Al Viro <viro@ZenIV.linux.org.uk>
+Cc: Linus Torvalds <torvalds@linux-foundation.org>
+Link: https://lore.kernel.org/20241217024720.685917008@goodmis.org
+Fixes: 5013f454a352c ("tracing: Add check of trace event print fmts for dereferencing pointers")
+Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/trace/trace_events.c |  104 +++++++++++++++++++++++++++++++++++++-------
+ 1 file changed, 89 insertions(+), 15 deletions(-)
+
+--- a/kernel/trace/trace_events.c
++++ b/kernel/trace/trace_events.c
+@@ -227,19 +227,16 @@ int trace_event_get_offsets(struct trace
+       return tail->offset + tail->size;
+ }
+-/*
+- * Check if the referenced field is an array and return true,
+- * as arrays are OK to dereference.
+- */
+-static bool test_field(const char *fmt, struct trace_event_call *call)
++
++static struct trace_event_fields *find_event_field(const char *fmt,
++                                                 struct trace_event_call *call)
+ {
+       struct trace_event_fields *field = call->class->fields_array;
+-      const char *array_descriptor;
+       const char *p = fmt;
+       int len;
+       if (!(len = str_has_prefix(fmt, "REC->")))
+-              return false;
++              return NULL;
+       fmt += len;
+       for (p = fmt; *p; p++) {
+               if (!isalnum(*p) && *p != '_')
+@@ -250,11 +247,26 @@ static bool test_field(const char *fmt,
+       for (; field->type; field++) {
+               if (strncmp(field->name, fmt, len) || field->name[len])
+                       continue;
+-              array_descriptor = strchr(field->type, '[');
+-              /* This is an array and is OK to dereference. */
+-              return array_descriptor != NULL;
++
++              return field;
+       }
+-      return false;
++      return NULL;
++}
++
++/*
++ * Check if the referenced field is an array and return true,
++ * as arrays are OK to dereference.
++ */
++static bool test_field(const char *fmt, struct trace_event_call *call)
++{
++      struct trace_event_fields *field;
++
++      field = find_event_field(fmt, call);
++      if (!field)
++              return false;
++
++      /* This is an array and is OK to dereference. */
++      return strchr(field->type, '[') != NULL;
+ }
+ /* Look for a string within an argument */
+@@ -300,6 +312,53 @@ static bool process_pointer(const char *
+       return false;
+ }
++/* Return true if the string is safe */
++static bool process_string(const char *fmt, int len, struct trace_event_call *call)
++{
++      const char *r, *e, *s;
++
++      e = fmt + len;
++
++      /*
++       * There are several helper functions that return strings.
++       * If the argument contains a function, then assume its field is valid.
++       * It is considered that the argument has a function if it has:
++       *   alphanumeric or '_' before a parenthesis.
++       */
++      s = fmt;
++      do {
++              r = strstr(s, "(");
++              if (!r || r >= e)
++                      break;
++              for (int i = 1; r - i >= s; i++) {
++                      char ch = *(r - i);
++                      if (isspace(ch))
++                              continue;
++                      if (isalnum(ch) || ch == '_')
++                              return true;
++                      /* Anything else, this isn't a function */
++                      break;
++              }
++              /* A function could be wrapped in parethesis, try the next one */
++              s = r + 1;
++      } while (s < e);
++
++      /*
++       * If there's any strings in the argument consider this arg OK as it
++       * could be: REC->field ? "foo" : "bar" and we don't want to get into
++       * verifying that logic here.
++       */
++      if (find_print_string(fmt, "\"", e))
++              return true;
++
++      /* Dereferenced strings are also valid like any other pointer */
++      if (process_pointer(fmt, len, call))
++              return true;
++
++      /* Make sure the field is found, and consider it OK for now if it is */
++      return find_event_field(fmt, call) != NULL;
++}
++
+ /*
+  * Examine the print fmt of the event looking for unsafe dereference
+  * pointers using %p* that could be recorded in the trace event and
+@@ -309,6 +368,7 @@ static bool process_pointer(const char *
+ static void test_event_printk(struct trace_event_call *call)
+ {
+       u64 dereference_flags = 0;
++      u64 string_flags = 0;
+       bool first = true;
+       const char *fmt;
+       int parens = 0;
+@@ -399,8 +459,16 @@ static void test_event_printk(struct tra
+                                               star = true;
+                                               continue;
+                                       }
+-                                      if ((fmt[i + j] == 's') && star)
+-                                              arg++;
++                                      if ((fmt[i + j] == 's')) {
++                                              if (star)
++                                                      arg++;
++                                              if (WARN_ONCE(arg == 63,
++                                                            "Too many args for event: %s",
++                                                            trace_event_name(call)))
++                                                      return;
++                                              dereference_flags |= 1ULL << arg;
++                                              string_flags |= 1ULL << arg;
++                                      }
+                                       break;
+                               }
+                               break;
+@@ -447,7 +515,10 @@ static void test_event_printk(struct tra
+                       }
+                       if (dereference_flags & (1ULL << arg)) {
+-                              if (process_pointer(fmt + start_arg, e - start_arg, call))
++                              if (string_flags & (1ULL << arg)) {
++                                      if (process_string(fmt + start_arg, e - start_arg, call))
++                                              dereference_flags &= ~(1ULL << arg);
++                              } else if (process_pointer(fmt + start_arg, e - start_arg, call))
+                                       dereference_flags &= ~(1ULL << arg);
+                       }
+@@ -459,7 +530,10 @@ static void test_event_printk(struct tra
+       }
+       if (dereference_flags & (1ULL << arg)) {
+-              if (process_pointer(fmt + start_arg, i - start_arg, call))
++              if (string_flags & (1ULL << arg)) {
++                      if (process_string(fmt + start_arg, i - start_arg, call))
++                              dereference_flags &= ~(1ULL << arg);
++              } else if (process_pointer(fmt + start_arg, i - start_arg, call))
+                       dereference_flags &= ~(1ULL << arg);
+       }
diff --git a/queue-5.15/tracing-fix-test_event_printk-to-process-entire-print-argument.patch b/queue-5.15/tracing-fix-test_event_printk-to-process-entire-print-argument.patch
new file mode 100644 (file)
index 0000000..19b51a2
--- /dev/null
@@ -0,0 +1,184 @@
+From a6629626c584200daf495cc9a740048b455addcd Mon Sep 17 00:00:00 2001
+From: Steven Rostedt <rostedt@goodmis.org>
+Date: Mon, 16 Dec 2024 21:41:19 -0500
+Subject: tracing: Fix test_event_printk() to process entire print argument
+
+From: Steven Rostedt <rostedt@goodmis.org>
+
+commit a6629626c584200daf495cc9a740048b455addcd upstream.
+
+The test_event_printk() analyzes print formats of trace events looking for
+cases where it may dereference a pointer that is not in the ring buffer
+which can possibly be a bug when the trace event is read from the ring
+buffer and the content of that pointer no longer exists.
+
+The function needs to accurately go from one print format argument to the
+next. It handles quotes and parenthesis that may be included in an
+argument. When it finds the start of the next argument, it uses a simple
+"c = strstr(fmt + i, ',')" to find the end of that argument!
+
+In order to include "%s" dereferencing, it needs to process the entire
+content of the print format argument and not just the content of the first
+',' it finds. As there may be content like:
+
+ ({ const char *saved_ptr = trace_seq_buffer_ptr(p); static const char
+   *access_str[] = { "---", "--x", "w--", "w-x", "-u-", "-ux", "wu-", "wux"
+   }; union kvm_mmu_page_role role; role.word = REC->role;
+   trace_seq_printf(p, "sp gen %u gfn %llx l%u %u-byte q%u%s %s%s" " %snxe
+   %sad root %u %s%c", REC->mmu_valid_gen, REC->gfn, role.level,
+   role.has_4_byte_gpte ? 4 : 8, role.quadrant, role.direct ? " direct" : "",
+   access_str[role.access], role.invalid ? " invalid" : "", role.efer_nx ? ""
+   : "!", role.ad_disabled ? "!" : "", REC->root_count, REC->unsync ?
+   "unsync" : "sync", 0); saved_ptr; })
+
+Which is an example of a full argument of an existing event. As the code
+already handles finding the next print format argument, process the
+argument at the end of it and not the start of it. This way it has both
+the start of the argument as well as the end of it.
+
+Add a helper function "process_pointer()" that will do the processing during
+the loop as well as at the end. It also makes the code cleaner and easier
+to read.
+
+Cc: stable@vger.kernel.org
+Cc: Masami Hiramatsu <mhiramat@kernel.org>
+Cc: Mark Rutland <mark.rutland@arm.com>
+Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+Cc: Andrew Morton <akpm@linux-foundation.org>
+Cc: Al Viro <viro@ZenIV.linux.org.uk>
+Cc: Linus Torvalds <torvalds@linux-foundation.org>
+Link: https://lore.kernel.org/20241217024720.362271189@goodmis.org
+Fixes: 5013f454a352c ("tracing: Add check of trace event print fmts for dereferencing pointers")
+Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/trace/trace_events.c |   82 ++++++++++++++++++++++++++++----------------
+ 1 file changed, 53 insertions(+), 29 deletions(-)
+
+--- a/kernel/trace/trace_events.c
++++ b/kernel/trace/trace_events.c
+@@ -248,8 +248,7 @@ static bool test_field(const char *fmt,
+       len = p - fmt;
+       for (; field->type; field++) {
+-              if (strncmp(field->name, fmt, len) ||
+-                  field->name[len])
++              if (strncmp(field->name, fmt, len) || field->name[len])
+                       continue;
+               array_descriptor = strchr(field->type, '[');
+               /* This is an array and is OK to dereference. */
+@@ -258,6 +257,32 @@ static bool test_field(const char *fmt,
+       return false;
+ }
++/* Return true if the argument pointer is safe */
++static bool process_pointer(const char *fmt, int len, struct trace_event_call *call)
++{
++      const char *r, *e, *a;
++
++      e = fmt + len;
++
++      /* Find the REC-> in the argument */
++      r = strstr(fmt, "REC->");
++      if (r && r < e) {
++              /*
++               * Addresses of events on the buffer, or an array on the buffer is
++               * OK to dereference. There's ways to fool this, but
++               * this is to catch common mistakes, not malicious code.
++               */
++              a = strchr(fmt, '&');
++              if ((a && (a < r)) || test_field(r, call))
++                      return true;
++      } else if ((r = strstr(fmt, "__get_dynamic_array(")) && r < e) {
++              return true;
++      } else if ((r = strstr(fmt, "__get_sockaddr(")) && r < e) {
++              return true;
++      }
++      return false;
++}
++
+ /*
+  * Examine the print fmt of the event looking for unsafe dereference
+  * pointers using %p* that could be recorded in the trace event and
+@@ -268,12 +293,12 @@ static void test_event_printk(struct tra
+ {
+       u64 dereference_flags = 0;
+       bool first = true;
+-      const char *fmt, *c, *r, *a;
++      const char *fmt;
+       int parens = 0;
+       char in_quote = 0;
+       int start_arg = 0;
+       int arg = 0;
+-      int i;
++      int i, e;
+       fmt = call->print_fmt;
+@@ -386,42 +411,41 @@ static void test_event_printk(struct tra
+               case ',':
+                       if (in_quote || parens)
+                               continue;
++                      e = i;
+                       i++;
+                       while (isspace(fmt[i]))
+                               i++;
+-                      start_arg = i;
+-                      if (!(dereference_flags & (1ULL << arg)))
+-                              goto next_arg;
+-                      /* Find the REC-> in the argument */
+-                      c = strchr(fmt + i, ',');
+-                      r = strstr(fmt + i, "REC->");
+-                      if (r && (!c || r < c)) {
+-                              /*
+-                               * Addresses of events on the buffer,
+-                               * or an array on the buffer is
+-                               * OK to dereference.
+-                               * There's ways to fool this, but
+-                               * this is to catch common mistakes,
+-                               * not malicious code.
+-                               */
+-                              a = strchr(fmt + i, '&');
+-                              if ((a && (a < r)) || test_field(r, call))
++                      /*
++                       * If start_arg is zero, then this is the start of the
++                       * first argument. The processing of the argument happens
++                       * when the end of the argument is found, as it needs to
++                       * handle paranthesis and such.
++                       */
++                      if (!start_arg) {
++                              start_arg = i;
++                              /* Balance out the i++ in the for loop */
++                              i--;
++                              continue;
++                      }
++
++                      if (dereference_flags & (1ULL << arg)) {
++                              if (process_pointer(fmt + start_arg, e - start_arg, call))
+                                       dereference_flags &= ~(1ULL << arg);
+-                      } else if ((r = strstr(fmt + i, "__get_dynamic_array(")) &&
+-                                 (!c || r < c)) {
+-                              dereference_flags &= ~(1ULL << arg);
+-                      } else if ((r = strstr(fmt + i, "__get_sockaddr(")) &&
+-                                 (!c || r < c)) {
+-                              dereference_flags &= ~(1ULL << arg);
+                       }
+-              next_arg:
+-                      i--;
++                      start_arg = i;
+                       arg++;
++                      /* Balance out the i++ in the for loop */
++                      i--;
+               }
+       }
++      if (dereference_flags & (1ULL << arg)) {
++              if (process_pointer(fmt + start_arg, i - start_arg, call))
++                      dereference_flags &= ~(1ULL << arg);
++      }
++
+       /*
+        * If you triggered the below warning, the trace event reported
+        * uses an unsafe dereference pointer %p*. As the data stored