--- /dev/null
+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
+
--- /dev/null
+nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch
--- /dev/null
+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
+
--- /dev/null
+nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch
--- /dev/null
+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
+
--- /dev/null
+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
+
--- /dev/null
+nvmet-tcp-fix-race-between-icreq-handling-and-queue-.patch
+bpf-arm64-reject-out-of-range-b.cond-targets.patch
--- /dev/null
+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
+
--- /dev/null
+bpf-arm64-reject-out-of-range-b.cond-targets.patch
--- /dev/null
+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
+
--- /dev/null
+bpf-arm64-reject-out-of-range-b.cond-targets.patch
--- /dev/null
+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
+
--- /dev/null
+bpf-arm64-reject-out-of-range-b.cond-targets.patch