]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
Fixes for all trees
authorSasha Levin <sashal@kernel.org>
Fri, 3 Jul 2026 04:06:33 +0000 (00:06 -0400)
committerSasha Levin <sashal@kernel.org>
Fri, 3 Jul 2026 04:06:33 +0000 (00:06 -0400)
Signed-off-by: Sasha Levin <sashal@kernel.org>
13 files changed:
staging-5.10/nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch [new file with mode: 0644]
staging-5.10/series [new file with mode: 0644]
staging-5.15/nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch [new file with mode: 0644]
staging-5.15/series [new file with mode: 0644]
staging-6.1/bpf-arm64-reject-out-of-range-b.cond-targets.patch [new file with mode: 0644]
staging-6.1/nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch [new file with mode: 0644]
staging-6.1/series [new file with mode: 0644]
staging-6.12/bpf-arm64-reject-out-of-range-b.cond-targets.patch [new file with mode: 0644]
staging-6.12/series [new file with mode: 0644]
staging-6.18/bpf-arm64-reject-out-of-range-b.cond-targets.patch [new file with mode: 0644]
staging-6.18/series [new file with mode: 0644]
staging-6.6/bpf-arm64-reject-out-of-range-b.cond-targets.patch [new file with mode: 0644]
staging-6.6/series [new file with mode: 0644]

diff --git a/staging-5.10/nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch b/staging-5.10/nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch
new file mode 100644 (file)
index 0000000..81edf74
--- /dev/null
@@ -0,0 +1,108 @@
+From 139b4b67e98feb4931dd2e853ed707b908b2ac78 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 2 Jul 2026 20:38:39 +0800
+Subject: nvmet-tcp: fix race between ICReq handling and queue teardown
+
+From: Chaitanya Kulkarni <kch@nvidia.com>
+
+commit 5293a8882c549fab4a878bc76b0b6c951f980a61 upstream.
+
+nvmet_tcp_handle_icreq() updates queue->state after sending an
+Initialization Connection Response (ICResp), but it does so without
+serializing against target-side queue teardown.
+
+If an NVMe/TCP host sends an Initialization Connection Request
+(ICReq) and immediately closes the connection, target-side teardown
+may start in softirq context before io_work drains the already
+buffered ICReq. In that case, nvmet_tcp_schedule_release_queue()
+sets queue->state to NVMET_TCP_Q_DISCONNECTING and drops the queue
+reference under state_lock.
+
+If io_work later processes that ICReq, nvmet_tcp_handle_icreq() can
+still overwrite the state back to NVMET_TCP_Q_LIVE. That defeats the
+DISCONNECTING-state guard in nvmet_tcp_schedule_release_queue() and
+allows a later socket state change to re-enter teardown and issue a
+second kref_put() on an already released queue.
+
+The ICResp send failure path has the same problem. If teardown has
+already moved the queue to DISCONNECTING, a send error can still
+overwrite the state with NVMET_TCP_Q_FAILED, again reopening the
+window for a second teardown path to drop the queue reference.
+
+Fix this by serializing both post-send state transitions with
+state_lock and bailing out if teardown has already started.
+
+Use -ESHUTDOWN as an internal sentinel for that bail-out path rather
+than propagating it as a transport error like -ECONNRESET. Keep
+nvmet_tcp_socket_error() setting rcv_state to NVMET_TCP_RECV_ERR before
+honoring that sentinel so receive-side parsing stays quiesced until the
+existing release path completes.
+
+Fixes: c46a6465bac2 ("nvmet-tcp: add NVMe over TCP target driver")
+Cc: stable@vger.kernel.org
+Reported-by: Shivam Kumar <skumar47@syr.edu>
+Tested-by: Shivam Kumar <kumar.shivam43666@gmail.com>
+Signed-off-by: Chaitanya Kulkarni <kch@nvidia.com>
+Signed-off-by: Keith Busch <kbusch@kernel.org>
+[ context diff adaptation: drop `queue->state = NVMET_TCP_Q_FAILED` since
+ the enum introduced in 6.7, 675b453e0241 ("nvmet-tcp: enable TLS handshake
+ upcall" ]
+Signed-off-by: Philo Lu <lulie@linux.alibaba.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/nvme/target/tcp.c | 29 ++++++++++++++++++++++++++++-
+ 1 file changed, 28 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
+index edcc0662c3e571..4bd82e9b113ff5 100644
+--- a/drivers/nvme/target/tcp.c
++++ b/drivers/nvme/target/tcp.c
+@@ -345,6 +345,19 @@ static void nvmet_tcp_build_pdu_iovec(struct nvmet_tcp_cmd *cmd)
+ static void nvmet_tcp_fatal_error(struct nvmet_tcp_queue *queue)
+ {
++      /*
++       * Keep rcv_state at RECV_ERR even for the internal -ESHUTDOWN path.
++       * nvmet_tcp_handle_icreq() can return -ESHUTDOWN after the ICReq has
++       * already been consumed and queue teardown has started.
++       *
++       * If nvmet_tcp_data_ready() or nvmet_tcp_write_space() queues
++       * nvmet_tcp_io_work() again before nvmet_tcp_release_queue_work()
++       * cancels it, the queue must not keep that old receive state.
++       * Otherwise the next nvmet_tcp_io_work() run can reach
++       * nvmet_tcp_done_recv_pdu() and try to handle the same ICReq again.
++       *
++       * That is why queue->rcv_state needs to be updated before we return.
++       */
+       queue->rcv_state = NVMET_TCP_RECV_ERR;
+       if (queue->nvme_sq.ctrl)
+               nvmet_ctrl_fatal_error(queue->nvme_sq.ctrl);
+@@ -888,10 +901,24 @@ static int nvmet_tcp_handle_icreq(struct nvmet_tcp_queue *queue)
+       iov.iov_base = icresp;
+       iov.iov_len = sizeof(*icresp);
+       ret = kernel_sendmsg(queue->sock, &msg, &iov, 1, iov.iov_len);
+-      if (ret < 0)
++      if (ret < 0) {
++              spin_lock_bh(&queue->state_lock);
++              if (queue->state == NVMET_TCP_Q_DISCONNECTING) {
++                      spin_unlock_bh(&queue->state_lock);
++                      return -ESHUTDOWN;
++              }
++              spin_unlock_bh(&queue->state_lock);
+               return ret; /* queue removal will cleanup */
++      }
++      spin_lock_bh(&queue->state_lock);
++      if (queue->state == NVMET_TCP_Q_DISCONNECTING) {
++              spin_unlock_bh(&queue->state_lock);
++              /* Tell nvmet_tcp_socket_error() teardown is in progress. */
++              return -ESHUTDOWN;
++      }
+       queue->state = NVMET_TCP_Q_LIVE;
++      spin_unlock_bh(&queue->state_lock);
+       nvmet_prepare_receive_pdu(queue);
+       return 0;
+ }
+-- 
+2.53.0
+
diff --git a/staging-5.10/series b/staging-5.10/series
new file mode 100644 (file)
index 0000000..3e69bb8
--- /dev/null
@@ -0,0 +1 @@
+nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch
diff --git a/staging-5.15/nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch b/staging-5.15/nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch
new file mode 100644 (file)
index 0000000..46d6cf1
--- /dev/null
@@ -0,0 +1,108 @@
+From 3ab972c533b488a9780ce67058a3c592165c5748 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 2 Jul 2026 20:37:57 +0800
+Subject: nvmet-tcp: fix race between ICReq handling and queue teardown
+
+From: Chaitanya Kulkarni <kch@nvidia.com>
+
+commit 5293a8882c549fab4a878bc76b0b6c951f980a61 upstream.
+
+nvmet_tcp_handle_icreq() updates queue->state after sending an
+Initialization Connection Response (ICResp), but it does so without
+serializing against target-side queue teardown.
+
+If an NVMe/TCP host sends an Initialization Connection Request
+(ICReq) and immediately closes the connection, target-side teardown
+may start in softirq context before io_work drains the already
+buffered ICReq. In that case, nvmet_tcp_schedule_release_queue()
+sets queue->state to NVMET_TCP_Q_DISCONNECTING and drops the queue
+reference under state_lock.
+
+If io_work later processes that ICReq, nvmet_tcp_handle_icreq() can
+still overwrite the state back to NVMET_TCP_Q_LIVE. That defeats the
+DISCONNECTING-state guard in nvmet_tcp_schedule_release_queue() and
+allows a later socket state change to re-enter teardown and issue a
+second kref_put() on an already released queue.
+
+The ICResp send failure path has the same problem. If teardown has
+already moved the queue to DISCONNECTING, a send error can still
+overwrite the state with NVMET_TCP_Q_FAILED, again reopening the
+window for a second teardown path to drop the queue reference.
+
+Fix this by serializing both post-send state transitions with
+state_lock and bailing out if teardown has already started.
+
+Use -ESHUTDOWN as an internal sentinel for that bail-out path rather
+than propagating it as a transport error like -ECONNRESET. Keep
+nvmet_tcp_socket_error() setting rcv_state to NVMET_TCP_RECV_ERR before
+honoring that sentinel so receive-side parsing stays quiesced until the
+existing release path completes.
+
+Fixes: c46a6465bac2 ("nvmet-tcp: add NVMe over TCP target driver")
+Cc: stable@vger.kernel.org
+Reported-by: Shivam Kumar <skumar47@syr.edu>
+Tested-by: Shivam Kumar <kumar.shivam43666@gmail.com>
+Signed-off-by: Chaitanya Kulkarni <kch@nvidia.com>
+Signed-off-by: Keith Busch <kbusch@kernel.org>
+[ context diff adaptation: drop `queue->state = NVMET_TCP_Q_FAILED` since
+ the enum introduced in 6.7, 675b453e0241 ("nvmet-tcp: enable TLS handshake
+ upcall" ]
+Signed-off-by: Philo Lu <lulie@linux.alibaba.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/nvme/target/tcp.c | 29 ++++++++++++++++++++++++++++-
+ 1 file changed, 28 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
+index 29ed707e76e06d..06cfcf0850076b 100644
+--- a/drivers/nvme/target/tcp.c
++++ b/drivers/nvme/target/tcp.c
+@@ -357,6 +357,19 @@ static void nvmet_tcp_build_pdu_iovec(struct nvmet_tcp_cmd *cmd)
+ static void nvmet_tcp_fatal_error(struct nvmet_tcp_queue *queue)
+ {
++      /*
++       * Keep rcv_state at RECV_ERR even for the internal -ESHUTDOWN path.
++       * nvmet_tcp_handle_icreq() can return -ESHUTDOWN after the ICReq has
++       * already been consumed and queue teardown has started.
++       *
++       * If nvmet_tcp_data_ready() or nvmet_tcp_write_space() queues
++       * nvmet_tcp_io_work() again before nvmet_tcp_release_queue_work()
++       * cancels it, the queue must not keep that old receive state.
++       * Otherwise the next nvmet_tcp_io_work() run can reach
++       * nvmet_tcp_done_recv_pdu() and try to handle the same ICReq again.
++       *
++       * That is why queue->rcv_state needs to be updated before we return.
++       */
+       queue->rcv_state = NVMET_TCP_RECV_ERR;
+       if (queue->nvme_sq.ctrl)
+               nvmet_ctrl_fatal_error(queue->nvme_sq.ctrl);
+@@ -900,10 +913,24 @@ static int nvmet_tcp_handle_icreq(struct nvmet_tcp_queue *queue)
+       iov.iov_base = icresp;
+       iov.iov_len = sizeof(*icresp);
+       ret = kernel_sendmsg(queue->sock, &msg, &iov, 1, iov.iov_len);
+-      if (ret < 0)
++      if (ret < 0) {
++              spin_lock_bh(&queue->state_lock);
++              if (queue->state == NVMET_TCP_Q_DISCONNECTING) {
++                      spin_unlock_bh(&queue->state_lock);
++                      return -ESHUTDOWN;
++              }
++              spin_unlock_bh(&queue->state_lock);
+               return ret; /* queue removal will cleanup */
++      }
++      spin_lock_bh(&queue->state_lock);
++      if (queue->state == NVMET_TCP_Q_DISCONNECTING) {
++              spin_unlock_bh(&queue->state_lock);
++              /* Tell nvmet_tcp_socket_error() teardown is in progress. */
++              return -ESHUTDOWN;
++      }
+       queue->state = NVMET_TCP_Q_LIVE;
++      spin_unlock_bh(&queue->state_lock);
+       nvmet_prepare_receive_pdu(queue);
+       return 0;
+ }
+-- 
+2.53.0
+
diff --git a/staging-5.15/series b/staging-5.15/series
new file mode 100644 (file)
index 0000000..3e69bb8
--- /dev/null
@@ -0,0 +1 @@
+nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch
diff --git a/staging-6.1/bpf-arm64-reject-out-of-range-b.cond-targets.patch b/staging-6.1/bpf-arm64-reject-out-of-range-b.cond-targets.patch
new file mode 100644 (file)
index 0000000..23ab426
--- /dev/null
@@ -0,0 +1,58 @@
+From c77d935ccc7dcc6381a5f77ccc49b45d6457d124 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Apr 2026 14:14:02 +0200
+Subject: bpf, arm64: Reject out-of-range B.cond targets
+
+From: Daniel Borkmann <daniel@iogearbox.net>
+
+[ Upstream commit 48d83d94930eb4db4c93d2de44838b9455cff626 ]
+
+aarch64_insn_gen_cond_branch_imm() calls label_imm_common() to
+compute a 19-bit signed byte offset for a conditional branch,
+but unlike its siblings aarch64_insn_gen_branch_imm() and
+aarch64_insn_gen_comp_branch_imm(), it does not check whether
+label_imm_common() returned its out-of-range sentinel (range)
+before feeding the value to aarch64_insn_encode_immediate().
+
+aarch64_insn_encode_immediate() unconditionally masks the value
+with the 19-bit field mask, so an offset that was rejected by
+label_imm_common() gets silently truncated. With the sentinel
+value SZ_1M, the resulting field ends up with bit 18 (the sign
+bit of the 19-bit signed displacement) set, and the CPU decodes
+it as a ~1 MiB *backward* branch, producing an incorrectly
+targeted B.cond instruction. For code-gen locations like the
+emit_bpf_tail_call() this function is the only barrier between
+an overflowing displacement and a silently miscompiled branch.
+
+Fix it by returning AARCH64_BREAK_FAULT when the offset is out
+of range, so callers see a loud failure instead of a silently
+misencoded branch. validate_code() scans the generated image
+for any AARCH64_BREAK_FAULT and then lets the JIT fail.
+
+Fixes: 345e0d35ecdd ("arm64: introduce aarch64_insn_gen_cond_branch_imm()")
+Fixes: c94ae4f7c5ec ("arm64: insn: remove BUG_ON from codegen")
+Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
+Reviewed-by: Puranjay Mohan <puranjay@kernel.org>
+Link: https://lore.kernel.org/r/20260415121403.639619-1-daniel@iogearbox.net
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/arm64/lib/insn.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c
+index 44bb90ee2f414b..a50f6c061c27a2 100644
+--- a/arch/arm64/lib/insn.c
++++ b/arch/arm64/lib/insn.c
+@@ -423,6 +423,8 @@ u32 aarch64_insn_gen_cond_branch_imm(unsigned long pc, unsigned long addr,
+       long offset;
+       offset = label_imm_common(pc, addr, SZ_1M);
++      if (offset >= SZ_1M)
++              return AARCH64_BREAK_FAULT;
+       insn = aarch64_insn_get_bcond_value();
+-- 
+2.53.0
+
diff --git a/staging-6.1/nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch b/staging-6.1/nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch
new file mode 100644 (file)
index 0000000..2e09e3d
--- /dev/null
@@ -0,0 +1,108 @@
+From 86e1af6d17772cb8dac1e21f804485898136464e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 2 Jul 2026 20:36:44 +0800
+Subject: nvmet-tcp: fix race between ICReq handling and queue teardown
+
+From: Chaitanya Kulkarni <kch@nvidia.com>
+
+commit 5293a8882c549fab4a878bc76b0b6c951f980a61 upstream.
+
+nvmet_tcp_handle_icreq() updates queue->state after sending an
+Initialization Connection Response (ICResp), but it does so without
+serializing against target-side queue teardown.
+
+If an NVMe/TCP host sends an Initialization Connection Request
+(ICReq) and immediately closes the connection, target-side teardown
+may start in softirq context before io_work drains the already
+buffered ICReq. In that case, nvmet_tcp_schedule_release_queue()
+sets queue->state to NVMET_TCP_Q_DISCONNECTING and drops the queue
+reference under state_lock.
+
+If io_work later processes that ICReq, nvmet_tcp_handle_icreq() can
+still overwrite the state back to NVMET_TCP_Q_LIVE. That defeats the
+DISCONNECTING-state guard in nvmet_tcp_schedule_release_queue() and
+allows a later socket state change to re-enter teardown and issue a
+second kref_put() on an already released queue.
+
+The ICResp send failure path has the same problem. If teardown has
+already moved the queue to DISCONNECTING, a send error can still
+overwrite the state with NVMET_TCP_Q_FAILED, again reopening the
+window for a second teardown path to drop the queue reference.
+
+Fix this by serializing both post-send state transitions with
+state_lock and bailing out if teardown has already started.
+
+Use -ESHUTDOWN as an internal sentinel for that bail-out path rather
+than propagating it as a transport error like -ECONNRESET. Keep
+nvmet_tcp_socket_error() setting rcv_state to NVMET_TCP_RECV_ERR before
+honoring that sentinel so receive-side parsing stays quiesced until the
+existing release path completes.
+
+Fixes: c46a6465bac2 ("nvmet-tcp: add NVMe over TCP target driver")
+Cc: stable@vger.kernel.org
+Reported-by: Shivam Kumar <skumar47@syr.edu>
+Tested-by: Shivam Kumar <kumar.shivam43666@gmail.com>
+Signed-off-by: Chaitanya Kulkarni <kch@nvidia.com>
+Signed-off-by: Keith Busch <kbusch@kernel.org>
+[ context diff adaptation: drop `queue->state = NVMET_TCP_Q_FAILED` since
+ the enum introduced in 6.7, 675b453e0241 ("nvmet-tcp: enable TLS handshake
+ upcall" ]
+Signed-off-by: Philo Lu <lulie@linux.alibaba.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ drivers/nvme/target/tcp.c | 29 ++++++++++++++++++++++++++++-
+ 1 file changed, 28 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
+index 01d685499b97de..a0751ca359f57e 100644
+--- a/drivers/nvme/target/tcp.c
++++ b/drivers/nvme/target/tcp.c
+@@ -353,6 +353,19 @@ static int nvmet_tcp_build_pdu_iovec(struct nvmet_tcp_cmd *cmd)
+ static void nvmet_tcp_fatal_error(struct nvmet_tcp_queue *queue)
+ {
++      /*
++       * Keep rcv_state at RECV_ERR even for the internal -ESHUTDOWN path.
++       * nvmet_tcp_handle_icreq() can return -ESHUTDOWN after the ICReq has
++       * already been consumed and queue teardown has started.
++       *
++       * If nvmet_tcp_data_ready() or nvmet_tcp_write_space() queues
++       * nvmet_tcp_io_work() again before nvmet_tcp_release_queue_work()
++       * cancels it, the queue must not keep that old receive state.
++       * Otherwise the next nvmet_tcp_io_work() run can reach
++       * nvmet_tcp_done_recv_pdu() and try to handle the same ICReq again.
++       *
++       * That is why queue->rcv_state needs to be updated before we return.
++       */
+       queue->rcv_state = NVMET_TCP_RECV_ERR;
+       if (queue->nvme_sq.ctrl)
+               nvmet_ctrl_fatal_error(queue->nvme_sq.ctrl);
+@@ -896,10 +909,24 @@ static int nvmet_tcp_handle_icreq(struct nvmet_tcp_queue *queue)
+       iov.iov_base = icresp;
+       iov.iov_len = sizeof(*icresp);
+       ret = kernel_sendmsg(queue->sock, &msg, &iov, 1, iov.iov_len);
+-      if (ret < 0)
++      if (ret < 0) {
++              spin_lock_bh(&queue->state_lock);
++              if (queue->state == NVMET_TCP_Q_DISCONNECTING) {
++                      spin_unlock_bh(&queue->state_lock);
++                      return -ESHUTDOWN;
++              }
++              spin_unlock_bh(&queue->state_lock);
+               return ret; /* queue removal will cleanup */
++      }
++      spin_lock_bh(&queue->state_lock);
++      if (queue->state == NVMET_TCP_Q_DISCONNECTING) {
++              spin_unlock_bh(&queue->state_lock);
++              /* Tell nvmet_tcp_socket_error() teardown is in progress. */
++              return -ESHUTDOWN;
++      }
+       queue->state = NVMET_TCP_Q_LIVE;
++      spin_unlock_bh(&queue->state_lock);
+       nvmet_prepare_receive_pdu(queue);
+       return 0;
+ }
+-- 
+2.53.0
+
diff --git a/staging-6.1/series b/staging-6.1/series
new file mode 100644 (file)
index 0000000..403a025
--- /dev/null
@@ -0,0 +1,2 @@
+nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch
+bpf-arm64-reject-out-of-range-b.cond-targets.patch
diff --git a/staging-6.12/bpf-arm64-reject-out-of-range-b.cond-targets.patch b/staging-6.12/bpf-arm64-reject-out-of-range-b.cond-targets.patch
new file mode 100644 (file)
index 0000000..797db78
--- /dev/null
@@ -0,0 +1,59 @@
+From 808214007d7d6854270a3740dc78e83c54524002 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 2 Jul 2026 16:07:56 +0800
+Subject: bpf, arm64: Reject out-of-range B.cond targets
+
+From: Daniel Borkmann <daniel@iogearbox.net>
+
+commit 48d83d94930eb4db4c93d2de44838b9455cff626 upstream.
+
+aarch64_insn_gen_cond_branch_imm() calls label_imm_common() to
+compute a 19-bit signed byte offset for a conditional branch,
+but unlike its siblings aarch64_insn_gen_branch_imm() and
+aarch64_insn_gen_comp_branch_imm(), it does not check whether
+label_imm_common() returned its out-of-range sentinel (range)
+before feeding the value to aarch64_insn_encode_immediate().
+
+aarch64_insn_encode_immediate() unconditionally masks the value
+with the 19-bit field mask, so an offset that was rejected by
+label_imm_common() gets silently truncated. With the sentinel
+value SZ_1M, the resulting field ends up with bit 18 (the sign
+bit of the 19-bit signed displacement) set, and the CPU decodes
+it as a ~1 MiB *backward* branch, producing an incorrectly
+targeted B.cond instruction. For code-gen locations like the
+emit_bpf_tail_call() this function is the only barrier between
+an overflowing displacement and a silently miscompiled branch.
+
+Fix it by returning AARCH64_BREAK_FAULT when the offset is out
+of range, so callers see a loud failure instead of a silently
+misencoded branch. validate_code() scans the generated image
+for any AARCH64_BREAK_FAULT and then lets the JIT fail.
+
+Fixes: 345e0d35ecdd ("arm64: introduce aarch64_insn_gen_cond_branch_imm()")
+Fixes: c94ae4f7c5ec ("arm64: insn: remove BUG_ON from codegen")
+Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
+Reviewed-by: Puranjay Mohan <puranjay@kernel.org>
+Link: https://lore.kernel.org/r/20260415121403.639619-1-daniel@iogearbox.net
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Signed-off-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/arm64/lib/insn.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c
+index 36d33e064ea01b..b7cabd64bcc860 100644
+--- a/arch/arm64/lib/insn.c
++++ b/arch/arm64/lib/insn.c
+@@ -338,6 +338,8 @@ u32 aarch64_insn_gen_cond_branch_imm(unsigned long pc, unsigned long addr,
+       long offset;
+       offset = label_imm_common(pc, addr, SZ_1M);
++      if (offset >= SZ_1M)
++              return AARCH64_BREAK_FAULT;
+       insn = aarch64_insn_get_bcond_value();
+-- 
+2.53.0
+
diff --git a/staging-6.12/series b/staging-6.12/series
new file mode 100644 (file)
index 0000000..5702e74
--- /dev/null
@@ -0,0 +1 @@
+bpf-arm64-reject-out-of-range-b.cond-targets.patch
diff --git a/staging-6.18/bpf-arm64-reject-out-of-range-b.cond-targets.patch b/staging-6.18/bpf-arm64-reject-out-of-range-b.cond-targets.patch
new file mode 100644 (file)
index 0000000..b1ab5ae
--- /dev/null
@@ -0,0 +1,59 @@
+From 82e716a5a657bb911c85431dd213220c0bda4213 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 2 Jul 2026 16:07:56 +0800
+Subject: bpf, arm64: Reject out-of-range B.cond targets
+
+From: Daniel Borkmann <daniel@iogearbox.net>
+
+commit 48d83d94930eb4db4c93d2de44838b9455cff626 upstream.
+
+aarch64_insn_gen_cond_branch_imm() calls label_imm_common() to
+compute a 19-bit signed byte offset for a conditional branch,
+but unlike its siblings aarch64_insn_gen_branch_imm() and
+aarch64_insn_gen_comp_branch_imm(), it does not check whether
+label_imm_common() returned its out-of-range sentinel (range)
+before feeding the value to aarch64_insn_encode_immediate().
+
+aarch64_insn_encode_immediate() unconditionally masks the value
+with the 19-bit field mask, so an offset that was rejected by
+label_imm_common() gets silently truncated. With the sentinel
+value SZ_1M, the resulting field ends up with bit 18 (the sign
+bit of the 19-bit signed displacement) set, and the CPU decodes
+it as a ~1 MiB *backward* branch, producing an incorrectly
+targeted B.cond instruction. For code-gen locations like the
+emit_bpf_tail_call() this function is the only barrier between
+an overflowing displacement and a silently miscompiled branch.
+
+Fix it by returning AARCH64_BREAK_FAULT when the offset is out
+of range, so callers see a loud failure instead of a silently
+misencoded branch. validate_code() scans the generated image
+for any AARCH64_BREAK_FAULT and then lets the JIT fail.
+
+Fixes: 345e0d35ecdd ("arm64: introduce aarch64_insn_gen_cond_branch_imm()")
+Fixes: c94ae4f7c5ec ("arm64: insn: remove BUG_ON from codegen")
+Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
+Reviewed-by: Puranjay Mohan <puranjay@kernel.org>
+Link: https://lore.kernel.org/r/20260415121403.639619-1-daniel@iogearbox.net
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Signed-off-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/arm64/lib/insn.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c
+index 4e298baddc2e56..e9e9a70ac155c8 100644
+--- a/arch/arm64/lib/insn.c
++++ b/arch/arm64/lib/insn.c
+@@ -338,6 +338,8 @@ u32 aarch64_insn_gen_cond_branch_imm(unsigned long pc, unsigned long addr,
+       long offset;
+       offset = label_imm_common(pc, addr, SZ_1M);
++      if (offset >= SZ_1M)
++              return AARCH64_BREAK_FAULT;
+       insn = aarch64_insn_get_bcond_value();
+-- 
+2.53.0
+
diff --git a/staging-6.18/series b/staging-6.18/series
new file mode 100644 (file)
index 0000000..5702e74
--- /dev/null
@@ -0,0 +1 @@
+bpf-arm64-reject-out-of-range-b.cond-targets.patch
diff --git a/staging-6.6/bpf-arm64-reject-out-of-range-b.cond-targets.patch b/staging-6.6/bpf-arm64-reject-out-of-range-b.cond-targets.patch
new file mode 100644 (file)
index 0000000..460e212
--- /dev/null
@@ -0,0 +1,58 @@
+From 0971f2e442a3fdb8d14229d89c380c62cbf6543b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 15 Apr 2026 14:14:02 +0200
+Subject: bpf, arm64: Reject out-of-range B.cond targets
+
+From: Daniel Borkmann <daniel@iogearbox.net>
+
+[ Upstream commit 48d83d94930eb4db4c93d2de44838b9455cff626 ]
+
+aarch64_insn_gen_cond_branch_imm() calls label_imm_common() to
+compute a 19-bit signed byte offset for a conditional branch,
+but unlike its siblings aarch64_insn_gen_branch_imm() and
+aarch64_insn_gen_comp_branch_imm(), it does not check whether
+label_imm_common() returned its out-of-range sentinel (range)
+before feeding the value to aarch64_insn_encode_immediate().
+
+aarch64_insn_encode_immediate() unconditionally masks the value
+with the 19-bit field mask, so an offset that was rejected by
+label_imm_common() gets silently truncated. With the sentinel
+value SZ_1M, the resulting field ends up with bit 18 (the sign
+bit of the 19-bit signed displacement) set, and the CPU decodes
+it as a ~1 MiB *backward* branch, producing an incorrectly
+targeted B.cond instruction. For code-gen locations like the
+emit_bpf_tail_call() this function is the only barrier between
+an overflowing displacement and a silently miscompiled branch.
+
+Fix it by returning AARCH64_BREAK_FAULT when the offset is out
+of range, so callers see a loud failure instead of a silently
+misencoded branch. validate_code() scans the generated image
+for any AARCH64_BREAK_FAULT and then lets the JIT fail.
+
+Fixes: 345e0d35ecdd ("arm64: introduce aarch64_insn_gen_cond_branch_imm()")
+Fixes: c94ae4f7c5ec ("arm64: insn: remove BUG_ON from codegen")
+Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
+Reviewed-by: Puranjay Mohan <puranjay@kernel.org>
+Link: https://lore.kernel.org/r/20260415121403.639619-1-daniel@iogearbox.net
+Signed-off-by: Alexei Starovoitov <ast@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ arch/arm64/lib/insn.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c
+index 7232b1e70a125f..cb845b3649a188 100644
+--- a/arch/arm64/lib/insn.c
++++ b/arch/arm64/lib/insn.c
+@@ -338,6 +338,8 @@ u32 aarch64_insn_gen_cond_branch_imm(unsigned long pc, unsigned long addr,
+       long offset;
+       offset = label_imm_common(pc, addr, SZ_1M);
++      if (offset >= SZ_1M)
++              return AARCH64_BREAK_FAULT;
+       insn = aarch64_insn_get_bcond_value();
+-- 
+2.53.0
+
diff --git a/staging-6.6/series b/staging-6.6/series
new file mode 100644 (file)
index 0000000..5702e74
--- /dev/null
@@ -0,0 +1 @@
+bpf-arm64-reject-out-of-range-b.cond-targets.patch