Skip to contentSkip to conversation

6.1.191-rc1 review

PATCH In review Greg Kroah-Hartman 10 participants 1,907 messages Oct 28 – Nov 9 Page 14 of 16

Subsystems (12)
lore
Share & tools
lore
author maintainer
#1521 lore raw

PATCH 1515/1887 octeontx2-vf: fix workqueue and netdev race in probe/remove

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Anshumali Gaur <[email protected]>

[ Upstream commit 3ba97ff4f873481d370bee7f7dfb87f8296af9be ]

Initialize the VF workqueue before register_netdev() so ndo_set_rx_mode does not queue work on a NULL workqueue. Unregister the netdev before destroying the workqueue, and add proper probe error cleanup.

Fixes cbc100aa2205 ("octeontx2-nicvf: add ndo_set_rx_mode support for multicast & promisc")
---
 .../ethernet/marvell/octeontx2/nic/otx2_vf.c  | 20 ++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)
drivers/net/ethernet/marvell/octeontx2/nic/otx2_vf.c +13 -7
index af5d69e6d267a..f1ae4bb15b8cf 100644
@@ -685,16 +685,16 @@ static int otx2vf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 		snprintf(netdev->name, sizeof(netdev->name), "lbk%d", n);
 	}
 
+	err = otx2_wq_init(vf);
+	if (err)
+		goto err_ptp_destroy;
+
 	err = register_netdev(netdev);
 	if (err) {
 		dev_err(dev, "Failed to register netdevice\n");
-		goto err_ptp_destroy;
+		goto err_wq_destroy;
 	}
 
-	err = otx2_wq_init(vf);
-	if (err)
-		goto err_unreg_netdev;
-
 	otx2vf_set_ethtool_ops(netdev);
 
 	err = otx2vf_mcam_flow_init(vf);
@@ -722,6 +722,10 @@ static int otx2vf_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 	otx2_shutdown_tc(vf);
 err_unreg_netdev:
 	unregister_netdev(netdev);
+err_wq_destroy:
+	cancel_work_sync(&vf->reset_task);
+	cancel_work_sync(&vf->rx_mode_work);
+	destroy_workqueue(vf->otx2_wq);
 err_ptp_destroy:
 	otx2_ptp_destroy(vf);
 err_detach_rsrc:
@@ -769,11 +773,13 @@ static void otx2vf_remove(struct pci_dev *pdev)
 	}
 #endif
 
-	cancel_work_sync(&vf->reset_task);
 	otx2_unregister_dl(vf);
 	unregister_netdev(netdev);
-	if (vf->otx2_wq)
+	if (vf->otx2_wq) {
+		cancel_work_sync(&vf->reset_task);
+		cancel_work_sync(&vf->rx_mode_work);
 		destroy_workqueue(vf->otx2_wq);
+	}
 	otx2_ptp_destroy(vf);
 	otx2_mcam_flow_del(vf);
 	otx2_shutdown_tc(vf);
Signature · 1 line
2.53.0
author maintainer
#1522 lore raw

PATCH 1516/1887 Bluetooth: do not leak an hci_conn when a second LE connect is rejected

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Radek Podgorny <[email protected]>

[ Upstream commit 7e1e4047200fd7519f9bdfe8a001437715e618b4 ]

create_le_conn_complete() decides whether the failed connection is still pending by comparing it against hci_lookup_le_connect(), which returns the first LE connection in BT_CONNECT. That is the same connection only while at most one is pending.

Two can be pending. Connections created on the passive scan path sit in BT_CONNECT with HCI_CONN_SCANNING set and are invisible to hci_lookup_le_connect() until hci_le_create_conn_sync() clears the flag when their command is issued, so the -EBUSY guard in hci_connect_le() does not prevent a second connection from being queued while the first is still on the scan path. Whenever two connections are in BT_CONNECT at once, the lookup may return one connection while create_le_conn_complete() is reporting the failure of the other; the early exit then drops the error and hci_conn_failed() never runs on the connection that failed.

The controller also rejects a second HCI_OP_LE_CREATE_CONN issued while another connection creation is still outstanding, per Core Spec Vol 4, Part E. The spec calls for Command Disallowed there; the bcm43438 observed here answers with an LMP/LL error code instead, which bt_to_errno() maps to the -EPROTO (-71) in the log below.

The leaked connection stays in BT_CONNECT forever, and because hci_connect_le() refuses to dial while hci_lookup_le_connect() finds anything, every subsequent attempt to reach any peer fails with -EBUSY and no command reaches the controller at all.

Seen on a bcm43438 with two BLE peers polled on the same interval (state 5 is BT_CONNECT; both handles are UNSET ones, allocated from the ida above HCI_CONN_HANDLE_MAX):

Bluetooth: hci1: Opcode 0x2013 failed: -71

  # hcitool con
          < LE 14:9C:EF:03:68:81 handle 3840 state 5 lm CENTRAL
          < LE C4:D3:6A:8C:B5:38 handle 3841 state 5 lm CENTRAL

A btmon capture across the next ten minutes of connect attempts contains no HCI_OP_LE_CREATE_CONN at all; outgoing LE connections do not recover until the adapter is reset. With this change the same scenario fails the rejected connection cleanly and further connects to both peers go through.

Ask about the connection itself instead of about the device.

Fixes c9f73a2178c1 ("Bluetooth: hci_conn: Fix hci_connect_le_sync")
---
 net/bluetooth/hci_conn.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)
net/bluetooth/hci_conn.c +7 -2
index 5bc58e465146c..0c0c052294fc7 100644
@@ -1344,8 +1344,13 @@ static void create_le_conn_complete(struct hci_dev *hdev, void *data, int err)
 		goto done;
 	}
 
-	/* Check if connection is still pending */
-	if (conn != hci_lookup_le_connect(hdev))
+	/* Check if this connection is still pending.
+	 *
+	 * hci_lookup_le_connect() returns only the first LE connection
+	 * in BT_CONNECT, which is not necessarily this one when two are
+	 * pending at once, so ask the connection itself.
+	 */
+	if (!hci_conn_valid(hdev, conn) || conn->state != BT_CONNECT)
 		goto done;
 
 	hci_conn_failed(conn, bt_status(err));
Signature · 1 line
2.53.0
author maintainer
#1523 lore raw

PATCH 1517/1887 Drivers: hv: vmbus: Skip VMBus module cleanup for non-nested root partition

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Michael Kelley <[email protected]>

[ Upstream commit cee0d90bceae1dee3bcc70f8d6b2ceb5b87deb42 ]

The VMBus module initialization function, hv_acpi_init(), currently does nothing when running in the root partition and root is not nested in another VM. But the initialization function reports success, so the VMBus module is indeed loaded. VMBus functionality is not actually needed, but the VMBus module must be loaded so that hv_vmbus_exists() can answer correctly. Furthermore, the mshv_root dependency on the VMBus module is needed as described in the commit message for 840b740a35bf ("mshv: Add conditional VMBus dependency").

Loading the VMBus module without actually initializing it causes failures if the module should later be unloaded. The module unload code tries to clean up things that were never initialized, resulting in memory faults and a panic.

Fix this by having VMBus module exit function perform the same check for non-nested root partition, and do nothing in such a case, just like hv_acpi_init().

In the long run, the code that manages the Hyper-V provided SynIC should be refactored to better coordinate the requirements of root partition scenarios and normal VM scenarios, and to hopefully remove the hv_vmbus_exists() dependnecy between mshv_root and VMBus modules. Preventing the current unload failure scenario is an expediency until such a refactoring is done.

Fixes 7e279d78664aa ("Drivers: hv: vmbus: skip VMBus initialization if Linux is root")
---
 drivers/hv/vmbus_drv.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
drivers/hv/vmbus_drv.c +15 -0
index 8aa775c9382bf..fe9a1e1ad0c2d 100644
@@ -2594,6 +2594,13 @@ static int __init hv_acpi_init(void)
 		return -ENODEV;
 
 	if (hv_root_partition)
+		/*
+		 * A non-nested root partition does not need VMBus client
+		 * functionality. However, the mshv_root module may have
+		 * a dependency on the VMBus module as described in
+		 * commit 840b740a35bf. Return success so the module
+		 * loads even though no VMBus initialization is done.
+		 */
 		return 0;
 
 	/*
@@ -2643,6 +2650,14 @@ static void __exit vmbus_exit(void)
 {
 	int cpu;
 
+	if (hv_root_partition)
+		/*
+		 * If a non-nested root partition loaded the VMBus module,
+		 * hv_acpi_init() did not do any VMBus initialization.
+		 * There's nothing to clean up, so just return.
+		 */
+		return;
+
 	unregister_syscore_ops(&hv_synic_syscore_ops);
 
 	hv_remove_kexec_handler();
Signature · 1 line
2.53.0
author maintainer
#1524 lore raw

PATCH 1518/1887 net/sched: account classifier filter allocations to memcg

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Jamal Hadi Salim <[email protected]>

[ Upstream commit 1beb81947eb486716af80db7a584f9e9fef7e003 ]

Allocations in the tc classifier *_change() paths (filter objects, per-CPU counters, and per-filter aux data) use plain GFP_KERNEL without __GFP_ACCOUNT, allowing unprivileged users to pin kernel memory outside memcg charging. The shared tcf_exts_init_ex() action array allocation in cls_api.c was also uncharged; this patch closes it along with the per-classifier filter-object/percpu/aux allocations that remain unaccounted.

Add GFP_KERNEL_ACCOUNT to:
- the shared tcf_exts_init_ex() action array (cls_api.c), common to every filter of every classifier (32 pointers, 256 bytes);
- the filter-object, per-CPU-counter, and per-filter aux allocations in cls_basic, cls_bpf, cls_cgroup, cls_flow, cls_flower, cls_fw, cls_matchall, cls_route and cls_u32;
- the u32_init_knode() replace-path knode allocation (cls_u32.c), which allocates the same struct tc_u_knode + sel.keys on every replace of an existing knode and was missed by the create-path-only conversion.

Also fix the cls_basic error path: basic_change() inserts fnew into the IDR before allocating the per-CPU counter. If alloc_percpu() fails the errout path kfree'd fnew without idr_remove, leaving a dangling pointer in the IDR. With GFP_KERNEL_ACCOUNT the percpu alloc becomes failable on demand (memcg at memory.max), making the dead path attacker-reachable and burning the handle permanently. Add the idr_remove on the percpu failure path, matching the basic_set_parms failure-path pattern.

Note: [email protected] provided a poc for basic_cls, but it was easy to extend to the other classifiers.

Conditions to recreate the bug:
- CONFIG_NET_SCHED, CONFIG_NET_CLS_* (the classifier being used), CONFIG_NET_CLS_ACT, CONFIG_MEMCG, CONFIG_USER_NS, CONFIG_NET_NS.
- Unprivileged user in a fresh user+network namespace (unshare -Urn), or root with CAP_NET_ADMIN.
- Create a large number of tc filters (e.g. tc filter add dev lo ingress ... <classifier> ...) while watching a memcg-limited cgroup:
system slab grows far faster than memory.current, pinning kernel memory outside memcg charging.

Fixes 0da974f4f303 ("[NET]: Conversions from kmalloc+memset to k(z|c)alloc.")
Reported-by [email protected]
---
 include/net/pkt_cls.h    |  2 +-
 net/sched/cls_basic.c    |  6 ++++--
 net/sched/cls_bpf.c      |  6 +++---
 net/sched/cls_cgroup.c   |  2 +-
 net/sched/cls_flow.c     |  2 +-
 net/sched/cls_flower.c   |  4 ++--
 net/sched/cls_fw.c       |  4 ++--
 net/sched/cls_matchall.c |  4 ++--
 net/sched/cls_route.c    |  4 ++--
 net/sched/cls_u32.c      | 12 +++++++-----
 10 files changed, 25 insertions(+), 21 deletions(-)
include/net/pkt_cls.h +1 -1
index 7ee8c92c41fa7..7ab833b41d05f 100644
@@ -280,7 +280,7 @@ static inline int tcf_exts_init(struct tcf_exts *exts, struct net *net,
 	 */
 	exts->net = net;
 	exts->actions = kcalloc(TCA_ACT_MAX_PRIO, sizeof(struct tc_action *),
-				GFP_KERNEL);
+				GFP_KERNEL_ACCOUNT);
 	if (!exts->actions)
 		return -ENOMEM;
 #endif
net/sched/cls_basic.c +4 -2
index d229ce99e5548..bf500510f60ff 100644
@@ -191,7 +191,7 @@ static int basic_change(struct net *net, struct sk_buff *in_skb,
 			return -EINVAL;
 	}
 
-	fnew = kzalloc(sizeof(*fnew), GFP_KERNEL);
+	fnew = kzalloc(sizeof(*fnew), GFP_KERNEL_ACCOUNT);
 	if (!fnew)
 		return -ENOBUFS;
 
@@ -210,9 +210,11 @@ static int basic_change(struct net *net, struct sk_buff *in_skb,
 	if (err)
 		goto errout;
 	fnew->handle = handle;
-	fnew->pf = alloc_percpu(struct tc_basic_pcnt);
+	fnew->pf = alloc_percpu_gfp(struct tc_basic_pcnt, GFP_KERNEL_ACCOUNT);
 	if (!fnew->pf) {
 		err = -ENOMEM;
+		if (!fold)
+			idr_remove(&head->handle_idr, fnew->handle);
 		goto errout;
 	}
 
net/sched/cls_bpf.c +3 -3
index ff0a8dd0b9548..3e5316c05d2c6 100644
@@ -350,7 +350,7 @@ static int cls_bpf_prog_from_ops(struct nlattr **tb, struct cls_bpf_prog *prog)
 	if (bpf_size != nla_len(tb[TCA_BPF_OPS]))
 		return -EINVAL;
 
-	bpf_ops = kmemdup(nla_data(tb[TCA_BPF_OPS]), bpf_size, GFP_KERNEL);
+	bpf_ops = kmemdup(nla_data(tb[TCA_BPF_OPS]), bpf_size, GFP_KERNEL_ACCOUNT);
 	if (bpf_ops == NULL)
 		return -ENOMEM;
 
@@ -387,7 +387,7 @@ static int cls_bpf_prog_from_efd(struct nlattr **tb, struct cls_bpf_prog *prog,
 		return PTR_ERR(fp);
 
 	if (tb[TCA_BPF_NAME]) {
-		name = nla_memdup(tb[TCA_BPF_NAME], GFP_KERNEL);
+		name = nla_memdup(tb[TCA_BPF_NAME], GFP_KERNEL_ACCOUNT);
 		if (!name) {
 			bpf_prog_put(fp);
 			return -ENOMEM;
@@ -427,7 +427,7 @@ static int cls_bpf_change(struct net *net, struct sk_buff *in_skb,
 	if (ret < 0)
 		return ret;
 
-	prog = kzalloc(sizeof(*prog), GFP_KERNEL);
+	prog = kzalloc(sizeof(*prog), GFP_KERNEL_ACCOUNT);
 	if (!prog)
 		return -ENOBUFS;
 
net/sched/cls_cgroup.c +1 -1
index ed00001b528a6..300806f80b866 100644
@@ -93,7 +93,7 @@ static int cls_cgroup_change(struct net *net, struct sk_buff *in_skb,
 	if (head && handle != head->handle)
 		return -ENOENT;
 
-	new = kzalloc(sizeof(*head), GFP_KERNEL);
+	new = kzalloc(sizeof(*head), GFP_KERNEL_ACCOUNT);
 	if (!new)
 		return -ENOBUFS;
 
net/sched/cls_flow.c +1 -1
index 315d689adf651..50a707f2fe8e3 100644
@@ -436,7 +436,7 @@ static int flow_change(struct net *net, struct sk_buff *in_skb,
 			return -EOPNOTSUPP;
 	}
 
-	fnew = kzalloc(sizeof(*fnew), GFP_KERNEL);
+	fnew = kzalloc(sizeof(*fnew), GFP_KERNEL_ACCOUNT);
 	if (!fnew)
 		return -ENOBUFS;
 
net/sched/cls_flower.c +2 -2
index 67e0a9799bd0c..e2ce1d5057e2d 100644
@@ -2003,7 +2003,7 @@ static struct fl_flow_mask *fl_create_new_mask(struct cls_fl_head *head,
 	struct fl_flow_mask *newmask;
 	int err;
 
-	newmask = kzalloc(sizeof(*newmask), GFP_KERNEL);
+	newmask = kzalloc(sizeof(*newmask), GFP_KERNEL_ACCOUNT);
 	if (!newmask)
 		return ERR_PTR(-ENOMEM);
 
@@ -2196,7 +2196,7 @@ static int fl_change(struct net *net, struct sk_buff *in_skb,
 		goto errout_tb;
 	}
 
-	fnew = kzalloc(sizeof(*fnew), GFP_KERNEL);
+	fnew = kzalloc(sizeof(*fnew), GFP_KERNEL_ACCOUNT);
 	if (!fnew) {
 		err = -ENOBUFS;
 		goto errout_tb;
net/sched/cls_fw.c +2 -2
index 979662292dced..fe6097b457073 100644
@@ -274,7 +274,7 @@ static int fw_change(struct net *net, struct sk_buff *in_skb,
 		if (f->id != handle && handle)
 			return -EINVAL;
 
-		fnew = kzalloc(sizeof(struct fw_filter), GFP_KERNEL);
+		fnew = kzalloc(sizeof(struct fw_filter), GFP_KERNEL_ACCOUNT);
 		if (!fnew)
 			return -ENOBUFS;
 
@@ -328,7 +328,7 @@ static int fw_change(struct net *net, struct sk_buff *in_skb,
 		rcu_assign_pointer(tp->root, head);
 	}
 
-	f = kzalloc(sizeof(struct fw_filter), GFP_KERNEL);
+	f = kzalloc(sizeof(struct fw_filter), GFP_KERNEL_ACCOUNT);
 	if (f == NULL)
 		return -ENOBUFS;
 
net/sched/cls_matchall.c +2 -2
index d4bb817b3658a..4e54e7babda10 100644
@@ -187,7 +187,7 @@ static int mall_change(struct net *net, struct sk_buff *in_skb,
 			return -EINVAL;
 	}
 
-	new = kzalloc(sizeof(*new), GFP_KERNEL);
+	new = kzalloc(sizeof(*new), GFP_KERNEL_ACCOUNT);
 	if (!new)
 		return -ENOBUFS;
 
@@ -199,7 +199,7 @@ static int mall_change(struct net *net, struct sk_buff *in_skb,
 		handle = 1;
 	new->handle = handle;
 	new->flags = userflags;
-	new->pf = alloc_percpu(struct tc_matchall_pcnt);
+	new->pf = alloc_percpu_gfp(struct tc_matchall_pcnt, GFP_KERNEL_ACCOUNT);
 	if (!new->pf) {
 		err = -ENOMEM;
 		goto err_alloc_percpu;
net/sched/cls_route.c +2 -2
index c7fe52f59521c..3f63a37bc2bb7 100644
@@ -442,7 +442,7 @@ static int route4_set_parms(struct net *net, struct tcf_proto *tp,
 	h1 = to_hash(nhandle);
 	b = rtnl_dereference(head->table[h1]);
 	if (!b) {
-		b = kzalloc(sizeof(struct route4_bucket), GFP_KERNEL);
+		b = kzalloc(sizeof(struct route4_bucket), GFP_KERNEL_ACCOUNT);
 		if (b == NULL)
 			return -ENOBUFS;
 
@@ -513,7 +513,7 @@ static int route4_change(struct net *net, struct sk_buff *in_skb,
 			return -EINVAL;
 
 	err = -ENOBUFS;
-	f = kzalloc(sizeof(struct route4_filter), GFP_KERNEL);
+	f = kzalloc(sizeof(struct route4_filter), GFP_KERNEL_ACCOUNT);
 	if (!f)
 		goto errout;
 
net/sched/cls_u32.c +7 -5
index 599563b43def0..26874ff2db3e0 100644
@@ -839,7 +839,8 @@ static struct tc_u_knode *u32_init_knode(struct net *net, struct tcf_proto *tp,
 	struct tc_u32_sel *s = &n->sel;
 	struct tc_u_knode *new;
 
-	new = kzalloc(struct_size(new, sel.keys, s->nkeys), GFP_KERNEL);
+	new = kzalloc(struct_size(new, sel.keys, s->nkeys),
+		      GFP_KERNEL_ACCOUNT);
 	if (!new)
 		return NULL;
 
@@ -1137,15 +1138,16 @@ static int u32_change(struct net *net, struct sk_buff *in_skb,
 		goto erridr;
 	}
 
-	n = kzalloc(struct_size(n, sel.keys, s->nkeys), GFP_KERNEL);
+	n = kzalloc(struct_size(n, sel.keys, s->nkeys), GFP_KERNEL_ACCOUNT);
 	if (n == NULL) {
 		err = -ENOBUFS;
 		goto erridr;
 	}
 
 #ifdef CONFIG_CLS_U32_PERF
-	n->pf = __alloc_percpu(struct_size(n->pf, kcnts, s->nkeys),
-			       __alignof__(struct tc_u32_pcnt));
+	n->pf = __alloc_percpu_gfp(struct_size(n->pf, kcnts, s->nkeys),
+				   __alignof__(struct tc_u32_pcnt),
+				   GFP_KERNEL_ACCOUNT);
 	if (!n->pf) {
 		err = -ENOBUFS;
 		goto errfree;
@@ -1167,7 +1169,7 @@ static int u32_change(struct net *net, struct sk_buff *in_skb,
 		goto errout;
 
 #ifdef CONFIG_CLS_U32_MARK
-	n->pcpu_success = alloc_percpu(u32);
+	n->pcpu_success = alloc_percpu_gfp(u32, GFP_KERNEL_ACCOUNT);
 	if (!n->pcpu_success) {
 		err = -ENOMEM;
 		goto errout;
Signature · 1 line
2.53.0
author maintainer
#1525 lore raw

PATCH 1519/1887 NFSv4.1: zero referring call lists before decoding

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Jérémy Jean <[email protected]>

[ Upstream commit 8fa4804fe62ca4155a2d8fc2789d630376cbf2fc ]

decode_cb_sequence_args() allocates csa_rclists with kmalloc_objs(), so each referring_call_list starts uninitialized. decode_rc_list() assigns rcl_refcalls only when rcl_nrefcalls is nonzero. A valid list with zero referring calls therefore leaves the pointer uninitialized, and nfs4_callback_sequence() later passes stale slab contents to kfree().

Allocate csa_rclists with kzalloc_objs() so every rcl_refcalls member is NULL from the beginning, including valid empty referring call lists.

Fixes 4aece6a19cf7 ("nfs41: cb_sequence xdr implementation")
Assisted-by Codex:gpt-5
---
 fs/nfs/callback_xdr.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
fs/nfs/callback_xdr.c +3 -3
index fa519ce5c841f..5b538ade22100 100644
@@ -419,9 +419,9 @@ static __be32 decode_cb_sequence_args(struct svc_rqst *rqstp,
 	args->csa_nrclists = ntohl(*p++);
 	args->csa_rclists = NULL;
 	if (args->csa_nrclists) {
-		args->csa_rclists = kmalloc_array(args->csa_nrclists,
-						  sizeof(*args->csa_rclists),
-						  GFP_KERNEL);
+		args->csa_rclists = kcalloc(args->csa_nrclists,
+					    sizeof(*args->csa_rclists),
+					    GFP_KERNEL);
 		if (unlikely(args->csa_rclists == NULL))
 			return htonl(NFS4ERR_RESOURCE);
 
Signature · 1 line
2.53.0
author maintainer
#1526 lore raw

PATCH 1520/1887 vxlan: fix reading neigh ha

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Nikolay Aleksandrov <[email protected]>

[ Upstream commit b824059a673b2283e78c7aae2c7d257aad7f0e1d ]

Currently arp/neigh_reduce read neigh ha directly which can lead to partial reads while the neigh is being updated. Use neigh_ha_snapshot to take a stable snapshot of the address similar to route_shortcircuit which already does the right thing.

Fixes e4f67addf158 ("add DOVE extensions for VXLAN")
Fixes f564f45c4518 ("vxlan: add ipv6 proxy support")
---
 drivers/net/vxlan/vxlan_core.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)
drivers/net/vxlan/vxlan_core.c +13 -7
index 536c8384d635a..893212fad6b42 100644
@@ -1944,6 +1944,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 
 	if (n) {
 		struct vxlan_rdst *rdst = NULL;
+		u8 ha[ETH_ALEN] __aligned(2);
 		struct vxlan_fdb *f;
 		struct sk_buff	*reply;
 
@@ -1952,7 +1953,9 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 			goto out;
 		}
 
-		f = vxlan_find_mac(vxlan, n->ha, vni);
+		neigh_ha_snapshot(ha, n, n->dev);
+
+		f = vxlan_find_mac(vxlan, ha, vni);
 		if (f)
 			rdst = first_remote_rcu(f);
 		if (rdst && vxlan_addr_any(&rdst->remote_ip)) {
@@ -1962,7 +1965,7 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 		}
 
 		reply = arp_create(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, sha,
-				n->ha, sha);
+				   ha, sha);
 
 		neigh_release(n);
 
@@ -1995,7 +1998,8 @@ static int arp_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 
 #if IS_ENABLED(CONFIG_IPV6)
 static struct sk_buff *vxlan_na_create(struct sk_buff *request,
-	struct neighbour *n, bool isrouter)
+				       struct neighbour *n, u8 *ha,
+				       bool isrouter)
 {
 	struct net_device *dev = request->dev;
 	struct sk_buff *reply;
@@ -2043,7 +2047,7 @@ static struct sk_buff *vxlan_na_create(struct sk_buff *request,
 
 	/* Ethernet header */
 	ether_addr_copy(eth_hdr(reply)->h_dest, daddr);
-	ether_addr_copy(eth_hdr(reply)->h_source, n->ha);
+	ether_addr_copy(eth_hdr(reply)->h_source, ha);
 	eth_hdr(reply)->h_proto = htons(ETH_P_IPV6);
 	reply->protocol = htons(ETH_P_IPV6);
 
@@ -2072,7 +2076,7 @@ static struct sk_buff *vxlan_na_create(struct sk_buff *request,
 	na->icmph.icmp6_override = 1;
 	na->icmph.icmp6_solicited = 1;
 	na->target = ns->target;
-	ether_addr_copy(&na->opt[2], n->ha);
+	ether_addr_copy(&na->opt[2], ha);
 	na->opt[0] = ND_OPT_TARGET_LL_ADDR;
 	na->opt[1] = na_olen >> 3;
 
@@ -2115,6 +2119,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 
 	if (n) {
 		struct vxlan_rdst *rdst = NULL;
+		u8 ha[ETH_ALEN] __aligned(2);
 		struct vxlan_fdb *f;
 		struct sk_buff *reply;
 
@@ -2123,7 +2128,8 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 			goto out;
 		}
 
-		f = vxlan_find_mac(vxlan, n->ha, vni);
+		neigh_ha_snapshot(ha, n, n->dev);
+		f = vxlan_find_mac(vxlan, ha, vni);
 		if (f)
 			rdst = first_remote_rcu(f);
 		if (rdst && vxlan_addr_any(&rdst->remote_ip)) {
@@ -2132,7 +2138,7 @@ static int neigh_reduce(struct net_device *dev, struct sk_buff *skb, __be32 vni)
 			goto out;
 		}
 
-		reply = vxlan_na_create(skb, n,
+		reply = vxlan_na_create(skb, n, ha,
 					!!(f ? f->flags & NTF_ROUTER : 0));
 
 		neigh_release(n);
Signature · 1 line
2.53.0
author maintainer
#1527 lore raw

PATCH 1521/1887 net: bridge: arp/nd proxy: fix reading neigh ha

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Nikolay Aleksandrov <[email protected]>

[ Upstream commit 57549ab9079122991dfa0f8248ca00e101e8e69b ]

Currently neigh ha address is read directly, but that can result in torn/partial reads if the neigh is being updated. Use neigh_ha_snapshot to take a stable snapshot of the address.

Fixes 057658cb33fb ("bridge: suppress arp pkts on BR_NEIGH_SUPPRESS ports")
Fixes ed842faeb2bd ("bridge: suppress nd pkts on BR_NEIGH_SUPPRESS ports")
---
 net/bridge/br_arp_nd_proxy.c | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)
net/bridge/br_arp_nd_proxy.c +14 -10
index 97d0349189aaf..9143bb51d83ab 100644
@@ -191,13 +191,15 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
 	n = neigh_lookup(&arp_tbl, &tip, vlandev);
 	if (n) {
 		struct net_bridge_fdb_entry *f;
+		u8 ha[ETH_ALEN] __aligned(2);
 
 		if (!(READ_ONCE(n->nud_state) & NUD_VALID)) {
 			neigh_release(n);
 			return;
 		}
 
-		f = br_fdb_find_rcu(br, n->ha, vid);
+		neigh_ha_snapshot(ha, n, n->dev);
+		f = br_fdb_find_rcu(br, ha, vid);
 		if (f) {
 			const struct net_bridge_port *dst = READ_ONCE(f->dst);
 			bool replied = false;
@@ -207,10 +209,10 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
 						   BR_NEIGH_SUPPRESS)))) {
 				if (!vid)
 					br_arp_send(br, p, skb->dev, sip, tip,
-						    sha, n->ha, sha, 0, 0);
+						    sha, ha, sha, 0, 0);
 				else
 					br_arp_send(br, p, skb->dev, sip, tip,
-						    sha, n->ha, sha,
+						    sha, ha, sha,
 						    skb->vlan_proto,
 						    skb_vlan_tag_get(skb));
 				replied = true;
@@ -248,7 +250,7 @@ struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb, struct nd_msg *msg)
 }
 
 static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p,
-		       struct sk_buff *request, struct neighbour *n,
+		       struct sk_buff *request, struct neighbour *n, u8 *ha,
 		       __be16 vlan_proto, u16 vlan_tci)
 {
 	struct net_device *dev = request->dev;
@@ -306,7 +308,7 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p,
 		ipv6_eth_mc_map(&in6addr_linklocal_allnodes, eth_hdr(reply)->h_dest);
 	else
 		ether_addr_copy(eth_hdr(reply)->h_dest, daddr);
-	ether_addr_copy(eth_hdr(reply)->h_source, n->ha);
+	ether_addr_copy(eth_hdr(reply)->h_source, ha);
 	eth_hdr(reply)->h_proto = htons(ETH_P_IPV6);
 	reply->protocol = htons(ETH_P_IPV6);
 
@@ -336,7 +338,7 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p,
 	na->icmph.icmp6_override = 1;
 	na->icmph.icmp6_solicited = dad ? 0 : 1;
 	na->target = ns->target;
-	ether_addr_copy(&na->opt[2], n->ha);
+	ether_addr_copy(&na->opt[2], ha);
 	na->opt[0] = ND_OPT_TARGET_LL_ADDR;
 	na->opt[1] = na_olen >> 3;
 
@@ -365,7 +367,7 @@ static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p,
 		__vlan_hwaccel_put_tag(reply, vlan_proto, vlan_tci);
 
 	netdev_dbg(dev, "nd send dev %s dst %pI6 dst_hw %pM src %pI6 src_hw %pM\n",
-		   dev->name, &pip6->daddr, daddr, &pip6->saddr, n->ha);
+		   dev->name, &pip6->daddr, daddr, &pip6->saddr, ha);
 
 	if (p) {
 		dev_queue_xmit(reply);
@@ -462,24 +464,26 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
 	n = neigh_lookup(ipv6_stub->nd_tbl, &msg->target, vlandev);
 	if (n) {
 		struct net_bridge_fdb_entry *f;
+		u8 ha[ETH_ALEN] __aligned(2);
 
 		if (!(READ_ONCE(n->nud_state) & NUD_VALID)) {
 			neigh_release(n);
 			return;
 		}
 
-		f = br_fdb_find_rcu(br, n->ha, vid);
+		neigh_ha_snapshot(ha, n, n->dev);
+		f = br_fdb_find_rcu(br, ha, vid);
 		if (f) {
 			const struct net_bridge_port *dst = READ_ONCE(f->dst);
 			bool replied = false;
 
 			if (dst && (dst->flags & BR_NEIGH_SUPPRESS)) {
 				if (vid != 0)
-					br_nd_send(br, p, skb, n,
+					br_nd_send(br, p, skb, n, ha,
 						   skb->vlan_proto,
 						   skb_vlan_tag_get(skb));
 				else
-					br_nd_send(br, p, skb, n, 0, 0);
+					br_nd_send(br, p, skb, n, ha, 0, 0);
 				replied = true;
 			}
 
Signature · 1 line
2.53.0
author maintainer
#1528 lore raw

PATCH 1522/1887 net_sched: sch_fifo: implement lockless __fifo_dump()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Eric Dumazet <[email protected]>

[ Upstream commit 01daf66b791e1ddd1a4aae2a65ea65fa1d4a96ef ]

Instead of relying on RTNL, __fifo_dump() can use READ_ONCE() annotations, paired with WRITE_ONCE() ones in __fifo_init().

Also add missing READ_ONCE(sh->limit) in bfifo_enqueue(), pfifo_enqueue() and pfifo_tail_enqueue().

Stable-dep-of: 4c660ee8c809 ("net: sched: fix 32-bit backlog wrap in gred, bfifo and plug enqueue")

---
 net/sched/sch_fifo.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)
net/sched/sch_fifo.c +7 -6
index af5f2ab69b8d2..739432c4d8d39 100644
@@ -19,7 +19,8 @@
 static int bfifo_enqueue(struct sk_buff *skb, struct Qdisc *sch,
 			 struct sk_buff **to_free)
 {
-	if (likely(sch->qstats.backlog + qdisc_pkt_len(skb) <= sch->limit))
+	if (likely(sch->qstats.backlog + qdisc_pkt_len(skb) <=
+		   READ_ONCE(sch->limit)))
 		return qdisc_enqueue_tail(skb, sch);
 
 	return qdisc_drop(skb, sch, to_free);
@@ -28,7 +29,7 @@ static int bfifo_enqueue(struct sk_buff *skb, struct Qdisc *sch,
 static int pfifo_enqueue(struct sk_buff *skb, struct Qdisc *sch,
 			 struct sk_buff **to_free)
 {
-	if (likely(sch->q.qlen < sch->limit))
+	if (likely(sch->q.qlen < READ_ONCE(sch->limit)))
 		return qdisc_enqueue_tail(skb, sch);
 
 	return qdisc_drop(skb, sch, to_free);
@@ -42,7 +43,7 @@ static int pfifo_tail_enqueue(struct sk_buff *skb, struct Qdisc *sch,
 	if (unlikely(READ_ONCE(sch->limit) == 0))
 		return qdisc_drop(skb, sch, to_free);
 
-	if (likely(sch->q.qlen < sch->limit))
+	if (likely(sch->q.qlen < READ_ONCE(sch->limit)))
 		return qdisc_enqueue_tail(skb, sch);
 
 	prev_backlog = sch->qstats.backlog;
@@ -108,14 +109,14 @@ static int __fifo_init(struct Qdisc *sch, struct nlattr *opt,
 		if (is_bfifo)
 			limit *= psched_mtu(qdisc_dev(sch));
 
-		sch->limit = limit;
+		WRITE_ONCE(sch->limit, limit);
 	} else {
 		struct tc_fifo_qopt *ctl = nla_data(opt);
 
 		if (nla_len(opt) < sizeof(*ctl))
 			return -EINVAL;
 
-		sch->limit = ctl->limit;
+		WRITE_ONCE(sch->limit, ctl->limit);
 	}
 
 	if (is_bfifo)
@@ -157,7 +158,7 @@ static void fifo_destroy(struct Qdisc *sch)
 
 static int __fifo_dump(struct Qdisc *sch, struct sk_buff *skb)
 {
-	struct tc_fifo_qopt opt = { .limit = sch->limit };
+	struct tc_fifo_qopt opt = { .limit = READ_ONCE(sch->limit) };
 
 	if (nla_put(skb, TCA_OPTIONS, sizeof(opt), &opt))
 		goto nla_put_failure;
Signature · 1 line
2.53.0
author maintainer
#1529 lore raw

PATCH 1523/1887 net: sched: fix 32-bit backlog wrap in gred, bfifo and plug enqueue

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Jamal Hadi Salim <[email protected]>

[ Upstream commit 4c660ee8c809637909f4f7eb1017f7b9401c75c4 ]

gred_enqueue(), bfifo_enqueue() and plug_enqueue() admit a packet when the current backlog plus the packet length fits within the queue limit:

sch->qstats.backlog + qdisc_pkt_len(skb) <= sch->limit (gred default VQ) gred_backlog+qdisc_pkt_len(skb) <= q->limit (gred configured VQ) sch->qstats.backlog + qdisc_pkt_len(skb) <= sch->limit (bfifo)
sch->qstats.backlog + skb->len <= q->limit (plug)

sch->qstats.backlog and q->backlog are u32, and qdisc_pkt_len()/skb->len are unsigned int, so all sums are computed in 32 bits and wrap at 2^32. Once the true backlog exceeds 4 GiB the wrapped sum becomes small and admission keeps succeeding, so the queue grows without bound and the kernel can be driven to OOM.

Promote the sums to u64 so admission stops once the true backlog exceeds the limit. The limit is u32, so the bounded queue stays below 2^32 and the stored u32 backlog never wraps.

The bug can only be reproduced as root (albeit with ridiculous setup):
attach a gred (or bfifo/plug) qdisc with a limit near 4 GiB, leaving the default VQ unconfigured (for gred), and drive >4 GiB of queued traffic (e.g. via a size table / stab to inflate qdisc_pkt_len, or sustained high-rate traffic). The u32 backlog+len sum wraps at 2^32, admission keeps succeeding, and the queue grows unboundedly to OOM.

---
 net/sched/sch_fifo.c | 2 +-
 net/sched/sch_gred.c | 4 ++--
 net/sched/sch_plug.c | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)
net/sched/sch_fifo.c +1 -1
index 739432c4d8d39..91c9bd34a4a9d 100644
@@ -19,7 +19,7 @@
 static int bfifo_enqueue(struct sk_buff *skb, struct Qdisc *sch,
 			 struct sk_buff **to_free)
 {
-	if (likely(sch->qstats.backlog + qdisc_pkt_len(skb) <=
+	if (likely((u64)sch->qstats.backlog + qdisc_pkt_len(skb) <=
 		   READ_ONCE(sch->limit)))
 		return qdisc_enqueue_tail(skb, sch);
 
net/sched/sch_gred.c +2 -2
index fa7a1b69c0f35..4f44a268286fa 100644
@@ -179,7 +179,7 @@ static int gred_enqueue(struct sk_buff *skb, struct Qdisc *sch,
 			 * if no default DP has been configured. This
 			 * allows for DP flows to be left untouched.
 			 */
-			if (likely(sch->qstats.backlog + qdisc_pkt_len(skb) <=
+			if (likely((u64)sch->qstats.backlog + qdisc_pkt_len(skb) <=
 					sch->limit))
 				return qdisc_enqueue_tail(skb, sch);
 			else
@@ -244,7 +244,7 @@ static int gred_enqueue(struct sk_buff *skb, struct Qdisc *sch,
 		break;
 	}
 
-	if (gred_backlog(t, q, sch) + qdisc_pkt_len(skb) <= q->limit) {
+	if ((u64)gred_backlog(t, q, sch) + qdisc_pkt_len(skb) <= q->limit) {
 		q->backlog += qdisc_pkt_len(skb);
 		return qdisc_enqueue_tail(skb, sch);
 	}
net/sched/sch_plug.c +1 -1
index 35f49edf63dbf..6638343ca671a 100644
@@ -89,7 +89,7 @@ static int plug_enqueue(struct sk_buff *skb, struct Qdisc *sch,
 {
 	struct plug_sched_data *q = qdisc_priv(sch);
 
-	if (likely(sch->qstats.backlog + skb->len <= q->limit)) {
+	if (likely((u64)sch->qstats.backlog + skb->len <= q->limit)) {
 		if (!q->unplug_indefinite)
 			q->pkts_current_epoch++;
 		return qdisc_enqueue_tail(skb, sch);
Signature · 1 line
2.53.0
author maintainer
#1530 lore raw

PATCH 1524/1887 ip6mr: do not clone dst in ip6mr_cache_report()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Eric Dumazet <[email protected]>

[ Upstream commit 235b42b5860189eb8c27c36435ad932cae65a734 ]

IPv6 input attaches a non-refcounted (NOREF) dst to skbs under RCU. When an ingress multicast packet misses MFC lookup, ip6mr_cache_unresolved() places the skb onto the unresolved queue, escaping the receive-side RCU grace period.

If the underlying route is deleted and freed, and the MFC queue is later resolved with a wrong parent interface, ip6_mr_forward() invokes ip6mr_cache_report(..., MRT6MSG_WRONGMIF), which executes dst_clone(skb_dst(pkt)) on the freed dst entry, triggering a slab use-after-free.

Report packets queued to mroute6_sk (a raw socket) and netlink notifications do not require an attached dst entry.

Fix this by:
1. Removing dst_clone() in ip6mr_cache_report() and ensuring report skbs do not hold a dst.
2. Dropping skb_dst before queuing unresolved skbs in ip6mr_cache_unresolved(), matching the fact that multicast forwarding resolves outgoing routes anew via ip6_route_output().

---
 net/ipv6/ip6mr.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
net/ipv6/ip6mr.c +2 -1
index 944097e9d9873..b677f48179c87 100644
@@ -1128,7 +1128,6 @@ static int ip6mr_cache_report(const struct mr_table *mrt, struct sk_buff *pkt,
 	msg->im6_src = ipv6_hdr(pkt)->saddr;
 	msg->im6_dst = ipv6_hdr(pkt)->daddr;
 
-	skb_dst_set(skb, dst_clone(skb_dst(pkt)));
 	skb->ip_summed = CHECKSUM_UNNECESSARY;
 	}
 
@@ -1138,6 +1137,7 @@ static int ip6mr_cache_report(const struct mr_table *mrt, struct sk_buff *pkt,
 		return -EINVAL;
 	}
 
+	skb_dst_drop(skb);
 	mrt6msg_netlink_event(mrt, skb);
 
 	/* Deliver to user space multicast routing algorithms */
@@ -1217,6 +1217,7 @@ static int ip6mr_cache_unresolved(struct mr_table *mrt, mifi_t mifi,
 			skb->dev = dev;
 			skb->skb_iif = dev->ifindex;
 		}
+		skb_dst_drop(skb);
 		skb_queue_tail(&c->_c.mfc_un.unres.unresolved, skb);
 		err = 0;
 	}
Signature · 1 line
2.53.0
author maintainer
#1531 lore raw

PATCH 1525/1887 net: qlcnic: validate unified ROM sections before loading

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Pengpeng Hou <[email protected]>

[ Upstream commit 5e8076e4e4124dae75a3e080ddc20404700d7585 ]

The unified ROM parser reads directory, product, and data-descriptor fields from the firmware file. Existing validation forms table and data ends with unchecked additions and multiplications. Malformed values can wrap before they are compared with the firmware size. The parser also dereferences typed pointers at firmware-controlled offsets.

Valid descriptor extents alone are insufficient for the consumers. The loader reads a fixed-size bootloader regardless of its declared size, the version parser assumes a 17-byte tail, and a partial final firmware word is read as a full u64. A truncated image can therefore make the driver read beyond the firmware allocation during validation or loading.

Replace the pointer-returning parser with bounded range helpers. Validate table entry sizes, descriptor indices, section ranges, the fixed bootloader load length, and the version tail before exposing any section. Read all file fields with unaligned little-endian accessors and assemble a partial final word from only the bytes that remain. Apply the same range checks to the legacy image before reading its fixed fields.

Fixes af19b49152bd ("qlcnic: Qlogic ethernet driver for CNA devices")
---
 .../net/ethernet/qlogic/qlcnic/qlcnic_init.c  | 445 ++++++++++--------
 1 file changed, 248 insertions(+), 197 deletions(-)
drivers/net/ethernet/qlogic/qlcnic/qlcnic_init.c +248 -197
index 09f20c7947545..d6cc6288436dc 100644
@@ -6,6 +6,7 @@
 
 #include "qlcnic.h"
 #include "qlcnic_hw.h"
+#include <asm/unaligned.h>
 
 struct crb_addr_pair {
 	u32 addr;
@@ -740,159 +741,212 @@ qlcnic_has_mn(struct qlcnic_adapter *adapter)
 	return 0;
 }
 
-static
-struct uni_table_desc *qlcnic_get_table_desc(const u8 *unirom, int section)
-{
-	u32 i, entries;
-	struct uni_table_desc *directory = (struct uni_table_desc *) &unirom[0];
-	entries = le32_to_cpu(directory->num_entries);
-
-	for (i = 0; i < entries; i++) {
+#define FILEHEADER_SIZE (14 * 4)
+#define QLCNIC_UNI_DIR_TYPE_OFF		(8 * sizeof(__le32))
+#define QLCNIC_UNI_DIR_ENTRY_MIN_SIZE	(9 * sizeof(__le32))
+#define QLCNIC_UNI_PRODUCT_ENTRY_MIN_SIZE \
+	((QLCNIC_UNI_FIRMWARE_IDX_OFF + 1) * sizeof(__le32))
+#define QLCNIC_UNI_VERSION_TAIL_SIZE	17
+#define QLCNIC_UNI_BOOTLD_SIZE \
+	(QLCNIC_IMAGE_START - QLCNIC_BOOTLD_START)
+
+struct qlcnic_uni_data {
+	u32 offset;
+	u32 size;
+};
 
-		u32 offs = le32_to_cpu(directory->findex) +
-			   i * le32_to_cpu(directory->entry_size);
-		u32 tab_type = le32_to_cpu(*((__le32 *)&unirom[offs] + 8));
+static bool qlcnic_rom_range_valid(size_t size, size_t offset, size_t len)
+{
+	return offset <= size && len <= size - offset;
+}
 
-		if (tab_type == section)
-			return (struct uni_table_desc *) &unirom[offs];
-	}
+static bool qlcnic_rom_table_valid(size_t size, u32 offset, u32 entries,
+				   u32 entry_size, u32 min_entry_size)
+{
+	if (entry_size < min_entry_size || offset > size)
+		return false;
 
-	return NULL;
+	return entries <= (size - offset) / entry_size;
 }
 
-#define FILEHEADER_SIZE (14 * 4)
-
-static int
-qlcnic_validate_header(struct qlcnic_adapter *adapter)
+static int qlcnic_get_directory(struct qlcnic_adapter *adapter,
+				size_t *offset, u32 *entries, u32 *entry_size)
 {
-	const u8 *unirom = adapter->fw->data;
-	struct uni_table_desc *directory = (struct uni_table_desc *) &unirom[0];
-	u32 entries, entry_size, tab_size, fw_file_size;
-
-	fw_file_size = adapter->fw->size;
+	const struct firmware *fw = adapter->fw;
+	const u8 *directory = fw->data;
 
-	if (fw_file_size < FILEHEADER_SIZE)
+	if (fw->size < FILEHEADER_SIZE)
 		return -EINVAL;
 
-	entries = le32_to_cpu(directory->num_entries);
-	entry_size = le32_to_cpu(directory->entry_size);
-	tab_size = le32_to_cpu(directory->findex) + (entries * entry_size);
+	*offset = get_unaligned_le32(directory +
+				     offsetof(struct uni_table_desc, findex));
+	*entries = get_unaligned_le32(directory +
+				      offsetof(struct uni_table_desc, num_entries));
+	*entry_size = get_unaligned_le32(directory +
+					 offsetof(struct uni_table_desc, entry_size));
 
-	if (fw_file_size < tab_size)
+	if (!qlcnic_rom_table_valid(fw->size, *offset, *entries, *entry_size,
+				    QLCNIC_UNI_DIR_ENTRY_MIN_SIZE))
 		return -EINVAL;
 
 	return 0;
 }
 
-static int
-qlcnic_validate_bootld(struct qlcnic_adapter *adapter)
+static int qlcnic_get_table_desc(struct qlcnic_adapter *adapter, int section,
+				 size_t *desc_offset)
 {
-	struct uni_table_desc *tab_desc;
-	struct uni_data_desc *descr;
-	u32 offs, tab_size, data_size, idx;
 	const u8 *unirom = adapter->fw->data;
-	__le32 temp;
-
-	temp = *((__le32 *)&unirom[adapter->file_prd_off] +
-		 QLCNIC_UNI_BOOTLD_IDX_OFF);
-	idx = le32_to_cpu(temp);
-	tab_desc = qlcnic_get_table_desc(unirom, QLCNIC_UNI_DIR_SECT_BOOTLD);
-
-	if (!tab_desc)
-		return -EINVAL;
-
-	tab_size = le32_to_cpu(tab_desc->findex) +
-		   le32_to_cpu(tab_desc->entry_size) * (idx + 1);
-
-	if (adapter->fw->size < tab_size)
-		return -EINVAL;
+	size_t directory_offset;
+	u32 entries, entry_size;
+	size_t i;
+	int ret;
 
-	offs = le32_to_cpu(tab_desc->findex) +
-	       le32_to_cpu(tab_desc->entry_size) * idx;
-	descr = (struct uni_data_desc *)&unirom[offs];
+	ret = qlcnic_get_directory(adapter, &directory_offset, &entries,
+				   &entry_size);
+	if (ret)
+		return ret;
 
-	data_size = le32_to_cpu(descr->findex) + le32_to_cpu(descr->size);
+	for (i = 0; i < entries; i++) {
+		size_t offset = directory_offset + i * entry_size;
+		u32 table_type;
 
-	if (adapter->fw->size < data_size)
-		return -EINVAL;
+		table_type = get_unaligned_le32(unirom + offset +
+						QLCNIC_UNI_DIR_TYPE_OFF);
+		if (table_type == section) {
+			*desc_offset = offset;
+			return 0;
+		}
+	}
 
-	return 0;
+	return -ENOENT;
 }
 
 static int
-qlcnic_validate_fw(struct qlcnic_adapter *adapter)
+qlcnic_validate_header(struct qlcnic_adapter *adapter)
 {
-	struct uni_table_desc *tab_desc;
-	struct uni_data_desc *descr;
-	const u8 *unirom = adapter->fw->data;
-	u32 offs, tab_size, data_size, idx;
-	__le32 temp;
+	u32 entries, entry_size;
+	size_t offset;
+
+	return qlcnic_get_directory(adapter, &offset, &entries, &entry_size);
+}
 
-	temp = *((__le32 *)&unirom[adapter->file_prd_off] +
-		 QLCNIC_UNI_FIRMWARE_IDX_OFF);
-	idx = le32_to_cpu(temp);
-	tab_desc = qlcnic_get_table_desc(unirom, QLCNIC_UNI_DIR_SECT_FW);
+static int qlcnic_get_data_desc(struct qlcnic_adapter *adapter, u32 section,
+				u32 index_offset, struct qlcnic_uni_data *data)
+{
+	size_t table_desc_offset, table_offset, desc_offset;
+	const struct firmware *fw = adapter->fw;
+	const u8 *unirom = fw->data;
+	size_t product_index_offset;
+	u32 entries, entry_size, idx;
+	int ret;
 
-	if (!tab_desc)
+	product_index_offset = adapter->file_prd_off +
+				 (size_t)index_offset * sizeof(__le32);
+	if (!qlcnic_rom_range_valid(fw->size, product_index_offset,
+				    sizeof(__le32)))
 		return -EINVAL;
 
-	tab_size = le32_to_cpu(tab_desc->findex) +
-		   le32_to_cpu(tab_desc->entry_size) * (idx + 1);
+	idx = get_unaligned_le32(unirom + product_index_offset);
+	ret = qlcnic_get_table_desc(adapter, section, &table_desc_offset);
+	if (ret)
+		return ret;
 
-	if (adapter->fw->size < tab_size)
+	table_offset = get_unaligned_le32(unirom + table_desc_offset +
+					  offsetof(struct uni_table_desc, findex));
+	entries = get_unaligned_le32(unirom + table_desc_offset +
+				     offsetof(struct uni_table_desc, num_entries));
+	entry_size = get_unaligned_le32(unirom + table_desc_offset +
+					offsetof(struct uni_table_desc, entry_size));
+	if (!qlcnic_rom_table_valid(fw->size, table_offset, entries,
+				    entry_size, sizeof(struct uni_data_desc)) ||
+	    idx >= entries)
 		return -EINVAL;
 
-	offs = le32_to_cpu(tab_desc->findex) +
-	       le32_to_cpu(tab_desc->entry_size) * idx;
-	descr = (struct uni_data_desc *)&unirom[offs];
-	data_size = le32_to_cpu(descr->findex) + le32_to_cpu(descr->size);
+	desc_offset = table_offset + (size_t)idx * entry_size;
+	data->offset = get_unaligned_le32(unirom + desc_offset +
+					  offsetof(struct uni_data_desc, findex));
+	data->size = get_unaligned_le32(unirom + desc_offset +
+					offsetof(struct uni_data_desc, size));
 
-	if (adapter->fw->size < data_size)
+	if (!qlcnic_rom_range_valid(fw->size, data->offset, data->size))
 		return -EINVAL;
 
 	return 0;
 }
 
+static int qlcnic_validate_bootld(struct qlcnic_adapter *adapter)
+{
+	struct qlcnic_uni_data data;
+	int ret;
+
+	ret = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_BOOTLD,
+				   QLCNIC_UNI_BOOTLD_IDX_OFF, &data);
+	if (ret)
+		return ret;
+
+	return data.size < QLCNIC_UNI_BOOTLD_SIZE ? -EINVAL : 0;
+}
+
+static int qlcnic_validate_fw(struct qlcnic_adapter *adapter)
+{
+	struct qlcnic_uni_data data;
+	int ret;
+
+	ret = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_FW,
+				   QLCNIC_UNI_FIRMWARE_IDX_OFF, &data);
+	if (ret)
+		return ret;
+
+	return data.size < QLCNIC_UNI_VERSION_TAIL_SIZE ? -EINVAL : 0;
+}
+
 static int
 qlcnic_validate_product_offs(struct qlcnic_adapter *adapter)
 {
-	struct uni_table_desc *ptab_descr;
+	size_t table_desc_offset, table_offset;
 	const u8 *unirom = adapter->fw->data;
 	int mn_present = qlcnic_has_mn(adapter);
-	u32 entries, entry_size, tab_size, i;
-	__le32 temp;
-
-	ptab_descr = qlcnic_get_table_desc(unirom,
-				QLCNIC_UNI_DIR_SECT_PRODUCT_TBL);
-	if (!ptab_descr)
-		return -EINVAL;
+	u32 entries, entry_size;
+	size_t i;
+	int ret;
 
-	entries = le32_to_cpu(ptab_descr->num_entries);
-	entry_size = le32_to_cpu(ptab_descr->entry_size);
-	tab_size = le32_to_cpu(ptab_descr->findex) + (entries * entry_size);
+	ret = qlcnic_get_table_desc(adapter, QLCNIC_UNI_DIR_SECT_PRODUCT_TBL,
+				    &table_desc_offset);
+	if (ret)
+		return ret;
 
-	if (adapter->fw->size < tab_size)
+	table_offset = get_unaligned_le32(unirom + table_desc_offset +
+					  offsetof(struct uni_table_desc, findex));
+	entries = get_unaligned_le32(unirom + table_desc_offset +
+				     offsetof(struct uni_table_desc, num_entries));
+	entry_size = get_unaligned_le32(unirom + table_desc_offset +
+					offsetof(struct uni_table_desc, entry_size));
+	if (!qlcnic_rom_table_valid(adapter->fw->size, table_offset, entries,
+				    entry_size,
+				    QLCNIC_UNI_PRODUCT_ENTRY_MIN_SIZE))
 		return -EINVAL;
 
 nomn:
 	for (i = 0; i < entries; i++) {
-
-		u32 flags, file_chiprev, offs;
+		size_t offset = table_offset + i * entry_size;
 		u8 chiprev = adapter->ahw->revision_id;
+		u32 flags, file_chiprev;
 		u32 flagbit;
 
-		offs = le32_to_cpu(ptab_descr->findex) +
-		       i * le32_to_cpu(ptab_descr->entry_size);
-		temp = *((__le32 *)&unirom[offs] + QLCNIC_UNI_FLAGS_OFF);
-		flags = le32_to_cpu(temp);
-		temp = *((__le32 *)&unirom[offs] + QLCNIC_UNI_CHIP_REV_OFF);
-		file_chiprev = le32_to_cpu(temp);
+		flags = get_unaligned_le32(unirom + offset +
+					   QLCNIC_UNI_FLAGS_OFF * sizeof(__le32));
+		file_chiprev = get_unaligned_le32(unirom + offset +
+						  QLCNIC_UNI_CHIP_REV_OFF *
+						  sizeof(__le32));
 
 		flagbit = mn_present ? 1 : 2;
 
 		if ((chiprev == file_chiprev) &&
 					((1ULL << flagbit) & flags)) {
-			adapter->file_prd_off = offs;
+			if (offset > U32_MAX)
+				return -EINVAL;
+
+			adapter->file_prd_off = offset;
 			return 0;
 		}
 	}
@@ -933,93 +987,81 @@ qlcnic_validate_unified_romimage(struct qlcnic_adapter *adapter)
 	return 0;
 }
 
-static
-struct uni_data_desc *qlcnic_get_data_desc(struct qlcnic_adapter *adapter,
-			u32 section, u32 idx_offset)
-{
-	const u8 *unirom = adapter->fw->data;
-	struct uni_table_desc *tab_desc;
-	u32 offs, idx;
-	__le32 temp;
-
-	temp = *((__le32 *)&unirom[adapter->file_prd_off] + idx_offset);
-	idx = le32_to_cpu(temp);
-
-	tab_desc = qlcnic_get_table_desc(unirom, section);
-
-	if (tab_desc == NULL)
-		return NULL;
-
-	offs = le32_to_cpu(tab_desc->findex) +
-	       le32_to_cpu(tab_desc->entry_size) * idx;
-
-	return (struct uni_data_desc *)&unirom[offs];
-}
-
-static u8 *
-qlcnic_get_bootld_offs(struct qlcnic_adapter *adapter)
+static int qlcnic_get_bootld_data(struct qlcnic_adapter *adapter,
+				  const u8 **bootld)
 {
 	u32 offs = QLCNIC_BOOTLD_START;
-	struct uni_data_desc *data_desc;
-
-	data_desc = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_BOOTLD,
-					 QLCNIC_UNI_BOOTLD_IDX_OFF);
+	struct qlcnic_uni_data data;
+	int ret;
 
-	if (adapter->ahw->fw_type == QLCNIC_UNIFIED_ROMIMAGE)
-		offs = le32_to_cpu(data_desc->findex);
+	if (adapter->ahw->fw_type == QLCNIC_UNIFIED_ROMIMAGE) {
+		ret = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_BOOTLD,
+					   QLCNIC_UNI_BOOTLD_IDX_OFF, &data);
+		if (ret || data.size < QLCNIC_UNI_BOOTLD_SIZE)
+			return -EINVAL;
+		offs = data.offset;
+	} else if (!qlcnic_rom_range_valid(adapter->fw->size, offs,
+					   QLCNIC_UNI_BOOTLD_SIZE)) {
+		return -EINVAL;
+	}
 
-	return (u8 *)&adapter->fw->data[offs];
+	*bootld = adapter->fw->data + offs;
+	return 0;
 }
 
-static u8 *
-qlcnic_get_fw_offs(struct qlcnic_adapter *adapter)
+static int qlcnic_get_fw_data(struct qlcnic_adapter *adapter,
+			      const u8 **image, u32 *image_size)
 {
 	u32 offs = QLCNIC_IMAGE_START;
-	struct uni_data_desc *data_desc;
-
-	data_desc = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_FW,
-					 QLCNIC_UNI_FIRMWARE_IDX_OFF);
-	if (adapter->ahw->fw_type == QLCNIC_UNIFIED_ROMIMAGE)
-		offs = le32_to_cpu(data_desc->findex);
-
-	return (u8 *)&adapter->fw->data[offs];
-}
+	struct qlcnic_uni_data data;
+	int ret;
 
-static u32 qlcnic_get_fw_size(struct qlcnic_adapter *adapter)
-{
-	struct uni_data_desc *data_desc;
-	const u8 *unirom = adapter->fw->data;
+	if (adapter->ahw->fw_type == QLCNIC_UNIFIED_ROMIMAGE) {
+		ret = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_FW,
+					   QLCNIC_UNI_FIRMWARE_IDX_OFF, &data);
+		if (ret)
+			return ret;
+		offs = data.offset;
+		*image_size = data.size;
+	} else {
+		if (!qlcnic_rom_range_valid(adapter->fw->size,
+					    QLCNIC_FW_SIZE_OFFSET,
+					    sizeof(__le32)))
+			return -EINVAL;
+		*image_size = get_unaligned_le32(adapter->fw->data +
+						 QLCNIC_FW_SIZE_OFFSET);
+	}
 
-	data_desc = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_FW,
-					 QLCNIC_UNI_FIRMWARE_IDX_OFF);
+	if (!qlcnic_rom_range_valid(adapter->fw->size, offs, *image_size))
+		return -EINVAL;
 
-	if (adapter->ahw->fw_type == QLCNIC_UNIFIED_ROMIMAGE)
-		return le32_to_cpu(data_desc->size);
-	else
-		return le32_to_cpu(*(__le32 *)&unirom[QLCNIC_FW_SIZE_OFFSET]);
+	*image = adapter->fw->data + offs;
+	return 0;
 }
 
 static u32 qlcnic_get_fw_version(struct qlcnic_adapter *adapter)
 {
-	struct uni_data_desc *fw_data_desc;
+	char ver_str[QLCNIC_UNI_VERSION_TAIL_SIZE + 1];
 	const struct firmware *fw = adapter->fw;
+	struct qlcnic_uni_data data;
 	u32 major, minor, sub;
-	__le32 version_offset;
-	const u8 *ver_str;
 	int i, ret;
 
-	if (adapter->ahw->fw_type != QLCNIC_UNIFIED_ROMIMAGE) {
-		version_offset = *(__le32 *)&fw->data[QLCNIC_FW_VERSION_OFFSET];
-		return le32_to_cpu(version_offset);
-	}
+	if (adapter->ahw->fw_type != QLCNIC_UNIFIED_ROMIMAGE)
+		return get_unaligned_le32(fw->data + QLCNIC_FW_VERSION_OFFSET);
+
+	ret = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_FW,
+				   QLCNIC_UNI_FIRMWARE_IDX_OFF, &data);
+	if (ret || data.size < QLCNIC_UNI_VERSION_TAIL_SIZE)
+		return 0;
 
-	fw_data_desc = qlcnic_get_data_desc(adapter, QLCNIC_UNI_DIR_SECT_FW,
-			QLCNIC_UNI_FIRMWARE_IDX_OFF);
-	ver_str = fw->data + le32_to_cpu(fw_data_desc->findex) +
-		  le32_to_cpu(fw_data_desc->size) - 17;
+	memcpy(ver_str, fw->data + data.offset + data.size -
+			QLCNIC_UNI_VERSION_TAIL_SIZE,
+	       QLCNIC_UNI_VERSION_TAIL_SIZE);
+	ver_str[QLCNIC_UNI_VERSION_TAIL_SIZE] = '\0';
 
 	for (i = 0; i < 12; i++) {
-		if (!strncmp(&ver_str[i], "REV=", 4)) {
+		if (!strncmp(ver_str + i, "REV=", 4)) {
 			ret = sscanf(&ver_str[i+4], "%u.%u.%u ",
 					&major, &minor, &sub);
 			if (ret != 3)
@@ -1034,18 +1076,15 @@ static u32 qlcnic_get_fw_version(struct qlcnic_adapter *adapter)
 
 static u32 qlcnic_get_bios_version(struct qlcnic_adapter *adapter)
 {
-	const struct firmware *fw = adapter->fw;
 	u32 bios_ver, prd_off = adapter->file_prd_off;
-	u8 *version_offset;
-	__le32 temp;
+	const struct firmware *fw = adapter->fw;
 
-	if (adapter->ahw->fw_type != QLCNIC_UNIFIED_ROMIMAGE) {
-		version_offset = (u8 *)&fw->data[QLCNIC_BIOS_VERSION_OFFSET];
-		return le32_to_cpu(*(__le32 *)version_offset);
-	}
+	if (adapter->ahw->fw_type != QLCNIC_UNIFIED_ROMIMAGE)
+		return get_unaligned_le32(fw->data + QLCNIC_BIOS_VERSION_OFFSET);
 
-	temp = *((__le32 *)(&fw->data[prd_off]) + QLCNIC_UNI_BIOS_VERSION_OFF);
-	bios_ver = le32_to_cpu(temp);
+	bios_ver = get_unaligned_le32(fw->data + prd_off +
+				      QLCNIC_UNI_BIOS_VERSION_OFF *
+				      sizeof(__le32));
 
 	return (bios_ver << 16) + ((bios_ver >> 8) & 0xff00) + (bios_ver >> 24);
 }
@@ -1106,24 +1145,26 @@ static const char *fw_name[] = {
 int
 qlcnic_load_firmware(struct qlcnic_adapter *adapter)
 {
-	__le64 *ptr64;
-	u32 i, flashaddr, size;
 	const struct firmware *fw = adapter->fw;
 	struct pci_dev *pdev = adapter->pdev;
+	const u8 *bootld, *image;
+	u32 i, flashaddr, image_size;
+	int ret;
 
 	dev_info(&pdev->dev, "loading firmware from %s\n",
 		 fw_name[adapter->ahw->fw_type]);
 
 	if (fw) {
+		u32 words, remainder;
 		u64 data;
 
-		size = (QLCNIC_IMAGE_START - QLCNIC_BOOTLD_START) / 8;
-
-		ptr64 = (__le64 *)qlcnic_get_bootld_offs(adapter);
+		ret = qlcnic_get_bootld_data(adapter, &bootld);
+		if (ret)
+			return ret;
 		flashaddr = QLCNIC_BOOTLD_START;
 
-		for (i = 0; i < size; i++) {
-			data = le64_to_cpu(ptr64[i]);
+		for (i = 0; i < QLCNIC_UNI_BOOTLD_SIZE / sizeof(u64); i++) {
+			data = get_unaligned_le64(bootld + i * sizeof(u64));
 
 			if (qlcnic_pci_mem_write_2M(adapter, flashaddr, data))
 				return -EIO;
@@ -1131,13 +1172,15 @@ qlcnic_load_firmware(struct qlcnic_adapter *adapter)
 			flashaddr += 8;
 		}
 
-		size = qlcnic_get_fw_size(adapter) / 8;
-
-		ptr64 = (__le64 *)qlcnic_get_fw_offs(adapter);
+		ret = qlcnic_get_fw_data(adapter, &image, &image_size);
+		if (ret)
+			return ret;
+		words = image_size / sizeof(u64);
+		remainder = image_size % sizeof(u64);
 		flashaddr = QLCNIC_IMAGE_START;
 
-		for (i = 0; i < size; i++) {
-			data = le64_to_cpu(ptr64[i]);
+		for (i = 0; i < words; i++) {
+			data = get_unaligned_le64(image + i * sizeof(u64));
 
 			if (qlcnic_pci_mem_write_2M(adapter,
 						flashaddr, data))
@@ -1146,9 +1189,11 @@ qlcnic_load_firmware(struct qlcnic_adapter *adapter)
 			flashaddr += 8;
 		}
 
-		size = qlcnic_get_fw_size(adapter) % 8;
-		if (size) {
-			data = le64_to_cpu(ptr64[i]);
+		if (remainder) {
+			__le64 tail = 0;
+
+			memcpy(&tail, image + words * sizeof(u64), remainder);
+			data = le64_to_cpu(tail);
 
 			if (qlcnic_pci_mem_write_2M(adapter,
 						flashaddr, data))
@@ -1156,10 +1201,9 @@ qlcnic_load_firmware(struct qlcnic_adapter *adapter)
 		}
 
 	} else {
-		u64 data;
-		u32 hi, lo;
-		int ret;
 		struct qlcnic_flt_entry bootld_entry;
+		u32 hi, lo, size;
+		u64 data;
 
 		ret = qlcnic_get_flt_entry(adapter, QLCNIC_BOOTLD_REGION,
 					&bootld_entry);
@@ -1200,26 +1244,33 @@ qlcnic_load_firmware(struct qlcnic_adapter *adapter)
 static int
 qlcnic_validate_firmware(struct qlcnic_adapter *adapter)
 {
-	u32 val;
-	u32 ver, bios, min_size;
-	struct pci_dev *pdev = adapter->pdev;
 	const struct firmware *fw = adapter->fw;
+	struct pci_dev *pdev = adapter->pdev;
 	u8 fw_type = adapter->ahw->fw_type;
+	u32 ver, bios, min_size;
+	const u8 *data;
+	u32 data_size;
+	u32 val;
+
+	if (fw_type == QLCNIC_UNIFIED_ROMIMAGE)
+		min_size = QLCNIC_UNI_FW_MIN_SIZE;
+	else
+		min_size = QLCNIC_FW_MIN_SIZE;
+
+	if (fw->size < min_size)
+		return -EINVAL;
 
 	if (fw_type == QLCNIC_UNIFIED_ROMIMAGE) {
 		if (qlcnic_validate_unified_romimage(adapter))
 			return -EINVAL;
-
-		min_size = QLCNIC_UNI_FW_MIN_SIZE;
 	} else {
-		val = le32_to_cpu(*(__le32 *)&fw->data[QLCNIC_FW_MAGIC_OFFSET]);
+		val = get_unaligned_le32(fw->data + QLCNIC_FW_MAGIC_OFFSET);
 		if (val != QLCNIC_BDINFO_MAGIC)
 			return -EINVAL;
-
-		min_size = QLCNIC_FW_MIN_SIZE;
 	}
 
-	if (fw->size < min_size)
+	if (qlcnic_get_bootld_data(adapter, &data) ||
+	    qlcnic_get_fw_data(adapter, &data, &data_size))
 		return -EINVAL;
 
 	val = qlcnic_get_fw_version(adapter);
Signature · 1 line
2.53.0
author maintainer
#1532 lore raw

PATCH 1526/1887 ALSA: mtpav: shut down output timer before card teardown

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Runyu Xiao <[email protected]>

[ Upstream commit c73cb5b7c159246dd572277c668851a56e516019 ]

snd_mtpav_output_timer() rearms chip->timer while holding chip->spinlock and accesses the card-private mtpav state.

snd_mtpav_free() currently takes the same lock and calls timer_delete() when the timer is active. This only removes a pending timer; it does not wait for a callback that is already running and does not prevent the callback from rearming the timer.

A callback running on another CPU can therefore continue after snd_mtpav_free() releases the lock and access the card-private state while the card is being torn down. It can also rearm the timer after timer_delete() has returned.

Call timer_shutdown_sync() without holding chip->spinlock. This waits for any running callback to finish and prevents further rearming before the card-private mtpav state is released.

---
 sound/drivers/mtpav.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)
sound/drivers/mtpav.c +1 -5
index f212f233ea618..1fb6797fa45f8 100644
@@ -663,12 +663,8 @@ static int snd_mtpav_get_RAWMIDI(struct mtpav *mcard)
 static void snd_mtpav_free(struct snd_card *card)
 {
 	struct mtpav *crd = card->private_data;
-	unsigned long flags;
 
-	spin_lock_irqsave(&crd->spinlock, flags);
-	if (crd->istimer > 0)
-		snd_mtpav_remove_output_timer(crd);
-	spin_unlock_irqrestore(&crd->spinlock, flags);
+	timer_shutdown_sync(&crd->timer);
 }
 
 /*
Signature · 1 line
2.53.0
author maintainer
#1533 lore raw

PATCH 1527/1887 smb: client: fix request buffer leak in smb2_new_read_req()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Christopher Lusk <[email protected]>

[ Upstream commit deb6468f4164640e4dc875f008aa449cf55987a5 ]

smb2_new_read_req() allocates the request buffer with smb2_plain_req_init() but only publishes it to the caller with *buf = req at the very end of the function. Two error returns sit in between:

	rc = smb2_plain_req_init(SMB2_READ, io_parms->tcon, server,
				 (void **) &req, total_len);
	if (rc)
		return rc;

	if (server == NULL)
		return -ECONNABORTED;
	[...]
		rdata->mr = smbd_register_mr(server->smbd_conn,
					     &rdata->subreq.io_iter,
					     true, need_invalidate);
		if (!rdata->mr)
			return -EAGAIN;

On either of them the buffer is neither released nor handed back, so it is leaked. The caller cannot clean up after it: smb2_async_readv() does 'goto out' on a non-zero return, which skips the cifs_small_buf_release(buf) at async_readv_out, and buf has not been assigned at that point in any case.

The write path has never had this problem. smb2_async_writev() registers the memory region inline and jumps to its release label instead of returning:

	wdata->mr = smbd_register_mr(...);
	if (!wdata->mr) {
		rc = -EAGAIN;
		goto async_writev_out;
	}

Commit b7972092199f ("cifs: smbd: Retry on memory registration failure") changed both sides from -ENOBUFS to -EAGAIN in a single patch, which puts the two shapes next to each other.

Only the -EAGAIN return is reachable in practice, because smb2_plain_req_init() calls smb2_reconnect() first and that already fails with -EIO when server is NULL, before anything is allocated. Both returns are given the same treatment here rather than leaving one of them correct only by accident.

Because -EAGAIN is a replayable error, the failure also reaches the retry block at the end of smb2_async_readv(), which marks the subrequest NETFS_SREQ_NEED_RETRY, so a failing registration can be retried rather than ending the I/O, and every attempt that reaches it leaks another buffer. smb2_should_replay() short-circuits on tcon->retry, so on a hard mount the attempt count is not bounded by the retrans setting.

Only the asynchronous read path is affected. The synchronous SMB2_read() caller passes rdata == NULL and the memory registration block is guarded on rdata.

The memory registration failure path was pointed out by the Sashiko AI reviewer while it was reviewing an unrelated patch to smb2_async_readv().

Fixes bd3dcc6a22a9 ("CIFS: SMBD: Upper layer performs SMB read via RDMA write through memory registration")
Assisted-by Claude:claude-opus-5
---
 fs/smb/client/smb2pdu.c | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)
fs/smb/client/smb2pdu.c +12 -4
index 8f23b8968beb4..e69b4bb9dc919 100644
@@ -4287,8 +4287,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len,
 	if (rc)
 		return rc;
 
-	if (server == NULL)
-		return -ECONNABORTED;
+	if (!server) {
+		rc = -ECONNABORTED;
+		goto free_req;
+	}
 
 	shdr = &req->hdr;
 	shdr->Id.SyncId.ProcessId = cpu_to_le32(io_parms->pid);
@@ -4319,8 +4321,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len,
 				server->smbd_conn, rdata->pages,
 				rdata->nr_pages, rdata->page_offset,
 				rdata->tailsz, true, need_invalidate);
-		if (!rdata->mr)
-			return -EAGAIN;
+		if (!rdata->mr) {
+			rc = -EAGAIN;
+			goto free_req;
+		}
 
 		req->Channel = SMB2_CHANNEL_RDMA_V1_INVALIDATE;
 		if (need_invalidate)
@@ -4363,6 +4367,10 @@ smb2_new_read_req(void **buf, unsigned int *total_len,
 
 	*buf = req;
 	return rc;
+
+free_req:
+	cifs_small_buf_release(req);
+	return rc;
 }
 
 static void
Signature · 1 line
2.53.0
author maintainer
#1534 lore raw

PATCH 1528/1887 net: add missing ref_tracker_dir_exit() to alloc_netdev_mqs()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Tetsuo Handa <[email protected]>

[ Upstream commit 0b1c2af8a22c35cb099c735c2f63ea3ba757557d ]

sashiko is reporting that trying to read /sys/kernel/debug/ref_tracker/* causes use-afer-free crash when either alloc_percpu() or dev_addr_init() in alloc_netdev_mqs() failed, for commit 4d92b95ff2f9 ("net: add net device refcount tracker infrastructure") added ref_tracker_dir_exit() to only free_netdev() path.

---
 net/core/dev.c | 1 +
 1 file changed, 1 insertion(+)
net/core/dev.c +1 -0
index 4a1b16a0c04b8..804752cc864f4 100644
@@ -10873,6 +10873,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
 	free_percpu(dev->pcpu_refcnt);
 free_dev:
 #endif
+	ref_tracker_dir_exit(&dev->refcnt_tracker);
 	netdev_freemem(dev);
 	return NULL;
 }
Signature · 1 line
2.53.0
author maintainer
#1535 lore raw

PATCH 1529/1887 vsock: dont check the listeners sk_err in vsock_accept()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Nguyen Dinh Phi <[email protected]>

[ Upstream commit b8c899cf5e7be29840a172c183dedd8d3e7a0287 ]

Syzbot reported an issue which can be reproduced with these steps:

	r0 = socket(AF_VSOCK, SOCK_STREAM, 0)
	bind(r0, {VMADDR_CID_ANY, PORT})
	connect(r0, {VMADDR_CID_LOCAL, PORT}) -> -1, EPROTO (self-connect)
	listen(r0, backlog)                   -> 0
	r1 = socket(AF_VSOCK, SOCK_STREAM, 0)
	connect(r1, {VMADDR_CID_LOCAL, PORT}) -> 0
	accept(r0)                            -> -1, EPROTO (stale sk_err)

Basically, it creates a socket (r0) and triggers a self-connect after binding it. This self-connect fails with EPROTO because it loops back to r0 while the socket is still in the TCP_SYN_SENT state, causing it to be incorrectly dispatched to the connecting-client path. The unexpected packet type encountered there sets sk_err to EPROTO.

After that, it invokes a listen() call on the same socket. This listen() call succeeds because the kernel's listening path never inspects or clears sk_err. Then, a new socket (r1) is created as a normal client and connects to r0. However, vsock_accept() rejects this incoming connection because the listener's sk_err still holds the EPROTO error from the earlier failed self-connect.

This rejection causes the child socket created for r1's connection to never be freed on virtio or hyperv transports; only the VMCI transport implements pending_work to revisit and clean up a rejected socket.

For a non-blocking connect(), vsock_connect() may return -EINPROGRESS immediately, and vsock_connect_timeout() can later set sk->sk_err asynchronously.

Since no vsock transport ever sets sk_err on a socket while it is in TCP_LISTEN state, checking it in vsock_accept() serves no purpose and only carries forward errors left behind by earlier, unrelated connection attempts on the same socket. Remove the checks so accept() no longer rejects valid incoming connections because of a stale error, which also avoids the resource leak described above.

---
 net/vmw_vsock/af_vsock.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)
net/vmw_vsock/af_vsock.c +1 -5
index 92685d6da9ad1..364a0d5ef9645 100644
@@ -1586,8 +1586,7 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, int flags,
 	timeout = sock_rcvtimeo(listener, flags & O_NONBLOCK);
 	prepare_to_wait(sk_sleep(listener), &wait, TASK_INTERRUPTIBLE);
 
-	while ((connected = vsock_dequeue_accept(listener)) == NULL &&
-	       listener->sk_err == 0) {
+	while ((connected = vsock_dequeue_accept(listener)) == NULL) {
 		release_sock(listener);
 		timeout = schedule_timeout(timeout);
 		finish_wait(sk_sleep(listener), &wait);
@@ -1605,9 +1604,6 @@ static int vsock_accept(struct socket *sock, struct socket *newsock, int flags,
 	}
 	finish_wait(sk_sleep(listener), &wait);
 
-	if (listener->sk_err)
-		err = -listener->sk_err;
-
 	if (connected) {
 		sk_acceptq_removed(listener);
 
Signature · 1 line
2.53.0
author maintainer
#1536 lore raw

PATCH 1530/1887 fuse: check for NULL root inode in fuse_fill_super_submount

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Baokun Li <[email protected]>

[ Upstream commit 928f659a3e3650978a5b4829cc982324f72b474b ]

fuse_iget() can return NULL when its inode allocation fails, but fuse_fill_super_submount() passed the result straight to get_fuse_inode() and decremented fi->nlookup without checking it:

        root = fuse_iget(sb, parent_fi->nodeid, ...);
        fi = get_fuse_inode(root);
        fi->nlookup--;

Inside fuse_iget() the inode allocation can fail and return NULL. The submount root takes the iget5_locked() path, whose alloc_inode() can fail under memory pressure (the auto-submount branch can fail the same way in new_inode() or fuse_alloc_submount_lookup()):

        inode = iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set,
                             &nodeid);
        if (!inode)
                return NULL;

A NULL root makes get_fuse_inode() a container_of() on NULL and the nlookup decrement a write to a bogus address, oopsing the mount. With CONFIG_KASAN the following null pointer dereference is reported when the root inode allocation of an auto-submount fails (e.g. under memory pressure):

==================================================================

BUG: KASAN: null-ptr-deref in fuse_get_tree_submount+0x656/0x8b0
Read of size 8 at addr 00000000000002b0 by task ls/942
CPU: 0 PID: 942 Comm: ls Tainted: G W 6.6 #15
Call Trace:
 <TASK>
 fuse_get_tree_submount+0x656/0x8b0
 vfs_get_tree+0x48/0x140
 fc_mount+0x13/0x50
 fuse_dentry_automount+0x7a/0xb0
 __traverse_mounts+0xca/0x330
 step_into+0x339/0xac0
 path_lookupat+0xc5/0x2f0
 filename_lookup+0x163/0x2a0
 vfs_statx+0xd5/0x200
 do_statx+0x83/0xd0
 __x64_sys_statx+0xa0/0xc0
 do_syscall_64+0x37/0x90
 entry_SYSCALL_64_after_hwframe+0x78/0xe2
 </TASK>
==================================================================

Return -ENOMEM instead; the caller tears down the partially built superblock on error, matching the other error returns in this function.

Fixes 1866d779d5d2 ("fuse: Allow fuse_fill_super_common() for submounts")
---
 fs/fuse/inode.c | 2 ++
 1 file changed, 2 insertions(+)
fs/fuse/inode.c +2 -0
index 332cc885b815e..269699abdbfc7 100644
@@ -1537,6 +1537,8 @@ static int fuse_fill_super_submount(struct super_block *sb,
 
 	fuse_fill_attr_from_inode(&root_attr, parent_fi);
 	root = fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0);
+	if (!root)
+		return -ENOMEM;
 	/*
 	 * This inode is just a duplicate, so it is not looked up and
 	 * its nlookup should not be incremented.  fuse_iget() does
Signature · 1 line
2.53.0
author maintainer
#1537 lore raw

PATCH 1531/1887 bpf, xdp: move offload check into dev_xdp_install()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Jiayuan Chen <[email protected]>

[ Upstream commit ad27ed7d2309419a129078d781504f486b1b469a ]

bpf_xdp_link_update() calls dev_xdp_install() directly and skips dev_xdp_attach(), so the checks in dev_xdp_attach() do not run. A user can make an XDP link with a normal program and then swap in an offloaded or device-bound program with BPF_LINK_UPDATE, which puts it on the software path.

dev_xdp_install() is the one place all three paths go through:
"ip link set xdp" and BPF_LINK_CREATE reach it via dev_xdp_attach(), and BPF_LINK_UPDATE calls it directly. So move the program checks (offloaded, bound to another device, device-bound in generic mode, native vs generic, DEVMAP and CPUMAP) there, and keep only the netlink-flag check (XDP_FLAGS_UPDATE_IF_NOEXIST) in dev_xdp_attach().

Fixes 026a4c28e1db3 ("bpf, xdp: Implement LINK_UPDATE for BPF XDP link")
---
 net/core/dev.c | 43 +++++++++++++++++++++++--------------------
 1 file changed, 23 insertions(+), 20 deletions(-)
net/core/dev.c +23 -20
index 804752cc864f4..7c0d0905dd31b 100644
@@ -9249,6 +9249,29 @@ static int dev_xdp_install(struct net_device *dev, enum bpf_xdp_mode mode,
 	struct netdev_bpf xdp;
 	int err;
 
+	if (prog) {
+		enum bpf_xdp_mode other_mode = mode == XDP_MODE_SKB
+					       ? XDP_MODE_DRV : XDP_MODE_SKB;
+		bool offload = mode == XDP_MODE_HW;
+
+		if (!offload && dev_xdp_prog(dev, other_mode)) {
+			NL_SET_ERR_MSG(extack, "Native and generic XDP can't be active at the same time");
+			return -EEXIST;
+		}
+		if (!offload && bpf_prog_is_dev_bound(prog->aux)) {
+			NL_SET_ERR_MSG(extack, "Using device-bound program without HW_MODE flag is not supported");
+			return -EINVAL;
+		}
+		if (prog->expected_attach_type == BPF_XDP_DEVMAP) {
+			NL_SET_ERR_MSG(extack, "BPF_XDP_DEVMAP programs can not be attached to a device");
+			return -EINVAL;
+		}
+		if (prog->expected_attach_type == BPF_XDP_CPUMAP) {
+			NL_SET_ERR_MSG(extack, "BPF_XDP_CPUMAP programs can not be attached to a device");
+			return -EINVAL;
+		}
+	}
+
 	memset(&xdp, 0, sizeof(xdp));
 	xdp.command = mode == XDP_MODE_HW ? XDP_SETUP_PROG_HW : XDP_SETUP_PROG;
 	xdp.extack = extack;
@@ -9377,30 +9400,10 @@ static int dev_xdp_attach(struct net_device *dev, struct netlink_ext_ack *extack
 		new_prog = link->link.prog;
 
 	if (new_prog) {
-		bool offload = mode == XDP_MODE_HW;
-		enum bpf_xdp_mode other_mode = mode == XDP_MODE_SKB
-					       ? XDP_MODE_DRV : XDP_MODE_SKB;
-
 		if ((flags & XDP_FLAGS_UPDATE_IF_NOEXIST) && cur_prog) {
 			NL_SET_ERR_MSG(extack, "XDP program already attached");
 			return -EBUSY;
 		}
-		if (!offload && dev_xdp_prog(dev, other_mode)) {
-			NL_SET_ERR_MSG(extack, "Native and generic XDP can't be active at the same time");
-			return -EEXIST;
-		}
-		if (!offload && bpf_prog_is_dev_bound(new_prog->aux)) {
-			NL_SET_ERR_MSG(extack, "Using device-bound program without HW_MODE flag is not supported");
-			return -EINVAL;
-		}
-		if (new_prog->expected_attach_type == BPF_XDP_DEVMAP) {
-			NL_SET_ERR_MSG(extack, "BPF_XDP_DEVMAP programs can not be attached to a device");
-			return -EINVAL;
-		}
-		if (new_prog->expected_attach_type == BPF_XDP_CPUMAP) {
-			NL_SET_ERR_MSG(extack, "BPF_XDP_CPUMAP programs can not be attached to a device");
-			return -EINVAL;
-		}
 	}
 
 	/* don't call drivers if the effective program didn't change */
Signature · 1 line
2.53.0
author maintainer
#1538 lore raw

PATCH 1532/1887 NFSv4: Fix incorrect argument passed to nfs4_delete_lease() in nfs4_add_lease()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Zhansong Gao <[email protected]>

[ Upstream commit 468e458ffde907ba19acd2102ca1fbb8f6fbede2 ]

When nfs4_add_lease() races with a delegation return, it calls nfs4_delete_lease() to clean up. Previously, it passed priv, which can legitimately be NULL. Passing a NULL priv eventually leads to a NULL pointer dereference in generic_setlease().

Fixes e93a5e9306a5 ("NFSv4: Add support for application leases underpinned by a delegation")
---
 fs/nfs/nfs4proc.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
fs/nfs/nfs4proc.c +3 -1
index bde81060f833e..321c6e86b0b0a 100644
@@ -7640,6 +7640,7 @@ static int nfs4_add_lease(struct file *file, long arg, struct file_lock **lease,
 {
 	struct inode *inode = file_inode(file);
 	fmode_t type = arg == F_RDLCK ? FMODE_READ : FMODE_WRITE;
+	fl_owner_t owner = (*lease)->fl_owner;
 	int ret;
 
 	/* No delegation, no lease */
@@ -7649,7 +7650,8 @@ static int nfs4_add_lease(struct file *file, long arg, struct file_lock **lease,
 	if (ret || nfs4_have_delegation(inode, type))
 		return ret;
 	/* We raced with a delegation return */
-	nfs4_delete_lease(file, priv);
+	dprintk("%s: raced with a delegation return\n", __func__);
+	nfs4_delete_lease(file, &owner);
 	return -EAGAIN;
 }
 
Signature · 1 line
2.53.0
author maintainer
#1539 lore raw

PATCH 1533/1887 NFSv4/flexfiles: fix NULL dereference for NFSv4.0 data servers

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Jeuk Kim <[email protected]>

[ Upstream commit 2b03ebbf8d5e8f6af4ecd6c65375232dd1ec32cc ]

flexfiles accepts NFSv4.0 data servers, but two NFSv4 code paths assume the data server client has a session. Unlike NFSv4.1+, an NFSv4.0 client has no session (clp->cl_session is NULL; it uses clp->cl_slot_tbl), so I/O to a v4.0 flexfiles DS oopses:

- nfs4_init_ds_session() dereferences clp->cl_session->session_state while seeding the DS lease. It also only seeds cl_lease_time when NFS4_SESSION_INITING is set; without a session that never happens, so cl_lease_time stays 0 and nfs4_renew_state() busy-loops, requeuing every 5 seconds. Seed the lease whenever there is no session and return before touching session state.

- ff_layout_async_handle_error_v4() dereferences

    clp->cl_session->fc_slot_table on every DS I/O error. Fall back to the
    v4.0 transport slot table (clp->cl_slot_tbl) when there is no session.
Fixes a7878ca14008 ("nfs: flexfilelayout: remove v3-only data server limitation")
---
 fs/nfs/flexfilelayout/flexfilelayout.c |  3 ++-
 fs/nfs/nfs4session.c                   | 16 +++++++++++-----
 2 files changed, 13 insertions(+), 6 deletions(-)
fs/nfs/flexfilelayout/flexfilelayout.c +2 -1
index a5f69d1fce3fb..88140175a445f 100644
@@ -1127,7 +1127,8 @@ static int ff_layout_async_handle_error_v4(struct rpc_task *task,
 	struct pnfs_layout_hdr *lo = lseg->pls_layout;
 	struct inode *inode = lo->plh_inode;
 	struct nfs4_deviceid_node *devid = FF_LAYOUT_DEVID_NODE(lseg, idx);
-	struct nfs4_slot_table *tbl = &clp->cl_session->fc_slot_table;
+	struct nfs4_slot_table *tbl = nfs4_has_session(clp) ?
+		&clp->cl_session->fc_slot_table : clp->cl_slot_tbl;
 
 	switch (op_status) {
 	case NFS4_OK:
fs/nfs/nfs4session.c +11 -5
index 5db460476bf2a..c59e710eb7ccc 100644
@@ -634,16 +634,22 @@ int nfs4_init_ds_session(struct nfs_client *clp, unsigned long lease_time)
 	int ret;
 
 	spin_lock(&clp->cl_lock);
-	if (test_and_clear_bit(NFS4_SESSION_INITING, &session->session_state)) {
-		/*
-		 * Do not set NFS_CS_CHECK_LEASE_TIME instead set the
-		 * DS lease to be equal to the MDS lease.
-		 */
+	/*
+	 * Do not set NFS_CS_CHECK_LEASE_TIME instead set the
+	 * DS lease to be equal to the MDS lease.
+	 *
+	 * A v4.0 DS has no session, so seed the lease every time.
+	 */
+	if (!session ||
+	    test_and_clear_bit(NFS4_SESSION_INITING, &session->session_state)) {
 		clp->cl_lease_time = lease_time;
 		clp->cl_last_renewal = jiffies;
 	}
 	spin_unlock(&clp->cl_lock);
 
+	if (!session)
+		return 0;
+
 	ret = nfs41_check_session_ready(clp);
 	if (ret)
 		return ret;
Signature · 1 line
2.53.0
author maintainer
#1540 lore raw

PATCH 1534/1887 pnfs/blocklayout: Fix device leaks on parse failure

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: ZhangGuoDong <[email protected]>

[ Upstream commit c056f817e4200fb18079d5052c273a22f191ff0a ]

bl_parse_concat() and bl_parse_stripe() allocate a child device array and then parse each child in turn. If parsing a child fails, the failed child is not counted in nr_children and the parent may be left with a children array that bl_free_device() will not release when nr_children is zero.

Release the failed child and the already parsed children before returning the error. Also make bl_free_device() release the child array whenever the children pointer is set, so that partially initialised concat or stripe devices are cleaned up correctly.

bl_parse_scsi() can also fail after assigning d->bdev_file and dropping the file reference. Clear the pointer after fput() so that an outer cleanup path does not put it again.

Fixes 5c83746a0cf2 ("pnfs/blocklayout: in-kernel GETDEVICEINFO XDR parsing")
---
 fs/nfs/blocklayout/dev.c | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)
fs/nfs/blocklayout/dev.c +15 -4
index fc64139536002..09f4d9d1960a1 100644
@@ -16,12 +16,14 @@
 static void
 bl_free_device(struct pnfs_block_dev *dev)
 {
-	if (dev->nr_children) {
+	if (dev->children) {
 		int i;
 
 		for (i = 0; i < dev->nr_children; i++)
 			bl_free_device(&dev->children[i]);
 		kfree(dev->children);
+		dev->children = NULL;
+		dev->nr_children = 0;
 	} else {
 		if (dev->pr_registered) {
 			const struct pr_ops *ops =
@@ -34,8 +36,10 @@ bl_free_device(struct pnfs_block_dev *dev)
 				pr_err("failed to unregister PR key.\n");
 		}
 
-		if (dev->bdev)
+		if (dev->bdev) {
 			blkdev_put(dev->bdev, FMODE_READ | FMODE_WRITE);
+			dev->bdev = NULL;
+		}
 	}
 }
 
@@ -375,6 +379,7 @@ bl_parse_scsi(struct nfs_server *server, struct pnfs_block_dev *d,
 
 out_blkdev_put:
 	blkdev_put(d->bdev, FMODE_READ | FMODE_WRITE);
+	d->bdev = NULL;
 	return error;
 }
 
@@ -410,8 +415,11 @@ bl_parse_concat(struct nfs_server *server, struct pnfs_block_dev *d,
 	for (i = 0; i < v->concat.volumes_count; i++) {
 		ret = bl_parse_deviceid(server, &d->children[i],
 				volumes, v->concat.volumes[i], gfp_mask);
-		if (ret)
+		if (ret) {
+			bl_free_device(&d->children[i]);
+			bl_free_device(d);
 			return ret;
+		}
 
 		d->nr_children++;
 		d->children[i].start += len;
@@ -439,8 +447,11 @@ bl_parse_stripe(struct nfs_server *server, struct pnfs_block_dev *d,
 	for (i = 0; i < v->stripe.volumes_count; i++) {
 		ret = bl_parse_deviceid(server, &d->children[i],
 				volumes, v->stripe.volumes[i], gfp_mask);
-		if (ret)
+		if (ret) {
+			bl_free_device(&d->children[i]);
+			bl_free_device(d);
 			return ret;
+		}
 
 		d->nr_children++;
 		len += d->children[i].len;
Signature · 1 line
2.53.0
author maintainer
#1541 lore raw

PATCH 1535/1887 smb/server: fix session leak in ksmbd_session_register()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Ze Tan <[email protected]>

[ Upstream commit 99b25b046e47e4904373cfeb445c5483f1633d88 ]

See the procedure below:

smb2_sess_setup

    ksmbd_smb2_session_create
      __session_create
        atomic_set(&sess->refcnt, 2)
        hash_add(sessions_table, &sess->hlist, sess->id)
    ksmbd_session_register
      xa_store(&conn->sessions, sess->id, sess) // fail
    ksmbd_user_session_put
      atomic_dec(&sess->refcnt) // refcnt is 1, session is not freed

Remove the session from sessions_table and drop its table reference if xa_store() fails.

Fixes f5c779b7ddbd ("ksmbd: fix racy issue from session setup and logoff")
---
 fs/smb/server/mgmt/user_session.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)
fs/smb/server/mgmt/user_session.c +12 -1
index 33ae100fd07ed..abb5b4619f36c 100644
@@ -212,10 +212,21 @@ static void ksmbd_expire_session(struct ksmbd_conn *conn)
 int ksmbd_session_register(struct ksmbd_conn *conn,
 			   struct ksmbd_session *sess)
 {
+	int ret;
+
 	sess->dialect = conn->dialect;
 	memcpy(sess->ClientGUID, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
 	ksmbd_expire_session(conn);
-	return xa_err(xa_store(&conn->sessions, sess->id, sess, GFP_KERNEL));
+	ret = xa_err(xa_store(&conn->sessions, sess->id, sess,
+			      GFP_KERNEL));
+	if (ret) {
+		down_write(&sessions_table_lock);
+		hash_del(&sess->hlist);
+		up_write(&sessions_table_lock);
+		ksmbd_user_session_put(sess);
+	}
+
+	return ret;
 }
 
 static int ksmbd_chann_del(struct ksmbd_conn *conn, struct ksmbd_session *sess)
Signature · 1 line
2.53.0
author maintainer
#1542 lore raw

PATCH 1536/1887 ksmbd: bound smb_check_perm_dacl() ACE walks by DACL size

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Hang Nan <[email protected]>

[ Upstream commit 79decd88dd3f0d42e7fb0689b1a7853bfa302459 ]

smb_check_perm_dacl() validates that the DACL fits inside the NT security descriptor, but then bounds its two ACE walks by the remaining NTSD length (acl_size) rather than the DACL's declared size (pdacl_size).

When pdacl->size is smaller than the trailing NTSD buffer, bytes after the declared DACL boundary - still inside the stored security descriptor - are parsed as ACEs during access checks. A crafted DACL can place an access-granting ACE beyond pdacl->size, and the current code accepts it during SMB2_CREATE access validation, while parse_dacl() and smb_inherit_dacl() stop at pdacl_size.

Bound both ACE walks by pdacl_size to match the DACL boundary semantics used elsewhere in the server.

Validation:
- semantic KUnit harness shows the post-boundary ACE is selected before the fix and rejected (EACCES) after it
- linux master (7.2-rc6), x86_64

Fixes 8f0541186e9a ("ksmbd: fix heap-based overflow in set_ntacl_dacl()")
---
 fs/smb/server/smbacl.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
fs/smb/server/smbacl.c +2 -2
index 98ba287e3b2ed..81926bd0e5a92 100644
@@ -1483,7 +1483,7 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
 			DELETE;
 
 		ace = (struct smb_ace *)((char *)pdacl + sizeof(struct smb_acl));
-		aces_size = acl_size - sizeof(struct smb_acl);
+		aces_size = pdacl_size - sizeof(struct smb_acl);
 		for (i = 0; i < le32_to_cpu(pdacl->num_aces); i++) {
 			if (aces_size < offsetof(struct smb_ace, sid) +
 			    CIFS_SID_BASE_SIZE)
@@ -1507,7 +1507,7 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path,
 	id_to_sid(uid, sid_type, &sid);
 
 	ace = (struct smb_ace *)((char *)pdacl + sizeof(struct smb_acl));
-	aces_size = acl_size - sizeof(struct smb_acl);
+	aces_size = pdacl_size - sizeof(struct smb_acl);
 	for (i = 0; i < le32_to_cpu(pdacl->num_aces); i++) {
 		if (aces_size < offsetof(struct smb_ace, sid) +
 		    CIFS_SID_BASE_SIZE)
Signature · 1 line
2.53.0
author maintainer
#1543 lore raw

PATCH 1537/1887 ksmbd: detach blocked lock requests before freeing

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Namjae Jeon <[email protected]>

[ Upstream commit 215e8816b1ac25176d911abb8704390413ccee4b ]

A file_lock retained by ksmbd for byte-range lock bookkeeping can still be part of the VFS blocked-request graph. In particular, the VFS can chain a new waiter below an already blocked request through flc_blocked_requests. The ksmbd_file reference count does not cover that graph.

Both __ksmbd_close_fd() and the cross-request unlock path free these retained file_lock objects directly. If a dependent waiter is still attached, locks_release_private() hits BUG_ON(!list_empty(&flc->flc_blocked_requests)). The same lifetime mismatch can leave a freed ksmbd_lock reachable through its request-local llist.

Detach the file_lock from the blocked-request graph before freeing it in the close, cross-request unlock, and rollback paths. locks_delete_block() also wakes requests chained below the object. Remove llist when a completed lock is published so a globally visible ksmbd_lock no longer points into the submitting worker's stack.

Fixes d63528eb0d43 ("ksmbd: free ksmbd_lock when file is closed")
---
 fs/smb/server/smb2pdu.c   | 14 ++++++++++----
 fs/smb/server/vfs_cache.c |  8 +++++---
 2 files changed, 15 insertions(+), 7 deletions(-)
fs/smb/server/smb2pdu.c +10 -4
index 24506691be74e..f89b90eb9bf31 100644
@@ -7081,6 +7081,12 @@ static void smb2_remove_blocked_lock(void **argv)
 	wake_up(&flock->fl_wait);
 }
 
+static void smb2_free_lock(struct file_lock *flock)
+{
+	ksmbd_vfs_posix_lock_unblock(flock);
+	locks_free_lock(flock);
+}
+
 static inline bool lock_defer_pending(struct file_lock *fl)
 {
 	/* check pending lock waiters */
@@ -7234,14 +7240,14 @@ int smb2_lock(struct ksmbd_work *work)
 					    cmp_lock->end == smb_lock->end &&
 					    !lock_defer_pending(cmp_lock->fl)) {
 						nolock = 0;
-						list_del(&cmp_lock->flist);
-						list_del(&cmp_lock->clist);
+						list_del_init(&cmp_lock->flist);
+						list_del_init(&cmp_lock->clist);
 						cmp_lock->conn = NULL;
 						spin_unlock(&conn->llist_lock);
 						up_read(&conn_list_lock);
 
 						ksmbd_conn_put(conn);
-						locks_free_lock(cmp_lock->fl);
+						smb2_free_lock(cmp_lock->fl);
 						kfree(cmp_lock);
 						goto out_check_cl;
 					}
@@ -7439,7 +7445,7 @@ int smb2_lock(struct ksmbd_work *work)
 		spin_unlock(&conn->llist_lock);
 		ksmbd_conn_put(conn);
 
-		locks_free_lock(smb_lock->fl);
+		smb2_free_lock(smb_lock->fl);
 		if (rlock)
 			locks_free_lock(rlock);
 		kfree(smb_lock);
fs/smb/server/vfs_cache.c +5 -3
index f59abc34bbfe2..3eff8b7df9a2b 100644
@@ -353,8 +353,9 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
 	if (!IS_ERR_OR_NULL(filp))
 		fput(filp);
 
-	/* because the reference count of fp is 0, it is guaranteed that
-	 * there are not accesses to fp->lock_list.
+	/*
+	 * The zero fp reference count serializes access to fp->lock_list, but
+	 * the VFS may still have blocked requests chained below these locks.
 	 */
 	list_for_each_entry_safe(smb_lock, tmp_lock, &fp->lock_list, flist) {
 		struct ksmbd_conn *conn = smb_lock->conn;
@@ -367,7 +368,8 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
 			ksmbd_conn_put(conn);
 		}
 
-		list_del(&smb_lock->flist);
+		list_del_init(&smb_lock->flist);
+		ksmbd_vfs_posix_lock_unblock(smb_lock->fl);
 		locks_free_lock(smb_lock->fl);
 		kfree(smb_lock);
 	}
Signature · 1 line
2.53.0
author maintainer
#1544 lore raw

PATCH 1538/1887 ksmbd: safely discard unregistered deferred locks

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Namjae Jeon <[email protected]>

[ Upstream commit 054bcca4cd9f00719b01f7108b51a2168fb94f15 ]

When vfs_lock_file() defers a lock, smb2_lock() puts its ksmbd_lock on rollback_list before allocating and registering the asynchronous work. If either operation fails, rollback assumes that smb_lock->conn is initialized and dereferences NULL. The deferred file_lock also remains linked into the VFS blocked-lock state while it is freed.

Keep the lock off rollback_list until async setup succeeds. On setup failures, explicitly unblock and wake the deferred lock before freeing it and its ksmbd wrapper.

---
 fs/smb/server/smb2pdu.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)
fs/smb/server/smb2pdu.c +12 -1
index f89b90eb9bf31..5acfa540f8f9b 100644
@@ -7087,6 +7087,13 @@ static void smb2_free_lock(struct file_lock *flock)
 	locks_free_lock(flock);
 }
 
+static void smb2_free_blocked_lock(struct file_lock *flock)
+{
+	ksmbd_vfs_posix_lock_unblock(flock);
+	wake_up(&flock->fl_wait);
+	locks_free_lock(flock);
+}
+
 static inline bool lock_defer_pending(struct file_lock *fl)
 {
 	/* check pending lock waiters */
@@ -7329,11 +7336,12 @@ int smb2_lock(struct ksmbd_work *work)
 
 				ksmbd_debug(SMB,
 					    "would have to wait for getting lock\n");
-				list_add(&smb_lock->llist, &rollback_list);
 
 				argv = kmalloc(sizeof(void *), GFP_KERNEL);
 				if (!argv) {
 					err = -ENOMEM;
+					smb2_free_blocked_lock(flock);
+					kfree(smb_lock);
 					goto out;
 				}
 				argv[0] = flock;
@@ -7344,8 +7352,11 @@ int smb2_lock(struct ksmbd_work *work)
 				if (rc) {
 					kfree(argv);
 					err = -ENOMEM;
+					smb2_free_blocked_lock(flock);
+					kfree(smb_lock);
 					goto out;
 				}
+				list_add(&smb_lock->llist, &rollback_list);
 				spin_lock(&fp->f_lock);
 				list_add(&work->fp_entry, &fp->blocked_works);
 				spin_unlock(&fp->f_lock);
Signature · 1 line
2.53.0
author maintainer
#1545 lore raw

PATCH 1539/1887 erofs: fix interlaced ztailpacking pclusters

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Gao Xiang <[email protected]>

[ Upstream commit 862427ebb81d1f6abbf74d799790e1694b37b187 ]

On-disk sizes of interlaced pclusters should be block-aligned, and ztailpacking interlaced pclusters should be invalid at all.

Currently, mkfs.erofs won't generate any interlaced pcluster with ztailpacking enabled, so this doesn't affect any existing valid filesystems.

However, crafted images can contain invalid interlaced ztailpacking pclusters, resulting in an out-of-bounds read from a kmap'd page and copying irrelevant kernel memory into userspace-visible page cache.

Fixes fdffc091e6f9 ("erofs: support interlaced uncompressed data for compressed files")
---
 fs/erofs/zmap.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
fs/erofs/zmap.c +2 -1
index c120de200eb37..9159461be6109 100644
@@ -577,7 +577,8 @@ static int z_erofs_do_map_blocks(struct inode *inode,
 			err = -EFSCORRUPTED;
 			goto unmap_out;
 		}
-		afmt = vi->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER ?
+		afmt = (vi->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER) &&
+		       !(map->m_flags & EROFS_MAP_META) ?
 			Z_EROFS_COMPRESSION_INTERLACED :
 			Z_EROFS_COMPRESSION_SHIFTED;
 	} else {
Signature · 1 line
2.53.0
author maintainer
#1546 lore raw

PATCH 1540/1887 ksmbd: defer publishing granted locks to prevent UAF/double-free race

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Ilan Dudnik <[email protected]>

[ Upstream commit 29f74f0f2e6df3b393b7b66e810136d0c64e3c59 ]

In smb2_lock(), mid-batch granted locks are published to connection-wide (conn->lock_list) and file-wide (fp->lock_list) lists immediately upon vfs_lock_file() success, while also remaining tracked on the stack-local rollback_list.

If a subsequent element in the same SMB2_LOCK request array fails validation or execution, the thread jumps to out: and walks rollback_list to undo previously granted locks. However, because the granted lock was already published to conn->lock_list, a concurrent UNLOCK request on the same connection can find the lock object and kfree() it before the rollback loop executes.

When the granting thread subsequently walks rollback_list, it dereferences and frees the already-freed ksmbd_lock structure, resulting in a Use-After-Free and Double-Free (on both ksmbd_lock and struct file_lock).

Fix this by deferring the publication of granted locks to conn->lock_list and fp->lock_list until after the entire array of lock elements has been processed without error. Mid-batch grants remain tracked exclusively on the request-local rollback_list until the whole batch succeeds, eliminating the race window.

Fixes e2f34481b24d ("cifsd: add server-side procedures for SMB3")
---
 fs/smb/server/smb2pdu.c | 32 +++++++++++++++-----------------
 1 file changed, 15 insertions(+), 17 deletions(-)
fs/smb/server/smb2pdu.c +15 -17
index 5acfa540f8f9b..ea28cc5524c7c 100644
@@ -7389,16 +7389,10 @@ int smb2_lock(struct ksmbd_work *work)
 				rsp->hdr.Status =
 					STATUS_RANGE_NOT_LOCKED;
 				kfree(smb_lock);
-				goto out2;
+				/* rollback_list may still hold earlier grants */
+				goto out;
 			} else if (!rc) {
 				list_add(&smb_lock->llist, &rollback_list);
-				smb_lock->conn = ksmbd_conn_get(work->conn);
-				spin_lock(&work->conn->llist_lock);
-				list_add_tail(&smb_lock->clist,
-					      &work->conn->lock_list);
-				list_add_tail(&smb_lock->flist,
-					      &fp->lock_list);
-				spin_unlock(&work->conn->llist_lock);
 				ksmbd_debug(SMB, "successful in taking lock\n");
 			} else {
 				locks_free_lock(flock);
@@ -7420,6 +7414,19 @@ int smb2_lock(struct ksmbd_work *work)
 	if (err)
 		goto out;
 
+	/* publish only once the whole batch has committed */
+	if (!list_empty(&rollback_list)) {
+		spin_lock(&work->conn->llist_lock);
+		list_for_each_entry(smb_lock, &rollback_list, llist) {
+			smb_lock->conn = ksmbd_conn_get(work->conn);
+			list_add_tail(&smb_lock->clist,
+				      &work->conn->lock_list);
+			list_add_tail(&smb_lock->flist,
+				      &fp->lock_list);
+		}
+		spin_unlock(&work->conn->llist_lock);
+	}
+
 	ksmbd_fd_put(work, fp);
 	return 0;
 
@@ -7447,15 +7454,6 @@ int smb2_lock(struct ksmbd_work *work)
 		}
 
 		list_del(&smb_lock->llist);
-		conn = smb_lock->conn;
-		spin_lock(&conn->llist_lock);
-		if (!list_empty(&smb_lock->flist))
-			list_del(&smb_lock->flist);
-		list_del(&smb_lock->clist);
-		smb_lock->conn = NULL;
-		spin_unlock(&conn->llist_lock);
-		ksmbd_conn_put(conn);
-
 		smb2_free_lock(smb_lock->fl);
 		if (rlock)
 			locks_free_lock(rlock);
Signature · 1 line
2.53.0
author maintainer
#1547 lore raw

PATCH 1541/1887 smb/server: fix null-ptr-deref in ksmbd_ipc_tree_connect_request()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: ZhangGuoDong <[email protected]>

[ Upstream commit d2ccf905f47d2344270749f4dfa905afcd3edeb0 ]

See the procedure below:

ksmbd_tree_conn_connect

    ksmbd_share_config_get
      share->name = kstrdup() // fail
      if (!test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) // false
      // do not check `share->name`
    ksmbd_ipc_tree_connect_request
      strlen(share->name) // null-ptr-deref
Fixes e2f34481b24d ("cifsd: add server-side procedures for SMB3")
---
 fs/smb/server/mgmt/share_config.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
fs/smb/server/mgmt/share_config.c +6 -1
index 7db7345f1af94..cb89e54c4da84 100644
@@ -161,6 +161,11 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work,
 	atomic_set(&share->refcount, 1);
 	INIT_LIST_HEAD(&share->veto_list);
 	share->name = kstrdup(name, GFP_KERNEL);
+	if (!share->name) {
+		kill_share(share);
+		share = NULL;
+		goto out;
+	}
 
 	if (!test_share_config_flag(share, KSMBD_SHARE_FLAG_PIPE)) {
 		size_t path_len;
@@ -217,7 +222,7 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work,
 				share->path = NULL;
 			}
 		}
-		if (ret || !share->name) {
+		if (ret) {
 			kill_share(share);
 			share = NULL;
 			goto out;
Signature · 1 line
2.53.0
author maintainer
#1548 lore raw

PATCH 1542/1887 ksmbd: serialize oplock close with pending break ownership

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Namjae Jeon <[email protected]>

[ Upstream commit b0148dc5625dbfd50596ac63c7487c12e8a8ab03 ]

close may abort an in-flight oplock break while another breaker already holds an opinfo reference. Releasing pending_break wakes that waiter, but without serializing the close transition with bit acquisition it can become a new break owner through the test_and_set_bit() fast path. It can then overwrite OPLOCK_CLOSING with OPLOCK_ACK_WAIT and continue a break for a dying opinfo.

Make OPLOCK_CLOSING terminal once the opinfo is removed from the inode list. Serialize that transition, pending_break acquisition, and OPLOCK_ACK_WAIT setup with an opinfo state lock. A breaker which loses the race releases its ownership and returns -ENOENT. Explicitly wake pending_break waiters during close so they can observe the terminal state.

Also prevent ACK and timeout paths from replacing OPLOCK_CLOSING with OPLOCK_STATE_NONE.

Fixes e2f34481b24d ("cifsd: add server-side procedures for SMB3")
---
 fs/smb/server/oplock.c  | 81 ++++++++++++++++++++++++++++++++++-------
 fs/smb/server/oplock.h  |  1 +
 fs/smb/server/smb2pdu.c | 12 ++++--
 3 files changed, 77 insertions(+), 17 deletions(-)
fs/smb/server/oplock.c +67 -14
index a81c56b3fe965..64724b371e5f1 100644
@@ -41,6 +41,7 @@ static struct oplock_info *alloc_opinfo(struct ksmbd_work *work,
 	opinfo->conn = ksmbd_conn_get(work->conn);
 	opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
 	opinfo->op_state = OPLOCK_STATE_NONE;
+	spin_lock_init(&opinfo->state_lock);
 	opinfo->pending_break = 0;
 	opinfo->fid = id;
 	opinfo->Tid = Tid;
@@ -405,14 +406,23 @@ void close_id_del_oplock(struct ksmbd_file *fp)
 	opinfo_del(opinfo);
 
 	rcu_assign_pointer(fp->f_opinfo, NULL);
-	if (opinfo->op_state == OPLOCK_ACK_WAIT) {
-		opinfo->op_state = OPLOCK_CLOSING;
-		wake_up_interruptible_all(&opinfo->oplock_q);
-		if (opinfo->is_lease) {
-			atomic_set(&opinfo->breaking_cnt, 0);
-			wake_up_interruptible_all(&opinfo->oplock_brk);
-		}
-	}
+	spin_lock(&opinfo->state_lock);
+	if (opinfo->op_state == OPLOCK_ACK_WAIT && opinfo->is_lease)
+		atomic_set(&opinfo->breaking_cnt, 0);
+	/*
+	 * An opinfo that has been removed from the inode list is terminal. Keep
+	 * this transition and releasing pending_break under state_lock. a breaker
+	 * takes the same lock before it acquires pending_break or sets ACK_WAIT.
+	 */
+	opinfo->op_state = OPLOCK_CLOSING;
+	clear_bit_unlock(0, &opinfo->pending_break);
+	spin_unlock(&opinfo->state_lock);
+	wake_up_interruptible_all(&opinfo->oplock_q);
+	if (opinfo->is_lease)
+		wake_up_interruptible_all(&opinfo->oplock_brk);
+	/* memory barrier is needed for wake_up_bit() */
+	smp_mb__after_atomic();
+	wake_up_bit(&opinfo->pending_break, 0);
 
 	opinfo_count_dec(fp);
 	atomic_dec(&opinfo->refcount);
@@ -591,10 +601,16 @@ static void wait_for_break_ack(struct oplock_info *opinfo)
 
 	/* is this a timeout ? */
 	if (!rc) {
+		spin_lock(&opinfo->state_lock);
+		if (opinfo->op_state == OPLOCK_CLOSING) {
+			spin_unlock(&opinfo->state_lock);
+			return;
+		}
 		if (opinfo->is_lease)
 			opinfo->o_lease->state = SMB2_LEASE_NONE_LE;
 		opinfo->level = SMB2_OPLOCK_LEVEL_NONE;
 		opinfo->op_state = OPLOCK_STATE_NONE;
+		spin_unlock(&opinfo->state_lock);
 	}
 }
 
@@ -606,17 +622,46 @@ static void wake_up_oplock_break(struct oplock_info *opinfo)
 	wake_up_bit(&opinfo->pending_break, 0);
 }
 
+static bool oplock_break_set_ack_wait(struct oplock_info *opinfo)
+{
+	bool ret = false;
+
+	spin_lock(&opinfo->state_lock);
+	if (opinfo->op_state != OPLOCK_CLOSING) {
+		opinfo->op_state = OPLOCK_ACK_WAIT;
+		ret = true;
+	}
+	spin_unlock(&opinfo->state_lock);
+
+	return ret;
+}
+
 static int oplock_break_pending(struct oplock_info *opinfo, int req_op_level)
 {
-	while (test_and_set_bit(0, &opinfo->pending_break)) {
+	for (;;) {
+		bool closing;
+
+		spin_lock(&opinfo->state_lock);
+		closing = opinfo->op_state == OPLOCK_CLOSING;
+		if (!closing && !test_and_set_bit(0, &opinfo->pending_break)) {
+			spin_unlock(&opinfo->state_lock);
+			break;
+		}
+		spin_unlock(&opinfo->state_lock);
+		if (closing)
+			return -ENOENT;
+
 		wait_on_bit(&opinfo->pending_break, 0, TASK_UNINTERRUPTIBLE);
 
 		/* Not immediately break to none. */
 		opinfo->open_trunc = 0;
 
-		if (opinfo->op_state == OPLOCK_CLOSING)
+		spin_lock(&opinfo->state_lock);
+		closing = opinfo->op_state == OPLOCK_CLOSING;
+		spin_unlock(&opinfo->state_lock);
+		if (closing)
 			return -ENOENT;
-		else if (opinfo->level <= req_op_level) {
+		if (opinfo->level <= req_op_level) {
 			if (opinfo->is_lease == false)
 				return 1;
 
@@ -938,7 +983,11 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
 				release_async_work(in_work);
 			}
 
-			brk_opinfo->op_state = OPLOCK_ACK_WAIT;
+			if (!oplock_break_set_ack_wait(brk_opinfo)) {
+				atomic_dec_if_positive(&brk_opinfo->breaking_cnt);
+				wake_up_oplock_break(brk_opinfo);
+				return -ENOENT;
+			}
 		} else
 			atomic_dec(&brk_opinfo->breaking_cnt);
 	} else {
@@ -947,8 +996,12 @@ static int oplock_break(struct oplock_info *brk_opinfo, int req_op_level,
 			return err < 0 ? err : 0;
 
 		if (brk_opinfo->level == SMB2_OPLOCK_LEVEL_BATCH ||
-		    brk_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE)
-			brk_opinfo->op_state = OPLOCK_ACK_WAIT;
+		    brk_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
+			if (!oplock_break_set_ack_wait(brk_opinfo)) {
+				wake_up_oplock_break(brk_opinfo);
+				return -ENOENT;
+			}
+		}
 	}
 
 	if (brk_opinfo->is_lease)
fs/smb/server/oplock.h +1 -0
index 1890e7f82427e..a631006fc50d9 100644
@@ -66,6 +66,7 @@ struct oplock_info {
 	struct ksmbd_file	*o_fp;
 	int                     level;
 	int                     op_state;
+	spinlock_t		state_lock;
 	unsigned long		pending_break;
 	u64			fid;
 	atomic_t		breaking_cnt;
fs/smb/server/smb2pdu.c +9 -3
index ea28cc5524c7c..5b94db6480ad9 100644
@@ -8392,7 +8392,10 @@ static void smb20_oplock_break_ack(struct ksmbd_work *work)
 	smb2_set_err_rsp(work);
 
 out:
-	opinfo->op_state = OPLOCK_STATE_NONE;
+	spin_lock(&opinfo->state_lock);
+	if (opinfo->op_state != OPLOCK_CLOSING)
+		opinfo->op_state = OPLOCK_STATE_NONE;
+	spin_unlock(&opinfo->state_lock);
 	wake_up_interruptible_all(&opinfo->oplock_q);
 	opinfo_put(opinfo);
 	ksmbd_fd_put(work, fp);
@@ -8538,9 +8541,12 @@ static void smb21_lease_break_ack(struct ksmbd_work *work)
 		smb2_set_err_rsp(work);
 	}
 
-	opinfo->op_state = OPLOCK_STATE_NONE;
+	spin_lock(&opinfo->state_lock);
+	if (opinfo->op_state != OPLOCK_CLOSING)
+		opinfo->op_state = OPLOCK_STATE_NONE;
+	spin_unlock(&opinfo->state_lock);
 	wake_up_interruptible_all(&opinfo->oplock_q);
-	atomic_dec(&opinfo->breaking_cnt);
+	atomic_dec_if_positive(&opinfo->breaking_cnt);
 	wake_up_interruptible_all(&opinfo->oplock_brk);
 	opinfo_put(opinfo);
 }
Signature · 1 line
2.53.0
author maintainer
#1549 lore raw

PATCH 1543/1887 ksmbd: free preauth sessions on connection teardown

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Namjae Jeon <[email protected]>

[ Upstream commit 06c7b1d731bc105a8644f1b70165ba8b9416cbab ]

SMB3.1.1 multichannel binding preserves the preauthentication hash in a preauth_session between the NTLM negotiate and authenticate requests. The binding NTLM negotiate allocates this object and returns STATUS_MORE_PROCESSING_REQUIRED. If the client disconnects before it sends the authenticate request, neither the authenticate nor error cleanup paths free the object.

Release any remaining preauthentication sessions when tearing down the connection. Initialize the list when allocating the connection so that this cleanup is safe regardless of the negotiated dialect.

Fixes f5a544e3bab7 ("ksmbd: add support for SMB3 multichannel")
---
 fs/smb/server/connection.c        |  3 +++
 fs/smb/server/mgmt/user_session.c | 11 +++++++++++
 fs/smb/server/mgmt/user_session.h |  1 +
 fs/smb/server/smb2ops.c           |  1 -
 4 files changed, 15 insertions(+), 1 deletion(-)
fs/smb/server/connection.c +3 -0
index a0c548447c2af..f1184ee9b2242 100644
@@ -11,6 +11,7 @@
 #include "server.h"
 #include "smb_common.h"
 #include "mgmt/ksmbd_ida.h"
+#include "mgmt/user_session.h"
 #include "connection.h"
 #include "transport_tcp.h"
 #include "transport_rdma.h"
@@ -85,6 +86,7 @@ void ksmbd_conn_free(struct ksmbd_conn *conn)
 	kvfree(conn->request_buf);
 	kfree(conn->preauth_info);
 	kfree(conn->mechToken);
+	ksmbd_preauth_session_destroy(conn);
 	ksmbd_conn_put(conn);
 }
 
@@ -124,6 +126,7 @@ struct ksmbd_conn *ksmbd_conn_alloc(void)
 	INIT_LIST_HEAD(&conn->conns_list);
 	INIT_LIST_HEAD(&conn->requests);
 	INIT_LIST_HEAD(&conn->async_requests);
+	INIT_LIST_HEAD(&conn->preauth_sess_table);
 	spin_lock_init(&conn->request_lock);
 	spin_lock_init(&conn->credits_lock);
 	ida_init(&conn->async_ida);
fs/smb/server/mgmt/user_session.c +11 -0
index abb5b4619f36c..09c19d1f71e38 100644
@@ -382,6 +382,17 @@ struct preauth_session *ksmbd_preauth_session_alloc(struct ksmbd_conn *conn,
 	return sess;
 }
 
+void ksmbd_preauth_session_destroy(struct ksmbd_conn *conn)
+{
+	struct preauth_session *sess, *tmp;
+
+	list_for_each_entry_safe(sess, tmp, &conn->preauth_sess_table,
+				 preauth_entry) {
+		list_del(&sess->preauth_entry);
+		kfree(sess);
+	}
+}
+
 static bool ksmbd_preauth_session_id_match(struct preauth_session *sess,
 					   unsigned long long id)
 {
fs/smb/server/mgmt/user_session.h +1 -0
index 90ae30ba402f4..03b92c3667f62 100644
@@ -98,6 +98,7 @@ struct ksmbd_session *ksmbd_session_lookup_all(struct ksmbd_conn *conn,
 					       unsigned long long id);
 struct preauth_session *ksmbd_preauth_session_alloc(struct ksmbd_conn *conn,
 						    u64 sess_id);
+void ksmbd_preauth_session_destroy(struct ksmbd_conn *conn);
 struct preauth_session *ksmbd_preauth_session_lookup(struct ksmbd_conn *conn,
 						     unsigned long long id);
 
fs/smb/server/smb2ops.c +0 -1
index c1ed62cc82960..e5accc7153485 100644
@@ -283,7 +283,6 @@ int init_smb3_11_server(struct ksmbd_conn *conn)
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_SMB3_MULTICHANNEL)
 		conn->vals->req_capabilities |= SMB2_GLOBAL_CAP_MULTI_CHANNEL;
 
-	INIT_LIST_HEAD(&conn->preauth_sess_table);
 	return 0;
 }
 
Signature · 1 line
2.53.0
author maintainer
#1550 lore raw

PATCH 1544/1887 ksmbd: fix slab-out-of-bounds read in ksmbd_alloc_user()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Yunseong Kim <[email protected]>

[ Upstream commit 7405d0ba294306721843bc551611775e6edef516 ]

ksmbd_alloc_user() copies resp->hash_sz bytes out of the mountd IPC login response with

	user->passkey_sz = resp->hash_sz;
	user->passkey = kmalloc(resp->hash_sz, KSMBD_DEFAULT_GFP);
	if (user->passkey)
		memcpy(user->passkey, resp->hash, resp->hash_sz);

resp->hash_sz is a __u16 supplied by the response, but resp->hash[] is only KSMBD_REQ_MAX_HASH_SZ bytes. A malformed or malicious login response can set hash_sz well beyond that (up to 65535), so the memcpy() reads past the end of the response object. ipc_validate_msg() does not bound hash_sz, so reject any response whose hash_sz exceeds the on-stack hash[] buffer before allocating and copying.

[ 2030.238706] BUG: KASAN: slab-out-of-bounds in ksmbd_alloc_user+0x278/0x680
[ 2030.240549] Read of size 65535 at addr ffff888121bb6680 by task kworker/4:1/18611
[ 2030.242296]
[ 2030.242710] CPU: 4 UID: 0 PID: 18611 Comm: kworker/4:1 Not tainted 7.1.0-next-20260623-virtme #96 PREEMPT(lazy)
[ 2030.242732] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
[ 2030.242743] Workqueue: ksmbd-io handle_ksmbd_work
[ 2030.242763] Call Trace:
[ 2030.242769]  <TASK>
[ 2030.242776]  dump_stack_lvl+0xa2/0xd0
[ 2030.242794]  print_address_description+0x77/0x200
[ 2030.242815]  ? ksmbd_alloc_user+0x278/0x680
[ 2030.242831]  print_report+0x58/0x70
[ 2030.242848]  kasan_report+0x117/0x150
[ 2030.242869]  ? ksmbd_alloc_user+0x278/0x680
[ 2030.242888]  kasan_check_range+0x3c7/0x3f0
[ 2030.242908]  ? ksmbd_alloc_user+0x278/0x680
[ 2030.242925]  __asan_memcpy+0x29/0x70
[ 2030.242942]  ksmbd_alloc_user+0x278/0x680
[ 2030.242960]  ksmbd_login_user+0xc3/0x120
[ 2030.242978]  ntlm_authenticate+0x5e6/0x1b00
[ 2030.243017]  ? __pfx_ntlm_authenticate+0x10/0x10
[ 2030.243035]  ? ksmbd_session_lookup+0x188/0x1d0
[ 2030.243054]  ? __pfx_ksmbd_session_lookup+0x10/0x10
[ 2030.243090]  ? __sanitizer_cov_trace_switch+0x7b/0x140
[ 2030.243108]  smb2_sess_setup+0x1e4a/0x27b0
[ 2030.243126]  ? copy_from_kernel_nofault+0x199/0x300
[ 2030.243156]  ? __pfx_smb2_sess_setup+0x10/0x10
[ 2030.243173]  ? get_smb2_cmd_val+0xe3/0x1c0
[ 2030.243208]  handle_ksmbd_work+0x954/0x1280
[ 2030.243230]  ? __pfx_handle_ksmbd_work+0x10/0x10
[ 2030.243249]  ? process_scheduled_works+0xa07/0x1490
[ 2030.243270]  ? process_scheduled_works+0xa07/0x1490
[ 2030.243291]  process_scheduled_works+0xa70/0x1490
[ 2030.243320]  ? __pfx_process_scheduled_works+0x10/0x10
[ 2030.243340]  ? do_raw_spin_lock+0x130/0x300
[ 2030.243358]  ? lock_is_held_type+0x7b/0x110
[ 2030.243388]  worker_thread+0x932/0xe20
[ 2030.243415]  kthread+0x38a/0x470
[ 2030.243431]  ? __pfx_worker_thread+0x10/0x10
[ 2030.243451]  ? __pfx_kthread+0x10/0x10
[ 2030.243467]  ret_from_fork+0x484/0x910
[ 2030.243485]  ? __pfx_ret_from_fork+0x10/0x10
[ 2030.243501]  ? __switch_to+0xc77/0x12c0
[ 2030.243523]  ? __pfx_kthread+0x10/0x10
[ 2030.243540]  ret_from_fork_asm+0x1a/0x30
[ 2030.243564]  </TASK>
[ 2030.243570]
[ 2030.290164] Allocated by task 19279:
[ 2030.290911]  kasan_save_track+0x3e/0x80
[ 2030.292179]  __kasan_kmalloc+0x72/0x90
[ 2030.293217]  __kvmalloc_node_noprof+0x3ff/0x6b0
[ 2030.294467]  handle_generic_event+0x59b/0x750
[ 2030.295345]  genl_family_rcv_msg_doit+0x238/0x340
[ 2030.296553]  genl_rcv_msg+0x606/0x7b0
[ 2030.297129]  netlink_rcv_skb+0x22b/0x4a0
[ 2030.298500]  genl_rcv+0x2d/0x40
[ 2030.299273]  netlink_unicast+0x7ba/0x930
[ 2030.300019]  netlink_sendmsg+0x8c3/0xb00
[ 2030.301073]  __sock_sendmsg+0xec/0x140
[ 2030.301579]  __sys_sendto+0x357/0x470
[ 2030.302255]  __x64_sys_sendto+0xe3/0x100
[ 2030.303425]  do_syscall_64+0x135/0x460
[ 2030.304763]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 2030.305594]
[ 2030.305819] The buggy address belongs to the object at ffff888121bb6640
[ 2030.305819]  which belongs to the cache kmalloc-192 of size 192
[ 2030.309595] The buggy address is located 64 bytes inside of
[ 2030.309595]  allocated 166-byte region [ffff888121bb6640, ffff888121bb66e6)
[ 2030.312484]
[ 2030.312719] The buggy address belongs to the physical page:
[ 2030.314315] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x121bb6
[ 2030.316481] head: order:1 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
[ 2030.318248] flags: 0x100000000000040(head|node=0|zone=2)
[ 2030.319662] page_type: f5(slab)
[ 2030.320242] raw: 0100000000000040 ffff8881000424c0 ffffea00047c1510 ffff888100040468
[ 2030.321911] raw: 0000000000000000 0000000000150015 00000000f5000000 0000000000000000
[ 2030.324413] head: 0100000000000040 ffff8881000424c0 ffffea00047c1510 ffff888100040468
[ 2030.326150] head: 0000000000000000 0000000000150015 00000000f5000000 0000000000000000
[ 2030.327960] head: 0100000000000001 ffffffffffffff81 00000000ffffffff 00000000ffffffff
[ 2030.329615] head: ffff888121bb7ab0 0000000000000000 00000000ffffffff 0000000000000000
[ 2030.331861] page dumped because: kasan: bad access detected
[ 2030.332946]
[ 2030.333502] Memory state around the buggy address:
[ 2030.334698]  ffff888121bb6580: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 2030.336475]  ffff888121bb6600: fc fc fc fc fc fc fc fc 00 00 00 00 00 00 00 00
[ 2030.338143] >ffff888121bb6680: 00 00 00 00 00 00 00 00 00 00 00 00 06 fc fc fc
[ 2030.339116]                                                        ^
[ 2030.341315]  ffff888121bb6700: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[ 2030.342801]  ffff888121bb6780: fc fc fc fc fc fc fc fc fa fb fb fb fb fb fb fb
[ 2030.344643] ==================================================================

Found with ksmbdzzer [2], a KSMBD fuzzer that drives libFuzzer with a kcov-dataflow [1] coverage vector: it folds each instrumented comparison/argument's runtime operand value together with its PC (the default arm mixes them as pc⊕val) so that a new operand value at a known site counts as new coverage.

[1] https://lwn.net/Articles/1077606/
[2] https://github.com/yskzalloc/kcov-dataflow

Fixes e2f34481b24d ("cifsd: add server-side procedures for SMB3")
---
 fs/smb/server/mgmt/user_config.c | 11 +++++++++++
 1 file changed, 11 insertions(+)
fs/smb/server/mgmt/user_config.c +11 -0
index 279d00feff216..a9c0d80dc3680 100644
@@ -31,6 +31,17 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp)
 {
 	struct ksmbd_user *user = NULL;
 
+	/*
+	 * resp->hash_sz is a __u16 taken from the mountd IPC login response but
+	 * resp->hash[] is only KSMBD_REQ_MAX_HASH_SZ bytes.  A malformed or
+	 * malicious response can set hash_sz far beyond that (up to 65535),
+	 * making the memcpy() below read past the response object
+	 * (slab-out-of-bounds in ksmbd_alloc_user()).  Reject any oversized
+	 * hash rather than trust the length.
+	 */
+	if (resp->hash_sz > sizeof(resp->hash))
+		return NULL;
+
 	user = kmalloc(sizeof(struct ksmbd_user), GFP_KERNEL);
 	if (!user)
 		return NULL;
Signature · 1 line
2.53.0
author maintainer
#1551 lore raw

PATCH 1545/1887 apparmor: fix deadlock in complain-mode change_hat

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: John Johansen <[email protected]>

[ Upstream commit 4ec11f14d1d6fdda787d991b142537be7841d395 ]

The use of change_hat when in complain mode can cause a deadlock when the hat doesn't exist and a new learning profile is created for the missing profile. This is because change_hat() has taken the lock to search the hat list and creating the new learning profile needs to take the lock to add it to the list.

Quoted 1 line

From the bug report:

Originally found in 7.0.0 in LTS ubuntu 26.04 with pam_apparmor + su in complain mode set to change hats. Then verified in newest available vanilla kernel I've compiled to see if still present:

7.2-rc7 vanilla -> affected

checked also some other kernels:
6.18.44 vanilla -> affected
6.12.95 with debian patches -> unaffected

On systems without bug (for example 6.12.95 debian) it just prints:

aa_change_hat rc=0

On systems with bug, the executable always hangs, prints nothing and becomes unkillable. (And once stuck this way, it will cause any further hat changes to also cause the changing process to get stuck)

Then in syslog you can find hint about cause:

kernel: INFO: task hat:3409 blocked for more than 483 seconds.
kernel:       Not tainted 7.2.0-rc7 #1
kernel: "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
kernel: task:hat             state:D stack:0     pid:3409  tgid:3409  ppid:2605   task_flags:0x400000 flags:0x00080800
kernel: Call Trace:
kernel:  <TASK>
kernel:  __schedule+0x48f/0xfe0
kernel:  schedule+0x27/0xa0
kernel:  schedule_preempt_disabled+0x15/0x30
kernel:  __mutex_lock.constprop.0+0x569/0xa10
kernel:  aa_new_learning_profile+0x15f/0x210
kernel:  build_change_hat+0x19f/0x3b0
kernel:  change_hat.isra.0+0x5dd/0xd60
kernel:  aa_change_hat+0x2f3/0x710
kernel:  aa_setprocattr_changehat+0x121/0x1f0
kernel:  do_setattr+0x28c/0x340
kernel:  apparmor_setselfattr+0x20/0x50
kernel:  security_setselfattr+0xf6/0x110
kernel:  __x64_sys_lsm_set_self_attr+0x53/0x90
kernel:  do_syscall_64+0xdd/0x5e0
kernel:  ? __mod_memcg_lruvec_state+0xfd/0x260
kernel:  ? lruvec_stat_mod_folio+0x8d/0xd0
kernel:  ? __folio_mod_stat+0x2d/0x90
kernel:  ? map_anon_folio_pte_nopf+0xd1/0x1f0
kernel:  ? do_anonymous_page+0x184/0xa10
kernel:  ? __handle_mm_fault+0x805/0x870
kernel:  ? count_memcg_events+0xef/0x230
kernel:  ? handle_mm_fault+0x1f0/0x2f0
kernel:  ? do_user_addr_fault+0x2bb/0x7b0
kernel:  ? do_syscall_64+0x94/0x5e0
kernel:  ? exc_page_fault+0x75/0x160
kernel:  entry_SYSCALL_64_after_hwframe+0x76/0x7e
kernel: RIP: 0033:0x7f815e134c8d
kernel: RSP: 002b:00007fff6df94ea8 EFLAGS: 00000246 ORIG_RAX: 00000000000001cc
kernel: RAX: ffffffffffffffda RBX: 0000556d8c81d040 RCX: 00007f815e134c8d
kernel: RDX: 0000000000000046 RSI: 0000556d8c81d040 RDI: 0000000000000064
kernel: RBP: 00007fff6df94ef0 R08: 00007f815e212ac8 R09: 000000000000000c
kernel: R10: 0000000000000000 R11: 0000000000000246 R12: 0000556d8c81d010
kernel: R13: 0000000000000026 R14: 0000000000000046 R15: 0000000000000064
kernel:  </TASK>
kernel: INFO: task hat:3409 is blocked on a mutex likely owned by task hat:3409.

To fix the issue, lift the locking out of the core of aa_new_learning_profile(), introduce a wrapper function that takes the lock where needed, and have build_change_hat() call the core function that no longer takes the lock.

In addition fix 4 other issues introduced by commit
32e92764d6f8d ("apparmor: grab ns lock and refresh when looking up changehat child profiles")
- aa_get_profile_rcu() was replaced-by: aa_get_profile without the accompanying rcu_dereference_protected()
- an extra aa_get_label(label) was introduced at the start of
change_hat() without an accompanying aa_put_label() causing a
reference count leak.
- a reference count leak was introduced in the label_is_stale(label)
case, where the newest profile would be leaked instead of the
label passed to the function.
- a potential UAF when the lookup walks up the tree with new_ns != ns
the new label reference is put, and then used for the next lookup.
The mutex_lock, will block replacement, and removal in the locked
ns. However there are two cases where putting the reference can
result in the label being freed even with the lock held.

1. the label does not have a list reference (possible for temporary or special profiles) in which case the put can trigger the cleanup.
2. the new label reference is in a different namespace, which does not have a lock held on it. This extends case 1 to also include replacement, and removal that could be occurring in the namespace new is in.

Fixes 32e92764d6f8d ("apparmor: grab ns lock and refresh when looking up changehat child profiles")
---
 security/apparmor/domain.c         | 25 +++++++++++++++++--------
 security/apparmor/include/policy.h |  2 ++
 security/apparmor/policy.c         | 21 ++++++++++++++++-----
 3 files changed, 35 insertions(+), 13 deletions(-)
security/apparmor/domain.c +17 -8
index 52e16e75e63fa..e844e0e489436 100644
@@ -1006,8 +1006,8 @@ static struct aa_label *build_change_hat(struct aa_profile *profile,
 	if (!hat) {
 		error = -ENOENT;
 		if (COMPLAIN_MODE(profile)) {
-			hat = aa_new_null_profile(profile, true, name,
-						  GFP_KERNEL);
+			hat = __aa_new_null_profile(profile, true, name,
+						   GFP_KERNEL);
 			if (!hat) {
 				info = "failed null profile create";
 				error = -ENOMEM;
@@ -1043,6 +1043,7 @@ static struct aa_label *change_hat(struct aa_label *label, const char *hats[],
 	bool sibling = false;
 	const char *name, *info = NULL;
 	int i, error;
+	bool needput = false;
 
 	AA_BUG(!label);
 	AA_BUG(!hats);
@@ -1055,7 +1056,6 @@ static struct aa_label *change_hat(struct aa_label *label, const char *hats[],
 	 * the profiles and label, we can rely on the namespaces being live
 	 * and avoid incrementing their refcounts while grabbing the lock.
 	 */
-	label = aa_get_label(label);
 	ns = labels_ns(label);
 
 retry:
@@ -1063,15 +1063,19 @@ static struct aa_label *change_hat(struct aa_label *label, const char *hats[],
 	if (label_is_stale(label)) {
 		new = aa_get_newest_label(label);
 		new_ns = labels_ns(new);
+
+		if (needput)
+			/* aa_put_label() is safe to call when under lock */
+			aa_put_label(label);
+		label = new;
+		needput = true;
+		/* check if replaced with label in parent ns, and lock there */
 		if (new_ns != ns) {
-			aa_put_label(new);
 			mutex_unlock(&ns->lock);
 			ns = new_ns;
-			label = new;
+			/* retry will bottom out at the root of the tree */
 			goto retry;
 		}
-		aa_put_label(label);
-		label = new;
 	}
 
 	if (PROFILE_IS_HAT(labels_profile(label)))
@@ -1082,7 +1086,8 @@ static struct aa_label *change_hat(struct aa_label *label, const char *hats[],
 		name = hats[i];
 		label_for_each_in_ns(it, labels_ns(label), label, profile) {
 			if (sibling && PROFILE_IS_HAT(profile)) {
-				root = aa_get_profile(profile->parent);
+				root = aa_get_profile(rcu_dereference_protected(profile->parent,
+						      mutex_is_locked(&ns->lock)));
 			} else if (!sibling && !PROFILE_IS_HAT(profile)) {
 				root = aa_get_profile(profile);
 			} else {	/* conflicting change type */
@@ -1142,6 +1147,8 @@ static struct aa_label *change_hat(struct aa_label *label, const char *hats[],
 		}
 	}
 	mutex_unlock(&ns->lock);
+	if (needput)
+		aa_put_label(label);
 	return ERR_PTR(error);
 
 build:
@@ -1154,6 +1161,8 @@ static struct aa_label *change_hat(struct aa_label *label, const char *hats[],
 		goto fail;
 	} /* else if (IS_ERR) build_change_hat has logged error so return new */
 	mutex_unlock(&ns->lock);
+	if (needput)
+		aa_put_label(label);
 	return new;
 }
 
security/apparmor/include/policy.h +2 -0
index 44471bb057623..ae8a3626ac75a 100644
@@ -181,6 +181,8 @@ void aa_add_profile(struct aa_policy *common, struct aa_profile *profile);
 void aa_free_proxy_kref(struct kref *kref);
 struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy,
 				    gfp_t gfp);
+struct aa_profile *__aa_new_null_profile(struct aa_profile *parent, bool hat,
+					 const char *base, gfp_t gfp);
 struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat,
 				       const char *base, gfp_t gfp);
 void aa_free_profile(struct aa_profile *profile);
security/apparmor/policy.c +16 -5
index 257ca96a3d067..13d3041200cba 100644
@@ -507,7 +507,7 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
 }
 
 /**
- * aa_new_null_profile - create or find a null-X learning profile
+ * __aa_new_null_profile - create or find a null-X learning profile
  * @parent: profile that caused this profile to be created (NOT NULL)
  * @hat: true if the null- learning profile is a hat
  * @base: name to base the null profile off of
@@ -524,14 +524,15 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
  *
  * Returns: new refcounted profile else NULL on failure
  */
-struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat,
-				       const char *base, gfp_t gfp)
+struct aa_profile *__aa_new_null_profile(struct aa_profile *parent, bool hat,
+					 const char *base, gfp_t gfp)
 {
 	struct aa_profile *p, *profile;
 	const char *bname;
 	char *name = NULL;
 
 	AA_BUG(!parent);
+	AA_BUG(!mutex_is_locked(&parent->ns->lock));
 
 	if (base) {
 		name = kmalloc(strlen(parent->base.hname) + 8 + strlen(base),
@@ -572,7 +573,6 @@ struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat,
 	profile->file.dfa = aa_get_dfa(nulldfa);
 	profile->policy.dfa = aa_get_dfa(nulldfa);
 
-	mutex_lock_nested(&profile->ns->lock, profile->ns->level);
 	p = __find_child(&parent->base.profiles, bname);
 	if (p) {
 		aa_free_profile(profile);
@@ -580,7 +580,6 @@ struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat,
 	} else {
 		__add_profile(&parent->base.profiles, profile);
 	}
-	mutex_unlock(&profile->ns->lock);
 
 	/* refcount released by caller */
 out:
@@ -594,6 +593,18 @@ struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat,
 	return NULL;
 }
 
+struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat,
+				       const char *base, gfp_t gfp)
+{
+	struct aa_profile *profile;
+
+	mutex_lock_nested(&parent->ns->lock, parent->ns->level);
+	profile = __aa_new_null_profile(parent, hat, base, gfp);
+	mutex_unlock(&parent->ns->lock);
+
+	return profile;
+}
+
 /**
  * replacement_allowed - test to see if replacement is allowed
  * @profile: profile to test if it can be replaced  (MAYBE NULL)
Signature · 1 line
2.53.0
author maintainer
#1552 lore raw

PATCH 1546/1887 blk-cgroup: delay freeing policy data after rcu grace period

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Yu Kuai <[email protected]>

[ Upstream commit 0af3fedb8c8ed3c07b4f76927bd7fc88f6f82efb ]

Currently blkcg_print_blkgs() must hold RCU to iterate blkgs from a blkcg, and prfill() must hold queue_lock to prevent policy data from being freed by policy deactivation. As a consequence, queue_lock has to be nested under RCU from blkcg_print_blkgs().

Delay freeing policy data until after an RCU grace period so prfill() can be protected by RCU alone.

Adapted for this tree: bfqg_release() also exits the group stats, the throttle policy keeps its existing offline helpers, and the bfq_group reference counter becomes atomic because the final bfqg_put() now runs from an RCU callback without the locks that protect the other users.

Stable-dep-of: 97cb95d21488 ("blk-iocost: clear delay state when freeing policy data")

---
 block/bfq-cgroup.c    | 15 ++++++++++-----
 block/bfq-iosched.h   |  2 +-
 block/blk-cgroup.h    |  2 ++
 block/blk-iocost.c    | 14 ++++++++++++--
 block/blk-iolatency.c | 10 +++++++++-
 block/blk-throttle.c  | 13 +++++++++++--
 6 files changed, 45 insertions(+), 11 deletions(-)
block/bfq-cgroup.c +10 -5
index 8e2478efd6695..136a61a1458ba 100644
@@ -316,14 +316,12 @@ struct bfq_group *bfqq_group(struct bfq_queue *bfqq)
 
 static void bfqg_get(struct bfq_group *bfqg)
 {
-	bfqg->ref++;
+	atomic_inc(&bfqg->ref);
 }
 
 static void bfqg_put(struct bfq_group *bfqg)
 {
-	bfqg->ref--;
-
-	if (bfqg->ref == 0)
+	if (atomic_dec_and_test(&bfqg->ref))
 		kfree(bfqg);
 }
 
@@ -556,14 +554,21 @@ static void bfq_pd_init(struct blkg_policy_data *pd)
 	bfqg->rq_pos_tree = RB_ROOT;
 }
 
-static void bfq_pd_free(struct blkg_policy_data *pd)
+static void bfqg_release(struct rcu_head *rcu)
 {
+	struct blkg_policy_data *pd =
+		container_of(rcu, struct blkg_policy_data, rcu_head);
 	struct bfq_group *bfqg = pd_to_bfqg(pd);
 
 	bfqg_stats_exit(&bfqg->stats);
 	bfqg_put(bfqg);
 }
 
+static void bfq_pd_free(struct blkg_policy_data *pd)
+{
+	call_rcu(&pd->rcu_head, bfqg_release);
+}
+
 static void bfq_pd_reset_stats(struct blkg_policy_data *pd)
 {
 	struct bfq_group *bfqg = pd_to_bfqg(pd);
block/bfq-iosched.h +1 -1
index 2b413ddffbb95..278fb51919b5d 100644
@@ -953,7 +953,7 @@ struct bfq_group {
 	char blkg_path[128];
 
 	/* reference counter (see comments in bfq_bic_update_cgroup) */
-	int ref;
+	atomic_t ref;
 	/* Is bfq_group still online? */
 	bool online;
 
block/blk-cgroup.h +2 -0
index 59815b269a20e..840deb32746c1 100644
@@ -126,6 +126,8 @@ struct blkg_policy_data {
 	struct blkcg_gq			*blkg;
 	int				plid;
 	bool				online;
+
+	struct rcu_head			rcu_head;
 };
 
 /*
block/blk-iocost.c +12 -2
index 3989d8c796b0f..eeda41267bd43 100644
@@ -3003,6 +3003,16 @@ static void ioc_pd_init(struct blkg_policy_data *pd)
 	spin_unlock_irqrestore(&ioc->lock, flags);
 }
 
+static void iocg_release(struct rcu_head *rcu)
+{
+	struct blkg_policy_data *pd =
+		container_of(rcu, struct blkg_policy_data, rcu_head);
+	struct ioc_gq *iocg = pd_to_iocg(pd);
+
+	free_percpu(iocg->pcpu_stat);
+	kfree(iocg);
+}
+
 static void ioc_pd_free(struct blkg_policy_data *pd)
 {
 	struct ioc_gq *iocg = pd_to_iocg(pd);
@@ -3027,8 +3037,8 @@ static void ioc_pd_free(struct blkg_policy_data *pd)
 
 		hrtimer_cancel(&iocg->waitq_timer);
 	}
-	free_percpu(iocg->pcpu_stat);
-	kfree(iocg);
+
+	call_rcu(&pd->rcu_head, iocg_release);
 }
 
 static void ioc_pd_stat(struct blkg_policy_data *pd, struct seq_file *s)
block/blk-iolatency.c +9 -1
index 571fa95aafe96..884be27f0dda5 100644
@@ -1025,13 +1025,21 @@ static void iolatency_pd_offline(struct blkg_policy_data *pd)
 	iolatency_clear_scaling(blkg);
 }
 
-static void iolatency_pd_free(struct blkg_policy_data *pd)
+static void iolat_release(struct rcu_head *rcu)
 {
+	struct blkg_policy_data *pd =
+		container_of(rcu, struct blkg_policy_data, rcu_head);
 	struct iolatency_grp *iolat = pd_to_lat(pd);
+
 	free_percpu(iolat->stats);
 	kfree(iolat);
 }
 
+static void iolatency_pd_free(struct blkg_policy_data *pd)
+{
+	call_rcu(&pd->rcu_head, iolat_release);
+}
+
 static struct cftype iolatency_files[] = {
 	{
 		.name = "latency",
block/blk-throttle.c +11 -2
index 62a3f62316df1..7ae61c3f887e9 100644
@@ -486,16 +486,25 @@ static void throtl_pd_offline(struct blkg_policy_data *pd)
 		throtl_upgrade_state(tg->td);
 }
 
-static void throtl_pd_free(struct blkg_policy_data *pd)
+static void tg_release(struct rcu_head *rcu)
 {
+	struct blkg_policy_data *pd =
+		container_of(rcu, struct blkg_policy_data, rcu_head);
 	struct throtl_grp *tg = pd_to_tg(pd);
 
-	del_timer_sync(&tg->service_queue.pending_timer);
 	blkg_rwstat_exit(&tg->stat_bytes);
 	blkg_rwstat_exit(&tg->stat_ios);
 	kfree(tg);
 }
 
+static void throtl_pd_free(struct blkg_policy_data *pd)
+{
+	struct throtl_grp *tg = pd_to_tg(pd);
+
+	del_timer_sync(&tg->service_queue.pending_timer);
+	call_rcu(&pd->rcu_head, tg_release);
+}
+
 static struct throtl_grp *
 throtl_rb_first(struct throtl_service_queue *parent_sq)
 {
Signature · 1 line
2.53.0
author maintainer
#1553 lore raw

PATCH 1547/1887 blk-iocost: clear delay state when freeing policy data

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Usama Arif <[email protected]>

[ Upstream commit 97cb95d2148835ae86ff916b145aef332d40439d ]

iocg_kick_delay() turns sufficiently large debt into an explicit block-cgroup delay with blkcg_set_delay(), setting blkg->use_delay to -1 and incrementing blkcg->congestion_count. Clearing it again depends on iocg_kick_delay() running from the period timer, the waitq timer or the issue path.

ioc_pd_free() removes the iocg from active_iocgs and cancels its waitq timer, and no further bios can arrive, so once it has run nothing is left which can reduce the debt and clear the delay. The blkcg stays marked congested for the rest of its life.

blk_cgroup_congested() then returns true for every task in that cgroup and its descendants: page_cache_sync_ra() cuts readahead to a single page, page_cache_async_ra() skips it altogether, and __folio_throttle_swaprate() takes swap_avail_lock and schedules a throttle on anonymous folio allocation.

Clear it explicitly, after the list removal and the synchronous hrtimer_cancel() so that neither timer processing nor an I/O path can re-arm it. The free callback can also see policy data which was never attached to a blkg, hence the pd->blkg check.

---
 block/blk-iocost.c | 7 +++++++
 1 file changed, 7 insertions(+)
block/blk-iocost.c +7 -0
index eeda41267bd43..814ad657cc56e 100644
@@ -3016,6 +3016,7 @@ static void iocg_release(struct rcu_head *rcu)
 static void ioc_pd_free(struct blkg_policy_data *pd)
 {
 	struct ioc_gq *iocg = pd_to_iocg(pd);
+	struct blkcg_gq *blkg = pd_to_blkg(pd);
 	struct ioc *ioc = iocg->ioc;
 	unsigned long flags;
 
@@ -3038,6 +3039,12 @@ static void ioc_pd_free(struct blkg_policy_data *pd)
 		hrtimer_cancel(&iocg->waitq_timer);
 	}
 
+	/* off ->active_iocgs and timer gone, so nothing can re-arm the delay */
+	iocg->delay = 0;
+	iocg->indelay_since = 0;
+	if (blkg)
+		blkcg_clear_delay(blkg);
+
 	call_rcu(&pd->rcu_head, iocg_release);
 }
 
Signature · 1 line
2.53.0
author maintainer
#1554 lore raw

PATCH 1548/1887 blk-iolatency: clear delay state when freeing policy data

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Usama Arif <[email protected]>

[ Upstream commit 8935bf22c0a0db517a7f72f7097300e05dd852f5 ]

io.latency can throttle a group which has no latency target of its own. When a sibling misses its target, check_scale_change() scales down its peers, and a peer that reaches queue depth one gets blkcg_use_delay() called on it on every further scale-down, even with min_lat_nsec == 0.

iolatency_pd_offline() resets the target through iolatency_set_min_lat_nsec(), which clears the delay only on a nonzero to zero transition, so it never clears such a peer. Freeing the policy data then leaves blkg->use_delay set and blkcg->congestion_count elevated with nothing left that can drop it.

blk_cgroup_congested() then returns true for every task in that cgroup and its descendants for as long as the cgroup lives: page_cache_sync_ra() cuts readahead to a single page, page_cache_async_ra() skips it altogether, and __folio_throttle_swaprate() takes swap_avail_lock and schedules a throttle on anonymous folio allocation.

Clear the delay in iolatency_pd_free(). By then bio-held blkg references have drained, or the queue is frozen for policy deactivation, so check_scale_change() cannot re-arm it. The free callback can also see policy data which was never attached to a blkg, hence the pd->blkg check.

Fixes d70675121546 ("block: introduce blk-iolatency io controller")
---
 block/blk-iolatency.c | 9 +++++++++
 1 file changed, 9 insertions(+)
block/blk-iolatency.c +9 -0
index 884be27f0dda5..10d0ffccc874d 100644
@@ -1037,6 +1037,15 @@ static void iolat_release(struct rcu_head *rcu)
 
 static void iolatency_pd_free(struct blkg_policy_data *pd)
 {
+	struct blkcg_gq *blkg = pd_to_blkg(pd);
+
+	/*
+	 * Groups throttled as collateral have min_lat_nsec == 0, so
+	 * iolatency_pd_offline() leaves their delay set.  Drop it here, where
+	 * no in-flight bio can re-arm it via check_scale_change().
+	 */
+	if (blkg)
+		blkcg_clear_delay(blkg);
 	call_rcu(&pd->rcu_head, iolat_release);
 }
 
Signature · 1 line
2.53.0
author maintainer
#1555 lore raw

PATCH 1549/1887 null_blk: free zones array on device power-off

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Zizhi Wo <[email protected]>

[ Upstream commit 2a6357a9b935a34f5508618fee8a7fffbf7722a8 ]

null_init_zoned_dev() allocates dev->zones when a zoned device is powered on, but null_del_dev() never frees it on power-off; dev->zones is only freed later in null_free_dev(), when the configfs directory is removed. If the device is powered off and then on again, null_init_zoned_dev() allocates a new array and overwrites the dev->zones pointer, leaking the previous allocation each power cycle.

Free dev->zones in null_del_dev() via null_free_zoned_dev() to solve it. And calling null_free_zoned_dev() in null_free_dev() is no longer necessary because every caller already invokes null_del_dev() first: via nullb_group_drop_item() before nullb_device_release(), in the null_add_dev() error path of null_create_dev(), and in null_destroy_dev(). Remove the redundant call.

And take &lock around zone_cond_store() in the two store wrappers to serialize dev->zones check-and-deref against its alloc/free, which already run under &lock. The reason there was no problem before is that only nullb_device_release() or null_exit() frees the dev->zones, which guarantees that subsequent users won't access the configfs interface.

Fixes ca4b2a011948 ("null_blk: add zone support")
Assisted-by Claude-Code:GLM-5.2
---
 drivers/block/null_blk/main.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)
drivers/block/null_blk/main.c +13 -3
index 237e9a821374c..971dbe77b2947 100644
@@ -552,8 +552,13 @@ static ssize_t nullb_device_zone_readonly_store(struct config_item *item,
 						const char *page, size_t count)
 {
 	struct nullb_device *dev = to_nullb_device(item);
+	ssize_t ret;
+
+	mutex_lock(&lock);
+	ret = zone_cond_store(dev, page, count, BLK_ZONE_COND_READONLY);
+	mutex_unlock(&lock);
 
-	return zone_cond_store(dev, page, count, BLK_ZONE_COND_READONLY);
+	return ret;
 }
 CONFIGFS_ATTR_WO(nullb_device_, zone_readonly);
 
@@ -561,8 +566,13 @@ static ssize_t nullb_device_zone_offline_store(struct config_item *item,
 					       const char *page, size_t count)
 {
 	struct nullb_device *dev = to_nullb_device(item);
+	ssize_t ret;
 
-	return zone_cond_store(dev, page, count, BLK_ZONE_COND_OFFLINE);
+	mutex_lock(&lock);
+	ret = zone_cond_store(dev, page, count, BLK_ZONE_COND_OFFLINE);
+	mutex_unlock(&lock);
+
+	return ret;
 }
 CONFIGFS_ATTR_WO(nullb_device_, zone_offline);
 
@@ -746,7 +756,6 @@ static void null_free_dev(struct nullb_device *dev)
 	if (!dev)
 		return;
 
-	null_free_zoned_dev(dev);
 	badblocks_exit(&dev->badblocks);
 	kfree(dev);
 }
@@ -1825,6 +1834,7 @@ static void null_del_dev(struct nullb *nullb)
 	}
 
 	put_disk(nullb->disk);
+	null_free_zoned_dev(dev);
 	if (dev->queue_mode == NULL_Q_MQ &&
 	    nullb->tag_set == &nullb->__tag_set)
 		blk_mq_free_tag_set(nullb->tag_set);
Signature · 1 line
2.53.0
author maintainer
#1556 lore raw

PATCH 1550/1887 fs/ntfs3: fix integer overflow in MFT cluster validation

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Zhan Xusheng <[email protected]>

[ Upstream commit c510c63873103a5da6a498fe537bdb5d6f8d03a2 ]

In ntfs_init_from_boot(), the boot sector's MFT cluster numbers are validated against the volume size with:

	if (mlcn  * sct_per_clst >= sectors ||
	    mlcn2 * sct_per_clst >= sectors)
		goto out;

mlcn and mlcn2 are u64 fields read directly from the boot sector. sct_per_clst is bounded above by 4096 (true_sectors_per_clst() plus the is_power_of_2() check below it), but the multiplication is done in u64 and wraps when mlcn (or mlcn2) is large enough -- e.g. mlcn near 2^62 with sct_per_clst == 4 wraps to 0, which compares below any non-zero 'sectors', so the check is bypassed and the malformed record is accepted.

The accepted mlcn is then used unchanged in

	sbi->mft.lbo = mlcn << cluster_bits;

In practice the resulting reads fail at the block layer (sb_bread() returns NULL via grow_buffers()'s check_mul_overflow() guard), so today this manifests as mount failing in odd places rather than as something more dangerous, but the validation step is still wrong and there is no reason for callers to rely on the block layer to catch a value that should never have been accepted in the first place.

Use check_mul_overflow() to compute the two sector positions and fail the mount if either multiplication wraps; this preserves the existing semantics (mlcn * sct_per_clst >= sectors) instead of switching to division (mlcn >= sectors / sct_per_clst), which would tighten the check at edge cases where 'sectors' is not a multiple of sct_per_clst. The check_*_overflow() style is the one ntfs3 already uses for similar on-disk arithmetic in fs/ntfs3/run.c.

Fixes 82cae269cfa9 ("fs/ntfs3: Add initialization of super block")
---
 fs/ntfs3/super.c | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)
fs/ntfs3/super.c +11 -5
index 35dd0018e2bd4..9bf327a7cc7b9 100644
@@ -33,6 +33,7 @@
 #include <linux/minmax.h>
 #include <linux/module.h>
 #include <linux/nls.h>
+#include <linux/overflow.h>
 #include <linux/seq_file.h>
 #include <linux/statfs.h>
 
@@ -685,7 +686,7 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size,
 	struct ntfs_sb_info *sbi = sb->s_fs_info;
 	int err;
 	u32 mb, gb, boot_sector_size, sct_per_clst, record_size;
-	u64 sectors, clusters, mlcn, mlcn2;
+	u64 sectors, clusters, mlcn, mlcn2, mft_pos, mft2_pos;
 	struct NTFS_BOOT *boot;
 	struct buffer_head *bh;
 	struct MFT_REC *rec;
@@ -730,10 +731,15 @@ static int ntfs_init_from_boot(struct super_block *sb, u32 sector_size,
 	mlcn2 = le64_to_cpu(boot->mft2_clst);
 	sectors = le64_to_cpu(boot->sectors_per_volume);
 
-	if (mlcn * sct_per_clst >= sectors)
-		goto out;
-
-	if (mlcn2 * sct_per_clst >= sectors)
+	/*
+	 * Convert mlcn/mlcn2 to sector positions before comparing with
+	 * 'sectors'.  All three are u64 values that come from the boot
+	 * sector, so use check_mul_overflow() to keep a wraparound from
+	 * silently bypassing the comparison.
+	 */
+	if (check_mul_overflow(mlcn, (u64)sct_per_clst, &mft_pos) ||
+	    check_mul_overflow(mlcn2, (u64)sct_per_clst, &mft2_pos) ||
+	    mft_pos >= sectors || mft2_pos >= sectors)
 		goto out;
 
 	/* Check MFT record size. */
Signature · 1 line
2.53.0
author maintainer
#1557 lore raw

PATCH 1551/1887 ocfs2: validate DIO orphan slot during inode read

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: ZhengYuan Huang <[email protected]>

[ Upstream commit bb88131c9831075b8dc08cdd375743e5d44c7ca2 ]

[BUG]
A corrupted append-DIO dinode (high byte at offset 0xa1 corrupted from 0 to 1) can carry an i_dio_orphaned_slot outside the mounted filesystem slot range and trigger a use-after-free error:

BUG: KASAN: slab-use-after-free in ocfs2_get_system_file_inode+0x780/0x820 fs/ocfs2/sysfile.c:102
Read of size 8 at addr ffff88800b767c00 by task kworker/u8:3/85
Call Trace:
 ...
 ocfs2_get_system_file_inode+0x780/0x820 fs/ocfs2/sysfile.c:102
 ocfs2_wipe_inode+0x292/0xf70 fs/ocfs2/inode.c:840
 ocfs2_delete_inode fs/ocfs2/inode.c:1155 [inline]
 ocfs2_evict_inode+0x6c9/0x1170 fs/ocfs2/inode.c:1295
 evict+0x38e/0x8f0 fs/inode.c:810
 iput_final fs/inode.c:1914 [inline]
 iput fs/inode.c:1966 [inline]
 iput+0x55b/0x8b0 fs/inode.c:1926
 ocfs2_recover_orphans+0x610/0xe40 fs/ocfs2/journal.c:2374
 ocfs2_complete_recovery+0x5af/0xd00 fs/ocfs2/journal.c:1373
 ...

[CAUSE]
ocfs2_del_inode_from_orphan() uses i_dio_orphaned_slot to index the slot-local system inode cache. The dinode validator does not check this active slot, so an out-of-range value produces an invalid cache entry pointer that is dereferenced as an inode pointer.

[FIX]
Reject an active i_dio_orphaned_slot outside the slot range during dinode validation, before DIO orphan recovery can consume it.

Fixes 06ee5c75b575 ("ocfs2: add functions to add and remove inode in orphan dir")
Cc Mark Fasheh <[email protected]>
Cc Joel Becker <[email protected]>
Cc Junxiao Bi <[email protected]>
Cc Changwei Ge <[email protected]>
Cc Jun Piao <[email protected]>
Cc Heming Zhao <[email protected]>
---
 fs/ocfs2/inode.c | 8 ++++++++
 1 file changed, 8 insertions(+)
fs/ocfs2/inode.c +8 -0
index ed7b4be3ea7a5..8cd76357d3bf9 100644
@@ -1585,6 +1585,14 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
+	if ((le32_to_cpu(di->i_flags) & OCFS2_DIO_ORPHANED_FL) &&
+	    le16_to_cpu(di->i_dio_orphaned_slot) >= OCFS2_SB(sb)->max_slots) {
+		rc = ocfs2_error(sb, "Invalid dinode %llu: DIO orphaned slot %u\n",
+				 (unsigned long long)bh->b_blocknr,
+				 le16_to_cpu(di->i_dio_orphaned_slot));
+		goto bail;
+	}
+
 	if (S_ISLNK(le16_to_cpu(di->i_mode)) &&
 	    !le32_to_cpu(di->i_clusters)) {
 		int max_inline = ocfs2_fast_symlink_chars(sb);
Signature · 1 line
2.53.0
author maintainer
#1558 lore raw

PATCH 1552/1887 ocfs2: validate orphan slot during inode read

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: ZhengYuan Huang <[email protected]>

[ Upstream commit b2d31acbd3b182755b019183fb46949ba5e39b9f ]

Patch series "ocfs2: validate active orphan slots during inode read".

OCFS2 trusts active ordinary and append-DIO orphan slots read from dinodes. A corrupted slot can therefore index osb_orphan_wipes or the slot-local system-inode cache outside their allocations before the corruption is reported.

Patch 1 validates the ordinary orphan slot used by inode wipe processing. Patch 2 validates the append-DIO orphan slot used by DIO completion and orphan recovery. Both checks reject corrupt metadata at the existing inode validation boundary.

This patch (of 2):

[BUG]
A corrupted dinode with OCFS2_ORPHANED_FL can carry an i_orphaned_slot outside the mounted filesystem slot range. ocfs2_wipe_inode() uses it to index osb_orphan_wipes before looking up the orphan directory, causing an out-of-bounds memory access.

BUG: KASAN: slab-use-after-free in ocfs2_get_system_file_inode+0x780/0x820 fs/ocfs2/sysfile.c:102
Read of size 8 at addr ffff88800b767c00 by task kworker/u8:3/85
Call Trace:
 ...
 ocfs2_get_system_file_inode+0x780/0x820 fs/ocfs2/sysfile.c:102
 ocfs2_wipe_inode+0x292/0xf70 fs/ocfs2/inode.c:840
 ocfs2_delete_inode fs/ocfs2/inode.c:1155 [inline]
 ocfs2_evict_inode+0x6c9/0x1170 fs/ocfs2/inode.c:1295
 evict+0x38e/0x8f0 fs/inode.c:810
 iput_final fs/inode.c:1914 [inline]
 iput fs/inode.c:1966 [inline]
 iput+0x55b/0x8b0 fs/inode.c:1926
 ocfs2_recover_orphans+0x610/0xe40 fs/ocfs2/journal.c:2374
 ocfs2_complete_recovery+0x5af/0xd00 fs/ocfs2/journal.c:1373
 ...

[CAUSE]
ocfs2_validate_inode_block() validates i_suballoc_slot but leaves the active ordinary orphan slot unchecked. Downstream consumers assume that the value is smaller than osb->max_slots.

[FIX]
Reject an active i_orphaned_slot outside the slot range during dinode validation, before the inode reaches orphan wipe processing.

Fixes b4df6ed8db0c ("[PATCH] ocfs2: fix orphan recovery deadlock")
Cc Mark Fasheh <[email protected]>
Cc Joel Becker <[email protected]>
Cc Junxiao Bi <[email protected]>
Cc Changwei Ge <[email protected]>
Cc Jun Piao <[email protected]>
Cc Heming Zhao <[email protected]>
---
 fs/ocfs2/inode.c | 8 ++++++++
 1 file changed, 8 insertions(+)
fs/ocfs2/inode.c +8 -0
index 8cd76357d3bf9..10c2759e16e85 100644
@@ -1585,6 +1585,14 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
+	if ((le32_to_cpu(di->i_flags) & OCFS2_ORPHANED_FL) &&
+	    le16_to_cpu(di->i_orphaned_slot) >= OCFS2_SB(sb)->max_slots) {
+		rc = ocfs2_error(sb, "Invalid dinode %llu: orphaned slot %u\n",
+				 (unsigned long long)bh->b_blocknr,
+				 le16_to_cpu(di->i_orphaned_slot));
+		goto bail;
+	}
+
 	if ((le32_to_cpu(di->i_flags) & OCFS2_DIO_ORPHANED_FL) &&
 	    le16_to_cpu(di->i_dio_orphaned_slot) >= OCFS2_SB(sb)->max_slots) {
 		rc = ocfs2_error(sb, "Invalid dinode %llu: DIO orphaned slot %u\n",
Signature · 1 line
2.53.0
author maintainer
#1559 lore raw

PATCH 1553/1887 nvmet: fix max_qid race between configfs and controller allocation

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Maurizio Lombardi <[email protected]>

[ Upstream commit f1a8846e06388113dfdbb89dee005083fa9afdf9 ]

The function nvmet_subsys_attr_qid_max_store() can race against nvmet_alloc_ctrl() when a subsystem's max_qid limit is modified.

Suppose max_qid is currently 64. If nvmet_alloc_ctrl() executes:
ctrl->sqs = kzalloc_objs(struct nvmet_sq *, subsys->max_qid + 1); and at this exact point, a userspace process changes max_qid to 128, nvmet_subsys_attr_qid_max_store() will set the new max_qid value. It attempts to delete active controllers to force a reconnect, but the new controller won't be deleted because it hasn't been added to the subsys->ctrls list yet.

nvmet_alloc_ctrl() then proceeds and adds the new controller to the subsys->ctrls list. Later, when nvmet_install_queue() is called, it will see max_qid set to 128, but the memory allocated for sqs is only sized for 64 entries. This results in a KASAN out-of-bounds warning and potential memory corruptions.

Fix this by protecting the queue allocations and list insertion in nvmet_alloc_ctrl() with down_read(&nvmet_config_sem). Because nvmet_subsys_attr_qid_max_store() acquires down_write(&nvmet_config_sem) to modify the attribute, this safely prevents the configfs writer from modifying max_qid during controller creation.

Copy the max_qid from the subsystem to the controller's structure during the allocation; ctrl->max_qid never changes as long as the controller remains in LIVE state, so this will prevent similar race conditions.

Fixes 3e980f5995e0 ("nvmet: expose max queues to configfs")
Reported-by [email protected]
---
 drivers/nvme/target/admin-cmd.c   |  8 +++---
 drivers/nvme/target/core.c        | 41 ++++++++++++++++++-------------
 drivers/nvme/target/fabrics-cmd.c |  2 +-
 drivers/nvme/target/nvmet.h       |  6 +++++
 4 files changed, 35 insertions(+), 22 deletions(-)
drivers/nvme/target/admin-cmd.c +4 -4
index b28a31cbfeae4..9d4180ceceb23 100644
@@ -831,7 +831,7 @@ u16 nvmet_set_feat_async_event(struct nvmet_req *req, u32 mask)
 
 void nvmet_execute_set_features(struct nvmet_req *req)
 {
-	struct nvmet_subsys *subsys = nvmet_req_subsys(req);
+	struct nvmet_ctrl *ctrl = nvmet_req_ctrl(req);
 	u32 cdw10 = le32_to_cpu(req->cmd->common.cdw10);
 	u32 cdw11 = le32_to_cpu(req->cmd->common.cdw11);
 	u16 status = 0;
@@ -850,7 +850,7 @@ void nvmet_execute_set_features(struct nvmet_req *req)
 			break;
 		}
 		nvmet_set_result(req,
-			(subsys->max_qid - 1) | ((subsys->max_qid - 1) << 16));
+			(ctrl->max_qid - 1) | ((ctrl->max_qid - 1) << 16));
 		break;
 	case NVME_FEAT_KATO:
 		status = nvmet_set_feat_kato(req);
@@ -905,7 +905,7 @@ void nvmet_get_feat_async_event(struct nvmet_req *req)
 
 void nvmet_execute_get_features(struct nvmet_req *req)
 {
-	struct nvmet_subsys *subsys = nvmet_req_subsys(req);
+	struct nvmet_ctrl *ctrl = nvmet_req_ctrl(req);
 	u32 cdw10 = le32_to_cpu(req->cmd->common.cdw10);
 	u16 status = 0;
 
@@ -942,7 +942,7 @@ void nvmet_execute_get_features(struct nvmet_req *req)
 		break;
 	case NVME_FEAT_NUM_QUEUES:
 		nvmet_set_result(req,
-			(subsys->max_qid-1) | ((subsys->max_qid-1) << 16));
+			(ctrl->max_qid-1) | ((ctrl->max_qid-1) << 16));
 		break;
 	case NVME_FEAT_KATO:
 		nvmet_get_feat_kato(req);
drivers/nvme/target/core.c +24 -17

62 lines not on this page · raw message

drivers/nvme/target/fabrics-cmd.c +1 -1
index 6c882ca588bd9..7807cee08790b 100644
@@ -351,7 +351,7 @@ static void nvmet_execute_io_connect(struct nvmet_req *req)
 		goto out;
 	}
 
-	if (unlikely(qid > ctrl->subsys->max_qid)) {
+	if (unlikely(qid > ctrl->max_qid)) {
 		pr_warn("invalid queue id (%d)\n", qid);
 		status = NVME_SC_CONNECT_INVALID_PARAM | NVME_STATUS_DNR;
 		req->cqe->result.u32 = IPO_IATTR_CONNECT_SQE(qid);
drivers/nvme/target/nvmet.h +6 -0
index 5a8bdd51e30c9..09778c5d26791 100644
@@ -190,6 +190,7 @@ struct nvmet_ctrl {
 
 	uuid_t			hostid;
 	u16			cntlid;
+	u16			max_qid;
 	u32			kato;
 
 	struct nvmet_port	*port;
@@ -617,6 +618,11 @@ static inline struct nvmet_subsys *nvmet_req_subsys(struct nvmet_req *req)
 	return req->sq->ctrl->subsys;
 }
 
+static inline struct nvmet_ctrl *nvmet_req_ctrl(struct nvmet_req *req)
+{
+	return req->sq->ctrl;
+}
+
 static inline bool nvmet_is_disc_subsys(struct nvmet_subsys *subsys)
 {
     return subsys->type != NVME_NQN_NVME;
Signature · 1 line
2.53.0
author maintainer
#1560 lore raw

PATCH 1554/1887 clocksource/drivers/samsung_pwm: Switch to raw_spinlock_t type

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Marek Szyprowski <[email protected]>

[ Upstream commit 3b212f9d70805d91febae01d618868f4860e267c ]

Samsung PWM timer might be used as a clock source on some legacy systems. When PREEMPT_RT is enabled on ARM, regular spinlock is converted to a sleeping lock (mutex-based), which must not be used in atomic context such as hard interrupt handlers. Switch the samsung_pwm_lock to the raw_spinlock, which remains a true non-sleeping spinlock even under PREEMPT_RT.

Fixes 7aac482e6290 ("clocksource: samsung_pwm_timer: Make PWM spinlock global")
Fixes f11899894c0a ("clocksource: add samsung pwm timer driver")
---
 drivers/clocksource/samsung_pwm_timer.c | 22 +++++++++++-----------
 drivers/pwm/pwm-samsung.c               | 22 +++++++++++-----------
 include/clocksource/samsung_pwm.h       |  2 +-
 3 files changed, 23 insertions(+), 23 deletions(-)
drivers/clocksource/samsung_pwm_timer.c +11 -11
index 6e46781bc9acf..896ba9fe5e75f 100644
@@ -56,7 +56,7 @@
 #define TCON_AUTORELOAD(chan)		\
 	((chan < 5) ? _TCON_AUTORELOAD(chan) : _TCON_AUTORELOAD4(chan))
 
-DEFINE_SPINLOCK(samsung_pwm_lock);
+DEFINE_RAW_SPINLOCK(samsung_pwm_lock);
 EXPORT_SYMBOL(samsung_pwm_lock);
 
 struct samsung_pwm_clocksource {
@@ -87,14 +87,14 @@ static void samsung_timer_set_prescale(unsigned int channel, u16 prescale)
 	if (channel >= 2)
 		shift = TCFG0_PRESCALER1_SHIFT;
 
-	spin_lock_irqsave(&samsung_pwm_lock, flags);
+	raw_spin_lock_irqsave(&samsung_pwm_lock, flags);
 
 	reg = readl(pwm.base + REG_TCFG0);
 	reg &= ~(TCFG0_PRESCALER_MASK << shift);
 	reg |= (prescale - 1) << shift;
 	writel(reg, pwm.base + REG_TCFG0);
 
-	spin_unlock_irqrestore(&samsung_pwm_lock, flags);
+	raw_spin_unlock_irqrestore(&samsung_pwm_lock, flags);
 }
 
 static void samsung_timer_set_divisor(unsigned int channel, u8 divisor)
@@ -106,14 +106,14 @@ static void samsung_timer_set_divisor(unsigned int channel, u8 divisor)
 
 	bits = (fls(divisor) - 1) - pwm.variant.div_base;
 
-	spin_lock_irqsave(&samsung_pwm_lock, flags);
+	raw_spin_lock_irqsave(&samsung_pwm_lock, flags);
 
 	reg = readl(pwm.base + REG_TCFG1);
 	reg &= ~(TCFG1_MUX_MASK << shift);
 	reg |= bits << shift;
 	writel(reg, pwm.base + REG_TCFG1);
 
-	spin_unlock_irqrestore(&samsung_pwm_lock, flags);
+	raw_spin_unlock_irqrestore(&samsung_pwm_lock, flags);
 }
 
 static void samsung_time_stop(unsigned int channel)
@@ -124,13 +124,13 @@ static void samsung_time_stop(unsigned int channel)
 	if (channel > 0)
 		++channel;
 
-	spin_lock_irqsave(&samsung_pwm_lock, flags);
+	raw_spin_lock_irqsave(&samsung_pwm_lock, flags);
 
 	tcon = readl_relaxed(pwm.base + REG_TCON);
 	tcon &= ~TCON_START(channel);
 	writel_relaxed(tcon, pwm.base + REG_TCON);
 
-	spin_unlock_irqrestore(&samsung_pwm_lock, flags);
+	raw_spin_unlock_irqrestore(&samsung_pwm_lock, flags);
 }
 
 static void samsung_time_setup(unsigned int channel, unsigned long tcnt)
@@ -142,7 +142,7 @@ static void samsung_time_setup(unsigned int channel, unsigned long tcnt)
 	if (tcon_chan > 0)
 		++tcon_chan;
 
-	spin_lock_irqsave(&samsung_pwm_lock, flags);
+	raw_spin_lock_irqsave(&samsung_pwm_lock, flags);
 
 	tcon = readl_relaxed(pwm.base + REG_TCON);
 
@@ -153,7 +153,7 @@ static void samsung_time_setup(unsigned int channel, unsigned long tcnt)
 	writel_relaxed(tcnt, pwm.base + REG_TCMPB(channel));
 	writel_relaxed(tcon, pwm.base + REG_TCON);
 
-	spin_unlock_irqrestore(&samsung_pwm_lock, flags);
+	raw_spin_unlock_irqrestore(&samsung_pwm_lock, flags);
 }
 
 static void samsung_time_start(unsigned int channel, bool periodic)
@@ -164,7 +164,7 @@ static void samsung_time_start(unsigned int channel, bool periodic)
 	if (channel > 0)
 		++channel;
 
-	spin_lock_irqsave(&samsung_pwm_lock, flags);
+	raw_spin_lock_irqsave(&samsung_pwm_lock, flags);
 
 	tcon = readl_relaxed(pwm.base + REG_TCON);
 
@@ -178,7 +178,7 @@ static void samsung_time_start(unsigned int channel, bool periodic)
 
 	writel_relaxed(tcon, pwm.base + REG_TCON);
 
-	spin_unlock_irqrestore(&samsung_pwm_lock, flags);
+	raw_spin_unlock_irqrestore(&samsung_pwm_lock, flags);
 }
 
 static int samsung_set_next_event(unsigned long cycles,
drivers/pwm/pwm-samsung.c +11 -11
index 9c5b4f515641b..96f3ff4e6be16 100644
@@ -102,7 +102,7 @@ struct samsung_pwm_chip {
  * IP. Should this change, both drivers will need to be modified to
  * properly synchronize accesses to particular instances.
  */
-static DEFINE_SPINLOCK(samsung_pwm_lock);
+static DEFINE_RAW_SPINLOCK(samsung_pwm_lock);
 #endif
 
 static inline
@@ -141,14 +141,14 @@ static void pwm_samsung_set_divisor(struct samsung_pwm_chip *pwm,
 
 	bits = (fls(divisor) - 1) - pwm->variant.div_base;
 
-	spin_lock_irqsave(&samsung_pwm_lock, flags);
+	raw_spin_lock_irqsave(&samsung_pwm_lock, flags);
 
 	reg = readl(pwm->base + REG_TCFG1);
 	reg &= ~(TCFG1_MUX_MASK << shift);
 	reg |= bits << shift;
 	writel(reg, pwm->base + REG_TCFG1);
 
-	spin_unlock_irqrestore(&samsung_pwm_lock, flags);
+	raw_spin_unlock_irqrestore(&samsung_pwm_lock, flags);
 }
 
 static int pwm_samsung_is_tdiv(struct samsung_pwm_chip *chip, unsigned int chan)
@@ -258,7 +258,7 @@ static int pwm_samsung_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 	unsigned long flags;
 	u32 tcon;
 
-	spin_lock_irqsave(&samsung_pwm_lock, flags);
+	raw_spin_lock_irqsave(&samsung_pwm_lock, flags);
 
 	tcon = readl(our_chip->base + REG_TCON);
 
@@ -272,7 +272,7 @@ static int pwm_samsung_enable(struct pwm_chip *chip, struct pwm_device *pwm)
 
 	our_chip->disabled_mask &= ~BIT(pwm->hwpwm);
 
-	spin_unlock_irqrestore(&samsung_pwm_lock, flags);
+	raw_spin_unlock_irqrestore(&samsung_pwm_lock, flags);
 
 	return 0;
 }
@@ -284,7 +284,7 @@ static void pwm_samsung_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 	unsigned long flags;
 	u32 tcon;
 
-	spin_lock_irqsave(&samsung_pwm_lock, flags);
+	raw_spin_lock_irqsave(&samsung_pwm_lock, flags);
 
 	tcon = readl(our_chip->base + REG_TCON);
 	tcon &= ~TCON_AUTORELOAD(tcon_chan);
@@ -299,7 +299,7 @@ static void pwm_samsung_disable(struct pwm_chip *chip, struct pwm_device *pwm)
 
 	our_chip->disabled_mask |= BIT(pwm->hwpwm);
 
-	spin_unlock_irqrestore(&samsung_pwm_lock, flags);
+	raw_spin_unlock_irqrestore(&samsung_pwm_lock, flags);
 }
 
 static void pwm_samsung_manual_update(struct samsung_pwm_chip *chip,
@@ -307,11 +307,11 @@ static void pwm_samsung_manual_update(struct samsung_pwm_chip *chip,
 {
 	unsigned long flags;
 
-	spin_lock_irqsave(&samsung_pwm_lock, flags);
+	raw_spin_lock_irqsave(&samsung_pwm_lock, flags);
 
 	__pwm_samsung_manual_update(chip, pwm);
 
-	spin_unlock_irqrestore(&samsung_pwm_lock, flags);
+	raw_spin_unlock_irqrestore(&samsung_pwm_lock, flags);
 }
 
 static int __pwm_samsung_config(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -400,7 +400,7 @@ static void pwm_samsung_set_invert(struct samsung_pwm_chip *chip,
 	unsigned long flags;
 	u32 tcon;
 
-	spin_lock_irqsave(&samsung_pwm_lock, flags);
+	raw_spin_lock_irqsave(&samsung_pwm_lock, flags);
 
 	tcon = readl(chip->base + REG_TCON);
 
@@ -414,7 +414,7 @@ static void pwm_samsung_set_invert(struct samsung_pwm_chip *chip,
 
 	writel(tcon, chip->base + REG_TCON);
 
-	spin_unlock_irqrestore(&samsung_pwm_lock, flags);
+	raw_spin_unlock_irqrestore(&samsung_pwm_lock, flags);
 }
 
 static int pwm_samsung_set_polarity(struct pwm_chip *chip,
include/clocksource/samsung_pwm.h +1 -1
index 9b435caa95fef..36f6f246e5594 100644
@@ -15,7 +15,7 @@
  * spinlock is not shared between both drivers.
  */
 #ifdef CONFIG_CLKSRC_SAMSUNG_PWM
-extern spinlock_t samsung_pwm_lock;
+extern raw_spinlock_t samsung_pwm_lock;
 #endif
 
 struct samsung_pwm_variant {
Signature · 1 line
2.53.0
author maintainer
#1561 lore raw

PATCH 1555/1887 firewire: core: fix memory leak in error path of build_tree()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Takashi Sakamoto <[email protected]>

[ Upstream commit 05bfb1327dc5fb61528bab31cd8f0c1e4bddec23 ]

In the error path of build_tree(), node instances can remain in the local linked list when the function returns.

Whenever an invalid value is detected in the self ID sequence, each allocated node instance is either an entry in the linked list or an entry in the ports array of its parent node. Therefore, the allocate node instances can be safely released by traversing the linked list from its head.

Release the remaining node instances with for_each_fw_node() before returning to the caller.

---
 drivers/firewire/core-topology.c | 40 +++++++++++++++++++++-----------
 1 file changed, 26 insertions(+), 14 deletions(-)
drivers/firewire/core-topology.c +26 -14
index f40c815343812..ddcc3fb6cfbd4 100644
@@ -161,6 +161,17 @@ static inline struct fw_node *fw_node(struct list_head *l)
 	return list_entry(l, struct fw_node, link);
 }
 
+typedef void (*fw_node_callback_t)(struct fw_card *card, struct fw_node *node,
+				   struct fw_node *parent);
+
+static void for_each_fw_node(struct fw_card *card, struct fw_node *root,
+			     fw_node_callback_t callback);
+
+static void free_fw_node(struct fw_card *card, struct fw_node *node, struct fw_node *parent)
+{
+	fw_node_put(node);
+}
+
 /*
  * This function builds the tree representation of the topology given
  * by the self IDs from the latest bus reset.  During the construction
@@ -193,19 +204,19 @@ static struct fw_node *build_tree(struct fw_card *card,
 
 		if (next_sid == NULL) {
 			fw_err(card, "inconsistent extended self IDs\n");
-			return NULL;
+			goto error;
 		}
 
 		q = *sid;
 		if (phy_id != SELF_ID_PHY_ID(q)) {
 			fw_err(card, "PHY ID mismatch in self ID: %d != %d\n",
 			       phy_id, SELF_ID_PHY_ID(q));
-			return NULL;
+			goto error;
 		}
 
 		if (child_port_count > stack_depth) {
 			fw_err(card, "topology stack underflow\n");
-			return NULL;
+			goto error;
 		}
 
 		/*
@@ -223,7 +234,7 @@ static struct fw_node *build_tree(struct fw_card *card,
 		node = fw_node_create(q, port_count, card->color);
 		if (node == NULL) {
 			fw_err(card, "out of memory while building topology\n");
-			return NULL;
+			goto error;
 		}
 
 		if (phy_id == (card->node_id & 0x3f))
@@ -264,6 +275,11 @@ static struct fw_node *build_tree(struct fw_card *card,
 			}
 		}
 
+		/* Pop the child nodes off the stack and push the new node. */
+		__list_del(h->prev, &stack);
+		list_add_tail(&node->link, &stack);
+		stack_depth += 1 - child_port_count;
+
 		/*
 		 * Check that the node reports exactly one parent
 		 * port, except for the root, which of course should
@@ -273,14 +289,9 @@ static struct fw_node *build_tree(struct fw_card *card,
 		    (next_sid < end && parent_count != 1)) {
 			fw_err(card, "parent port inconsistency for node %d: "
 			       "parent_count=%d\n", phy_id, parent_count);
-			return NULL;
+			goto error;
 		}
 
-		/* Pop the child nodes off the stack and push the new node. */
-		__list_del(h->prev, &stack);
-		list_add_tail(&node->link, &stack);
-		stack_depth += 1 - child_port_count;
-
 		if (node->phy_speed == SCODE_BETA &&
 		    parent_count + child_port_count > 1)
 			beta_repeaters_present = true;
@@ -304,12 +315,13 @@ static struct fw_node *build_tree(struct fw_card *card,
 	card->beta_repeaters_present = beta_repeaters_present;
 
 	return local_node;
+error:
+	++card->color;
+	list_for_each_entry_safe(node, child, &stack, link)
+		for_each_fw_node(card, node, free_fw_node);
+	return NULL;
 }
 
-typedef void (*fw_node_callback_t)(struct fw_card * card,
-				   struct fw_node * node,
-				   struct fw_node * parent);
-
 static void for_each_fw_node(struct fw_card *card, struct fw_node *root,
 			     fw_node_callback_t callback)
 {
Signature · 1 line
2.53.0
author maintainer
#1562 lore raw

PATCH 1556/1887 nvme-apple: Destroy the admin queue on removal

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Sven Peter <[email protected]>

[ Upstream commit 87d5b9864c8118d26f54de4b66d2bddf2c659272 ]

The admin queue is allocated with blk_mq_alloc_queue() but never destroyed. nvme_free_ctrl() only drops the last reference and blk_mq_exit_queue() and blk_sync_queue() never run: the hctx is never moved to q->unused_hctx_list and the timeout timer and work stay armed on a queue that is about to be freed which will eventually oops inside blk_mq_timeout_work().

This can only be triggered when the controller fails to come up and is then immediately torn down again which is why no one ever ran into this before.

Let's just copy what the pcie driver does: unquiesce and destroy the admin queue before nvme_uninit_ctrl().

With this the following WARN followed by a panic no longer happens:

  WARNING: block/blk-mq.c:4390 at blk_mq_release+0x194/0x238, CPU#4: kworker/u34:4/119
  CPU: 4 UID: 0 PID: 119 Comm: kworker/u34:4 Not tainted 7.2.0-rc1-dirty #248 PREEMPT
  Hardware name: Apple Mac mini (M1, 2020) (DT)
  Workqueue: nvme-wq apple_nvme_remove_dead_ctrl_work
  pstate: 61400005 (nZCv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--)
  pc : blk_mq_release+0x194/0x238
  lr : blk_mq_release+0x58/0x238
  sp : ffffc000833a3b50
  x29: ffffc000833a3b50 x28: ffff80001d0450f8 x27: ffff800020c95200
  x26: 0000000000000088 x25: 0000000000000000 x24: ffff800020f36805
  x23: 0000000000000000 x22: ffffc00081a86878 x21: ffff800020be9c60
  x20: 0000000000000000 x19: ffff800022501698 x18: 000000000000000a
  x17: 7365757165722066 x16: 666f7265776f7020 x15: 0000000000000000
  x14: 0000000000000028 x13: 0000000000004def x12: 0000000000000003
  x11: 0000000000000000 x10: 0000000000000000 x9 : ffffc000805b4fc8
  x8 : ffffc00081915820 x7 : ffffc00081c4f3c8 x6 : 0000000000000001
  x5 : 0000000000000004 x4 : ffff800022498d80 x3 : ffffc000833a3b14
  x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffff800022501698
  Call trace:
   blk_mq_release+0x194/0x238 (P)
   blk_put_queue+0x8c/0xf0
   nvme_free_ctrl+0x4c/0x260
   device_release+0x44/0x128
   kobject_put+0xa0/0x120
   put_device+0x1c/0x40
   nvme_uninit_ctrl+0x48/0x60
   apple_nvme_remove+0x54/0xb0
   platform_remove+0x28/0x40
   device_remove+0x54/0x98
   device_release_driver_internal+
   device_release_driver+0x20/0x38
   apple_nvme_remove_dead_ctrl_wor
   process_one_work+0x1f4/0x770
   worker_thread+0x1b8/0x360
   kthread+0x140/0x160
   ret_from_fork+0x10/0x20
  irq event stamp: 448
  hardirqs last  enabled at (447):in_unlock_irqrestore+0x74/0x80
  hardirqs last disabled at (448): [<ffffc000811cf5c0>] el1_brk64+0x20/0x60
  softirqs last  enabled at (0): [ess+0xb28/0x2698
  softirqs last disabled at (0): [<0000000000000000>] 0x0
  ---[ end trace 0000000000000000
  Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000
  Mem abort info:
    ESR = 0x0000000096000005
    EC = 0x25: DABT (current EL),
    SET = 0, FnV = 0
    EA = 0, S1PTW = 0
    FSC = 0x05: level 1 translation fault
  Data abort info:
    ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000
    CM = 0, WnR = 0, TnD = 0, TagA
    GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
  [0000000000000000] user address
  Internal error: Oops: 0000000096000005 [#1]  SMP
  CPU: 7 UID: 0 PID: 54 Comm: kwor          7.2.0-rc1-dirty #248PREEMPT
  Tainted: [W]=WARN
  Hardware name: Apple Mac mini (M1, 2020) (DT)
  Workqueue: kblockd blk_mq_timeou
  pstate: 01400005 (nzcv daif +PAN -UAO -TCO +DIT -SSBS BTYPE=--)
  pc : percpu_ref_tryget_many.cons
  lr : percpu_ref_tryget_many.constprop.0+0xc0/0x168
  sp : ffffc000829cbce0
  x29: ffffc000829cbce0 x28: ffff800020be9f48 x27: ffff800013e503c0
  x26: 0000000000000108 x25: 000009c05
  x23: 0000000000000000 x22: ffffc000819f5000 x21: ffff800020be9f48
  x20: ffff8001deda4808 x19: ffff8000a
  x17: 00000000580e1fac x16: ffffc00082bbbb7c x15: 0000000000000000
  x14: 0000000000000028 x13: 000000001
  x11: 0000000000000000 x10: 0000000000000000 x9 : ffffc000829cbc20
  x8 : ffffc00081915820 x7 : ffffc0001
  x5 : ffff80001ca77d08 x4 : 0000000000000000 x3 : ffff80001ca77cb8
  x2 : 0000000000000000 x1 : 000000007
  Call trace:
   percpu_ref_tryget_many.constpro
   blk_mq_timeout_work+0x48/0x298
   process_one_work+0x1f4/0x770
   worker_thread+0x1b8/0x360
   kthread+0x140/0x160
   ret_from_fork+0x10/0x20
  Code: 91282000 97ed44b2 17ffffd2
  ---[ end trace 0000000000000000 ]---
Fixes 5bd2927aceba ("nvme-apple: Add initial Apple SoC NVMe driver")
---
 drivers/nvme/host/apple.c | 9 +++++++++
 1 file changed, 9 insertions(+)
drivers/nvme/host/apple.c +9 -0
index 4822248a624ad..2d8904505b5ac 100644
@@ -1566,6 +1566,15 @@ static int apple_nvme_remove(struct platform_device *pdev)
 	nvme_stop_ctrl(&anv->ctrl);
 	nvme_remove_namespaces(&anv->ctrl);
 	apple_nvme_disable(anv, true);
+	if (anv->ctrl.admin_q && !blk_queue_dying(anv->ctrl.admin_q)) {
+		/*
+		 * If the controller was reset during removal, it's possible
+		 * user requests may be waiting on a stopped queue. Start the
+		 * queue to flush these to completion.
+		 */
+		nvme_start_admin_queue(&anv->ctrl);
+		blk_mq_destroy_queue(anv->ctrl.admin_q);
+	}
 	nvme_uninit_ctrl(&anv->ctrl);
 
 	if (apple_rtkit_is_running(anv->rtk))
Signature · 1 line
2.53.0
author maintainer
#1563 lore raw

PATCH 1557/1887 amt: Dont support cross-netns setup.

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Kuniyuki Iwashima <[email protected]>

[ Upstream commit e99ecc3046ea5f5c6b5e1f8b4ef854c6c6998e06 ]

When a lower device is unregistered, amt_device_event() tries to unregister its upper AMT device, but it has two problems.

1. amt_lookup_upper_dev() looks up an upper device in the lower device's netns only

2. amt_device_event() unregisters a single upper device only

If AMT device is created on a lower device in another netns, removing the lower device triggers the splat below and gets stuck until all upper devices are removed. [0]

The cross-netns setup seems unintentional considering 1. and the following points:

* amt_link_setup() sets dev->netns_immutable to true
* skb_scrub_packet() is not called in the fast path
* iproute2 binary fails to find cross-netns lower device via link-netns:
# ip -n ns1 link add amt0 link-netns ns2 type amt dev veth1 Cannot find device "veth1"

Instead of supporting it properly and preparing for per-netns netdev unreg, let's forbid cross-netns setup.

Note that the problem 2. needs a separate fix.

[0]:

WARNING: net/core/dev.c:12518 at unregister_netdevice_many_notify+0x1cce/0x2250, CPU#48: ip/2031
Modules linked in:
CPU: 48 UID: 0 PID: 2031 Comm: ip Not tainted 7.2.0-rc5+ #27 PREEMPT(full)
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
RIP: 0010:unregister_netdevice_many_notify (net/core/dev.c:12518)
Code: 89 ef e8 d5 52 ae fe e9 d0 f4 ff ff 48 8d 3d f9 3b 9c 02 48 c7 c6 c0 0b 63 84 ba ab 1f 00 00 67 48 0f b9 3a e9 65 ff ff ff 90 <0f> 0b 90 eb 81 48 8d 3d f6 3b 9c 02 48 c7 c6 c0 0b 63 84 ba e2 1f
RSP: 0018:ffffc90004abf160 EFLAGS: 00010212
RAX: ffff888104d38260 RBX: ffff88800b0911b8 RCX: dffffc0000000000
RDX: 0000000000000000 RSI: 0000000000000008 RDI: ffffffff85b9f880
RBP: ffffc90004abf2d0 R08: ffffffff85b9f887 R09: 1ffffffff0b73f10
R10: dffffc0000000000 R11: fffffbfff0b73f11 R12: ffff88800b091d08
R13: ffff88800b091178 R14: dffffc0000000000 R15: ffff88800b091000
FS:  00007f555b86c600(0000) GS:ffff8881942a0000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000562107d489c0 CR3: 0000000109a40002 CR4: 0000000000372ef0
Call Trace:
 <TASK>
 rtnl_dellink (net/core/rtnetlink.c:3632 net/core/rtnetlink.c:3674)
 rtnetlink_rcv_msg (net/core/rtnetlink.c:7112)
 netlink_rcv_skb (net/netlink/af_netlink.c:2556)
 netlink_unicast (net/netlink/af_netlink.c:1319)
 netlink_sendmsg (net/netlink/af_netlink.c:1900)
 ____sys_sendmsg (net/socket.c:775)
 __sys_sendmsg (net/socket.c:2738)
 do_syscall_64 (arch/x86/entry/syscall_64.c:63)
 entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
...
unregister_netdevice: waiting for veth0 to become free. Usage count = 7
ref_tracker: netdev@ffff88800d7496d8 has 3/3 users at
     __netdev_adjacent_dev_insert (./include/linux/netdevice.h:4525 ./include/linux/netdevice.h:4554 net/core/dev.c:8791)
     __netdev_upper_dev_link (net/core/dev.c:8879 net/core/dev.c:8963)
     netdev_upper_dev_link (net/core/dev.c:9009)
     amt_newlink (drivers/net/amt.c:3321)
---
 drivers/net/amt.c | 3 +++
 1 file changed, 3 insertions(+)
drivers/net/amt.c +3 -0
index 33614c802be1c..9bdc6c0e7bf9c 100644
@@ -3195,6 +3195,9 @@ static int amt_newlink(struct net *net, struct net_device *dev,
 	struct amt_dev *amt = netdev_priv(dev);
 	int err = -EINVAL;
 
+	if (!net_eq(net, dev_net(dev)))
+		return err;
+
 	amt->net = net;
 	amt->mode = nla_get_u32(data[IFLA_AMT_MODE]);
 
Signature · 1 line
2.53.0
author maintainer
#1564 lore raw

PATCH 1558/1887 NFS: Return a delegation the client fails to record

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Chuck Lever <[email protected]>

[ Upstream commit 220af23d863995091f0edeb1e6aa0945b3db8b37 ]

When an NFS server grants a delegation in an OPEN reply, nfs_inode_set_delegation() records it on the client. However, three of its error flows return without sending DELEGRETURN.

A delegation can be relinquished only by DELEGRETURN (RFC 8881 Section 20.2.4), so dropping one silently leaves the server believing the client still holds it. If the server happens to recall that delegation, the client answers CB_RECALL with NFS4ERR_BADHANDLE because it has no record of the stateid. The server revokes the delegation and moves it onto its cl_revoked list, because the client never sends the FREE_STATEID that would drain it. Every subsequent SEQUENCE reply then carries SEQ4_STATUS_RECALLABLE_STATE_REVOKED, and the client's state manager loops issuing TEST_STATEID across its delegations without ever clearing the condition.

The window is easy to reach now that a server offers a write delegation on any write OPEN: a delegation recalled for one opener races a re-open that the server answers with a fresh write delegation.

Instead of dropping it, hand the delegation back during these error flows.

Fixes ade04647dd56 ("NFSv4: Ensure we honour NFS_DELEGATION_RETURNING in nfs_inode_set_delegation()")
---
 fs/nfs/delegation.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)
fs/nfs/delegation.c +14 -4
index 9002b479a125b..64a869d40774b 100644
@@ -443,11 +443,14 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
 	struct nfs_inode *nfsi = NFS_I(inode);
 	struct nfs_delegation *delegation, *old_delegation;
 	struct nfs_delegation *freeme = NULL;
+	bool orphaned = false;
 	int status = 0;
 
 	delegation = kmalloc(sizeof(*delegation), GFP_KERNEL_ACCOUNT);
-	if (delegation == NULL)
+	if (delegation == NULL) {
+		nfs4_proc_delegreturn(inode, cred, stateid, 0);
 		return -ENOMEM;
+	}
 	nfs4_stateid_copy(&delegation->stateid, stateid);
 	refcount_set(&delegation->refcount, 1);
 	delegation->type = type;
@@ -489,12 +492,16 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
 			goto out;
 		}
 		if (test_and_set_bit(NFS_DELEGATION_RETURNING,
-					&old_delegation->flags))
+					&old_delegation->flags)) {
+			orphaned = true;
 			goto out;
+		}
 	}
 	freeme = nfs_detach_delegation_locked(nfsi, old_delegation, clp);
-	if (freeme == NULL)
+	if (freeme == NULL) {
+		orphaned = true;
 		goto out;
+	}
 add_new:
 	/*
 	 * If we didn't revalidate the change attribute before setting
@@ -521,8 +528,11 @@ int nfs_inode_set_delegation(struct inode *inode, const struct cred *cred,
 	trace_nfs4_set_delegation(inode, type);
 out:
 	spin_unlock(&clp->cl_lock);
-	if (delegation != NULL)
+	if (delegation != NULL) {
+		if (orphaned)
+			nfs_do_return_delegation(inode, delegation, 0);
 		__nfs_free_delegation(delegation);
+	}
 	if (freeme != NULL) {
 		nfs_do_return_delegation(inode, freeme, 0);
 		nfs_free_delegation(freeme);
Signature · 1 line
2.53.0
author maintainer
#1565 lore raw

PATCH 1559/1887 fs: dlm: use listen sock as dlm running indicator

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Alexander Aring <[email protected]>

[ Upstream commit 1037c2a94ab51997d8b1ef9e7f6ed697e6e17d84 ]

This patch will switch from dlm_allow_conn to check if dlm lowcomms is running or not to if we actually have a listen socket set or not. The list socket will be set and unset in lowcomms start and shutdown functionality. To synchronize with data_ready() callback we will set the socket callback to NULL while socket lock is held.

Stable-dep-of: 33930840b5f0 ("sunrpc: xprtsock: annotate shared socket callbacks with READ_ONCE/WRITE_ONCE")

---
 fs/dlm/config.c   |  4 ++--
 fs/dlm/lowcomms.c | 17 ++++++-----------
 fs/dlm/lowcomms.h |  4 ++--
 3 files changed, 10 insertions(+), 15 deletions(-)
fs/dlm/config.c +2 -2
index ac8b62106ce0e..20b60709eccf0 100644
@@ -183,7 +183,7 @@ static int dlm_check_protocol_and_dlm_running(unsigned int x)
 		return -EINVAL;
 	}
 
-	if (dlm_allow_conn)
+	if (dlm_lowcomms_is_running())
 		return -EBUSY;
 
 	return 0;
@@ -194,7 +194,7 @@ static int dlm_check_zero_and_dlm_running(unsigned int x)
 	if (!x)
 		return -EINVAL;
 
-	if (dlm_allow_conn)
+	if (dlm_lowcomms_is_running())
 		return -EBUSY;
 
 	return 0;
fs/dlm/lowcomms.c +6 -11
index 51a641822d6c4..cefc930c2e429 100644
@@ -176,7 +176,6 @@ static DEFINE_SPINLOCK(dlm_node_addrs_spin);
 static struct listen_connection listen_con;
 static struct sockaddr_storage dlm_local_addr[DLM_MAX_ADDR_COUNT];
 static int dlm_local_count;
-int dlm_allow_conn;
 
 /* Work queues */
 static struct workqueue_struct *recv_workqueue;
@@ -191,6 +190,11 @@ static const struct dlm_proto_ops *dlm_proto_ops;
 static void process_recv_sockets(struct work_struct *work);
 static void process_send_sockets(struct work_struct *work);
 
+bool dlm_lowcomms_is_running(void)
+{
+	return !!listen_con.sock;
+}
+
 static void writequeue_entry_ctor(void *data)
 {
 	struct writequeue_entry *entry = data;
@@ -514,9 +518,6 @@ static void lowcomms_data_ready(struct sock *sk)
 
 static void lowcomms_listen_data_ready(struct sock *sk)
 {
-	if (!dlm_allow_conn)
-		return;
-
 	queue_work(recv_workqueue, &listen_con.rwork);
 }
 
@@ -1676,10 +1677,7 @@ void dlm_lowcomms_shutdown(void)
 {
 	int idx;
 
-	/* Set all the flags to prevent any
-	 * socket activity.
-	 */
-	dlm_allow_conn = 0;
+	restore_callbacks(listen_con.sock);
 
 	if (recv_workqueue)
 		flush_workqueue(recv_workqueue);
@@ -1982,8 +1980,6 @@ int dlm_lowcomms_start(void)
 	if (error)
 		goto fail;
 
-	dlm_allow_conn = 1;
-
 	/* Start listening */
 	switch (dlm_config.ci_protocol) {
 	case DLM_PROTO_TCP:
@@ -2008,7 +2004,6 @@ int dlm_lowcomms_start(void)
 fail_listen:
 	dlm_proto_ops = NULL;
 fail_proto_ops:
-	dlm_allow_conn = 0;
 	work_stop();
 fail:
 	return error;
fs/dlm/lowcomms.h +2 -2
index bbce7a18416dc..acaf1b0b3885b 100644
@@ -29,8 +29,8 @@ static inline int nodeid_hash(int nodeid)
 	return nodeid & (CONN_HASH_SIZE-1);
 }
 
-/* switch to check if dlm is running */
-extern int dlm_allow_conn;
+/* check if dlm is running */
+bool dlm_lowcomms_is_running(void);
 
 int dlm_lowcomms_start(void);
 void dlm_lowcomms_shutdown(void);
Signature · 1 line
2.53.0
author maintainer
#1566 lore raw

PATCH 1560/1887 fs: dlm: remove socket shutdown handling

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Alexander Aring <[email protected]>

[ Upstream commit 4f567acb0b8622fe265aac4b0037a03ef544ba24 ]

Since commit 489d8e559c65 ("fs: dlm: add reliable connection if reconnect") we have functionality like TCP offers for half-closed sockets on dlm application protocol layer. This feature is required because the cluster manager events about leaving resource memberships can be locally already occurred but other cluster nodes having a pending leaving membership over the cluster manager protocol happening. In this time the local dlm node already shutdown it's connection and don't transmit anymore any new dlm messages, but however it still needs to be able to accept dlm messages because the pending leave membership request of the cluster manager protocol which the dlm kernel implementation has no control about it.

We have this functionality on the application for two reasons, the main reason is that SCTP does not support such functionality on socket layer. But we can do it inside application layer.

Another small issue is that this feature is broken in the TCP world because some NAT devices does not implement such functionality correctly. This is the same reason why the reliable connection session layer in DLM exists. We give up on middle devices in the networking which sends e.g. TCP resets out. In DLM we cannot have any message dropping and we ensure it over a session layer that it can't happen.

Back to the half-closed grace shutdown handling. It's not necessary anymore to do it on socket layer (which is only support for TCP sockets) because we do it on application layer. This patch removes this handling, if there are still issues then we have a problem on the application layer for such handling.

Stable-dep-of: 33930840b5f0 ("sunrpc: xprtsock: annotate shared socket callbacks with READ_ONCE/WRITE_ONCE")

---
 fs/dlm/lowcomms.c | 127 ++++++++--------------------------------------
 fs/dlm/lowcomms.h |   1 +
 fs/dlm/midcomms.c |   6 ++-
 3 files changed, 27 insertions(+), 107 deletions(-)
fs/dlm/lowcomms.c +22 -105
index cefc930c2e429..2151579094255 100644
@@ -65,7 +65,6 @@
 
 /* Number of messages to send before rescheduling */
 #define MAX_SEND_MSG_COUNT 25
-#define DLM_SHUTDOWN_WAIT_TIMEOUT msecs_to_jiffies(10000)
 
 struct connection {
 	struct socket *sock;	/* NULL if not connected */
@@ -79,14 +78,11 @@ struct connection {
 #define CF_CLOSE 6
 #define CF_APP_LIMITED 7
 #define CF_CLOSING 8
-#define CF_SHUTDOWN 9
-#define CF_CONNECTED 10
-#define CF_RECONNECT 11
-#define CF_DELAY_CONNECT 12
-#define CF_EOF 13
+#define CF_CONNECTED 9
+#define CF_RECONNECT 10
+#define CF_DELAY_CONNECT 11
 	struct list_head writequeue;  /* List of outgoing writequeue_entries */
 	spinlock_t writequeue_lock;
-	atomic_t writequeue_cnt;
 	int retries;
 #define MAX_CONNECT_RETRIES 3
 	struct hlist_node list;
@@ -94,7 +90,6 @@ struct connection {
 	struct connection *sendcon;
 	struct work_struct rwork; /* Receive workqueue */
 	struct work_struct swork; /* Send workqueue */
-	wait_queue_head_t shutdown_wait; /* wait for graceful shutdown */
 	unsigned char *rx_buf;
 	int rx_buflen;
 	int rx_leftover;
@@ -157,10 +152,6 @@ struct dlm_proto_ops {
 	int (*listen_validate)(void);
 	void (*listen_sockopts)(struct socket *sock);
 	int (*listen_bind)(struct socket *sock);
-	/* What to do to shutdown */
-	void (*shutdown_action)(struct connection *con);
-	/* What to do to eof check */
-	bool (*eof_condition)(struct connection *con);
 };
 
 static struct listen_sock_callbacks {
@@ -244,11 +235,6 @@ static struct connection *__find_con(int nodeid, int r)
 	return NULL;
 }
 
-static bool tcp_eof_condition(struct connection *con)
-{
-	return atomic_read(&con->writequeue_cnt);
-}
-
 static int dlm_con_init(struct connection *con, int nodeid)
 {
 	con->rx_buflen = dlm_config.ci_buffer_size;
@@ -260,10 +246,8 @@ static int dlm_con_init(struct connection *con, int nodeid)
 	mutex_init(&con->sock_mutex);
 	INIT_LIST_HEAD(&con->writequeue);
 	spin_lock_init(&con->writequeue_lock);
-	atomic_set(&con->writequeue_cnt, 0);
 	INIT_WORK(&con->swork, process_send_sockets);
 	INIT_WORK(&con->rwork, process_recv_sockets);
-	init_waitqueue_head(&con->shutdown_wait);
 
 	return 0;
 }
@@ -774,7 +758,6 @@ static void free_entry(struct writequeue_entry *e)
 	}
 
 	list_del(&e->list);
-	atomic_dec(&e->con->writequeue_cnt);
 	kref_put(&e->ref, dlm_page_release);
 }
 
@@ -837,56 +820,10 @@ static void close_connection(struct connection *con, bool and_other,
 	clear_bit(CF_CONNECTED, &con->flags);
 	clear_bit(CF_DELAY_CONNECT, &con->flags);
 	clear_bit(CF_RECONNECT, &con->flags);
-	clear_bit(CF_EOF, &con->flags);
 	mutex_unlock(&con->sock_mutex);
 	clear_bit(CF_CLOSING, &con->flags);
 }
 
-static void shutdown_connection(struct connection *con)
-{
-	int ret;
-
-	flush_work(&con->swork);
-
-	mutex_lock(&con->sock_mutex);
-	/* nothing to shutdown */
-	if (!con->sock) {
-		mutex_unlock(&con->sock_mutex);
-		return;
-	}
-
-	set_bit(CF_SHUTDOWN, &con->flags);
-	ret = kernel_sock_shutdown(con->sock, SHUT_WR);
-	mutex_unlock(&con->sock_mutex);
-	if (ret) {
-		log_print("Connection %p failed to shutdown: %d will force close",
-			  con, ret);
-		goto force_close;
-	} else {
-		ret = wait_event_timeout(con->shutdown_wait,
-					 !test_bit(CF_SHUTDOWN, &con->flags),
-					 DLM_SHUTDOWN_WAIT_TIMEOUT);
-		if (ret == 0) {
-			log_print("Connection %p shutdown timed out, will force close",
-				  con);
-			goto force_close;
-		}
-	}
-
-	return;
-
-force_close:
-	clear_bit(CF_SHUTDOWN, &con->flags);
-	close_connection(con, false, true, true);
-}
-
-static void dlm_tcp_shutdown(struct connection *con)
-{
-	if (con->othercon)
-		shutdown_connection(con->othercon);
-	shutdown_connection(con);
-}
-
 static int con_realloc_receive_buf(struct connection *con, int newlen)
 {
 	unsigned char *newbuf;
@@ -978,19 +915,8 @@ static int receive_from_sock(struct connection *con)
 		log_print("connection %p got EOF from %d",
 			  con, con->nodeid);
 
-		if (dlm_proto_ops->eof_condition &&
-		    dlm_proto_ops->eof_condition(con)) {
-			set_bit(CF_EOF, &con->flags);
-			mutex_unlock(&con->sock_mutex);
-		} else {
-			mutex_unlock(&con->sock_mutex);
-			close_connection(con, false, true, false);
-
-			/* handling for tcp shutdown */
-			clear_bit(CF_SHUTDOWN, &con->flags);
-			wake_up(&con->shutdown_wait);
-		}
-
+		mutex_unlock(&con->sock_mutex);
+		close_connection(con, false, true, false);
 		/* signal to breaking receive worker */
 		ret = -1;
 	} else {
@@ -1253,7 +1179,6 @@ static struct writequeue_entry *new_wq_entry(struct connection *con, int len,
 	kref_get(&e->ref);
 	*ppc = page_address(e->page);
 	e->end += len;
-	atomic_inc(&con->writequeue_cnt);
 	if (cb)
 		cb(data);
 
@@ -1454,20 +1379,6 @@ static void send_to_sock(struct connection *con)
 	}
 	spin_unlock(&con->writequeue_lock);
 
-	/* close if we got EOF */
-	if (test_and_clear_bit(CF_EOF, &con->flags)) {
-		mutex_unlock(&con->sock_mutex);
-		close_connection(con, false, false, true);
-
-		/* handling for tcp shutdown */
-		clear_bit(CF_SHUTDOWN, &con->flags);
-		wake_up(&con->shutdown_wait);
-	} else {
-		mutex_unlock(&con->sock_mutex);
-	}
-
-	return;
-
 out:
 	mutex_unlock(&con->sock_mutex);
 	return;
@@ -1667,16 +1578,8 @@ static int work_start(void)
 	return 0;
 }
 
-static void shutdown_conn(struct connection *con)
-{
-	if (dlm_proto_ops->shutdown_action)
-		dlm_proto_ops->shutdown_action(con);
-}
-
 void dlm_lowcomms_shutdown(void)
 {
-	int idx;
-
 	restore_callbacks(listen_con.sock);
 
 	if (recv_workqueue)
@@ -1685,9 +1588,25 @@ void dlm_lowcomms_shutdown(void)
 		flush_workqueue(send_workqueue);
 
 	dlm_close_sock(&listen_con.sock);
+}
+
+void dlm_lowcomms_shutdown_node(int nodeid, bool force)
+{
+	struct connection *con;
+	int idx;
 
 	idx = srcu_read_lock(&connections_srcu);
-	foreach_conn(shutdown_conn);
+	con = nodeid2con(nodeid, 0);
+	if (WARN_ON_ONCE(!con)) {
+		srcu_read_unlock(&connections_srcu, idx);
+		return;
+	}
+
+	WARN_ON_ONCE(!force && !list_empty(&con->writequeue));
+	clean_one_writequeue(con);
+	if (con->othercon)
+		clean_one_writequeue(con->othercon);
+	close_connection(con, true, true, true);
 	srcu_read_unlock(&connections_srcu, idx);
 }
 
@@ -1899,8 +1818,6 @@ static const struct dlm_proto_ops dlm_tcp_ops = {
 	.listen_validate = dlm_tcp_listen_validate,
 	.listen_sockopts = dlm_tcp_listen_sockopts,
 	.listen_bind = dlm_tcp_listen_bind,
-	.shutdown_action = dlm_tcp_shutdown,
-	.eof_condition = tcp_eof_condition,
 };
 
 static int dlm_sctp_bind(struct socket *sock)
fs/dlm/lowcomms.h +1 -0
index acaf1b0b3885b..3e8dca66183b8 100644
@@ -34,6 +34,7 @@ bool dlm_lowcomms_is_running(void);
 
 int dlm_lowcomms_start(void);
 void dlm_lowcomms_shutdown(void);
+void dlm_lowcomms_shutdown_node(int nodeid, bool force);
 void dlm_lowcomms_stop(void);
 void dlm_lowcomms_init(void);
 void dlm_lowcomms_exit(void);
fs/dlm/midcomms.c +4 -2
index 42d54196e766f..553cdb3780372 100644
@@ -1416,11 +1416,13 @@ static void midcomms_shutdown(struct midcomms_node *node)
 		pr_debug("active shutdown timed out for node %d with state %s\n",
 			 node->nodeid, dlm_state_str(node->state));
 		midcomms_node_reset(node);
+		dlm_lowcomms_shutdown_node(node->nodeid, true);
 		return;
 	}
 
 	pr_debug("active shutdown done for node %d with state %s\n",
 		 node->nodeid, dlm_state_str(node->state));
+	dlm_lowcomms_shutdown_node(node->nodeid, false);
 }
 
 void dlm_midcomms_shutdown(void)
@@ -1428,6 +1430,8 @@ void dlm_midcomms_shutdown(void)
 	struct midcomms_node *node;
 	int i, idx;
 
+	dlm_lowcomms_shutdown();
+
 	mutex_lock(&close_lock);
 	idx = srcu_read_lock(&nodes_srcu);
 	for (i = 0; i < CONN_HASH_SIZE; i++) {
@@ -1445,8 +1449,6 @@ void dlm_midcomms_shutdown(void)
 	}
 	srcu_read_unlock(&nodes_srcu, idx);
 	mutex_unlock(&close_lock);
-
-	dlm_lowcomms_shutdown();
 }
 
 int dlm_midcomms_close(int nodeid)
Signature · 1 line
2.53.0
author maintainer
#1567 lore raw

PATCH 1561/1887 fs: dlm: cleanup listen sock handling

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Alexander Aring <[email protected]>

[ Upstream commit c3d88dfd15835f42c79467eee2c40db3095e5271 ]

This patch removes save_listen_callbacks() and add_listen_sock() as they are only used once in lowcomms functionality. For shutdown lowcomms it's not necessary to whole flush the workqueues to synchronize with restoring the old sk_data_ready() callback. Only the listen con receive work need to be cancelled. For each individual node shutdown we should be sure that last ack was been transmitted which is done by flushing per connection swork worker.

Stable-dep-of: 33930840b5f0 ("sunrpc: xprtsock: annotate shared socket callbacks with READ_ONCE/WRITE_ONCE")

---
 fs/dlm/lowcomms.c | 51 ++++++++++++++++-------------------------------
 1 file changed, 17 insertions(+), 34 deletions(-)
fs/dlm/lowcomms.c +17 -34
index 2151579094255..3248fc147442f 100644
@@ -650,17 +650,6 @@ static void lowcomms_error_report(struct sock *sk)
 		orig_report(sk);
 }
 
-/* Note: sk_callback_lock must be locked before calling this function. */
-static void save_listen_callbacks(struct socket *sock)
-{
-	struct sock *sk = sock->sk;
-
-	listen_sock.sk_data_ready = sk->sk_data_ready;
-	listen_sock.sk_state_change = sk->sk_state_change;
-	listen_sock.sk_write_space = sk->sk_write_space;
-	listen_sock.sk_error_report = sk->sk_error_report;
-}
-
 static void restore_callbacks(struct socket *sock)
 {
 	struct sock *sk = sock->sk;
@@ -674,21 +663,6 @@ static void restore_callbacks(struct socket *sock)
 	release_sock(sk);
 }
 
-static void add_listen_sock(struct socket *sock, struct listen_connection *con)
-{
-	struct sock *sk = sock->sk;
-
-	lock_sock(sk);
-	save_listen_callbacks(sock);
-	con->sock = sock;
-
-	sk->sk_user_data = con;
-	sk->sk_allocation = GFP_NOFS;
-	/* Install a data_ready callback */
-	sk->sk_data_ready = lowcomms_listen_data_ready;
-	release_sock(sk);
-}
-
 /* Make a socket active */
 static void add_sock(struct socket *sock, struct connection *con)
 {
@@ -1580,13 +1554,12 @@ static int work_start(void)
 
 void dlm_lowcomms_shutdown(void)
 {
-	restore_callbacks(listen_con.sock);
-
-	if (recv_workqueue)
-		flush_workqueue(recv_workqueue);
-	if (send_workqueue)
-		flush_workqueue(send_workqueue);
+	/* stop lowcomms_listen_data_ready calls */
+	lock_sock(listen_con.sock->sk);
+	listen_con.sock->sk->sk_data_ready = listen_sock.sk_data_ready;
+	release_sock(listen_con.sock->sk);
 
+	cancel_work_sync(&listen_con.rwork);
 	dlm_close_sock(&listen_con.sock);
 }
 
@@ -1602,6 +1575,7 @@ void dlm_lowcomms_shutdown_node(int nodeid, bool force)
 		return;
 	}
 
+	flush_work(&con->swork);
 	WARN_ON_ONCE(!force && !list_empty(&con->writequeue));
 	clean_one_writequeue(con);
 	if (con->othercon)
@@ -1722,8 +1696,17 @@ static int dlm_listen_for_all(void)
 	if (result < 0)
 		goto out;
 
-	save_listen_callbacks(sock);
-	add_listen_sock(sock, &listen_con);
+	lock_sock(sock->sk);
+	listen_sock.sk_data_ready = sock->sk->sk_data_ready;
+	listen_sock.sk_write_space = sock->sk->sk_write_space;
+	listen_sock.sk_error_report = sock->sk->sk_error_report;
+	listen_sock.sk_state_change = sock->sk->sk_state_change;
+
+	listen_con.sock = sock;
+
+	sock->sk->sk_allocation = GFP_NOFS;
+	sock->sk->sk_data_ready = lowcomms_listen_data_ready;
+	release_sock(sock->sk);
 
 	INIT_WORK(&listen_con.rwork, process_listen_recv_socket);
 	result = sock->ops->listen(sock, 5);
Signature · 1 line
2.53.0
author maintainer
#1568 lore raw

PATCH 1562/1887 net: Introduce sk_use_task_frag in struct sock.

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Guillaume Nault <[email protected]>

[ Upstream commit fb87bd47516d9a26b6d549231aa743b20fd4a569 ]

Sockets that can be used while recursing into memory reclaim, like those used by network block devices and file systems, mustn't use current->task_frag: if the current process is already using it, then the inner memory reclaim call would corrupt the task_frag structure.

To avoid this, sk_page_frag() uses ->sk_allocation to detect sockets that mustn't use current->task_frag, assuming that those used during memory reclaim had their allocation constraints reflected in ->sk_allocation.

This unfortunately doesn't cover all cases: in an attempt to remove all usage of GFP_NOFS and GFP_NOIO, sunrpc stopped setting these flags in ->sk_allocation, and used memalloc_nofs critical sections instead. This breaks the sk_page_frag() heuristic since the allocation constraints are now stored in current->flags, which sk_page_frag() can't read without risking triggering a cache miss and slowing down TCP's fast path.

This patch creates a new field in struct sock, named sk_use_task_frag, which sockets with memory reclaim constraints can set to false if they can't safely use current->task_frag. In such cases, sk_page_frag() now always returns the socket's page_frag (->sk_frag). The first user is sunrpc, which needs to avoid using current->task_frag but can keep ->sk_allocation set to GFP_KERNEL otherwise.

Eventually, it might be possible to simplify sk_page_frag() by only testing ->sk_use_task_frag and avoid relying on the ->sk_allocation heuristic entirely (assuming other sockets will set ->sk_use_task_frag according to their constraints in the future).

The new ->sk_use_task_frag field is placed in a hole in struct sock and belongs to a cache line shared with ->sk_shutdown. Therefore it should be hot and shouldn't have negative performance impacts on TCP's fast path (sk_shutdown is tested just before the while() loop in tcp_sendmsg_locked()).

Stable-dep-of: 33930840b5f0 ("sunrpc: xprtsock: annotate shared socket callbacks with READ_ONCE/WRITE_ONCE")

---
 include/net/sock.h | 11 +++++++++--
 net/core/sock.c    |  1 +
 2 files changed, 10 insertions(+), 2 deletions(-)
include/net/sock.h +9 -2
index 4006584514f24..13c112bd2d316 100644
@@ -319,6 +319,9 @@ struct sk_filter;
   *	@sk_stamp: time stamp of last packet received
   *	@sk_stamp_seq: lock for accessing sk_stamp on 32 bit architectures only
   *	@sk_tsflags: SO_TIMESTAMPING flags
+  *	@sk_use_task_frag: allow sk_page_frag() to use current->task_frag.
+  *			   Sockets that can be used under memory reclaim should
+  *			   set this to false.
   *	@sk_bind_phc: SO_TIMESTAMPING bind PHC index of PTP virtual clock
   *	              for timestamping
   *	@sk_tskey: counter to disambiguate concurrent tstamp requests
@@ -520,6 +523,7 @@ struct sock {
 	u8			sk_txtime_deadline_mode : 1,
 				sk_txtime_report_errors : 1,
 				sk_txtime_unused : 6;
+	bool			sk_use_task_frag;
 
 	struct socket		*sk_socket;
 	void			*sk_user_data;
@@ -2685,14 +2689,17 @@ static inline void sk_stream_moderate_sndbuf(struct sock *sk)
  * socket operations and end up recursing into sk_page_frag()
  * while it's already in use: explicitly avoid task page_frag
  * usage if the caller is potentially doing any of them.
- * This assumes that page fault handlers use the GFP_NOFS flags.
+ * This assumes that page fault handlers use the GFP_NOFS flags or
+ * explicitly disable sk_use_task_frag.
  *
  * Return: a per task page_frag if context allows that,
  * otherwise a per socket one.
  */
 static inline struct page_frag *sk_page_frag(struct sock *sk)
 {
-	if ((sk->sk_allocation & (__GFP_DIRECT_RECLAIM | __GFP_MEMALLOC | __GFP_FS)) ==
+	if (sk->sk_use_task_frag &&
+	    (sk->sk_allocation & (__GFP_DIRECT_RECLAIM | __GFP_MEMALLOC |
+				  __GFP_FS)) ==
 	    (__GFP_DIRECT_RECLAIM | __GFP_FS))
 		return &current->task_frag;
 
net/core/sock.c +1 -0
index fcd47d51b9f23..d05c0cb1e52e2 100644
@@ -3440,6 +3440,7 @@ void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)
 	sk->sk_rcvbuf		=	READ_ONCE(sysctl_rmem_default);
 	sk->sk_sndbuf		=	READ_ONCE(sysctl_wmem_default);
 	sk->sk_state		=	TCP_CLOSE;
+	sk->sk_use_task_frag	=	true;
 	sk_set_socket(sk, sock);
 
 	sock_set_flag(sk, SOCK_ZAPPED);
Signature · 1 line
2.53.0
author maintainer
#1569 lore raw

PATCH 1563/1887 Treewide: Stop corrupting sockets task_frag

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Benjamin Coddington <[email protected]>

[ Upstream commit 98123866fcf3fe95a0c1b198ef122dfdbd351916 ]

Since moving to memalloc_nofs_save/restore, SUNRPC has stopped setting the GFP_NOIO flag on sk_allocation which the networking system uses to decide when it is safe to use current->task_frag. The results of this are unexpected corruption in task_frag when SUNRPC is involved in memory reclaim.

The corruption can be seen in crashes, but the root cause is often difficult to ascertain as a crashing machine's stack trace will have no evidence of being near NFS or SUNRPC code. I believe this problem to be much more pervasive than reports to the community may indicate.

Fix this by having kernel users of sockets that may corrupt task_frag due to reclaim set sk_use_task_frag = false. Preemptively correcting this situation for users that still set sk_allocation allows them to convert to memalloc_nofs_save/restore without the same unexpected corruptions that are sure to follow, unlikely to show up in testing, and difficult to bisect.

CC Philipp Reisner <[email protected]>
CC Lars Ellenberg <[email protected]>
CC "Christoph Böhmwalder" <[email protected]>
CC Jens Axboe <[email protected]>
CC Josef Bacik <[email protected]>
CC Keith Busch <[email protected]>
CC Christoph Hellwig <[email protected]>
CC Sagi Grimberg <[email protected]>
CC Lee Duncan <[email protected]>
CC Chris Leech <[email protected]>
CC Mike Christie <[email protected]>
CC "James E.J. Bottomley" <[email protected]>
CC "Martin K. Petersen" <[email protected]>
CC Valentina Manea <[email protected]>
CC Shuah Khan <[email protected]>
CC Greg Kroah-Hartman <[email protected]>
CC David Howells <[email protected]>
CC Marc Dionne <[email protected]>
CC Steve French <[email protected]>
CC Christine Caulfield <[email protected]>
CC David Teigland <[email protected]>
CC Mark Fasheh <[email protected]>
CC Joel Becker <[email protected]>
CC Joseph Qi <[email protected]>
CC Eric Van Hensbergen <[email protected]>
CC Latchesar Ionkov <[email protected]>
CC Dominique Martinet <[email protected]>
CC Ilya Dryomov <[email protected]>
CC Xiubo Li <[email protected]>
CC Chuck Lever <[email protected]>
CC Jeff Layton <[email protected]>
CC Trond Myklebust <[email protected]>
CC Anna Schumaker <[email protected]>
CC Steffen Klassert <[email protected]>
CC Herbert Xu <[email protected]>

Stable-dep-of: 33930840b5f0 ("sunrpc: xprtsock: annotate shared socket callbacks with READ_ONCE/WRITE_ONCE")

---
 drivers/block/drbd/drbd_receiver.c | 3 +++
 drivers/block/nbd.c                | 1 +
 drivers/nvme/host/tcp.c            | 1 +
 drivers/scsi/iscsi_tcp.c           | 1 +
 drivers/usb/usbip/usbip_common.c   | 1 +
 fs/dlm/lowcomms.c                  | 2 ++
 fs/ocfs2/cluster/tcp.c             | 1 +
 fs/smb/client/connect.c            | 1 +
 net/9p/trans_fd.c                  | 1 +
 net/ceph/messenger.c               | 1 +
 net/sunrpc/xprtsock.c              | 3 +++
 net/xfrm/espintcp.c                | 1 +
 12 files changed, 17 insertions(+)
drivers/block/drbd/drbd_receiver.c +3 -0
index 309d7bb028db2..c3dee6265ca31 100644
@@ -1030,6 +1030,9 @@ static int conn_connect(struct drbd_connection *connection)
 	sock.socket->sk->sk_allocation = GFP_NOIO;
 	msock.socket->sk->sk_allocation = GFP_NOIO;
 
+	sock.socket->sk->sk_use_task_frag = false;
+	msock.socket->sk->sk_use_task_frag = false;
+
 	sock.socket->sk->sk_priority = TC_PRIO_INTERACTIVE_BULK;
 	msock.socket->sk->sk_priority = TC_PRIO_INTERACTIVE;
 
drivers/block/nbd.c +1 -0
index 008534aad0a18..26971f58c684c 100644
@@ -520,6 +520,7 @@ static int __sock_xmit(struct nbd_device *nbd, struct socket *sock, int send,
 	noreclaim_flag = memalloc_noreclaim_save();
 	do {
 		sock->sk->sk_allocation = GFP_NOIO | __GFP_MEMALLOC;
+		sock->sk->sk_use_task_frag = false;
 		msg.msg_flags = msg_flags | MSG_NOSIGNAL;
 
 		if (send)
drivers/nvme/host/tcp.c +1 -0
index 19aed2125a18b..996940c972cd1 100644
@@ -1655,6 +1655,7 @@ static int nvme_tcp_alloc_queue(struct nvme_ctrl *nctrl, int qid)
 	queue->sock->sk->sk_rcvtimeo = 10 * HZ;
 
 	queue->sock->sk->sk_allocation = GFP_ATOMIC;
+	queue->sock->sk->sk_use_task_frag = false;
 	nvme_tcp_set_queue_io_cpu(queue);
 	queue->request = NULL;
 	queue->data_remaining = 0;
drivers/scsi/iscsi_tcp.c +1 -0
index 56ade46309707..050a0ca92bc07 100644
@@ -742,6 +742,7 @@ iscsi_sw_tcp_conn_bind(struct iscsi_cls_session *cls_session,
 	sk->sk_reuse = SK_CAN_REUSE;
 	sk->sk_sndtimeo = 15 * HZ; /* FIXME: make it configurable */
 	sk->sk_allocation = GFP_ATOMIC;
+	sk->sk_use_task_frag = false;
 	sk_set_memalloc(sk);
 	sock_no_linger(sk);
 
drivers/usb/usbip/usbip_common.c +1 -0
index d6dbd038af67b..ba9e7c616e129 100644
@@ -315,6 +315,7 @@ int usbip_recv(struct socket *sock, void *buf, int size)
 
 	do {
 		sock->sk->sk_allocation = GFP_NOIO;
+		sock->sk->sk_use_task_frag = false;
 
 		result = sock_recvmsg(sock, &msg, MSG_WAITALL);
 		if (result <= 0)
fs/dlm/lowcomms.c +2 -0
index 3248fc147442f..4ad492d035dd1 100644
@@ -677,6 +677,7 @@ static void add_sock(struct socket *sock, struct connection *con)
 	sk->sk_write_space = lowcomms_write_space;
 	sk->sk_state_change = lowcomms_state_change;
 	sk->sk_allocation = GFP_NOFS;
+	sk->sk_use_task_frag = false;
 	sk->sk_error_report = lowcomms_error_report;
 	release_sock(sk);
 }
@@ -1705,6 +1706,7 @@ static int dlm_listen_for_all(void)
 	listen_con.sock = sock;
 
 	sock->sk->sk_allocation = GFP_NOFS;
+	sock->sk->sk_use_task_frag = false;
 	sock->sk->sk_data_ready = lowcomms_listen_data_ready;
 	release_sock(sock->sk);
 
fs/ocfs2/cluster/tcp.c +1 -0
index 0f6c2e3e79258..20396e1aacd25 100644
@@ -1604,6 +1604,7 @@ static void o2net_start_connect(struct work_struct *work)
 	sc->sc_sock = sock; /* freed by sc_kref_release */
 
 	sock->sk->sk_allocation = GFP_ATOMIC;
+	sock->sk->sk_use_task_frag = false;
 
 	myaddr.sin_family = AF_INET;
 	myaddr.sin_addr.s_addr = mynode->nd_ipv4_address;
fs/smb/client/connect.c +1 -0
index fd74814bdc84a..cb1bf349411d8 100644
@@ -3115,6 +3115,7 @@ generic_ip_connect(struct TCP_Server_Info *server)
 		cifs_dbg(FYI, "Socket created\n");
 		server->ssocket = socket;
 		socket->sk->sk_allocation = GFP_NOFS;
+		socket->sk->sk_use_task_frag = false;
 		if (sfamily == AF_INET6)
 			cifs_reclassify_socket6(socket);
 		else
net/9p/trans_fd.c +1 -0
index 1c42360ff3e67..ed7e96c40948b 100644
@@ -875,6 +875,7 @@ static int p9_socket_open(struct p9_client *client, struct socket *csocket)
 	}
 
 	csocket->sk->sk_allocation = GFP_NOIO;
+	csocket->sk->sk_use_task_frag = false;
 	file = sock_alloc_file(csocket, 0, NULL);
 	if (IS_ERR(file)) {
 		pr_err("%s (%d): failed to map fd\n",
net/ceph/messenger.c +1 -0
index db2794d50bdb9..3009df467c8dd 100644
@@ -446,6 +446,7 @@ int ceph_tcp_connect(struct ceph_connection *con)
 	if (ret)
 		return ret;
 	sock->sk->sk_allocation = GFP_NOFS;
+	sock->sk->sk_use_task_frag = false;
 
 #ifdef CONFIG_LOCKDEP
 	lockdep_set_class(&sock->sk->sk_lock, &socket_class);
net/sunrpc/xprtsock.c +3 -0
index 74689050038a2..5315872f6a9c8 100644
@@ -1889,6 +1889,7 @@ static int xs_local_finish_connecting(struct rpc_xprt *xprt,
 		sk->sk_write_space = xs_udp_write_space;
 		sk->sk_state_change = xs_local_state_change;
 		sk->sk_error_report = xs_error_report;
+		sk->sk_use_task_frag = false;
 
 		xprt_clear_connected(xprt);
 
@@ -2089,6 +2090,7 @@ static void xs_udp_finish_connecting(struct rpc_xprt *xprt, struct socket *sock)
 		sk->sk_user_data = xprt;
 		sk->sk_data_ready = xs_data_ready;
 		sk->sk_write_space = xs_udp_write_space;
+		sk->sk_use_task_frag = false;
 
 		xprt_set_connected(xprt);
 
@@ -2257,6 +2259,7 @@ static int xs_tcp_finish_connecting(struct rpc_xprt *xprt, struct socket *sock)
 		sk->sk_state_change = xs_tcp_state_change;
 		sk->sk_write_space = xs_tcp_write_space;
 		sk->sk_error_report = xs_error_report;
+		sk->sk_use_task_frag = false;
 
 		/* socket options */
 		sock_reset_flag(sk, SOCK_LINGER);
net/xfrm/espintcp.c +1 -0
index b98fa2887c864..a35a170c39201 100644
@@ -491,6 +491,7 @@ static int espintcp_init_sk(struct sock *sk)
 
 	/* avoid using task_frag */
 	sk->sk_allocation = GFP_ATOMIC;
+	sk->sk_use_task_frag = false;
 
 	return 0;
 
Signature · 1 line
2.53.0
author maintainer
#1570 lore raw

PATCH 1564/1887 sunrpc: xprtsock: annotate shared socket callbacks with READ_ONCE/WRITE_ONCE

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Runyu Xiao <[email protected]>

[ Upstream commit 33930840b5f0a79f826e7c69dc6cd78f72a67481 ]

xprtsock replaces and restores sk->sk_data_ready and sk->sk_write_space on live sockets with plain stores, and xs_udp_do_set_buffer_size() invokes sk->sk_write_space via a plain load. These callback pointers are shared with generic socket and protocol paths that may read or invoke them concurrently, so xprtsock needs the same READ_ONCE()/WRITE_ONCE() callback visibility contract that the validated 4022 family applied elsewhere.

When SUNRPC takes over an AF_LOCAL, UDP, or TCP socket and later restores the lower-socket callbacks during teardown, another CPU may still hold an earlier callback snapshot. The plain replace/restore pattern leaves the same visibility hole as the validated 4022 family, so a stale snapshot can still invoke xs_data_ready() or xs_udp_write_space() after the live callback fields have already been restored to the lower-socket handlers.

Use WRITE_ONCE() for the shared sk_data_ready and sk_write_space stores in xs_local_finish_connecting(), xs_udp_finish_connecting(), xs_tcp_finish_connecting(), and xs_restore_old_callbacks(). Use READ_ONCE() for the direct sk_write_space invocation in xs_udp_do_set_buffer_size(). This matches the required callback visibility contract while leaving adjacent sk_state_change and sk_error_report handling unchanged.

Fixes a246b0105bbd ("[PATCH] RPC: introduce client-side transport switch")
---
 net/sunrpc/xprtsock.c | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)
net/sunrpc/xprtsock.c +11 -9
index 5315872f6a9c8..e812396e869b7 100644
@@ -1115,9 +1115,9 @@ static void xs_save_old_callbacks(struct sock_xprt *transport, struct sock *sk)
 
 static void xs_restore_old_callbacks(struct sock_xprt *transport, struct sock *sk)
 {
-	sk->sk_data_ready = transport->old_data_ready;
+	WRITE_ONCE(sk->sk_data_ready, transport->old_data_ready);
 	sk->sk_state_change = transport->old_state_change;
-	sk->sk_write_space = transport->old_write_space;
+	WRITE_ONCE(sk->sk_write_space, transport->old_write_space);
 	sk->sk_error_report = transport->old_error_report;
 }
 
@@ -1563,6 +1563,7 @@ static void xs_udp_do_set_buffer_size(struct rpc_xprt *xprt)
 {
 	struct sock_xprt *transport = container_of(xprt, struct sock_xprt, xprt);
 	struct sock *sk = transport->inet;
+	void (*write_space)(struct sock *sock);
 
 	if (transport->rcvsize) {
 		sk->sk_userlocks |= SOCK_RCVBUF_LOCK;
@@ -1571,7 +1572,8 @@ static void xs_udp_do_set_buffer_size(struct rpc_xprt *xprt)
 	if (transport->sndsize) {
 		sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
 		sk->sk_sndbuf = transport->sndsize * xprt->max_reqs * 2;
-		sk->sk_write_space(sk);
+		write_space = READ_ONCE(sk->sk_write_space);
+		write_space(sk);
 	}
 }
 
@@ -1885,8 +1887,8 @@ static int xs_local_finish_connecting(struct rpc_xprt *xprt,
 		xs_save_old_callbacks(transport, sk);
 
 		sk->sk_user_data = xprt;
-		sk->sk_data_ready = xs_data_ready;
-		sk->sk_write_space = xs_udp_write_space;
+		WRITE_ONCE(sk->sk_data_ready, xs_data_ready);
+		WRITE_ONCE(sk->sk_write_space, xs_udp_write_space);
 		sk->sk_state_change = xs_local_state_change;
 		sk->sk_error_report = xs_error_report;
 		sk->sk_use_task_frag = false;
@@ -2088,8 +2090,8 @@ static void xs_udp_finish_connecting(struct rpc_xprt *xprt, struct socket *sock)
 		xs_save_old_callbacks(transport, sk);
 
 		sk->sk_user_data = xprt;
-		sk->sk_data_ready = xs_data_ready;
-		sk->sk_write_space = xs_udp_write_space;
+		WRITE_ONCE(sk->sk_data_ready, xs_data_ready);
+		WRITE_ONCE(sk->sk_write_space, xs_udp_write_space);
 		sk->sk_use_task_frag = false;
 
 		xprt_set_connected(xprt);
@@ -2255,9 +2257,9 @@ static int xs_tcp_finish_connecting(struct rpc_xprt *xprt, struct socket *sock)
 		xs_save_old_callbacks(transport, sk);
 
 		sk->sk_user_data = xprt;
-		sk->sk_data_ready = xs_data_ready;
+		WRITE_ONCE(sk->sk_data_ready, xs_data_ready);
 		sk->sk_state_change = xs_tcp_state_change;
-		sk->sk_write_space = xs_tcp_write_space;
+		WRITE_ONCE(sk->sk_write_space, xs_tcp_write_space);
 		sk->sk_error_report = xs_error_report;
 		sk->sk_use_task_frag = false;
 
Signature · 1 line
2.53.0
author maintainer
#1571 lore raw

PATCH 1565/1887 bpf: Fix mmap_lock leak in irq_work path

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Sanghyun Park <[email protected]>

[ Upstream commit fa9dcacdcdf487f0ffef64bf67622f1caed509f1 ]

stack_map_get_build_id_offset() introduced a per-CPU irq_work to defer mmap_read_unlock() from NMI context, and bpf_find_vma() later reused the same mmap_unlock_work. Both callers only check whether the work is busy before taking mmap_lock, so a nested caller can reuse the slot before the first caller queues it. Two read locks may then be acquired while only one deferred unlock runs, leaking a read lock and blocking exit_mmap().

Reserve the per-CPU slot before mmap_read_trylock(). Use the same wrapper in stackmap and bpf_find_vma() so both callers release the reservation on trylock failure. Keep rejecting the slot while the irq_work remains busy. Release it after the irq_work callback unlocks the mm.

---
 kernel/bpf/mmap_unlock_work.h | 51 ++++++++++++++++++++---------------
 kernel/bpf/stackmap.c         | 35 +++++++++++++-----------
 kernel/bpf/task_iter.c        | 14 +++++++---
 3 files changed, 59 insertions(+), 41 deletions(-)
kernel/bpf/mmap_unlock_work.h +29 -22
index 5d18d7d85bef9..1834db20b8612 100644
@@ -4,12 +4,15 @@
 
 #ifndef __MMAP_UNLOCK_WORK_H__
 #define __MMAP_UNLOCK_WORK_H__
+#include <linux/atomic.h>
+#include <linux/err.h>
 #include <linux/irq_work.h>
 
 /* irq_work to run mmap_read_unlock() in irq_work */
 struct mmap_unlock_irq_work {
 	struct irq_work irq_work;
 	struct mm_struct *mm;
+	atomic_t active;
 };
 
 DECLARE_PER_CPU(struct mmap_unlock_irq_work, mmap_unlock_work);
@@ -18,32 +21,36 @@ DECLARE_PER_CPU(struct mmap_unlock_irq_work, mmap_unlock_work);
  * We cannot do mmap_read_unlock() when the irq is disabled, because of
  * risk to deadlock with rq_lock. To look up vma when the irqs are
  * disabled, we need to run mmap_read_unlock() in irq_work. We use a
- * percpu variable to do the irq_work. If the irq_work is already used
- * by another lookup, we fall over.
+ * percpu variable to do the irq_work. The active flag reserves the slot
+ * before mmap_read_trylock() and until the irq_work callback consumes mm.
  */
-static inline bool bpf_mmap_unlock_get_irq_work(struct mmap_unlock_irq_work **work_ptr)
+static inline struct mmap_unlock_irq_work *bpf_mmap_unlock_guard_get(void)
 {
-	struct mmap_unlock_irq_work *work = NULL;
-	bool irq_work_busy = false;
+	struct mmap_unlock_irq_work *work;
 
-	if (irqs_disabled()) {
-		if (!IS_ENABLED(CONFIG_PREEMPT_RT)) {
-			work = this_cpu_ptr(&mmap_unlock_work);
-			if (irq_work_is_busy(&work->irq_work)) {
-				/* cannot queue more up_read, fallback */
-				irq_work_busy = true;
-			}
-		} else {
-			/*
-			 * PREEMPT_RT does not allow to trylock mmap sem in
-			 * interrupt disabled context. Force the fallback code.
-			 */
-			irq_work_busy = true;
-		}
-	}
+	if (!irqs_disabled())
+		return NULL;
+
+	/*
+	 * PREEMPT_RT does not allow to trylock mmap sem in interrupt
+	 * disabled context. Force the fallback code.
+	 */
+	if (IS_ENABLED(CONFIG_PREEMPT_RT))
+		return ERR_PTR(-EBUSY);
+
+	work = this_cpu_ptr(&mmap_unlock_work);
+	if (irq_work_is_busy(&work->irq_work) ||
+	    atomic_cmpxchg_acquire(&work->active, 0, 1))
+		return ERR_PTR(-EBUSY);
 
-	*work_ptr = work;
-	return irq_work_busy;
+	return work;
+}
+
+static inline void
+bpf_mmap_unlock_guard_put(struct mmap_unlock_irq_work *work)
+{
+	if (work)
+		atomic_set_release(&work->active, 0);
 }
 
 static inline void bpf_mmap_unlock_mm(struct mmap_unlock_irq_work *work, struct mm_struct *mm)
kernel/bpf/stackmap.c +20 -15
index a8d54f650ff90..b8df42496f4fe 100644
@@ -131,24 +131,20 @@ static void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
 					  u64 *ips, u32 trace_nr, bool user)
 {
 	int i;
-	struct mmap_unlock_irq_work *work = NULL;
-	bool irq_work_busy = bpf_mmap_unlock_get_irq_work(&work);
+	struct mmap_unlock_irq_work *work;
 	struct vm_area_struct *vma, *prev_vma = NULL;
 	const char *prev_build_id;
 
-	/* If the irq_work is in use, fall back to report ips. Same
-	 * fallback is used for kernel stack (!user) on a stackmap with
-	 * build_id.
-	 */
-	if (!user || !current || !current->mm || irq_work_busy ||
-	    !mmap_read_trylock(current->mm)) {
-		/* cannot access current->mm, fall back to ips */
-		for (i = 0; i < trace_nr; i++) {
-			id_offs[i].status = BPF_STACK_BUILD_ID_IP;
-			id_offs[i].ip = ips[i];
-			memset(id_offs[i].build_id, 0, BUILD_ID_SIZE_MAX);
-		}
-		return;
+	if (!user || !current || !current->mm)
+		goto fallback;
+
+	work = bpf_mmap_unlock_guard_get();
+	if (IS_ERR(work))
+		goto fallback;
+
+	if (!mmap_read_trylock(current->mm)) {
+		bpf_mmap_unlock_guard_put(work);
+		goto fallback;
 	}
 
 	for (i = 0; i < trace_nr; i++) {
@@ -174,6 +170,15 @@ static void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
 		prev_build_id = id_offs[i].build_id;
 	}
 	bpf_mmap_unlock_mm(work, current->mm);
+	return;
+
+fallback:
+	/* cannot access current->mm, fall back to ips */
+	for (i = 0; i < trace_nr; i++) {
+		id_offs[i].status = BPF_STACK_BUILD_ID_IP;
+		id_offs[i].ip = ips[i];
+		memset(id_offs[i].build_id, 0, BUILD_ID_SIZE_MAX);
+	}
 }
 
 static struct perf_callchain_entry *
kernel/bpf/task_iter.c +10 -4
index 30dd2b31a38d9..481e2b43f0ed1 100644
@@ -780,9 +780,8 @@ static struct bpf_iter_reg task_vma_reg_info = {
 BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
 	   bpf_callback_t, callback_fn, void *, callback_ctx, u64, flags)
 {
-	struct mmap_unlock_irq_work *work = NULL;
+	struct mmap_unlock_irq_work *work;
 	struct vm_area_struct *vma;
-	bool irq_work_busy = false;
 	bool __maybe_unused mmput_needed = false;
 	struct mm_struct *mm;
 	int ret = -ENOENT;
@@ -819,9 +818,14 @@ BPF_CALL_5(bpf_find_vma, struct task_struct *, task, u64, start,
 	if (!mm)
 		return -ENOENT;
 
-	irq_work_busy = bpf_mmap_unlock_get_irq_work(&work);
+	work = bpf_mmap_unlock_guard_get();
+	if (IS_ERR(work)) {
+		ret = PTR_ERR(work);
+		goto out;
+	}
 
-	if (irq_work_busy || !mmap_read_trylock(mm)) {
+	if (!mmap_read_trylock(mm)) {
+		bpf_mmap_unlock_guard_put(work);
 		ret = -EBUSY;
 		goto out;
 	}
@@ -864,6 +868,8 @@ static void do_mmap_read_unlock(struct irq_work *entry)
 
 	work = container_of(entry, struct mmap_unlock_irq_work, irq_work);
 	mmap_read_unlock_non_owner(work->mm);
+	work->mm = NULL;
+	bpf_mmap_unlock_guard_put(work);
 }
 
 static int __init task_iter_init(void)
Signature · 1 line
2.53.0
author maintainer
#1572 lore raw

PATCH 1566/1887 bpf, cgroup: Fix storage null-ptr-deref after replacing prog

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Pu Lehui <[email protected]>

[ Upstream commit 3f562c537e9ecf4bc5e206cfffc2cc047f1b7e94 ]

Syzkaller reported a storage null-ptr-deref issue after replacing prog. This occurs in the following scenario:
1. prog A, an empty prog, is attached to a cgrp.
2. prog B uses BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE and calls the bpf_get_local_storage helper.
3. link_update is called to replace prog A with prog B.

The reason is that __cgroup_bpf_replace fails to alloc and assign the required cgrp storage for the incoming replacement prog. Consequently, the new prog inherits an uninit storage, leading to null-ptr-deref panic when kick the new prog.

Fix this by rejecting a link update if new_prog's cgroup storage is incompatible with link->prog.

---
 kernel/bpf/cgroup.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
kernel/bpf/cgroup.c +17 -0
index 6158b021f617f..3771a041d9de5 100644
@@ -797,6 +797,20 @@ static void replace_effective_prog(struct cgroup *cgrp,
 	}
 }
 
+static bool cgroup_bpf_storages_compatible(struct bpf_prog *old_prog,
+					   struct bpf_prog *new_prog)
+{
+	enum bpf_cgroup_storage_type stype;
+
+	for_each_cgroup_storage_type(stype) {
+		if (old_prog->aux->cgroup_storage[stype] !=
+		    new_prog->aux->cgroup_storage[stype])
+			return false;
+	}
+
+	return true;
+}
+
 /**
  * __cgroup_bpf_replace() - Replace link's program and propagate the change
  *                          to descendants
@@ -834,6 +848,9 @@ static int __cgroup_bpf_replace(struct cgroup *cgrp,
 	if (!found)
 		return -ENOENT;
 
+	if (!cgroup_bpf_storages_compatible(link->link.prog, new_prog))
+		return -EINVAL;
+
 	old_prog = xchg(&link->link.prog, new_prog);
 	replace_effective_prog(cgrp, atype, link);
 	bpf_prog_put(old_prog);
Signature · 1 line
2.53.0
author maintainer
#1573 lore raw

PATCH 1567/1887 btrfs: qgroup: fix a wrong length calculation in qgroup_free_reserved_data()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Qu Wenruo <[email protected]>

[ Upstream commit 9102b179512e11644fb0489ae62010a09afa199c ]

In that function, we round down the start position and round up the ending position.

But during the calculation of @len, we use "round_up(start + len, sectorsize)", which is the rounded up end position, not the rounded up length.

Which results a much larger length, and later we are still using "start + len", which is completely incorrect.

Fix it by declaring a local @aligned_start and @aligned_len and use them instead.

Fixes bc42bda22345 ("btrfs: qgroup: Fix qgroup reserved space underflow by only freeing reserved ranges")
---
 fs/btrfs/qgroup.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
fs/btrfs/qgroup.c +11 -7
index e927111567fa2..ff3cced9eff02 100644
@@ -3878,12 +3878,13 @@ static int qgroup_free_reserved_data(struct btrfs_inode *inode,
 	struct ulist_node *unode;
 	struct ulist_iterator uiter;
 	struct extent_changeset changeset;
+	const u32 sectorsize = root->fs_info->sectorsize;
+	const u64 aligned_start = round_down(start, sectorsize);
+	const u64 aligned_len = round_up(start + len, sectorsize) - aligned_start;
 	u64 freed = 0;
 	int ret;
 
 	extent_changeset_init(&changeset);
-	len = round_up(start + len, root->fs_info->sectorsize);
-	start = round_down(start, root->fs_info->sectorsize);
 
 	ULIST_ITER_INIT(&uiter);
 	while ((unode = ulist_next(&reserved->range_changed, &uiter))) {
@@ -3895,12 +3896,15 @@ static int qgroup_free_reserved_data(struct btrfs_inode *inode,
 
 		extent_changeset_release(&changeset);
 
-		/* Only free range in range [start, start + len) */
-		if (range_start >= start + len ||
-		    range_start + range_len <= start)
+		/*
+		 * Only free the range within
+		 * [aligned_start, aligned_start + aligned_len).
+		 */
+		if (range_start >= aligned_start + aligned_len ||
+		    range_start + range_len <= aligned_start)
 			continue;
-		free_start = max(range_start, start);
-		free_len = min(start + len, range_start + range_len) -
+		free_start = max(range_start, aligned_start);
+		free_len = min(aligned_start + aligned_len, range_start + range_len) -
 			   free_start;
 		/*
 		 * TODO: To also modify reserved->ranges_reserved to reflect
Signature · 1 line
2.53.0
author maintainer
#1574 lore raw

PATCH 1568/1887 btrfs: check if root is readonly when setting posix acl

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Sun YangKai <[email protected]>

[ Upstream commit 01c2f41fc441f75b3f9326443372faceb1cb6638 ]

For a filesystem which has btrfs read-only property set to true, all write operations including acl and xattr should be denied. However, acl can still be set even if btrfs ro property is true.

This happens because no function on the set_acl code path checks the root is readonly or not. It was checked in btrfs_setxattr_trans() but got removed in commit 353c2ea735e4 ("btrfs: remove redundant readonly root check in btrfs_setxattr_trans")

That commit didn't check if all the callers properly check the root's read-only flag. A previous fix is commit b51111271b03 ("btrfs: check if root is readonly while setting security xattr").

Always check if the root is read-only before performing the set acl operation.

Fixes 353c2ea735e4 ("btrfs: remove redundant readonly root check in btrfs_setxattr_trans")
---
 fs/btrfs/acl.c | 3 +++
 1 file changed, 3 insertions(+)
fs/btrfs/acl.c +3 -0
index 548d6a5477b42..038dc7315be90 100644
@@ -116,6 +116,9 @@ int btrfs_set_acl(struct user_namespace *mnt_userns, struct inode *inode,
 	int ret;
 	umode_t old_mode = inode->i_mode;
 
+	if (btrfs_root_readonly(BTRFS_I(inode)->root))
+		return -EROFS;
+
 	if (type == ACL_TYPE_ACCESS && acl) {
 		ret = posix_acl_update_mode(mnt_userns, inode,
 					    &inode->i_mode, &acl);
Signature · 1 line
2.53.0
author maintainer
#1575 lore raw

PATCH 1569/1887 btrfs: always wait for ordered extents to avoid OE races

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Qu Wenruo <[email protected]>

[ Upstream commit ec78575dde998c21be7e0cb2503b5620f34b6255 ]

[BUG]
Syzbot reported a bug that there can be conflicting OEs for the same range:

BTRFS critical (device loop4): panic in insert_ordered_extent:264: overlapping ordered extents, existing oe file_offset 16384 num_bytes 430080 flags 0x1089, new oe file_offset 16384 num_bytes 430080 flags 0x80 (errno=-17 Object alrea[ 179.162726][ T6897] BTRFS critical (device loop4): panic in insert_ordered_extent:264: overlapping ordered extents, existing oe file_offset 16384 num_bytes 430080 flags 0x1089, new oe file_offset 16384 num_bytes 430080 flags 0x80 (errno=-17 Object already exists)

  ------------[ cut here ]------------
  kernel BUG at fs/btrfs/ordered-data.c:264!
  Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
  RIP: 0010:btrfs_alloc_ordered_extent+0x943/0xad0
  Call Trace:
   <TASK>
   cow_file_range+0x744/0x12a0
   fallback_to_cow+0x5ea/0xa00
   run_delalloc_nocow+0x110c/0x17a0
   btrfs_run_delalloc_range+0xbe4/0x1c20
   writepage_delalloc+0x104d/0x1ba0
   btrfs_writepages+0x1667/0x28b0
   do_writepages+0x338/0x560
   filemap_fdatawrite_range+0x1f2/0x300
   btrfs_fdatawrite_range+0x54/0xf0
   btrfs_direct_write+0x6a0/0xc30
   btrfs_do_write_iter+0x329/0x790
   do_iter_readv_writev+0x624/0x8d0
   vfs_writev+0x34c/0x990
   __se_sys_pwritev2+0x17a/0x2a0
   do_syscall_64+0x174/0x580
   entry_SYSCALL_64_after_hwframe+0x77/0x7f
   </TASK>
  ---[ end trace 0000000000000000 ]---

[CAUSE]
Since commit ff66fe666233 ("btrfs: fix incorrect buffered IO fallback for append direct writes"), if the direct IO finished short, we will revert the isize back to the original one, so that append writes can be respected during the buffered fallback.

Normally we rely on lock_and_cleanup_extent_if_need() function during buffered writeback to wait for any existing ordered extents.

But that ordered extent waiting only happens if the start_pos is inside the isize.
Since we have reverted the isize during failed direct IO, we will not wait for any ordered extents.

This means we can have a race where the direct IO OE is still in the tree, finished but not yet removed, then we're inserting the OE for the buffered write, causing the above crash.

[FIX]
Make the OE wait to be unconditional, to handle the reverted isize situation.

And since lock_and_cleanup_extent_if_need() now either lock the extents or return -EAGAIN, also remove the branches that handles no-extent-locked cases, and rename it to remove the "_if_need" suffix.

The following micro benchmark shows the runtime difference for btrfs_buffered_write(), doing xfs_io -f -c "pwrite 0 1m" workload, all values are the average runtime in nano seconds.

function runtime | before | after
-----------------------------------+-------------+---------------

 lock_and_cleanup_extent_if_need()  |     58.2    |    183.0
 btrfs_buffered_write()             |   2115.6    |   2973.3

The overall runtime of btrfs_buffered_write() is still pretty tiny (still less than 3 micro seconds), I'd say the extra cost is still acceptable.

An alternative to fix this problem is to wait ordered extents during iomap_end() where the isize revert is done.

But that solution will break nowait requirement, as if a nowait direct IO finished short, we have to wait for the OEs unconditionally or the next append buffered IO can still hit the same problem.

So here we have to move the wait cost to buffered write, but at least the code is slightly more streamline.

Reported-by [email protected]
Fixes ff66fe666233 ("btrfs: fix incorrect buffered IO fallback for append direct writes")
---
 fs/btrfs/file.c | 103 ++++++++++++++++++++----------------------------
 1 file changed, 42 insertions(+), 61 deletions(-)
fs/btrfs/file.c +42 -61
index 0970ec776ff8e..d34407035b6a1 100644
@@ -949,69 +949,63 @@ static noinline int prepare_pages(struct inode *inode, struct page **pages,
 
 /*
  * This function locks the extent and properly waits for data=ordered extents
- * to finish before allowing the pages to be modified if need.
+ * to finish before allowing the pages to be modified.
  *
  * The return value:
- * 1 - the extent is locked
- * 0 - the extent is not locked, and everything is OK
+ * 0 - the extent is locked
  * -EAGAIN - need re-prepare the pages
  * the other < 0 number - Something wrong happens
  */
 static noinline int
-lock_and_cleanup_extent_if_need(struct btrfs_inode *inode, struct page **pages,
-				size_t num_pages, loff_t pos,
-				size_t write_bytes,
-				u64 *lockstart, u64 *lockend, bool nowait,
-				struct extent_state **cached_state)
+lock_and_cleanup_extent(struct btrfs_inode *inode, struct page **pages,
+			size_t num_pages, loff_t pos,
+			size_t write_bytes,
+			u64 *lockstart, u64 *lockend, bool nowait,
+			struct extent_state **cached_state)
 {
 	struct btrfs_fs_info *fs_info = inode->root->fs_info;
+	struct btrfs_ordered_extent *ordered;
 	u64 start_pos;
 	u64 last_pos;
 	int i;
-	int ret = 0;
 
 	start_pos = round_down(pos, fs_info->sectorsize);
 	last_pos = round_up(pos + write_bytes, fs_info->sectorsize) - 1;
 
-	if (start_pos < inode->vfs_inode.i_size) {
-		struct btrfs_ordered_extent *ordered;
-
-		if (nowait) {
-			if (!try_lock_extent(&inode->io_tree, start_pos, last_pos)) {
-				for (i = 0; i < num_pages; i++) {
-					unlock_page(pages[i]);
-					put_page(pages[i]);
-					pages[i] = NULL;
-				}
-
-				return -EAGAIN;
-			}
-		} else {
-			lock_extent(&inode->io_tree, start_pos, last_pos, cached_state);
-		}
-
-		ordered = btrfs_lookup_ordered_range(inode, start_pos,
-						     last_pos - start_pos + 1);
-		if (ordered &&
-		    ordered->file_offset + ordered->num_bytes > start_pos &&
-		    ordered->file_offset <= last_pos) {
-			unlock_extent(&inode->io_tree, start_pos, last_pos,
-				      cached_state);
+	if (nowait) {
+		if (!try_lock_extent(&inode->io_tree, start_pos, last_pos)) {
 			for (i = 0; i < num_pages; i++) {
 				unlock_page(pages[i]);
 				put_page(pages[i]);
+				pages[i] = NULL;
 			}
-			btrfs_start_ordered_extent(ordered, 1);
-			btrfs_put_ordered_extent(ordered);
+
 			return -EAGAIN;
 		}
-		if (ordered)
-			btrfs_put_ordered_extent(ordered);
+	} else {
+		lock_extent(&inode->io_tree, start_pos, last_pos, cached_state);
+	}
 
-		*lockstart = start_pos;
-		*lockend = last_pos;
-		ret = 1;
+	ordered = btrfs_lookup_ordered_range(inode, start_pos,
+					     last_pos - start_pos + 1);
+	if (ordered &&
+	    ordered->file_offset + ordered->num_bytes > start_pos &&
+	    ordered->file_offset <= last_pos) {
+		unlock_extent(&inode->io_tree, start_pos, last_pos,
+			      cached_state);
+		for (i = 0; i < num_pages; i++) {
+			unlock_page(pages[i]);
+			put_page(pages[i]);
+		}
+		btrfs_start_ordered_extent(ordered, 1);
+		btrfs_put_ordered_extent(ordered);
+		return -EAGAIN;
 	}
+	if (ordered)
+		btrfs_put_ordered_extent(ordered);
+
+	*lockstart = start_pos;
+	*lockend = last_pos;
 
 	/*
 	 * We should be called after prepare_pages() which should have locked
@@ -1020,7 +1014,7 @@ lock_and_cleanup_extent_if_need(struct btrfs_inode *inode, struct page **pages,
 	for (i = 0; i < num_pages; i++)
 		WARN_ON(!PageLocked(pages[i]));
 
-	return ret;
+	return 0;
 }
 
 /*
@@ -1214,7 +1208,6 @@ static noinline ssize_t btrfs_buffered_write(struct kiocb *iocb,
 		size_t copied;
 		size_t dirty_sectors;
 		size_t num_sectors;
-		int extents_locked;
 
 		/*
 		 * Fault pages before locking them in prepare_pages
@@ -1298,17 +1291,15 @@ static noinline ssize_t btrfs_buffered_write(struct kiocb *iocb,
 			break;
 		}
 
-		extents_locked = lock_and_cleanup_extent_if_need(
-				BTRFS_I(inode), pages,
-				num_pages, pos, write_bytes, &lockstart,
-				&lockend, nowait, &cached_state);
-		if (extents_locked < 0) {
-			if (!nowait && extents_locked == -EAGAIN)
+		ret = lock_and_cleanup_extent(BTRFS_I(inode), pages,
+					       num_pages, pos, write_bytes, &lockstart,
+					       &lockend, nowait, &cached_state);
+		if (ret < 0) {
+			if (!nowait && ret == -EAGAIN)
 				goto again;
 
 			btrfs_delalloc_release_extents(BTRFS_I(inode),
 						       reserve_bytes);
-			ret = extents_locked;
 			break;
 		}
 
@@ -1361,18 +1352,8 @@ static noinline ssize_t btrfs_buffered_write(struct kiocb *iocb,
 					dirty_pages, pos, copied,
 					&cached_state, only_release_metadata);
 
-		/*
-		 * If we have not locked the extent range, because the range's
-		 * start offset is >= i_size, we might still have a non-NULL
-		 * cached extent state, acquired while marking the extent range
-		 * as delalloc through btrfs_dirty_pages(). Therefore free any
-		 * possible cached extent state to avoid a memory leak.
-		 */
-		if (extents_locked)
-			unlock_extent(&BTRFS_I(inode)->io_tree, lockstart,
-				      lockend, &cached_state);
-		else
-			free_extent_state(cached_state);
+		unlock_extent(&BTRFS_I(inode)->io_tree, lockstart, lockend,
+			      &cached_state);
 
 		btrfs_delalloc_release_extents(BTRFS_I(inode), reserve_bytes);
 		if (ret) {
Signature · 1 line
2.53.0
author maintainer
#1576 lore raw

PATCH 1570/1887 btrfs: defrag: fix deadlock between defrag and delalloc space reservation

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Filipe Manana <[email protected]>

[ Upstream commit ba02eab28041f9a4bbe9fc90c7249644fef6de0f ]

While running fsstress with autodefrag and flushoncommit, hit a deadlock due to the fact that defrag reserves delalloc space while it's holding dirty and locked folios, besides the extent range lock. The stack traces are the following:

   [958.624] task:kworker/u50:3   state:D stack:0     pid:20365 tgid:20365 ppid:2      task_flags:0x4208060 flags:0x00080000
   [958.626] Workqueue: events_unbound btrfs_async_reclaim_metadata_space [btrfs]
   [958.627] Call Trace:
   [958.628]  <TASK>
   [958.628]  __schedule+0x4be/0x10f0
   [958.629]  ? preempt_count_add+0x69/0xa0
   [958.630]  schedule+0x26/0xd0
   [958.631]  wait_current_trans+0x102/0x160 [btrfs]
   [958.632]  ? __pfx_autoremove_wake_function+0x10/0x10
   [958.633]  start_transaction+0x374/0x900 [btrfs]
   [958.634]  btrfs_commit_current_transaction+0x1d/0x70 [btrfs]
   [958.635]  flush_space+0xca/0x5e0 [btrfs]
   [958.636]  ? _raw_spin_unlock+0x15/0x30
   [958.637]  ? btrfs_reduce_alloc_profile+0x8c/0x190 [btrfs]
   [958.639]  ? _raw_spin_unlock+0x15/0x30
   [958.640]  ? calc_available_free_space.isra.0+0x6f/0x110 [btrfs]
   [958.641]  do_async_reclaim_metadata_space+0x84/0x190 [btrfs]
   [958.642]  btrfs_async_reclaim_metadata_space+0x64/0x80 [btrfs]
   [958.644]  process_one_work+0x19d/0x3a0
   [958.644]  worker_thread+0x1c4/0x330
   [958.645]  ? __pfx_worker_thread+0x10/0x10
   [958.646]  kthread+0xfc/0x130
   [958.647]  ? __pfx_kthread+0x10/0x10
   [958.648]  ret_from_fork+0x1f7/0x2c0
   [958.648]  ? __pfx_kthread+0x10/0x10
   [958.649]  ret_from_fork_asm+0x1a/0x30
   [958.650]  </TASK>
   [958.651] task:kworker/u49:7   state:D stack:0     pid:52990 tgid:52990 ppid:2      task_flags:0x4208060 flags:0x00080000
   [958.653] Workqueue: writeback wb_workfn (flush-btrfs-334)
   [958.655] Call Trace:
   [958.655]  <TASK>
   [958.656]  __schedule+0x4be/0x10f0
   [958.657]  ? __blk_flush_plug+0xe9/0x140
   [958.658]  schedule+0x26/0xd0
   [958.658]  io_schedule+0x42/0x70
   [958.659]  folio_wait_bit_common+0x12b/0x330
   [958.660]  ? folio_wait_bit_common+0x100/0x330
   [958.662]  ? __pfx_wake_page_function+0x10/0x10
   [958.663]  extent_write_cache_pages+0x599/0x830 [btrfs]
   [958.664]  ? acpi_fwnode_get_reference_args+0x1fa/0x270
   [958.665]  btrfs_writepages+0x77/0x130 [btrfs]
   [958.666]  ? __pfx_end_bbio_data_write+0x10/0x10 [btrfs]
   [958.667]  do_writepages+0xc6/0x160
   [958.668]  __writeback_single_inode+0x42/0x310
   [958.669]  writeback_sb_inodes+0x231/0x570
   [958.670]  wb_writeback+0x8a/0x340
   [958.671]  wb_workfn+0xbf/0x450
   [958.672]  ? finish_task_switch.isra.0+0xc1/0x350
   [958.673]  process_one_work+0x19d/0x3a0
   [958.673]  worker_thread+0x1c4/0x330
   [958.674]  ? __pfx_worker_thread+0x10/0x10
   [958.675]  kthread+0xfc/0x130
   [958.676]  ? __pfx_kthread+0x10/0x10
   [958.676]  ret_from_fork+0x1f7/0x2c0
   [958.677]  ? __pfx_kthread+0x10/0x10
   [958.678]  ret_from_fork_asm+0x1a/0x30
   [958.679]  </TASK>
   [958.679] task:btrfs-cleaner   state:D stack:0     pid:296750 tgid:296750 ppid:2      task_flags:0x208040 flags:0x00080000
   [958.681] Call Trace:
   [958.682]  <TASK>
   [958.682]  __schedule+0x4be/0x10f0
   [958.683]  schedule+0x26/0xd0
   [958.684]  handle_reserve_ticket+0x1b9/0x2c0 [btrfs]
   [958.685]  ? __pfx_autoremove_wake_function+0x10/0x10
   [958.686]  reserve_bytes+0x283/0x4c0 [btrfs]
   [958.687]  btrfs_reserve_metadata_bytes+0x18/0xb0 [btrfs]
   [958.688]  btrfs_delalloc_reserve_metadata+0x121/0x320 [btrfs]
   [958.690]  btrfs_delalloc_reserve_space+0x46/0xb0 [btrfs]
   [958.691]  btrfs_defrag_file+0x903/0x1110 [btrfs]
   [958.692]  btrfs_run_defrag_inodes+0x334/0x430 [btrfs]
   [958.694]  cleaner_kthread+0x97/0x1c0 [btrfs]
   [958.694]  ? __pfx_cleaner_kthread+0x10/0x10 [btrfs]
   [958.696]  kthread+0xfc/0x130
   [958.696]  ? __pfx_kthread+0x10/0x10
   [958.697]  ret_from_fork+0x1f7/0x2c0
   [958.698]  ? __pfx_kthread+0x10/0x10
   [958.699]  ret_from_fork_asm+0x1a/0x30
   [958.700]  </TASK>
   [958.716] task:fsstress        state:D stack:0     pid:296769 tgid:296769 ppid:296768 task_flags:0x400140 flags:0x00080000
   [958.718] Call Trace:
   [958.719]  <TASK>
   [958.719]  __schedule+0x4be/0x10f0
   [958.720]  ? preempt_count_add+0x69/0xa0
   [958.721]  schedule+0x26/0xd0
   [958.722]  wb_wait_for_completion+0x79/0xc0
   [958.723]  ? __pfx_autoremove_wake_function+0x10/0x10
   [958.724]  __writeback_inodes_sb_nr+0xc5/0xf0
   [958.725]  try_to_writeback_inodes_sb+0x55/0x70
   [958.726]  btrfs_commit_transaction+0x19d/0xeb0 [btrfs]
   [958.727]  ? start_transaction+0x343/0x900 [btrfs]
   [958.728]  btrfs_mksubvol+0x28b/0x4e0 [btrfs]
   [958.729]  btrfs_mksnapshot+0x74/0xa0 [btrfs]
   [958.730]  __btrfs_ioctl_snap_create+0x194/0x210 [btrfs]
   [958.732]  btrfs_ioctl_snap_create_v2+0xef/0x150 [btrfs]
   [958.733]  btrfs_ioctl+0x7ec/0x2a70 [btrfs]
   [958.734]  ? __virt_addr_valid+0xe4/0x180
   [958.735]  ? __check_object_size+0x1cd/0x1f0
   [958.736]  ? kmem_cache_free+0x146/0x380
   [958.737]  ? _raw_spin_unlock+0x15/0x30
   [958.738]  ? do_sys_openat2+0x83/0xd0
   [958.739]  __x64_sys_ioctl+0x92/0xe0
   [958.740]  do_syscall_64+0x60/0x590
   [958.741]  ? clear_bhb_loop+0x60/0xb0
   [958.742]  entry_SYSCALL_64_after_hwframe+0x76/0x7e
   [958.743] RIP: 0033:0x7f4431e108db
   [958.744] RSP: 002b:00007ffcd147db20 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
   [958.746] RAX: ffffffffffffffda RBX: 0000000000000004 RCX: 00007f4431e108db
   [958.747] RDX: 00007ffcd147eb90 RSI: 0000000050009417 RDI: 0000000000000005
   [958.749] RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
   [958.751] R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffcd147fbf0
   [958.752] R13: 00007ffcd147eb90 R14: 0000000000000005 R15: 0000000000000003
   [958.754]  </TASK>

What happens is the following:

1) The cleaner kthread is running autodefrag, and in defrag_one_range() it acquired all the folios for the range and locked them.

Then it locked the extent range in the inode's iotree.

It got two subranges from defrag_collect_targets(), the first one with folio A and the second one with folio B.

After it defragged the first subrange, folio A remains locked and dirty - it's only unlocked when defrag_one_range() returns.

When it attempts to defrag the second subrange (containing folio B), btrfs_delalloc_reserve_space() creates a space reservation ticket, due to lack of free metadata space and blocks waiting for the async metadata reclaim task to free space and wake it up;

2) The async reclaim metadata task attempts to commit the current transaction, but it blocks because there is another task that started the commit first;

3) A task creating a snapshot is committing the transaction and because the fs was mounted with flushoncommit, it calls try_to_writeback_inodes_sb(), which spawns a task to flush delalloc and waits for it to complete;

4) The task flushing delalloc (kworker/u49:7), finds that folio A for the inode being defragged is dirty, so it tries to lock it...

But it blocks because folio A is locked by the defrag task (the cleaner kthread) which is blocked waiting for the reservation ticket to be served, but the async reclaim metadata task is blocked waiting for the transaction commit, which in turn is blocked waiting for the delalloc flush task, which is trying to lock folio A, resulting in a deadlock.

The same type of problem can happen if the async reclaim task starts to flush delalloc, as that requires both locking the folio and the extent range in the inode's io tree, and in this case we don't need the fs to be mounted with flushoncommit. This type of problem has ocurred several times in the past with reflinks for example, where we had a dirty folio while holding the extent range locked and then starting a transaction blocked waiting for the async reclaim task due to lack of free metadata space.

So fix this by reserving delalloc space before locking folios and locking the extent range in the inode's iotree. We can not simply unlock the folios for each subrange given by defrag_collect_targets() after we defrag it because the same folio may be present too in the next subrange (due to large folios).

Fixes 22b398eeeed4 ("btrfs: defrag: introduce helper to defrag a contiguous prepared range")
---
 fs/btrfs/ioctl.c | 50 ++++++++++++++++++++++++++++++++----------------
 1 file changed, 33 insertions(+), 17 deletions(-)
fs/btrfs/ioctl.c +33 -17
index cae064c262cc9..46534e5d40bca 100644
@@ -1630,26 +1630,21 @@ static_assert(IS_ALIGNED(CLUSTER_SIZE, PAGE_SIZE));
  *
  * - Extent bits are locked
  */
-static int defrag_one_locked_target(struct btrfs_inode *inode,
-				    struct defrag_target_range *target,
-				    struct page **pages, int nr_pages,
-				    struct extent_state **cached_state)
+static void defrag_one_locked_target(struct btrfs_inode *inode,
+				     struct defrag_target_range *target,
+				     struct page **pages, int nr_pages,
+				     struct extent_state **cached_state)
 {
 	struct btrfs_fs_info *fs_info = inode->root->fs_info;
-	struct extent_changeset *data_reserved = NULL;
 	const u64 start = target->start;
 	const u64 len = target->len;
 	unsigned long last_index = (start + len - 1) >> PAGE_SHIFT;
 	unsigned long start_index = start >> PAGE_SHIFT;
 	unsigned long first_index = page_index(pages[0]);
-	int ret = 0;
 	int i;
 
 	ASSERT(last_index - first_index + 1 <= nr_pages);
 
-	ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len);
-	if (ret < 0)
-		return ret;
 	clear_extent_bit(&inode->io_tree, start, start + len - 1,
 			 EXTENT_DELALLOC | EXTENT_DO_ACCOUNTING |
 			 EXTENT_DEFRAG, cached_state);
@@ -1660,10 +1655,6 @@ static int defrag_one_locked_target(struct btrfs_inode *inode,
 		ClearPageChecked(pages[i]);
 		btrfs_page_clamp_set_dirty(fs_info, pages[i], start, len);
 	}
-	btrfs_delalloc_release_extents(inode, len);
-	extent_changeset_free(data_reserved);
-
-	return ret;
 }
 
 static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len,
@@ -1679,6 +1670,8 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len,
 	u64 last_index = (start + len - 1) >> PAGE_SHIFT;
 	u64 start_index = start >> PAGE_SHIFT;
 	unsigned int nr_pages = last_index - start_index + 1;
+	struct extent_changeset *data_reserved = NULL;
+	u64 last_defrag_end = start;
 	int ret = 0;
 	int i;
 
@@ -1689,6 +1682,22 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len,
 	if (!pages)
 		return -ENOMEM;
 
+	/*
+	 * Reserve delalloc space before locking the range and before locking
+	 * and dirtying any pages - otherwise we could deadlock, for example
+	 * after defrag of one range we dirty pages and keep them locked when
+	 * we move to the next range, so reserving delalloc space right before
+	 * each range could trigger flushing of delalloc and deadlock on the
+	 * extent lock or trigger a transaction commit with flushoncommit, which
+	 * can either deadlock on the lock of a page made dirty in the previous
+	 * range or the extent lock.
+	 */
+	ret = btrfs_delalloc_reserve_space(inode, &data_reserved, start, len);
+	if (ret < 0) {
+		kfree(pages);
+		return ret;
+	}
+
 	/* Prepare all pages */
 	for (i = 0; i < nr_pages; i++) {
 		pages[i] = defrag_prepare_one_page(inode, start_index + i);
@@ -1719,10 +1728,11 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len,
 		goto unlock_extent;
 
 	list_for_each_entry(entry, &target_list, list) {
-		ret = defrag_one_locked_target(inode, entry, pages, nr_pages,
-					       &cached_state);
-		if (ret < 0)
-			break;
+		defrag_one_locked_target(inode, entry, pages, nr_pages, &cached_state);
+		if (entry->start > last_defrag_end)
+			btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end,
+						     entry->start - last_defrag_end, true);
+		last_defrag_end = entry->start + entry->len;
 	}
 
 	list_for_each_entry_safe(entry, tmp, &target_list, list) {
@@ -1741,6 +1751,12 @@ static int defrag_one_range(struct btrfs_inode *inode, u64 start, u32 len,
 		}
 	}
 	kfree(pages);
+	btrfs_delalloc_release_extents(inode, len);
+	if (last_defrag_end < start + len)
+		btrfs_delalloc_release_space(inode, data_reserved, last_defrag_end,
+					     start + len - last_defrag_end, true);
+	extent_changeset_free(data_reserved);
+
 	return ret;
 }
 
Signature · 1 line
2.53.0
author maintainer
#1577 lore raw

PATCH 1571/1887 scsi: sd: Fix special_vec mempool leak when scsi_alloc_sgtables() fails

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Yang Xiuwei <[email protected]>

[ Upstream commit bb31844d88b77138b67aa20c3600203baff40140 ]

sd_set_special_bvec() allocates a special payload page for UNMAP and WRITE SAME commands. If scsi_alloc_sgtables() fails afterward in sd_setup_unmap_cmnd() or sd_setup_write_same{10,16}_cmnd(), the SCSI midlayer does not call uninit_command() because RQF_DONTPREP is not set yet, leaking the page.

Call sd_uninit_command() on error, and clear RQF_SPECIAL_PAYLOAD after freeing the page.

---
 drivers/scsi/sd.c | 25 ++++++++++++++++++++-----
 1 file changed, 20 insertions(+), 5 deletions(-)
drivers/scsi/sd.c +20 -5
index 5e6e5753387d1..32f850c6be114 100644
@@ -920,6 +920,7 @@ static blk_status_t sd_setup_unmap_cmnd(struct scsi_cmnd *cmd)
 	u32 nr_blocks = sectors_to_logical(sdp, blk_rq_sectors(rq));
 	unsigned int data_len = 24;
 	char *buf;
+	blk_status_t ret;
 
 	rq->special_vec.bv_page = mempool_alloc(sd_page_pool, GFP_ATOMIC);
 	if (!rq->special_vec.bv_page)
@@ -943,7 +944,10 @@ static blk_status_t sd_setup_unmap_cmnd(struct scsi_cmnd *cmd)
 	cmd->transfersize = data_len;
 	rq->timeout = SD_TIMEOUT;
 
-	return scsi_alloc_sgtables(cmd);
+	ret = scsi_alloc_sgtables(cmd);
+	if (ret != BLK_STS_OK)
+		sd_uninit_command(cmd);
+	return ret;
 }
 
 static blk_status_t sd_setup_write_same16_cmnd(struct scsi_cmnd *cmd,
@@ -955,6 +959,7 @@ static blk_status_t sd_setup_write_same16_cmnd(struct scsi_cmnd *cmd,
 	u64 lba = sectors_to_logical(sdp, blk_rq_pos(rq));
 	u32 nr_blocks = sectors_to_logical(sdp, blk_rq_sectors(rq));
 	u32 data_len = sdp->sector_size;
+	blk_status_t ret;
 
 	rq->special_vec.bv_page = mempool_alloc(sd_page_pool, GFP_ATOMIC);
 	if (!rq->special_vec.bv_page)
@@ -975,7 +980,10 @@ static blk_status_t sd_setup_write_same16_cmnd(struct scsi_cmnd *cmd,
 	cmd->transfersize = data_len;
 	rq->timeout = unmap ? SD_TIMEOUT : SD_WRITE_SAME_TIMEOUT;
 
-	return scsi_alloc_sgtables(cmd);
+	ret = scsi_alloc_sgtables(cmd);
+	if (ret != BLK_STS_OK)
+		sd_uninit_command(cmd);
+	return ret;
 }
 
 static blk_status_t sd_setup_write_same10_cmnd(struct scsi_cmnd *cmd,
@@ -987,6 +995,7 @@ static blk_status_t sd_setup_write_same10_cmnd(struct scsi_cmnd *cmd,
 	u64 lba = sectors_to_logical(sdp, blk_rq_pos(rq));
 	u32 nr_blocks = sectors_to_logical(sdp, blk_rq_sectors(rq));
 	u32 data_len = sdp->sector_size;
+	blk_status_t ret;
 
 	rq->special_vec.bv_page = mempool_alloc(sd_page_pool, GFP_ATOMIC);
 	if (!rq->special_vec.bv_page)
@@ -1007,7 +1016,10 @@ static blk_status_t sd_setup_write_same10_cmnd(struct scsi_cmnd *cmd,
 	cmd->transfersize = data_len;
 	rq->timeout = unmap ? SD_TIMEOUT : SD_WRITE_SAME_TIMEOUT;
 
-	return scsi_alloc_sgtables(cmd);
+	ret = scsi_alloc_sgtables(cmd);
+	if (ret != BLK_STS_OK)
+		sd_uninit_command(cmd);
+	return ret;
 }
 
 static blk_status_t sd_setup_write_zeroes_cmnd(struct scsi_cmnd *cmd)
@@ -1352,8 +1364,11 @@ static void sd_uninit_command(struct scsi_cmnd *SCpnt)
 {
 	struct request *rq = scsi_cmd_to_rq(SCpnt);
 
-	if (rq->rq_flags & RQF_SPECIAL_PAYLOAD)
-		mempool_free(rq->special_vec.bv_page, sd_page_pool);
+	if (!(rq->rq_flags & RQF_SPECIAL_PAYLOAD))
+		return;
+
+	mempool_free(rq->special_vec.bv_page, sd_page_pool);
+	rq->rq_flags &= ~RQF_SPECIAL_PAYLOAD;
 }
 
 static bool sd_need_revalidate(struct block_device *bdev,
Signature · 1 line
2.53.0
author maintainer
#1578 lore raw

PATCH 1572/1887 coresight: etm4x: missing cscfg_csdev_disable_active_config() in perf enable

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Yeoreum Yun <[email protected]>

[ Upstream commit 0a47f0be6557b4a851addd430383a4dc7ee08752 ]

In the perf enable path, there are missing cases where cscfg_csdev_disable_active_config() is not called:

- Branch broadcast is selected but not supported by the hardware
- etm4_enable_hw() fails

This can lead to a leak of config_desc->active_cnt. Fix this by properly calling cscfg_csdev_disable_active_config() in these error paths.

Fixes 810ac401db1f ("coresight: etm4x: Add complex configuration handlers to etmv4")
---
 .../coresight/coresight-etm4x-core.c          | 38 +++++++++++--------
 1 file changed, 22 insertions(+), 16 deletions(-)
drivers/hwtracing/coresight/coresight-etm4x-core.c +22 -16
index 32be2cd1b3b7d..0b77c180d8557 100644
@@ -669,8 +669,6 @@ static int etm4_parse_event_config(struct coresight_device *csdev,
 	struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
 	struct etmv4_config *config = &drvdata->config;
 	struct perf_event_attr *attr = &event->attr;
-	unsigned long cfg_hash;
-	int preset;
 
 	/* Clear configuration from previous run */
 	memset(config, 0, sizeof(struct etmv4_config));
@@ -740,20 +738,6 @@ static int etm4_parse_event_config(struct coresight_device *csdev,
 		/* bit[12], Return stack enable bit */
 		config->cfg |= TRCCONFIGR_RS;
 
-	/*
-	 * Set any selected configuration and preset.
-	 *
-	 * This extracts the values of PMU_FORMAT_ATTR(configid) and PMU_FORMAT_ATTR(preset)
-	 * in the perf attributes defined in coresight-etm-perf.c.
-	 * configid uses bits 63:32 of attr->config2, preset uses bits 3:0 of attr->config.
-	 * A zero configid means no configuration active, preset = 0 means no preset selected.
-	 */
-	if (attr->config2 & GENMASK_ULL(63, 32)) {
-		cfg_hash = (u32)(attr->config2 >> 32);
-		preset = attr->config & 0xF;
-		ret = cscfg_csdev_enable_active_config(csdev, cfg_hash, preset);
-	}
-
 	/* branch broadcast - enable if selected and supported */
 	if (attr->config & BIT(ETM_OPT_BRANCH_BROADCAST)) {
 		if (!drvdata->trcbb) {
@@ -777,6 +761,9 @@ static int etm4_enable_perf(struct coresight_device *csdev,
 {
 	int ret = 0;
 	struct etmv4_drvdata *drvdata = dev_get_drvdata(csdev->dev.parent);
+	struct perf_event_attr *attr = &event->attr;
+	unsigned long cfg_hash = 0;
+	int preset;
 
 	if (WARN_ON_ONCE(drvdata->cpu != smp_processor_id())) {
 		ret = -EINVAL;
@@ -787,8 +774,27 @@ static int etm4_enable_perf(struct coresight_device *csdev,
 	ret = etm4_parse_event_config(csdev, event);
 	if (ret)
 		goto out;
+
+	/*
+	 * Set any selected configuration and preset.
+	 *
+	 * This extracts the values of PMU_FORMAT_ATTR(configid) and PMU_FORMAT_ATTR(preset)
+	 * in the perf attributes defined in coresight-etm-perf.c.
+	 * configid uses bits 63:32 of attr->config2, preset uses bits 3:0 of attr->config.
+	 * A zero configid means no configuration active, preset = 0 means no preset selected.
+	 */
+	if (attr->config2 & GENMASK_ULL(63, 32)) {
+		cfg_hash = (u32)(attr->config2 >> 32);
+		preset = attr->config & 0xF;
+		ret = cscfg_csdev_enable_active_config(csdev, cfg_hash, preset);
+		if (ret)
+			goto out;
+	}
+
 	/* And enable it */
 	ret = etm4_enable_hw(drvdata);
+	if (ret && cfg_hash)
+		cscfg_csdev_disable_active_config(csdev);
 
 out:
 	return ret;
Signature · 1 line
2.53.0
author maintainer
#1579 lore raw

PATCH 1573/1887 drm/amd/display: Resize MST HDCP per-connector arrays to 32

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Harry Wentland <[email protected]>

[ Upstream commit 261e0fe4e2c99f687114b64b10b98db964b475f4 ]

AMDGPU_DM_MAX_DISPLAY_INDEX is 31. It suggest a maximum number of 32 connectors. But the way it's used is like MAX_DISPLAY_COUNT. Hence we're off by one with DRM core, which supports a max of 32 connectors.

Rename AMDGPU_DM_MAX_DISPLAY_INDEX to AMDGPU_DM_MAX_DISPLAY_COUNT to match its actual use, and increase the size to 32 to match the originally intended size.

Fixes 82986fd631fa ("drm/amd/display: save restore hdcp state when display is unplugged from mst hub")
Assisted-by Copilot:claude-opus-4.8
---
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h      |  2 +-
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c | 10 +++++-----
 drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h |  8 ++++----
 3 files changed, 10 insertions(+), 10 deletions(-)
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +1 -1
index 12385b6f8443b..6036b138245ba 100644
@@ -43,7 +43,7 @@
  * in amdgpu_dm_kms.h file
  */
 
-#define AMDGPU_DM_MAX_DISPLAY_INDEX 31
+#define AMDGPU_DM_MAX_DISPLAY_COUNT 32
 
 #define AMDGPU_DM_MAX_CRTC 6
 
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.c +5 -5
index 6110d88efdbba..678ee48075e99 100644
@@ -256,7 +256,7 @@ void hdcp_reset_display(struct hdcp_workqueue *hdcp_work, unsigned int link_inde
 
 	cancel_delayed_work(&hdcp_w->property_validate_dwork);
 
-	for (conn_index = 0; conn_index < AMDGPU_DM_MAX_DISPLAY_INDEX; conn_index++) {
+	for (conn_index = 0; conn_index < AMDGPU_DM_MAX_DISPLAY_COUNT; conn_index++) {
 		hdcp_w->encryption_status[conn_index] =
 			MOD_HDCP_ENCRYPTION_STATUS_HDCP_OFF;
 		if (hdcp_w->aconnector[conn_index]) {
@@ -303,7 +303,7 @@ static void event_property_update(struct work_struct *work)
 	struct drm_connector *connector;
 	struct drm_connector_state *conn_state;
 
-	for (conn_index = 0; conn_index < AMDGPU_DM_MAX_DISPLAY_INDEX; conn_index++) {
+	for (conn_index = 0; conn_index < AMDGPU_DM_MAX_DISPLAY_COUNT; conn_index++) {
 		aconnector = hdcp_work->aconnector[conn_index];
 
 		if (!aconnector)
@@ -375,7 +375,7 @@ static void event_property_validate(struct work_struct *work)
 
 	guard(mutex)(&hdcp_work->mutex);
 
-	for (conn_index = 0; conn_index < AMDGPU_DM_MAX_DISPLAY_INDEX;
+	for (conn_index = 0; conn_index < AMDGPU_DM_MAX_DISPLAY_COUNT;
 	     conn_index++) {
 		aconnector = hdcp_work->aconnector[conn_index];
 
@@ -753,10 +753,10 @@ struct hdcp_workqueue *hdcp_create_workqueue(struct amdgpu_device *adev,
 
 		memset(hdcp_work[i].aconnector, 0,
 		       sizeof(struct amdgpu_dm_connector *) *
-			       AMDGPU_DM_MAX_DISPLAY_INDEX);
+			       AMDGPU_DM_MAX_DISPLAY_COUNT);
 		memset(hdcp_work[i].encryption_status, 0,
 		       sizeof(enum mod_hdcp_encryption_status) *
-			       AMDGPU_DM_MAX_DISPLAY_INDEX);
+			       AMDGPU_DM_MAX_DISPLAY_COUNT);
 	}
 
 	cp_psp->funcs.update_stream_config = update_config;
drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_hdcp.h +4 -4
index 69b445b011c8c..d18c4cc7d69f3 100644
@@ -43,7 +43,7 @@ struct hdcp_workqueue {
 	struct delayed_work callback_dwork;
 	struct delayed_work watchdog_timer_dwork;
 	struct delayed_work property_validate_dwork;
-	struct amdgpu_dm_connector *aconnector[AMDGPU_DM_MAX_DISPLAY_INDEX];
+	struct amdgpu_dm_connector *aconnector[AMDGPU_DM_MAX_DISPLAY_COUNT];
 	struct mutex mutex;
 
 	struct mod_hdcp hdcp;
@@ -51,7 +51,7 @@ struct hdcp_workqueue {
 	struct mod_hdcp_display display;
 	struct mod_hdcp_link link;
 
-	enum mod_hdcp_encryption_status encryption_status[AMDGPU_DM_MAX_DISPLAY_INDEX];
+	enum mod_hdcp_encryption_status encryption_status[AMDGPU_DM_MAX_DISPLAY_COUNT];
 	/* when display is unplugged from mst hub, connctor will be
 	 * destroyed within dm_dp_mst_connector_destroy. connector
 	 * hdcp perperties, like type, undesired, desired, enabled,
@@ -61,9 +61,9 @@ struct hdcp_workqueue {
 	 * will be retrieved from hdcp_work within dm_dp_mst_get_modes
 	 */
 	/* un-desired, desired, enabled */
-	unsigned int content_protection[AMDGPU_DM_MAX_DISPLAY_INDEX];
+	unsigned int content_protection[AMDGPU_DM_MAX_DISPLAY_COUNT];
 	/* hdcp1.x, hdcp2.x */
-	unsigned int hdcp_content_type[AMDGPU_DM_MAX_DISPLAY_INDEX];
+	unsigned int hdcp_content_type[AMDGPU_DM_MAX_DISPLAY_COUNT];
 
 	uint8_t max_link;
 
Signature · 1 line
2.53.0
author maintainer
#1580 lore raw

PATCH 1574/1887 bpf: Clear buf on error in __bpf_get_task_stack

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Jiri Olsa <[email protected]>

[ Upstream commit f5d242825ca417bb6afe35fde6e8880f97ca43fb ]

Both bpf_get_task_stack and bpf_get_task_stack_sleepable helpers that use __bpf_get_task_stack have buf defined as ARG_PTR_TO_UNINIT_MEM argument and we should initialize the buf on every return path.

Adding missing buf memset for __bpf_get_task_stack fail paths. This provides deterministic buffer contents, which is useful when the buffer is used directly as a map key.

Fixes 06ab134ce8ec ("bpf: Refcount task stack in bpf_get_task_stack")
Fixes b992f01e6615 ("bpf: Guard against accessing NULL pt_regs in bpf_get_task_stack()")
---
 kernel/bpf/stackmap.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)
kernel/bpf/stackmap.c +5 -2
index b8df42496f4fe..3de70a1578ca1 100644
@@ -504,14 +504,17 @@ BPF_CALL_4(bpf_get_task_stack, struct task_struct *, task, void *, buf,
 	struct pt_regs *regs;
 	long res = -EINVAL;
 
-	if (!try_get_task_stack(task))
+	if (!try_get_task_stack(task)) {
+		memset(buf, 0, size);
 		return -EFAULT;
+	}
 
 	regs = task_pt_regs(task);
 	if (regs)
 		res = __bpf_get_stack(regs, task, NULL, buf, size, flags);
+	else
+		memset(buf, 0, size);
 	put_task_stack(task);
-
 	return res;
 }
 
Signature · 1 line
2.53.0
author maintainer
#1581 lore raw

PATCH 1575/1887 ocfs2/heartbeat: fix slot mapping rollback leaks on error paths

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Yufan Chen <[email protected]>

[ Upstream commit 5686459423d03d192134166f0ec45f98bb2d5d57 ]

o2hb_map_slot_data() allocates hr_tmp_block, hr_slots, hr_slot_data, and pages in stages. If a later allocation fails, the current code returns without unwinding the earlier allocations.

o2hb_region_dev_store() also leaves slot mapping resources behind when setup aborts, and it keeps hr_aborted_start/hr_node_deleted set across retries. That leaves stale state behind after a failed start.

Factor the slot cleanup into o2hb_unmap_slot_data(), use it from both o2hb_map_slot_data() and o2hb_region_release(), and call it from the dev_store() rollback after stopping a started heartbeat thread. While freeing pages, clear each hr_slot_data entry as it is released, and reset the start state before each new setup attempt.

This closes the slot mapping leak on allocation/setup failure paths and keeps failed setup attempts retryable.

Stable-dep-of: 011291b70ba4 ("ocfs2: o2hb: quiesce negotiate handlers and timeout work")

---
 fs/ocfs2/cluster/heartbeat.c | 69 +++++++++++++++++++++++++-----------
 1 file changed, 49 insertions(+), 20 deletions(-)
fs/ocfs2/cluster/heartbeat.c +49 -20
index a350633bf7222..18c10fa9d7761 100644
@@ -1511,33 +1511,45 @@ static struct o2hb_region *to_o2hb_region(struct config_item *item)
 	return item ? container_of(item, struct o2hb_region, hr_item) : NULL;
 }
 
-/* drop_item only drops its ref after killing the thread, nothing should
- * be using the region anymore.  this has to clean up any state that
- * attributes might have built up. */
-static void o2hb_region_release(struct config_item *item)
+static void o2hb_unmap_slot_data(struct o2hb_region *reg)
 {
 	int i;
 	struct page *page;
-	struct o2hb_region *reg = to_o2hb_region(item);
-
-	mlog(ML_HEARTBEAT, "hb region release (%pg)\n", reg->hr_bdev);
-
-	kfree(reg->hr_tmp_block);
 
 	if (reg->hr_slot_data) {
 		for (i = 0; i < reg->hr_num_pages; i++) {
 			page = reg->hr_slot_data[i];
-			if (page)
+			if (page) {
 				__free_page(page);
+				reg->hr_slot_data[i] = NULL;
+			}
 		}
 		kfree(reg->hr_slot_data);
+		reg->hr_slot_data = NULL;
 	}
 
+	kfree(reg->hr_slots);
+	reg->hr_slots = NULL;
+
+	kfree(reg->hr_tmp_block);
+	reg->hr_tmp_block = NULL;
+}
+
+/* drop_item only drops its ref after killing the thread, nothing should
+ * be using the region anymore.  this has to clean up any state that
+ * attributes might have built up.
+ */
+static void o2hb_region_release(struct config_item *item)
+{
+	struct o2hb_region *reg = to_o2hb_region(item);
+
+	mlog(ML_HEARTBEAT, "hb region release (%pg)\n", reg->hr_bdev);
+
+	o2hb_unmap_slot_data(reg);
+
 	if (reg->hr_bdev)
 		blkdev_put(reg->hr_bdev, FMODE_READ|FMODE_WRITE);
 
-	kfree(reg->hr_slots);
-
 	debugfs_remove_recursive(reg->hr_debug_dir);
 	kfree(reg->hr_db_livenodes);
 	kfree(reg->hr_db_regnum);
@@ -1688,6 +1700,7 @@ static void o2hb_init_region_params(struct o2hb_region *reg)
 static int o2hb_map_slot_data(struct o2hb_region *reg)
 {
 	int i, j;
+	int ret = -ENOMEM;
 	unsigned int last_slot;
 	unsigned int spp = reg->hr_slots_per_page;
 	struct page *page;
@@ -1695,15 +1708,15 @@ static int o2hb_map_slot_data(struct o2hb_region *reg)
 	struct o2hb_disk_slot *slot;
 
 	reg->hr_tmp_block = kmalloc(reg->hr_block_bytes, GFP_KERNEL);
-	if (reg->hr_tmp_block == NULL)
-		return -ENOMEM;
+	if (!reg->hr_tmp_block)
+		goto out;
 
 	reg->hr_slots = kcalloc(reg->hr_blocks,
 				sizeof(struct o2hb_disk_slot), GFP_KERNEL);
-	if (reg->hr_slots == NULL)
-		return -ENOMEM;
+	if (!reg->hr_slots)
+		goto out;
 
-	for(i = 0; i < reg->hr_blocks; i++) {
+	for (i = 0; i < reg->hr_blocks; i++) {
 		slot = &reg->hr_slots[i];
 		slot->ds_node_num = i;
 		INIT_LIST_HEAD(&slot->ds_live_item);
@@ -1718,12 +1731,12 @@ static int o2hb_map_slot_data(struct o2hb_region *reg)
 	reg->hr_slot_data = kcalloc(reg->hr_num_pages, sizeof(struct page *),
 				    GFP_KERNEL);
 	if (!reg->hr_slot_data)
-		return -ENOMEM;
+		goto out;
 
-	for(i = 0; i < reg->hr_num_pages; i++) {
+	for (i = 0; i < reg->hr_num_pages; i++) {
 		page = alloc_page(GFP_KERNEL);
 		if (!page)
-			return -ENOMEM;
+			goto out;
 
 		reg->hr_slot_data[i] = page;
 
@@ -1743,6 +1756,10 @@ static int o2hb_map_slot_data(struct o2hb_region *reg)
 	}
 
 	return 0;
+
+out:
+	o2hb_unmap_slot_data(reg);
+	return ret;
 }
 
 /* Read in all the slots available and populate the tracking
@@ -1833,6 +1850,8 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
 		goto out3;
 	}
 
+	reg->hr_aborted_start = 0;
+	reg->hr_node_deleted = 0;
 	o2hb_init_region_params(reg);
 
 	/* Generation of zero is invalid */
@@ -1924,6 +1943,16 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
 
 out3:
 	if (ret < 0) {
+		spin_lock(&o2hb_live_lock);
+		hb_task = reg->hr_task;
+		reg->hr_task = NULL;
+		spin_unlock(&o2hb_live_lock);
+
+		if (hb_task)
+			kthread_stop(hb_task);
+
+		o2hb_unmap_slot_data(reg);
+
 		blkdev_put(reg->hr_bdev, FMODE_READ | FMODE_WRITE);
 		reg->hr_bdev = NULL;
 	}
Signature · 1 line
2.53.0
author maintainer
#1582 lore raw

PATCH 1576/1887 ocfs2/cluster: keep heartbeat local node stable

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Cen Zhang <[email protected]>

[ Upstream commit 688bc88e2046dd6ce81ce18079b5254cb8dadc0e ]

o2nm_node_local_store() handles local=0 by stopping o2net and setting cl_local_node to O2NM_INVALID_NODE_NUM, but it leaves cl_has_local set. That stale state makes o2nm_this_node() return 255, blocks a later local=1 attempt with -EBUSY, and can feed 255 to heartbeat users that call o2nm_this_node() dynamically.

Clearing cl_has_local is required when the local node is reset. But heartbeat threads can still be running at that point. They pin the local node config item at startup, yet o2hb_do_disk_heartbeat() and thread teardown re-read o2nm_this_node() for the local slot and for o2nm_undepend_this_node(). Once local=0 has cleared the live local-node state, those dynamic reads return O2NM_MAX_NODES, which is also the invalid node number 255.

Store the local node number in the heartbeat region when the region starts. Use that stable node for heartbeat slot writes/checks, negotiation messages, and the final configfs undepend. Stop the heartbeat loop when the current local node no longer matches the stored node, and clear cl_has_local together with cl_local_node in the local=0 path so nodemanager state matches node removal.

Validation reproduced this kernel report:
KASAN slab-out-of-bounds in o2hb_do_disk_heartbeat+0x372/0xb30

RIP: 0010:memset+0xf/0x20
Read of size 8
Call trace:
  dump_stack_lvl+0x66/0xa0
  print_report+0xd0/0x630
  o2hb_do_disk_heartbeat+0x372/0xb30 (fs/ocfs2/cluster/heartbeat.c:1079)
  srso_alias_return_thunk+0x5/0xfbef5
  __virt_addr_valid+0x188/0x2f0
  kasan_report+0xe4/0x120
  o2hb_do_disk_heartbeat+0x5/0xb30 (fs/ocfs2/cluster/heartbeat.c:1079)
  o2hb_thread+0x14e/0x770
  kthread_affine_node+0x139/0x180
  lockdep_hardirqs_on_prepare+0xda/0x190
  trace_hardirqs_on+0x18/0x130
  kthread+0x19d/0x1e0
  ret_from_fork+0x37a/0x4d0
  __switch_to+0x2d5/0x6f0
  ret_from_fork_asm+0x1a/0x30
Fixes a7f6a5fb4bde ("[PATCH] OCFS2: The Second Oracle Cluster Filesystem")
Assisted-by Codex:gpt-5.5
Cc Mark Fasheh <[email protected]>
Cc Joel Becker <[email protected]>
Cc Junxiao Bi <[email protected]>
Cc Changwei Ge <[email protected]>
Cc Jun Piao <[email protected]>
Cc Heming Zhao <[email protected]>

Stable-dep-of: 011291b70ba4 ("ocfs2: o2hb: quiesce negotiate handlers and timeout work")

---
 fs/ocfs2/cluster/heartbeat.c   | 43 +++++++++++++++++++++++-----------
 fs/ocfs2/cluster/nodemanager.c | 19 +++++++++++----
 fs/ocfs2/cluster/nodemanager.h |  2 ++
 3 files changed, 46 insertions(+), 18 deletions(-)
fs/ocfs2/cluster/heartbeat.c +29 -14
index 18c10fa9d7761..3b1afb46b199e 100644
@@ -210,6 +210,7 @@ struct o2hb_region {
 
 	/* protected by the hr_callback_sem */
 	struct task_struct 	*hr_task;
+	u8			hr_node_num;
 
 	unsigned int		hr_blocks;
 	unsigned long long	hr_start_block;
@@ -352,12 +353,12 @@ static void o2hb_disarm_timeout(struct o2hb_region *reg)
 	cancel_delayed_work_sync(&reg->hr_nego_timeout_work);
 }
 
-static int o2hb_send_nego_msg(int key, int type, u8 target)
+static int o2hb_send_nego_msg(int key, int type, u8 target, u8 node_num)
 {
 	struct o2hb_nego_msg msg;
 	int status, ret;
 
-	msg.node_num = o2nm_this_node();
+	msg.node_num = node_num;
 again:
 	ret = o2net_send_message(type, key, &msg, sizeof(msg),
 			target, &status);
@@ -375,8 +376,10 @@ static void o2hb_nego_timeout(struct work_struct *work)
 	unsigned long live_node_bitmap[BITS_TO_LONGS(O2NM_MAX_NODES)];
 	int master_node, i, ret;
 	struct o2hb_region *reg;
+	u8 node_num;
 
 	reg = container_of(work, struct o2hb_region, hr_nego_timeout_work.work);
+	node_num = reg->hr_node_num;
 	/* don't negotiate timeout if last hb failed since it is very
 	 * possible io failed. Should let write timeout fence self.
 	 */
@@ -387,10 +390,10 @@ static void o2hb_nego_timeout(struct work_struct *work)
 	/* lowest node as master node to make negotiate decision. */
 	master_node = find_first_bit(live_node_bitmap, O2NM_MAX_NODES);
 
-	if (master_node == o2nm_this_node()) {
+	if (master_node == node_num) {
 		if (!test_bit(master_node, reg->hr_nego_node_bitmap)) {
 			printk(KERN_NOTICE "o2hb: node %d hb write hung for %ds on region %s (%pg).\n",
-				o2nm_this_node(), O2HB_NEGO_TIMEOUT_MS/1000,
+				node_num, O2HB_NEGO_TIMEOUT_MS / 1000,
 				config_item_name(&reg->hr_item), reg->hr_bdev);
 			set_bit(master_node, reg->hr_nego_node_bitmap);
 		}
@@ -418,7 +421,7 @@ static void o2hb_nego_timeout(struct work_struct *work)
 
 			mlog(ML_HEARTBEAT, "send NEGO_APPROVE msg to node %d\n", i);
 			ret = o2hb_send_nego_msg(reg->hr_key,
-					O2HB_NEGO_APPROVE_MSG, i);
+					O2HB_NEGO_APPROVE_MSG, i, node_num);
 			if (ret)
 				mlog(ML_ERROR, "send NEGO_APPROVE msg to node %d fail %d\n",
 					i, ret);
@@ -426,10 +429,10 @@ static void o2hb_nego_timeout(struct work_struct *work)
 	} else {
 		/* negotiate timeout with master node. */
 		printk(KERN_NOTICE "o2hb: node %d hb write hung for %ds on region %s (%pg), negotiate timeout with node %d.\n",
-			o2nm_this_node(), O2HB_NEGO_TIMEOUT_MS/1000, config_item_name(&reg->hr_item),
+			node_num, O2HB_NEGO_TIMEOUT_MS / 1000, config_item_name(&reg->hr_item),
 			reg->hr_bdev, master_node);
 		ret = o2hb_send_nego_msg(reg->hr_key, O2HB_NEGO_TIMEOUT_MSG,
-				master_node);
+				master_node, node_num);
 		if (ret)
 			mlog(ML_ERROR, "send NEGO_TIMEOUT msg to node %d fail %d\n",
 				master_node, ret);
@@ -601,7 +604,9 @@ static int o2hb_issue_node_write(struct o2hb_region *reg,
 
 	o2hb_bio_wait_init(write_wc);
 
-	slot = o2nm_this_node();
+	slot = reg->hr_node_num;
+	if (slot >= O2NM_MAX_NODES)
+		return -EINVAL;
 
 	bio = o2hb_setup_one_bio(reg, write_wc, &slot, slot+1,
 				 REQ_OP_WRITE | REQ_SYNC);
@@ -670,8 +675,12 @@ static int o2hb_check_own_slot(struct o2hb_region *reg)
 	struct o2hb_disk_slot *slot;
 	struct o2hb_disk_heartbeat_block *hb_block;
 	char *errstr;
+	u8 node_num = reg->hr_node_num;
+
+	if (node_num >= O2NM_MAX_NODES)
+		return 0;
 
-	slot = &reg->hr_slots[o2nm_this_node()];
+	slot = &reg->hr_slots[node_num];
 	/* Don't check on our 1st timestamp */
 	if (!slot->ds_last_time)
 		return 0;
@@ -712,7 +721,10 @@ static inline void o2hb_prepare_block(struct o2hb_region *reg,
 	struct o2hb_disk_slot *slot;
 	struct o2hb_disk_heartbeat_block *hb_block;
 
-	node_num = o2nm_this_node();
+	node_num = reg->hr_node_num;
+	if (node_num >= O2NM_MAX_NODES)
+		return;
+
 	slot = &reg->hr_slots[node_num];
 
 	hb_block = (struct o2hb_disk_heartbeat_block *)slot->ds_raw_block;
@@ -1206,7 +1218,7 @@ static int o2hb_thread(void *data)
 	set_user_nice(current, MIN_NICE);
 
 	/* Pin node */
-	ret = o2nm_depend_this_node();
+	ret = o2nm_depend_node(reg->hr_node_num);
 	if (ret) {
 		mlog(ML_ERROR, "Node has been deleted, ret = %d\n", ret);
 		reg->hr_node_deleted = 1;
@@ -1215,7 +1227,8 @@ static int o2hb_thread(void *data)
 	}
 
 	while (!kthread_should_stop() &&
-	       !reg->hr_unclean_stop && !reg->hr_aborted_start) {
+	       !reg->hr_unclean_stop && !reg->hr_aborted_start &&
+	       o2nm_this_node() == reg->hr_node_num) {
 		/* We track the time spent inside
 		 * o2hb_do_disk_heartbeat so that we avoid more than
 		 * hr_timeout_ms between disk writes. On busy systems
@@ -1264,7 +1277,7 @@ static int o2hb_thread(void *data)
 	}
 
 	/* Unpin node */
-	o2nm_undepend_this_node();
+	o2nm_undepend_node(reg->hr_node_num);
 
 	mlog(ML_HEARTBEAT|ML_KTHREAD, "o2hb thread exiting\n");
 
@@ -1812,7 +1825,8 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
 
 	/* We can't heartbeat without having had our node number
 	 * configured yet. */
-	if (o2nm_this_node() == O2NM_MAX_NODES)
+	reg->hr_node_num = o2nm_this_node();
+	if (reg->hr_node_num == O2NM_MAX_NODES)
 		goto out;
 
 	fd = simple_strtol(p, &p, 0);
@@ -2060,6 +2074,7 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g
 		ret = -ENAMETOOLONG;
 		goto free;
 	}
+	reg->hr_node_num = O2NM_MAX_NODES;
 
 	spin_lock(&o2hb_live_lock);
 	reg->hr_region_num = 0;
fs/ocfs2/cluster/nodemanager.c +15 -4
index d244def401eab..adb60bc6e9bc2 100644
@@ -368,6 +368,7 @@ static ssize_t o2nm_node_local_store(struct config_item *item, const char *page,
 	if (!tmp && cluster->cl_has_local &&
 	    cluster->cl_local_node == node->nd_num) {
 		o2net_stop_listening(node);
+		cluster->cl_has_local = 0;
 		cluster->cl_local_node = O2NM_INVALID_NODE_NUM;
 	}
 
@@ -791,12 +792,12 @@ void o2nm_undepend_item(struct config_item *item)
 	configfs_undepend_item(item);
 }
 
-int o2nm_depend_this_node(void)
+int o2nm_depend_node(u8 node_num)
 {
 	int ret = 0;
 	struct o2nm_node *local_node;
 
-	local_node = o2nm_get_node_by_num(o2nm_this_node());
+	local_node = o2nm_get_node_by_num(node_num);
 	if (!local_node) {
 		ret = -EINVAL;
 		goto out;
@@ -809,17 +810,27 @@ int o2nm_depend_this_node(void)
 	return ret;
 }
 
-void o2nm_undepend_this_node(void)
+void o2nm_undepend_node(u8 node_num)
 {
 	struct o2nm_node *local_node;
 
-	local_node = o2nm_get_node_by_num(o2nm_this_node());
+	local_node = o2nm_get_node_by_num(node_num);
 	BUG_ON(!local_node);
 
 	o2nm_undepend_item(&local_node->nd_item);
 	o2nm_node_put(local_node);
 }
 
+int o2nm_depend_this_node(void)
+{
+	return o2nm_depend_node(o2nm_this_node());
+}
+
+void o2nm_undepend_this_node(void)
+{
+	o2nm_undepend_node(o2nm_this_node());
+}
+
 
 static void __exit exit_o2nm(void)
 {
fs/ocfs2/cluster/nodemanager.h +2 -0
index 2f72f56996bd9..ca3483fb54504 100644
@@ -66,6 +66,8 @@ void o2nm_node_put(struct o2nm_node *node);
 int o2nm_depend_item(struct config_item *item);
 int o2nm_depend_item_unlocked(struct config_item *item);
 void o2nm_undepend_item(struct config_item *item);
+int o2nm_depend_node(u8 node_num);
+void o2nm_undepend_node(u8 node_num);
 int o2nm_depend_this_node(void);
 void o2nm_undepend_this_node(void);
 
Signature · 1 line
2.53.0
author maintainer
#1583 lore raw

PATCH 1577/1887 ocfs2: o2hb: quiesce negotiate handlers and timeout work

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Cen Zhang <[email protected]>

[ Upstream commit 011291b70ba4832e136b7b581825b2bc0f525bf6 ]

Heartbeat regions publish struct o2hb_region as the private data for the NEGO_TIMEOUT and NEGO_APPROVE o2net handlers as soon as make_item() creates the configfs region. The approve handler can call o2hb_arm_timeout(), so a peer can touch the region timeout work before dev_store() has finished building the heartbeat runtime, or after teardown has started to shut that runtime back down.

The final configfs put also has to keep reg alive until the last in-flight o2net callback drops its handler reference. o2net_unregister_handler_list() blocks future handler lookups, but it does not wait for sc_rx_work that already passed o2net_handler_get(). That drain needs to cover local listener teardown as well, where the o2net ordered workqueue may already be inside destroy_workqueue().

Fix the lifetime rule in both directions. Initialize the region delayed works before publishing reg through the o2net handler table, keep new or stopping regions non-armable with hr_stopping, and quiesce both delayed works on failed-start and teardown paths even when no heartbeat thread is left to call o2hb_disarm_timeout(). Then unregister handlers before tearing down handler-visible region state and make the drain wait for the active or destroying o2net ordered workqueue before release frees reg.

The buggy scenario involves two paths, with each column showing the order within that path:

region lifecycle: late negotiate callback:
1. make_item() registers the 1. o2net_process_message() gets a
region handlers before heartbeat handler for reg.
dev_store() has built a 2. The callback runs after the lookup
runnable heartbeat context. lock is dropped and dereferences reg.
2. A failed start or rmdir 3. An approve or timeout path tries to

   stops the heartbeat thread,      queue reg's delayed work, or release
   quiesces existing work, and      races the callback body after handler
   drops the final configfs ref.    unregister.

3. region_release() must drain 4. The callback or delayed work can
handler-visible o2net rx work outlive reg unless lifecycle code
before freeing reg. keeps the region non-armable and
drains the active-or-destroying o2net workqueue.

Validation reproduced this kernel report:
KASAN slab-use-after-free in __run_timers+0x22c/0x5b0

Write of size 8
Call trace:
  dump_stack_lvl+0x66/0xa0
  print_report+0xce/0x630
  __run_timers+0x22c/0x5b0
  kasan_report+0xe0/0x110
  _raw_spin_unlock_irqrestore+0x27/0x60
  try_to_wake_up+0x191/0xf70
  timer_expire_remote+0xae/0xf0
  run_timer_softirq+0x19b/0x1a0
  handle_softirqs+0x156/0x660
  __irq_exit_rcu+0xc4/0x160
  irq_exit_rcu+0xe/0x20
  sysvec_apic_timer_interrupt+0x6c/0x80
  asm_sysvec_apic_timer_interrupt+0x1a/0x20

Allocated by task stack:
  kasan_save_stack+0x33/0x60
  kasan_save_track+0x14/0x30
  __kasan_kmalloc+0xaa/0xb0
  o2hb_heartbeat_group_make_item+0x3c/0x600
Fixes 34069b886f95 ("ocfs2: o2hb: add NEGO_TIMEOUT message")
Fixes e76f8237a2f7 ("ocfs2: o2hb: add NEGOTIATE_APPROVE message")
Assisted-by Codex:gpt-5.5
Cc Mark Fasheh <[email protected]>
Cc Joel Becker <[email protected]>
Cc Junxiao Bi <[email protected]>
Cc Changwei Ge <[email protected]>
Cc Jun Piao <[email protected]>
Cc Heming Zhao <[email protected]>
---
 fs/ocfs2/cluster/heartbeat.c |  56 ++++++++++++++++---
 fs/ocfs2/cluster/tcp.c       | 103 ++++++++++++++++++++++++++++++-----
 fs/ocfs2/cluster/tcp.h       |   1 +
 3 files changed, 138 insertions(+), 22 deletions(-)
fs/ocfs2/cluster/heartbeat.c +48 -8
index 3b1afb46b199e..77d65f3d3d605 100644
@@ -14,6 +14,7 @@
 #include <linux/file.h>
 #include <linux/kthread.h>
 #include <linux/configfs.h>
+#include <linux/mutex.h>
 #include <linux/random.h>
 #include <linux/crc32.h>
 #include <linux/time.h>
@@ -265,6 +266,9 @@ struct o2hb_region {
 	/* Message key for negotiate timeout message. */
 	unsigned int		hr_key;
 	struct list_head	hr_handler_list;
+	/* Serializes timeout arming against failed-start and teardown. */
+	struct mutex		hr_arming_mutex;
+	bool			hr_stopping;
 
 	/* last hb status, 0 for success, other value for error. */
 	int			hr_last_hb_status;
@@ -324,9 +328,14 @@ static void o2hb_write_timeout(struct work_struct *work)
 
 static void o2hb_arm_timeout(struct o2hb_region *reg)
 {
+	mutex_lock(&reg->hr_arming_mutex);
+
+	if (reg->hr_stopping)
+		goto out_unlock;
+
 	/* Arm writeout only after thread reaches steady state */
 	if (atomic_read(&reg->hr_steady_iterations) != 0)
-		return;
+		goto out_unlock;
 
 	mlog(ML_HEARTBEAT, "Queue write timeout for %u ms\n",
 	     O2HB_MAX_WRITE_TIMEOUT_MS);
@@ -345,6 +354,18 @@ static void o2hb_arm_timeout(struct o2hb_region *reg)
 	schedule_delayed_work(&reg->hr_nego_timeout_work,
 			      msecs_to_jiffies(O2HB_NEGO_TIMEOUT_MS));
 	memset(reg->hr_nego_node_bitmap, 0, sizeof(reg->hr_nego_node_bitmap));
+
+out_unlock:
+	mutex_unlock(&reg->hr_arming_mutex);
+}
+
+static void o2hb_queue_nego_timeout(struct o2hb_region *reg,
+				    unsigned long delay)
+{
+	mutex_lock(&reg->hr_arming_mutex);
+	if (!reg->hr_stopping)
+		schedule_delayed_work(&reg->hr_nego_timeout_work, delay);
+	mutex_unlock(&reg->hr_arming_mutex);
 }
 
 static void o2hb_disarm_timeout(struct o2hb_region *reg)
@@ -353,6 +374,19 @@ static void o2hb_disarm_timeout(struct o2hb_region *reg)
 	cancel_delayed_work_sync(&reg->hr_nego_timeout_work);
 }
 
+static void o2hb_set_region_stopping(struct o2hb_region *reg, bool stopping)
+{
+	mutex_lock(&reg->hr_arming_mutex);
+	reg->hr_stopping = stopping;
+	mutex_unlock(&reg->hr_arming_mutex);
+}
+
+static void o2hb_quiesce_timeout(struct o2hb_region *reg)
+{
+	o2hb_set_region_stopping(reg, true);
+	o2hb_disarm_timeout(reg);
+}
+
 static int o2hb_send_nego_msg(int key, int type, u8 target, u8 node_num)
 {
 	struct o2hb_nego_msg msg;
@@ -402,8 +436,7 @@ static void o2hb_nego_timeout(struct work_struct *work)
 			/* check negotiate bitmap every second to do timeout
 			 * approve decision.
 			 */
-			schedule_delayed_work(&reg->hr_nego_timeout_work,
-				msecs_to_jiffies(1000));
+			o2hb_queue_nego_timeout(reg, msecs_to_jiffies(1000));
 
 			return;
 		}
@@ -1558,6 +1591,8 @@ static void o2hb_region_release(struct config_item *item)
 
 	mlog(ML_HEARTBEAT, "hb region release (%pg)\n", reg->hr_bdev);
 
+	o2hb_quiesce_timeout(reg);
+	o2net_unregister_and_flush_handler_list(&reg->hr_handler_list);
 	o2hb_unmap_slot_data(reg);
 
 	if (reg->hr_bdev)
@@ -1573,7 +1608,6 @@ static void o2hb_region_release(struct config_item *item)
 	list_del(&reg->hr_all_item);
 	spin_unlock(&o2hb_live_lock);
 
-	o2net_unregister_handler_list(&reg->hr_handler_list);
 	kfree(reg);
 }
 
@@ -1886,9 +1920,6 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
 		goto out3;
 	}
 
-	INIT_DELAYED_WORK(&reg->hr_write_timeout_work, o2hb_write_timeout);
-	INIT_DELAYED_WORK(&reg->hr_nego_timeout_work, o2hb_nego_timeout);
-
 	/*
 	 * A node is considered live after it has beat LIVE_THRESHOLD
 	 * times.  We're not steady until we've given them a chance
@@ -1908,6 +1939,7 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
 	atomic_set(&reg->hr_steady_iterations, live_threshold);
 	/* unsteady_iterations is triple the steady_iterations */
 	atomic_set(&reg->hr_unsteady_iterations, (live_threshold * 3));
+	o2hb_set_region_stopping(reg, false);
 
 	hb_task = kthread_run(o2hb_thread, reg, "o2hb-%s",
 			      reg->hr_item.ci_name);
@@ -1957,6 +1989,8 @@ static ssize_t o2hb_region_dev_store(struct config_item *item,
 
 out3:
 	if (ret < 0) {
+		o2hb_quiesce_timeout(reg);
+
 		spin_lock(&o2hb_live_lock);
 		hb_task = reg->hr_task;
 		reg->hr_task = NULL;
@@ -2099,6 +2133,10 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g
 	 */
 	reg->hr_key = crc32_le(reg->hr_region_num + O2NM_MAX_REGIONS,
 		name, strlen(name));
+	mutex_init(&reg->hr_arming_mutex);
+	reg->hr_stopping = true;
+	INIT_DELAYED_WORK(&reg->hr_write_timeout_work, o2hb_write_timeout);
+	INIT_DELAYED_WORK(&reg->hr_nego_timeout_work, o2hb_nego_timeout);
 	INIT_LIST_HEAD(&reg->hr_handler_list);
 	ret = o2net_register_handler(O2HB_NEGO_TIMEOUT_MSG, reg->hr_key,
 			sizeof(struct o2hb_nego_msg),
@@ -2119,7 +2157,7 @@ static struct config_item *o2hb_heartbeat_group_make_item(struct config_group *g
 	return &reg->hr_item;
 
 unregister_handler:
-	o2net_unregister_handler_list(&reg->hr_handler_list);
+	o2net_unregister_and_flush_handler_list(&reg->hr_handler_list);
 remove_item:
 	spin_lock(&o2hb_live_lock);
 	list_del(&reg->hr_all_item);
@@ -2138,6 +2176,8 @@ static void o2hb_heartbeat_group_drop_item(struct config_group *group,
 	struct o2hb_region *reg = to_o2hb_region(item);
 	int quorum_region = 0;
 
+	o2hb_quiesce_timeout(reg);
+
 	/* stop the thread when the user removes the region dir */
 	spin_lock(&o2hb_live_lock);
 	hb_task = reg->hr_task;
fs/ocfs2/cluster/tcp.c +89 -14

160 lines not on this page · raw message

fs/ocfs2/cluster/tcp.h +1 -0
index 2e86d42b5faf9..a11bcee289479 100644
@@ -89,6 +89,7 @@ int o2net_register_handler(u32 msg_type, u32 key, u32 max_len,
 			   o2net_post_msg_handler_func *post_func,
 			   struct list_head *unreg_list);
 void o2net_unregister_handler_list(struct list_head *list);
+void o2net_unregister_and_flush_handler_list(struct list_head *list);
 
 void o2net_fill_node_map(unsigned long *map, unsigned bytes);
 
Signature · 1 line
2.53.0
author maintainer
#1584 lore raw

PATCH 1578/1887 RDMA/erdma: Hold QP references for AE and CM processing

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Cheng Xu <[email protected]>

[ Upstream commit a52eeff32024f190b3bdc99088c7becccd4fa60b ]

AE QP fatal events and iWARP CM paths load QPs from dev->qp_xa and then use or reference them outside the xarray lock. erdma_destroy_qp() can drop the destroy-path reference and free QP resources while such a lookup is in flight.

Add erdma_qp_get_by_qpn() to acquire a kref under the xarray lock with kref_get_unless_zero(). Remove the QP from the xarray before dropping the destroy-path reference so no new lookup can acquire it while destruction waits for existing users.

Fixes 155055771704 ("RDMA/erdma: Add verbs implementation")
---
 drivers/infiniband/hw/erdma/erdma_cm.c    |  6 ++----
 drivers/infiniband/hw/erdma/erdma_eq.c    |  3 ++-
 drivers/infiniband/hw/erdma/erdma_verbs.c |  6 +++++-
 drivers/infiniband/hw/erdma/erdma_verbs.h | 15 +++++++++++++++
 4 files changed, 24 insertions(+), 6 deletions(-)
drivers/infiniband/hw/erdma/erdma_cm.c +2 -4
index 1156ae62c4683..dfa4664bcadb3 100644
@@ -1014,10 +1014,9 @@ int erdma_connect(struct iw_cm_id *id, struct iw_cm_conn_param *params)
 	if (laddr->sa_family != AF_INET || raddr->sa_family != AF_INET)
 		return -EAFNOSUPPORT;
 
-	qp = find_qp_by_qpn(dev, params->qpn);
+	qp = erdma_qp_get_by_qpn(dev, params->qpn);
 	if (!qp)
 		return -ENOENT;
-	erdma_qp_get(qp);
 
 	ret = sock_create(AF_INET, SOCK_STREAM, IPPROTO_TCP, &s);
 	if (ret < 0)
@@ -1146,10 +1145,9 @@ int erdma_accept(struct iw_cm_id *id, struct iw_cm_conn_param *params)
 		return -ECONNRESET;
 	}
 
-	qp = find_qp_by_qpn(dev, params->qpn);
+	qp = erdma_qp_get_by_qpn(dev, params->qpn);
 	if (!qp)
 		return -ENOENT;
-	erdma_qp_get(qp);
 
 	down_write(&qp->state_lock);
 	if (qp->attrs.state > ERDMA_QP_STATE_RTR) {
drivers/infiniband/hw/erdma/erdma_eq.c +2 -1
index da146516db3ee..9abcd2effeda1 100644
@@ -65,7 +65,7 @@ void erdma_aeq_event_handler(struct erdma_dev *dev)
 			erdma_cq_put(cq);
 		} else {
 			qpn = le32_to_cpu(aeqe->event_data0);
-			qp = find_qp_by_qpn(dev, qpn);
+			qp = erdma_qp_get_by_qpn(dev, qpn);
 			if (!qp)
 				continue;
 
@@ -75,6 +75,7 @@ void erdma_aeq_event_handler(struct erdma_dev *dev)
 			if (qp->ibqp.event_handler)
 				qp->ibqp.event_handler(&event,
 						       qp->ibqp.qp_context);
+			erdma_qp_put(qp);
 		}
 	}
 
drivers/infiniband/hw/erdma/erdma_verbs.c +5 -1
index 0894517d33be8..d8f21f9d9101c 100644
@@ -1026,6 +1026,7 @@ int erdma_destroy_qp(struct ib_qp *ibqp, struct ib_udata *udata)
 	struct erdma_ucontext *ctx = rdma_udata_to_drv_context(
 		udata, struct erdma_ucontext, ibucontext);
 	struct erdma_qp_attrs qp_attrs;
+	unsigned long flags;
 	int err;
 	struct erdma_cmdq_destroy_qp_req req;
 
@@ -1042,6 +1043,10 @@ int erdma_destroy_qp(struct ib_qp *ibqp, struct ib_udata *udata)
 	if (err)
 		return err;
 
+	xa_lock_irqsave(&dev->qp_xa, flags);
+	__xa_erase(&dev->qp_xa, QP_ID(qp));
+	xa_unlock_irqrestore(&dev->qp_xa, flags);
+
 	erdma_qp_put(qp);
 	wait_for_completion(&qp->safe_free);
 
@@ -1064,7 +1069,6 @@ int erdma_destroy_qp(struct ib_qp *ibqp, struct ib_udata *udata)
 
 	if (qp->cep)
 		erdma_cep_put(qp->cep);
-	xa_erase(&dev->qp_xa, QP_ID(qp));
 
 	return 0;
 }
drivers/infiniband/hw/erdma/erdma_verbs.h +15 -0
index 03ce6e171be8c..67becf90d53f5 100644
@@ -250,6 +250,21 @@ static inline struct erdma_qp *find_qp_by_qpn(struct erdma_dev *dev, int id)
 	return (struct erdma_qp *)xa_load(&dev->qp_xa, id);
 }
 
+static inline struct erdma_qp *erdma_qp_get_by_qpn(struct erdma_dev *dev,
+						   int id)
+{
+	struct erdma_qp *qp;
+	unsigned long flags;
+
+	xa_lock_irqsave(&dev->qp_xa, flags);
+	qp = xa_load(&dev->qp_xa, id);
+	if (qp && !kref_get_unless_zero(&qp->ref))
+		qp = NULL;
+	xa_unlock_irqrestore(&dev->qp_xa, flags);
+
+	return qp;
+}
+
 static inline struct erdma_cq *erdma_cq_get_by_cqn(struct erdma_dev *dev,
 						   int id)
 {
Signature · 1 line
2.53.0
author maintainer
#1585 lore raw

PATCH 1579/1887 thermal: hwmon: Remove hwmon class device along with its parent

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Rafael J. Wysocki <[email protected]>

[ Upstream commit 2b57e24d34b2dfc43c81942bb359d6315bf302fb ]

The current code creates one hwmon device per thermal zone type and that device is registered under the first thermal zone of the given type.

That turns out to be problematic when the thermal zone holding the hwmon device is removed.

For example, say that there are two ACPI thermal zones on a system

/sys/devices/virtual/thermal/thermal_zone0/
/sys/devices/virtual/thermal/thermal_zone1/

The current code registers a hwmon class device for thermal_zone0 only:

/sys/devices/virtual/thermal/thermal_zone0/hwmon0/

because the type is "acpitz" for both of them, but it adds a sysfs attribute that belongs to thermal_zone1 under it:

/sys/devices/virtual/thermal/thermal_zone0/hwmon0/temp2_input

There is also

/sys/devices/virtual/thermal/thermal_zone0/hwmon0/temp1_input

which belongs to thermal_zone0.

When thermal_zone0 is removed, say because the ACPI thermal driver is unbound from the underlying platform device, thermal_remove_hwmon_sysfs() skips the removal of hwmon0 because of the temp2_input attribute belonging to thermal_zone1 which effectively prevents thermal_zone0 removal from making progress.

Address this by making thermal_remove_hwmon_sysfs() remove the entire hwmon class device interface for the given thermal zone type when the thermal zone device holding it is removed.

To prevent races with thermal_add_hwmon_sysfs() that may interfere with this, carry out the entire addition and removal of hwmon sysfs interfaces for thermal zones under thermal_hwmon_list_lock.

Also adjust the layout of the labels in thermal_add_hwmon_sysfs() to the current kernel coding style to align with the new "unlock" label.

Fixes f6b6b52ef7a5 ("thermal_hwmon: Pass the originating device down to hwmon_device_register_with_info")
---
 drivers/thermal/thermal_hwmon.c | 87 +++++++++++++--------------------
 1 file changed, 34 insertions(+), 53 deletions(-)
drivers/thermal/thermal_hwmon.c +34 -53
index 1013e0221e371..8edc59454d629 100644
@@ -92,34 +92,12 @@ thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz)
 	struct thermal_hwmon_device *hwmon;
 	char type[THERMAL_NAME_LENGTH];
 
-	mutex_lock(&thermal_hwmon_list_lock);
 	list_for_each_entry(hwmon, &thermal_hwmon_list, node) {
 		strcpy(type, tz->type);
 		strreplace(type, '-', '_');
-		if (!strcmp(hwmon->type, type)) {
-			mutex_unlock(&thermal_hwmon_list_lock);
+		if (!strcmp(hwmon->type, type))
 			return hwmon;
-		}
 	}
-	mutex_unlock(&thermal_hwmon_list_lock);
-
-	return NULL;
-}
-
-/* Find the temperature input matching a given thermal zone */
-static struct thermal_hwmon_temp *
-thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
-			  const struct thermal_zone_device *tz)
-{
-	struct thermal_hwmon_temp *temp;
-
-	mutex_lock(&thermal_hwmon_list_lock);
-	list_for_each_entry(temp, &hwmon->tz_list, hwmon_node)
-		if (temp->tz == tz) {
-			mutex_unlock(&thermal_hwmon_list_lock);
-			return temp;
-		}
-	mutex_unlock(&thermal_hwmon_list_lock);
 
 	return NULL;
 }
@@ -135,7 +113,9 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
 	struct thermal_hwmon_device *hwmon;
 	struct thermal_hwmon_temp *temp;
 	int new_hwmon_device = 1;
-	int result;
+	int result = 0;
+
+	mutex_lock(&thermal_hwmon_list_lock);
 
 	hwmon = thermal_hwmon_lookup_by_type(tz);
 	if (hwmon) {
@@ -144,8 +124,10 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
 	}
 
 	hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL);
-	if (!hwmon)
-		return -ENOMEM;
+	if (!hwmon) {
+		result = -ENOMEM;
+		goto unlock;
+	}
 
 	INIT_LIST_HEAD(&hwmon->tz_list);
 	strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
@@ -193,24 +175,24 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
 		temp->temp_crit_present = true;
 	}
 
-	mutex_lock(&thermal_hwmon_list_lock);
 	if (new_hwmon_device)
 		list_add_tail(&hwmon->node, &thermal_hwmon_list);
 	list_add_tail(&temp->hwmon_node, &hwmon->tz_list);
-	mutex_unlock(&thermal_hwmon_list_lock);
 
-	return 0;
+	goto unlock;
 
- unregister_input:
+unregister_input:
 	device_remove_file(hwmon->device, &temp->temp_input.attr);
- free_temp_mem:
+free_temp_mem:
 	kfree(temp);
- unregister_name:
+unregister_name:
 	if (new_hwmon_device)
 		hwmon_device_unregister(hwmon->device);
- free_mem:
+free_mem:
 	if (new_hwmon_device)
 		kfree(hwmon);
+unlock:
+	mutex_unlock(&thermal_hwmon_list_lock);
 
 	return result;
 }
@@ -218,8 +200,11 @@ EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs);
 
 void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
 {
+	struct thermal_hwmon_temp *temp, *entry;
 	struct thermal_hwmon_device *hwmon;
-	struct thermal_hwmon_temp *temp;
+	bool unregister;
+
+	guard(mutex)(&thermal_hwmon_list_lock);
 
 	hwmon = thermal_hwmon_lookup_by_type(tz);
 	if (unlikely(!hwmon)) {
@@ -228,29 +213,25 @@ void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
 		return;
 	}
 
-	temp = thermal_hwmon_lookup_temp(hwmon, tz);
-	if (unlikely(!temp)) {
-		/* Should never happen... */
-		dev_dbg(&tz->device, "temperature input lookup failed!\n");
-		return;
-	}
+	unregister = hwmon->device->parent == &tz->device;
 
-	device_remove_file(hwmon->device, &temp->temp_input.attr);
-	if (temp->temp_crit_present)
-		device_remove_file(hwmon->device, &temp->temp_crit.attr);
+	list_for_each_entry_safe_reverse(temp, entry, &hwmon->tz_list, hwmon_node) {
+		if (!unregister && temp->tz != tz)
+			continue;
 
-	mutex_lock(&thermal_hwmon_list_lock);
-	list_del(&temp->hwmon_node);
-	kfree(temp);
-	if (!list_empty(&hwmon->tz_list)) {
-		mutex_unlock(&thermal_hwmon_list_lock);
-		return;
+		device_remove_file(hwmon->device, &temp->temp_input.attr);
+		if (temp->temp_crit_present)
+			device_remove_file(hwmon->device, &temp->temp_crit.attr);
+
+		list_del(&temp->hwmon_node);
+		kfree(temp);
 	}
-	list_del(&hwmon->node);
-	mutex_unlock(&thermal_hwmon_list_lock);
 
-	hwmon_device_unregister(hwmon->device);
-	kfree(hwmon);
+	if (unregister) {
+		list_del(&hwmon->node);
+		hwmon_device_unregister(hwmon->device);
+		kfree(hwmon);
+	}
 }
 EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs);
 
Signature · 1 line
2.53.0
author maintainer
#1586 lore raw

PATCH 1580/1887 bpf, cgroup: Fix invalid storage access after __cgroup_bpf_attach failed

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Pu Lehui <[email protected]>

[ Upstream commit 6655c409707ec8ce9ce0850ffe4fe02331fd4d9c ]

A potential invalid storage access issue can occur after replacing a cgroup bpf prog.

This occurs in the following scenario:
1. prog1 with storage is attached to a cgroup in multi-attach mode.
2. prog1 is replaced with prog2 using BPF_F_REPLACE in multi-attach mode, but fails midway (e.g. in bpf_trampoline_link_cgroup_shim or update_effective_progs).
3. A new prog3 is attached to the cgroup in multi-attach mode.

The reason is that __cgroup_bpf_attach overwrites pl->storage with the new storage prior to attachment completion. When attachment fails midway, the cleanup path calls bpf_cgroup_storages_free(new_storage) to free the newly allocated storage, but fails to restore pl->storage back to old_storage.

Consequently, the still-active prog1 holds invalid or dangling storage pointers, leading to an invalid memory access when prog1 executes and calls bpf_get_local_storage. Additionally, original pl->flags and cgrp->bpf.flags[atype] are left unrestored.

Fix this by saving old_pl_flags, old_storage, and old_flags prior to the update, and properly restoring all of them in the cleanup path on error.

Fixes 7d9c3427894f ("bpf: Make cgroup storages shared between programs on the same cgroup")
---
 kernel/bpf/cgroup.c | 6 ++++++
 1 file changed, 6 insertions(+)
kernel/bpf/cgroup.c +6 -0
index 3771a041d9de5..3140fb0950771 100644
@@ -630,8 +630,10 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
 	struct bpf_prog *old_prog = NULL;
 	struct bpf_cgroup_storage *storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
 	struct bpf_cgroup_storage *new_storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
+	struct bpf_cgroup_storage *old_storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
 	struct bpf_prog *new_prog = prog ? : link->link.prog;
 	enum cgroup_bpf_attach_type atype;
+	u32 old_flags;
 	struct bpf_prog_list *pl;
 	struct hlist_head *progs;
 	int err;
@@ -677,6 +679,7 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
 
 	if (pl) {
 		old_prog = pl->prog;
+		bpf_cgroup_storages_assign(old_storage, pl->storage);
 	} else {
 		struct hlist_node *last = NULL;
 
@@ -699,6 +702,7 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
 	pl->prog = prog;
 	pl->link = link;
 	bpf_cgroup_storages_assign(pl->storage, storage);
+	old_flags = cgrp->bpf.flags[atype];
 	cgrp->bpf.flags[atype] = saved_flags;
 
 	if (type == BPF_LSM_CGROUP) {
@@ -729,12 +733,14 @@ static int __cgroup_bpf_attach(struct cgroup *cgrp,
 	if (old_prog) {
 		pl->prog = old_prog;
 		pl->link = NULL;
+		bpf_cgroup_storages_assign(pl->storage, old_storage);
 	}
 	bpf_cgroup_storages_free(new_storage);
 	if (!old_prog) {
 		hlist_del(&pl->node);
 		kfree(pl);
 	}
+	cgrp->bpf.flags[atype] = old_flags;
 	return err;
 }
 
Signature · 1 line
2.53.0
author maintainer
#1587 lore raw

PATCH 1581/1887 HID: steam: Reject short reads

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Vicki Pfau <[email protected]>

[ Upstream commit 33ff7b49c38b39b1f3d27db508ac0720fb25c08a ]

Steam Controller FEATURE reports encode the size of the message in the message itself. Previously we were trusting that the size reported matched the size we actually read, leading to a potential issue with short reads. Instead, we should actually verify the length of the read.

Fixes c164d6abf384 ("HID: add driver for Valve Steam Controller")
Reported-by [email protected]
---
 drivers/hid/hid-steam.c | 34 +++++++++++++++++++++++++++++++---
 1 file changed, 31 insertions(+), 3 deletions(-)
drivers/hid/hid-steam.c +31 -3
index bbc150167a7f1..a54bf625ae47f 100644
@@ -135,6 +135,13 @@ static int steam_recv_report(struct steam_device *steam,
 	u8 *buf;
 	int ret;
 
+	/*
+	 * All reports start with a two byte header.
+	 * We must read at least two bytes to get a sensible output.
+	 */
+	if (size < 2)
+		return -EINVAL;
+
 	r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
 	if (!r) {
 		hid_err(steam->hdev, "No HID_FEATURE_REPORT submitted -  nothing to read\n");
@@ -157,10 +164,31 @@ static int steam_recv_report(struct steam_device *steam,
 	ret = hid_hw_raw_request(steam->hdev, 0x00,
 			buf, hid_report_len(r) + 1,
 			HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
-	if (ret > 0)
-		memcpy(data, buf + 1, min(size, ret - 1));
+	if (ret > 0) {
+		/* Remove the report ID from the return buffer */
+		ret--;
+		size = min(size, ret);
+		memcpy(data, buf + 1, size);
+	}
 	kfree(buf);
-	return ret;
+
+	if (ret < 0)
+		hid_err(steam->hdev, "%s: error %d\n", __func__, ret);
+	else
+		hid_dbg(steam->hdev, "Received report %*ph\n", size, data);
+	if (ret < 0)
+		return ret;
+
+	if (ret < 2) {
+		hid_err(steam->hdev, "%s: reply too short\n", __func__);
+		return -EPROTO;
+	}
+	if (ret < data[1] + 2) {
+		hid_err(steam->hdev, "%s: expected %u bytes, read %i\n",
+				__func__, data[1] + 2, ret);
+		return -EPROTO;
+	}
+	return size;
 }
 
 static int steam_send_report(struct steam_device *steam,
Signature · 1 line
2.53.0
author maintainer
#1588 lore raw

PATCH 1582/1887 HID: logitech-hidpp: Fix FF device cleanup on init failure

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Haoxiang Li <[email protected]>

[ Upstream commit dd5be4d9ce2dfc4d4a4527ef7d31d21a78e3cdac ]

hidpp_ff_init() creates the input force-feedback device with input_ff_create(), then allocates the HID++ FF private data, effect ID array, and workqueue.

If any of those allocations fail after input_ff_create() succeeds, the function returns an error without destroying the FF device. Add an unwind path that frees the private allocations made by hidpp_ff_init() and calls input_ff_destroy() for failures after input_ff_create() succeeds.

Fixes ff21a635dd1a ("HID: logitech-hidpp: Force feedback support for the Logitech G920")
---
 drivers/hid/hid-logitech-hidpp.c | 23 ++++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)
drivers/hid/hid-logitech-hidpp.c +16 -7
index ebaca3588342f..741be3c1fe5b4 100644
@@ -2634,18 +2634,19 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
 	 * ownership to FF core
 	 */
 	data = kmemdup(data, sizeof(*data), GFP_KERNEL);
-	if (!data)
-		return -ENOMEM;
+	if (!data) {
+		error = -ENOMEM;
+		goto err_destroy_ff;
+	}
 	data->effect_ids = kcalloc(num_slots, sizeof(int), GFP_KERNEL);
 	if (!data->effect_ids) {
-		kfree(data);
-		return -ENOMEM;
+		error = -ENOMEM;
+		goto err_free_data;
 	}
 	data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue");
 	if (!data->wq) {
-		kfree(data->effect_ids);
-		kfree(data);
-		return -ENOMEM;
+		error = -ENOMEM;
+		goto err_free_effect_ids;
 	}
 
 	data->hidpp = hidpp;
@@ -2675,6 +2676,14 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
 		 version);
 
 	return 0;
+
+err_free_effect_ids:
+	kfree(data->effect_ids);
+err_free_data:
+	kfree(data);
+err_destroy_ff:
+	input_ff_destroy(dev);
+	return error;
 }
 
 /* ************************************************************************** */
Signature · 1 line
2.53.0
author maintainer
#1589 lore raw

PATCH 1583/1887 tty: make tty_class a static const structure

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Greg Kroah-Hartman <[email protected]>

[ Upstream commit 862d8312eed994a8a9af7aa8e9e15456183b10a7 ]

Now that the driver core allows for struct class to be in read-only memory, move the tty_class structure to be declared at build time placing it into read-only memory, instead of having to be dynamically allocated at boot time.

Stable-dep-of: c3b5623fd976 ("tty: skip cdev_del() when no cdev is registered")

---
 drivers/tty/pty.c    |  2 +-
 drivers/tty/tty_io.c | 24 +++++++++++-------------
 drivers/tty/vt/vt.c  |  2 +-
 include/linux/tty.h  |  2 +-
 4 files changed, 14 insertions(+), 16 deletions(-)
drivers/tty/pty.c +1 -1
index c658cacdb9334..335f5744f320c 100644
@@ -931,7 +931,7 @@ static void __init unix98_pty_init(void)
 	if (cdev_add(&ptmx_cdev, MKDEV(TTYAUX_MAJOR, 2), 1) ||
 	    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, "/dev/ptmx") < 0)
 		panic("Couldn't register /dev/ptmx driver");
-	device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx");
+	device_create(&tty_class, NULL, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx");
 }
 
 #else
drivers/tty/tty_io.c +11 -13
index 4124603a0b44f..1e78cc10b77d4 100644
@@ -3086,7 +3086,7 @@ static struct device *tty_get_device(struct tty_struct *tty)
 {
 	dev_t devt = tty_devnum(tty);
 
-	return class_find_device_by_devt(tty_class, devt);
+	return class_find_device_by_devt(&tty_class, devt);
 }
 
 
@@ -3160,8 +3160,6 @@ int tty_put_char(struct tty_struct *tty, unsigned char ch)
 }
 EXPORT_SYMBOL_GPL(tty_put_char);
 
-struct class *tty_class;
-
 static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
 		unsigned int index, unsigned int count)
 {
@@ -3258,7 +3256,7 @@ struct device *tty_register_device_attr(struct tty_driver *driver,
 		return ERR_PTR(-ENOMEM);
 
 	dev->devt = devt;
-	dev->class = tty_class;
+	dev->class = &tty_class;
 	dev->parent = device;
 	dev->release = tty_device_create_release;
 	dev_set_name(dev, "%s", name);
@@ -3315,8 +3313,7 @@ EXPORT_SYMBOL_GPL(tty_register_device_attr);
  */
 void tty_unregister_device(struct tty_driver *driver, unsigned index)
 {
-	device_destroy(tty_class,
-		MKDEV(driver->major, driver->minor_start) + index);
+	device_destroy(&tty_class, MKDEV(driver->major, driver->minor_start) + index);
 	if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
 		cdev_del(driver->cdevs[index]);
 		driver->cdevs[index] = NULL;
@@ -3531,13 +3528,14 @@ static char *tty_devnode(struct device *dev, umode_t *mode)
 	return NULL;
 }
 
+struct class tty_class = {
+	.name		= "tty",
+	.devnode	= tty_devnode,
+};
+
 static int __init tty_class_init(void)
 {
-	tty_class = class_create(THIS_MODULE, "tty");
-	if (IS_ERR(tty_class))
-		return PTR_ERR(tty_class);
-	tty_class->devnode = tty_devnode;
-	return 0;
+	return class_register(&tty_class);
 }
 
 postcore_initcall(tty_class_init);
@@ -3610,13 +3608,13 @@ int __init tty_init(void)
 	if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
 	    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
 		panic("Couldn't register /dev/tty driver\n");
-	device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");
+	device_create(&tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");
 
 	cdev_init(&console_cdev, &console_fops);
 	if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
 	    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)
 		panic("Couldn't register /dev/console driver\n");
-	consdev = device_create_with_groups(tty_class, NULL,
+	consdev = device_create_with_groups(&tty_class, NULL,
 					    MKDEV(TTYAUX_MAJOR, 1), NULL,
 					    cons_dev_groups, "console");
 	if (IS_ERR(consdev))
drivers/tty/vt/vt.c +1 -1
index 9b1448d7cb4e4..bdfbf6a1efe22 100644
@@ -3603,7 +3603,7 @@ int __init vty_init(const struct file_operations *console_fops)
 	if (cdev_add(&vc0_cdev, MKDEV(TTY_MAJOR, 0), 1) ||
 	    register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1, "/dev/vc/0") < 0)
 		panic("Couldn't register /dev/tty0 driver\n");
-	tty0dev = device_create_with_groups(tty_class, NULL,
+	tty0dev = device_create_with_groups(&tty_class, NULL,
 					    MKDEV(TTY_MAJOR, 0), NULL,
 					    vt_dev_groups, "tty0");
 	if (IS_ERR(tty0dev))
include/linux/tty.h +1 -1
index 5bfdf02df9c75..2b90fe8afadeb 100644
@@ -389,7 +389,7 @@ extern struct ktermios tty_std_termios;
 
 int vcs_init(void);
 
-extern struct class *tty_class;
+extern struct class tty_class;
 
 /**
  *	tty_kref_get		-	get a tty reference
Signature · 1 line
2.53.0
author maintainer
#1590 lore raw

PATCH 1584/1887 tty: skip cdev_del() when no cdev is registered

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Karl Mehltretter <[email protected]>

[ Upstream commit c3b5623fd97648f2747444aa8932ae604662df93 ]

TTY device registration can fail before a cdev is allocated. Serial core keeps the port so setserial can still use it, and later removal passes the NULL cdev slot to cdev_del(), causing a NULL-pointer dereference.

Only delete the cdev when the slot is not NULL.

Fixes a3a10ce3429e ("Avoid usb reset crashes by making tty_io cdevs truly dynamic")
Fixes da4c279942b0 ("serial: enable serdev support")
Assisted-by Claude:claude-fable-5
---
 drivers/tty/tty_io.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/tty/tty_io.c +1 -1
index 1e78cc10b77d4..1e36eda7fd3b2 100644
@@ -3314,7 +3314,7 @@ EXPORT_SYMBOL_GPL(tty_register_device_attr);
 void tty_unregister_device(struct tty_driver *driver, unsigned index)
 {
 	device_destroy(&tty_class, MKDEV(driver->major, driver->minor_start) + index);
-	if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
+	if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) && driver->cdevs[index]) {
 		cdev_del(driver->cdevs[index]);
 		driver->cdevs[index] = NULL;
 	}
Signature · 1 line
2.53.0
author maintainer
#1591 lore raw

PATCH 1585/1887 serial: core: clear freed pointers on uart_register_driver() failure

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Karl Mehltretter <[email protected]>

[ Upstream commit 61a2fb25551be0375bc16ef2a70c987dfca26183 ]

uart_register_driver() leaves drv->state pointing to freed memory when tty_alloc_driver() fails. If tty_register_driver() fails, drv->tty_driver also retains a pointer after its reference is dropped.

Drivers that use drv->state as an "already registered" flag can then skip registration on the next probe and pass the freed state to uart_add_one_port().

This issue was found with failslab on QEMU's raspi1ap board by failing registration and binding the PL011 port again.

Clear both pointers on their failure paths, as uart_unregister_driver() already does.

Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")
Fixes 9e845abfc8a8 ("serial: fix NULL pointer dereference")
Assisted-by Claude:claude-fable-5
---
 drivers/tty/serial/serial_core.c | 2 ++
 1 file changed, 2 insertions(+)
drivers/tty/serial/serial_core.c +2 -0
index 4b035292a3840..3e2ecbf5dd91d 100644
@@ -2812,8 +2812,10 @@ int uart_register_driver(struct uart_driver *drv)
 	for (i = 0; i < drv->nr; i++)
 		tty_port_destroy(&drv->state[i].port);
 	tty_driver_kref_put(normal);
+	drv->tty_driver = NULL;
 out_kfree:
 	kfree(drv->state);
+	drv->state = NULL;
 out:
 	return retval;
 }
Signature · 1 line
2.53.0
author maintainer
#1592 lore raw

PATCH 1586/1887 serial: core: do fallible allocations before the console can be registered

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Karl Mehltretter <[email protected]>

[ Upstream commit 1a0e4fbce5d9c1bc179a35a2fd9ed142664299e3 ]

serial_core_add_one_port() allocates uport->tty_groups after uart_configure_port(), which may register the console. If the allocation fails, the driver unwinds the port while its console remains registered. The earlier uport->name allocation has a related failure path that leaves state->uart_port linked to a port being freed.

Failslab reproduced a NULL dereference in PL011 console output and a KASAN use-after-free in i.MX console output after failed binds.

Allocate the name and tty_groups before linking the port and configuring it. Reserve space for the optional driver attribute group because config_port() may populate uport->attr_group during configuration.

Fixes 266dcff03eed ("Serial: allow port drivers to have a default attribute group")
Fixes f7048b15900f ("tty: serial_core: Add name field to uart_port struct")
Assisted-by Claude:claude-fable-5
---
 drivers/tty/serial/serial_core.c | 37 ++++++++++++++++----------------
 1 file changed, 19 insertions(+), 18 deletions(-)
drivers/tty/serial/serial_core.c +19 -18
index 3e2ecbf5dd91d..69845f9e7aea0 100644
@@ -3094,7 +3094,6 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
 	struct tty_port *port;
 	int ret = 0;
 	struct device *tty_dev;
-	int num_groups;
 
 	if (uport->line >= drv->nr)
 		return -EINVAL;
@@ -3109,6 +3108,25 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
 		goto out;
 	}
 
+	uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name,
+				drv->tty_driver->name_base + uport->line);
+	if (!uport->name) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	/*
+	 * uart_configure_port() may set uport->attr_group and register the
+	 * console. Allocate room for both groups and a NULL terminator first.
+	 */
+	uport->tty_groups = kcalloc(3, sizeof(*uport->tty_groups), GFP_KERNEL);
+	if (!uport->tty_groups) {
+		kfree(uport->name);
+		ret = -ENOMEM;
+		goto out;
+	}
+	uport->tty_groups[0] = &tty_dev_attr_group;
+
 	/* Link the port to the driver state table and vice versa */
 	atomic_set(&state->refcount, 1);
 	init_waitqueue_head(&state->remove_wait);
@@ -3118,12 +3136,6 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
 	state->pm_state = UART_PM_STATE_UNDEFINED;
 	uport->cons = drv->cons;
 	uport->minor = drv->tty_driver->minor_start + uport->line;
-	uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name,
-				drv->tty_driver->name_base + uport->line);
-	if (!uport->name) {
-		ret = -ENOMEM;
-		goto out;
-	}
 
 	/*
 	 * If this port is in use as a console then the spinlock is already
@@ -3140,17 +3152,6 @@ int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
 
 	port->console = uart_console(uport);
 
-	num_groups = 2;
-	if (uport->attr_group)
-		num_groups++;
-
-	uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),
-				    GFP_KERNEL);
-	if (!uport->tty_groups) {
-		ret = -ENOMEM;
-		goto out;
-	}
-	uport->tty_groups[0] = &tty_dev_attr_group;
 	if (uport->attr_group)
 		uport->tty_groups[1] = uport->attr_group;
 
Signature · 1 line
2.53.0
author maintainer
#1593 lore raw

PATCH 1587/1887 wifi: cfg80211: stop PMSR before P2P and NAN teardown

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Zhao Li <[email protected]>

[ Upstream commit 6c5fc504d0d6934132637aa3db4b9b58148eaa78 ]

PMSR request teardown must abort active measurements while the wireless_dev is still present in the driver. cfg80211_leave_locked() and cfg80211_stop_pd() already do this before invoking the driver's stop callback, but cfg80211_stop_p2p_device() and cfg80211_stop_nan() do not.

Those helpers are also called directly by nl80211, rfkill shutdown, and wireless_dev unregister paths. If one of these paths stops a P2P device or NAN interface with a pending request, it removes the mac80211 subinterface from the driver first. Subsequent request cleanup cannot reach the lower driver's abort callback, but cfg80211 frees the request regardless. Driver state can then retain a stale request and use it when it later reports a result.

Call cfg80211_pmsr_wdev_down() before stopping the P2P device or NAN interface. This keeps lower-driver request state and cfg80211 request ownership in sync for all of the helpers' callers.

Fixes 9bb7e0f24e7e ("cfg80211: add peer measurement with FTM initiator API")
Assisted-by Codex:gpt-5.6-sol
---
 net/wireless/core.c | 7 +++++++
 1 file changed, 7 insertions(+)
net/wireless/core.c +7 -0
index a781893473afe..1cd214a743428 100644
@@ -232,6 +232,9 @@ void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev,
 	if (!wdev_running(wdev))
 		return;
 
+	wdev_lock(wdev);
+	cfg80211_pmsr_wdev_down(wdev);
+	wdev_unlock(wdev);
 	rdev_stop_p2p_device(rdev, wdev);
 	wdev->is_running = false;
 
@@ -257,6 +260,10 @@ void cfg80211_stop_nan(struct cfg80211_registered_device *rdev,
 	if (!wdev_running(wdev))
 		return;
 
+	wdev_lock(wdev);
+	cfg80211_pmsr_wdev_down(wdev);
+	wdev_unlock(wdev);
+
 	rdev_stop_nan(rdev, wdev);
 	wdev->is_running = false;
 
Signature · 1 line
2.53.0
author maintainer
#1594 lore raw

PATCH 1588/1887 wifi: brcmfmac: fix P2P action frame handling without device vif

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Jason Huang <[email protected]>

[ Upstream commit 1b1edb9ebed49099bdc924cef49a9aea8b552199 ]

Some P2P action frame paths assume the P2P device vif is always available. That is not true when userspace sends non-P2P public action frames through the primary interface, or when action-frame abort runs after the P2P device vif has not been created.

Fall back to the primary vif when aborting an action frame without a P2P device vif, and guard P2P device saved IE access before using it for peer channel search.

Fixes 30fb1b272909 ("brcmfmac: use actframe_abort to cancel ongoing action frame")
Fixes 6eda4e2c5425 ("brcmfmac: Add tx p2p off-channel support.")
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c | 4 ++++
 1 file changed, 4 insertions(+)
drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c +4 -0
index ac6b70ff15355..b3aeaf8a476aa 100644
@@ -1281,6 +1281,9 @@ static s32 brcmf_p2p_abort_action_frame(struct brcmf_cfg80211_info *cfg)
 	brcmf_dbg(TRACE, "Enter\n");
 
 	vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif;
+	if (!vif)
+		vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif;
+
 	err = brcmf_fil_bsscfg_data_set(vif->ifp, "actframe_abort", &int_val,
 					sizeof(s32));
 	if (err)
@@ -1817,6 +1820,7 @@ bool brcmf_p2p_send_action_frame(struct brcmf_if *ifp,
 	/* validate channel and p2p ies */
 	if (config_af_params.search_channel &&
 	    IS_P2P_SOCIAL_CHANNEL(le32_to_cpu(af_params->channel)) &&
+	    p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif &&
 	    p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif->saved_ie.probe_req_ie_len) {
 		afx_hdl = &p2p->afx_hdl;
 		afx_hdl->peer_listen_chan = le32_to_cpu(af_params->channel);
Signature · 1 line
2.53.0
author maintainer
#1595 lore raw

PATCH 1589/1887 wifi: nl80211: clean up color-change beacon data on errors

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Zhao Li <[email protected]>

[ Upstream commit 927ee844c47ac2aef22c8f7a35f098ff576b398b ]

nl80211_color_change() calls nl80211_parse_beacon() for the beacon_next template, which can allocate params.beacon_next.mbssid_ies and .rnr_ies. A parsing failure returned directly instead of using the out: cleanup, leaking any allocations completed before the error.

Allocate the nested attribute table before parsing beacon_next. Its allocation failure can then return before beacon data exists, while a later parsing failure uses out: to release the parsed data.

Fixes dc1e3cb8da8b ("nl80211: MBSSID and EMA support in AP mode")
Assisted-by Codex:gpt-5
Assisted-by Claude:opus-4.8
Assisted-by Kimi:K3
---
 net/wireless/nl80211.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
net/wireless/nl80211.c +5 -5
index dbd777ce43409..fab77741663ff 100644
@@ -15968,15 +15968,15 @@ static int nl80211_color_change(struct sk_buff *skb, struct genl_info *info)
 	params.count = nla_get_u8(info->attrs[NL80211_ATTR_COLOR_CHANGE_COUNT]);
 	params.color = nla_get_u8(info->attrs[NL80211_ATTR_COLOR_CHANGE_COLOR]);
 
-	err = nl80211_parse_beacon(rdev, info->attrs, &params.beacon_next,
-				   info->extack);
-	if (err)
-		return err;
-
 	tb = kcalloc(NL80211_ATTR_MAX + 1, sizeof(*tb), GFP_KERNEL);
 	if (!tb)
 		return -ENOMEM;
 
+	err = nl80211_parse_beacon(rdev, info->attrs, &params.beacon_next,
+				   info->extack);
+	if (err)
+		goto out;
+
 	err = nla_parse_nested(tb, NL80211_ATTR_MAX,
 			       info->attrs[NL80211_ATTR_COLOR_CHANGE_ELEMS],
 			       nl80211_policy, info->extack);
Signature · 1 line
2.53.0
author maintainer
#1596 lore raw

PATCH 1590/1887 arm64: ptrace: Keep orig_x0 in-sync with x0 on syscall entry

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Will Deacon <[email protected]>

[ Upstream commit 88b839ce497ccb1ff92f7ae742c78dd2937ba572 ]

Commit e057b9477232 ("arm64: syscall: Ensure saved x0 is kept in-sync with tracer updates") attempted to resolve a long-standing issue with syscall entry tracing, where a tracer is able to manipulate the first syscall argument without being subjected to seccomp or audit checking.

Unfortunately, that fix was incomplete [1], as it failed to update 'orig_x0' between a tracer updating x0 during a seccomp ptrace exit (SECCOMP_RET_TRACE) and the seccomp filter being re-evaluated.

Rather than add hooks to the core seccomp code, instead move the synchronisation code into the ptrace GPR and syscall setting code so that 'orig_x0' is kept up to date with x0 whenever we're stopped on the syscall entry path.

Cc Kees Cook <[email protected]>
Cc Jinjie Ruan <[email protected]>
Cc Mark Rutland <[email protected]>
Fixes e057b9477232 ("arm64: syscall: Ensure saved x0 is kept in-sync with tracer updates")
Fixes a5cd110cb836 ("arm64/ptrace: run seccomp after ptrace")
---
 arch/arm64/kernel/ptrace.c | 50 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)
arch/arm64/kernel/ptrace.c +50 -0
index 3025d14c253de..6fcbde1ed943f 100644
@@ -564,6 +564,42 @@ static int gpr_get(struct task_struct *target,
 	return membuf_write(&to, uregs, sizeof(*uregs));
 }
 
+static void update_syscall_orig_x0_after_ptrace(struct task_struct *target)
+{
+	struct pt_regs *regs = task_pt_regs(target);
+	struct kernel_siginfo *info = target->last_siginfo;
+
+	/*
+	 * Skip the update for NO_SYSCALL (set either by the user or the
+	 * tracer), as regs[0] holds the return value (see the comment in
+	 * el0_svc_common()) and can be unwound using syscall_rollback().
+	 */
+	if (regs->syscallno == NO_SYSCALL)
+		return;
+
+	/* We should only be called when target is in a ptrace stop */
+	if (WARN_ON_ONCE(!info))
+		return;
+
+	/*
+	 * For compat tasks, orig_r0 is provided directly through GPR index
+	 * 17.
+	 */
+	if (is_compat_thread(task_thread_info(target)))
+		return;
+
+	/*
+	 * Don't update orig_x0 for a syscall-exit-stop, as x0 now contains the
+	 * return value of the system call.
+	 */
+	if ((info->si_code & ~0x80) == SIGTRAP &&
+	    target->ptrace_message == PTRACE_EVENTMSG_SYSCALL_EXIT) {
+		return;
+	}
+
+	regs->orig_x0 = regs->regs[0];
+}
+
 static int gpr_set(struct task_struct *target, const struct user_regset *regset,
 		   unsigned int pos, unsigned int count,
 		   const void *kbuf, const void __user *ubuf)
@@ -579,6 +615,14 @@ static int gpr_set(struct task_struct *target, const struct user_regset *regset,
 		return -EINVAL;
 
 	task_pt_regs(target)->user_regs = newregs;
+
+	/*
+	 * Keep orig_x0 authoritative so that seccomp (via
+	 * syscall_get_arguments()), audit and the restart path all see the same
+	 * first argument the syscall is dispatched with, even if it has been
+	 * updated by a tracer.
+	 */
+	update_syscall_orig_x0_after_ptrace(target);
 	return 0;
 }
 
@@ -722,6 +766,12 @@ static int system_call_set(struct task_struct *target,
 		return ret;
 
 	task_pt_regs(target)->syscallno = syscallno;
+
+	/*
+	 * Re-sync orig_x0 in case the syscall number has been changed
+	 * from NO_SYSCALL.
+	 */
+	update_syscall_orig_x0_after_ptrace(target);
 	return ret;
 }
 
Signature · 1 line
2.53.0
author maintainer
#1597 lore raw

PATCH 1591/1887 bpf, x86: Fix trampoline stack size for 128-bit arguments

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Yonghong Song <[email protected]>

[ Upstream commit 814cba835ef648e0c5eb79505c96c0493b29eea6 ]

btf_distill_func_proto() accepts a function argument up to 16 bytes, so a 128-bit scalar such as __int128 reaches the x86 trampoline with arg_size == 16. But the current implementation assumes an __int128 argument only needs one register, so the register save area is under-allocated and save_args() overwrites adjacent stack slots.

Compute the register count from arg_size for all arguments to fix it.

Fixes a9c5ad31fbdc ("bpf: x86: Support in-register struct arguments in trampoline programs")
---
 arch/x86/net/bpf_jit_comp.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)
arch/x86/net/bpf_jit_comp.c +1 -3
index 30c6a021abcaf..803fa8b249e36 100644
@@ -2131,10 +2131,8 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image, void *i
 	u8 *prog;
 	bool save_ret;
 
-	/* extra registers for struct arguments */
 	for (i = 0; i < m->nr_args; i++)
-		if (m->arg_flags[i] & BTF_FMODEL_STRUCT_ARG)
-			nr_regs += (m->arg_size[i] + 7) / 8 - 1;
+		nr_regs += (m->arg_size[i] + 7) / 8 - 1;
 
 	/* x86-64 supports up to 6 arguments. 7+ can be added in the future */
 	if (nr_regs > 6)
Signature · 1 line
2.53.0
author maintainer
#1598 lore raw

PATCH 1592/1887 wifi: mt76: cancel reset and rc work on device unregister

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Felix Fietkau <[email protected]>

[ Upstream commit e995d3dccc9d980af13a60ad37409ed1561f9eeb ]

Both drivers cancelled dump_work on unregister but left reset_work and rc_work to be flushed only by destroy_workqueue() in mt76_free_device(), which runs after the hw is unregistered and the hardware stopped. A reset_work that fires in that window calls ieee80211_restart_hw() and re-arms mac_work on an unregistered hw, and rc_work touches station state being torn down. Cancel both up front, alongside dump_work.

Fixes e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets")
Fixes 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices")
---
 drivers/net/wireless/mediatek/mt76/mt7915/init.c | 2 ++
 1 file changed, 2 insertions(+)
drivers/net/wireless/mediatek/mt76/mt7915/init.c +2 -0
index 782ce83bdab96..35a8052cf1e31 100644
@@ -1152,6 +1152,8 @@ int mt7915_register_device(struct mt7915_dev *dev)
 void mt7915_unregister_device(struct mt7915_dev *dev)
 {
 	cancel_work_sync(&dev->dump_work);
+	cancel_work_sync(&dev->reset_work);
+	cancel_work_sync(&dev->rc_work);
 	mt7915_unregister_ext_phy(dev);
 	mt7915_coredump_unregister(dev);
 	mt7915_unregister_thermal(&dev->phy);
Signature · 1 line
2.53.0
author maintainer
#1599 lore raw

PATCH 1593/1887 wifi: mt76: mt7915: allocate vif wcid in the same range as stations

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Felix Fietkau <[email protected]>

[ Upstream commit f3049b88b2b32326df97461813ae73e8bbc296fc ]

Reduces the amount of unnecessary WTBL bank switching, while still reserving WTBL entries for vifs.

Stable-dep-of: 2fb6480c52f6 ("wifi: mt76: mt7915: unwind state on add_interface failure")

---
 drivers/net/wireless/mediatek/mt76/mt7915/main.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
drivers/net/wireless/mediatek/mt76/mt7915/main.c +4 -1
index 0da672a86ae1f..cee7cd4ad26d7 100644
@@ -236,7 +236,9 @@ static int mt7915_add_interface(struct ieee80211_hw *hw,
 	dev->mt76.vif_mask |= BIT_ULL(mvif->mt76.idx);
 	phy->omac_mask |= BIT_ULL(mvif->mt76.omac_idx);
 
-	idx = MT7915_WTBL_RESERVED - mvif->mt76.idx;
+	idx = mt76_wcid_alloc(dev->mt76.wcid_mask, mt7915_wtbl_size(dev));
+	if (idx < 0)
+		return -ENOSPC;
 
 	INIT_LIST_HEAD(&mvif->sta.rc_list);
 	INIT_LIST_HEAD(&mvif->sta.poll_list);
@@ -283,6 +285,7 @@ static void mt7915_remove_interface(struct ieee80211_hw *hw,
 
 	mt7915_mcu_add_bss_info(phy, vif, false);
 	mt7915_mcu_add_sta(dev, vif, NULL, false);
+	mt76_wcid_mask_clear(dev->mt76.wcid_mask, mvif->sta.wcid.idx);
 
 	mutex_lock(&dev->mt76.mutex);
 	mt76_testmode_reset(phy->mt76, true);
Signature · 1 line
2.53.0
author maintainer
#1600 lore raw

PATCH 1594/1887 wifi: mt76: mt7915: Fix an error handling path in mt7915_add_interface()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Christophe JAILLET <[email protected]>

[ Upstream commit 126a516fe30639708e759678bcb10178938cc718 ]

If mt76_wcid_alloc() fails, the "mt76.mutex" mutex needs to be released as done in the other error handling paths of mt7915_add_interface().

Stable-dep-of: 2fb6480c52f6 ("wifi: mt76: mt7915: unwind state on add_interface failure")

---
 drivers/net/wireless/mediatek/mt76/mt7915/main.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
drivers/net/wireless/mediatek/mt76/mt7915/main.c +4 -2
index cee7cd4ad26d7..0b79039e53178 100644
@@ -237,8 +237,10 @@ static int mt7915_add_interface(struct ieee80211_hw *hw,
 	phy->omac_mask |= BIT_ULL(mvif->mt76.omac_idx);
 
 	idx = mt76_wcid_alloc(dev->mt76.wcid_mask, mt7915_wtbl_size(dev));
-	if (idx < 0)
-		return -ENOSPC;
+	if (idx < 0) {
+		ret = -ENOSPC;
+		goto out;
+	}
 
 	INIT_LIST_HEAD(&mvif->sta.rc_list);
 	INIT_LIST_HEAD(&mvif->sta.poll_list);
Signature · 1 line
2.53.0
author maintainer
#1601 lore raw

PATCH 1595/1887 wifi: mt76: mt7915: unwind state on add_interface failure

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Felix Fietkau <[email protected]>

[ Upstream commit 2fb6480c52f611338e1b0abe5e6219be1fc9ab75 ]

When mt76_wcid_alloc() fails, mt7915_add_interface() returned without clearing the vif_mask/omac_mask bits it had already set, without removing the firmware dev info added earlier, and without clearing a monitor_vif pointer to the vif mac80211 is about to free. mac80211 does not call remove_interface() for a failed add, so the indices and firmware dev entry leaked permanently and testmode could dereference the stale monitor_vif. Add a proper error unwind.

Fixes b619e01380ee ("mt76: fix MBSS index condition in DBDC mode")
---
 drivers/net/wireless/mediatek/mt76/mt7915/main.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)
drivers/net/wireless/mediatek/mt76/mt7915/main.c +11 -1
index 0b79039e53178..4707e7727f179 100644
@@ -239,7 +239,7 @@ static int mt7915_add_interface(struct ieee80211_hw *hw,
 	idx = mt76_wcid_alloc(dev->mt76.wcid_mask, mt7915_wtbl_size(dev));
 	if (idx < 0) {
 		ret = -ENOSPC;
-		goto out;
+		goto err;
 	}
 
 	INIT_LIST_HEAD(&mvif->sta.rc_list);
@@ -270,7 +270,17 @@ static int mt7915_add_interface(struct ieee80211_hw *hw,
 	mt7915_mcu_add_sta(dev, vif, NULL, true);
 	rcu_assign_pointer(dev->mt76.wcid[idx], &mvif->sta.wcid);
 
+	mutex_unlock(&dev->mt76.mutex);
+
+	return 0;
+
+err:
+	dev->mt76.vif_mask &= ~BIT_ULL(mvif->mt76.idx);
+	phy->omac_mask &= ~BIT_ULL(mvif->mt76.omac_idx);
+	mt7915_mcu_add_dev_info(phy, vif, false);
 out:
+	if (phy->monitor_vif == vif)
+		phy->monitor_vif = NULL;
 	mutex_unlock(&dev->mt76.mutex);
 
 	return ret;
Signature · 1 line
2.53.0
author maintainer
#1602 lore raw

PATCH 1596/1887 wifi: mt76: fix non-AQL packet accounting for MLO stations

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Michael-CY Lee <[email protected]>

[ Upstream commit 8ae659743ba936b22ecb4620815887728e2820d6 ]

__mt76_tx_queue_skb() overrides the wcid passed by the driver with sta->drv_priv, so the wcid might incorrectly be changed after TX, causing wcid->non_aql_packets to be counted on the wrong wcid. For example, on the AP side, if a station's setup link is the 5G link and the station uses 2G to transmit a frame, the value of non_aql_packets is increased on the 5G wcid but decreased on the 2G wcid. Once the inflated counter exceeds MT_MAX_NON_AQL_PKT, the TX scheduler permanently refuses to service the station.

Drop the reassignment and account on the wcid used for transmission. This also records the actual wcid in the queue entry.

Fixes e1378e5228aa ("mt76: rely on AQL for burst size limits on tx queueing")
---
 drivers/net/wireless/mediatek/mt76/tx.c | 1 -
 1 file changed, 1 deletion(-)
drivers/net/wireless/mediatek/mt76/tx.c +0 -1
index 4482e4ff78044..3551a013208d3 100644
@@ -298,7 +298,6 @@ __mt76_tx_queue_skb(struct mt76_phy *phy, int qid, struct sk_buff *skb,
 	if (idx < 0 || !sta)
 		return idx;
 
-	wcid = (struct mt76_wcid *)sta->drv_priv;
 	q->entry[idx].wcid = wcid->idx;
 
 	if (!non_aql)
Signature · 1 line
2.53.0
author maintainer
#1603 lore raw

PATCH 1597/1887 misc: sgi-gru: remove interrupt-context page-table walks

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Muhammad Usama Anjum <[email protected]>

[ Upstream commit 928a8e9f523df845fc496bcb9811013b67aabec5 ]

The GRU TLB miss handler walks a process's page tables without holding page-table locks or a reference to the mapped page. It also uses a kernel page-table accessor on user page tables and supports only PMD-level large mappings on x86-64.

Remove the direct walker. Send interrupt faults directly to user polling mode so the existing call-OS fallback retries them in process context.

Remove the mmap-lock failure statistic that can no longer be incremented.

---
 drivers/misc/sgi-gru/grufault.c  | 101 ++++---------------------------
 drivers/misc/sgi-gru/gruprocfs.c |   1 -
 drivers/misc/sgi-gru/grutables.h |   1 -
 3 files changed, 12 insertions(+), 91 deletions(-)
drivers/misc/sgi-gru/grufault.c +12 -89
index b836936e97471..c2f38e650b66b 100644
@@ -166,13 +166,8 @@ static void get_clear_fault_map(struct gru_state *gru,
 }
 
 /*
- * Atomic (interrupt context) & non-atomic (user context) functions to
- * convert a vaddr into a physical address. The size of the page
- * is returned in pageshift.
- * 	returns:
- * 		  0 - successful
- * 		< 0 - error code
- * 		  1 - (atomic only) try again in non-atomic context
+ * Convert a user virtual address to a physical address in process context.
+ * The size of the page is returned in pageshift.
  */
 static int non_atomic_pte_lookup(struct vm_area_struct *vma,
 				 unsigned long vaddr, int write,
@@ -192,87 +187,25 @@ static int non_atomic_pte_lookup(struct vm_area_struct *vma,
 	return 0;
 }
 
-/*
- * atomic_pte_lookup
- *
- * Convert a user virtual address to a physical address
- * Only supports Intel large pages (2MB only) on x86_64.
- *	ZZZ - hugepage support is incomplete
- *
- * NOTE: mmap_lock is already held on entry to this function. This
- * guarantees existence of the page tables.
- */
-static int atomic_pte_lookup(struct vm_area_struct *vma, unsigned long vaddr,
-	int write, unsigned long *paddr, int *pageshift)
-{
-	pgd_t *pgdp;
-	p4d_t *p4dp;
-	pud_t *pudp;
-	pmd_t *pmdp;
-	pte_t pte;
-
-	pgdp = pgd_offset(vma->vm_mm, vaddr);
-	if (unlikely(pgd_none(*pgdp)))
-		goto err;
-
-	p4dp = p4d_offset(pgdp, vaddr);
-	if (unlikely(p4d_none(*p4dp)))
-		goto err;
-
-	pudp = pud_offset(p4dp, vaddr);
-	if (unlikely(pud_none(*pudp)))
-		goto err;
-
-	pmdp = pmd_offset(pudp, vaddr);
-	if (unlikely(pmd_none(*pmdp)))
-		goto err;
-#ifdef CONFIG_X86_64
-	if (unlikely(pmd_large(*pmdp)))
-		pte = *(pte_t *) pmdp;
-	else
-#endif
-		pte = *pte_offset_kernel(pmdp, vaddr);
-
-	if (unlikely(!pte_present(pte) ||
-		     (write && (!pte_write(pte) || !pte_dirty(pte)))))
-		return 1;
-
-	*paddr = pte_pfn(pte) << PAGE_SHIFT;
-#ifdef CONFIG_HUGETLB_PAGE
-	*pageshift = is_vm_hugetlb_page(vma) ? HPAGE_SHIFT : PAGE_SHIFT;
-#else
-	*pageshift = PAGE_SHIFT;
-#endif
-	return 0;
-
-err:
-	return 1;
-}
-
 static int gru_vtop(struct gru_thread_state *gts, unsigned long vaddr,
 		    int write, int atomic, unsigned long *gpa, int *pageshift)
 {
 	struct mm_struct *mm = gts->ts_mm;
 	struct vm_area_struct *vma;
 	unsigned long paddr;
-	int ret, ps;
+	int ps;
 
 	vma = find_vma(mm, vaddr);
 	if (!vma)
 		goto inval;
 
-	/*
-	 * Atomic lookup is faster & usually works even if called in non-atomic
-	 * context.
-	 */
-	rmb();	/* Must/check ms_range_active before loading PTEs */
-	ret = atomic_pte_lookup(vma, vaddr, write, &paddr, &ps);
-	if (ret) {
-		if (atomic)
-			goto upm;
-		if (non_atomic_pte_lookup(vma, vaddr, write, &paddr, &ps))
-			goto inval;
-	}
+	if (atomic)
+		goto upm;
+
+	/* Order the caller's ms_range_active check before loading PTEs. */
+	rmb();
+	if (non_atomic_pte_lookup(vma, vaddr, write, &paddr, &ps))
+		goto inval;
 	if (is_gru_paddr(paddr))
 		goto inval;
 	paddr = paddr & ~((1UL << ps) - 1);
@@ -569,19 +502,9 @@ static irqreturn_t gru_intr(int chiplet, int blade)
 			continue;
 		}
 
-		/*
-		 * This is running in interrupt context. Trylock the mmap_lock.
-		 * If it fails, retry the fault in user context.
-		 */
+		/* Address translation may sleep, so retry the fault in user context. */
 		gts->ustats.fmm_tlbmiss++;
-		if (!gts->ts_force_cch_reload &&
-					mmap_read_trylock(gts->ts_mm)) {
-			gru_try_dropin(gru, gts, tfh, NULL);
-			mmap_read_unlock(gts->ts_mm);
-		} else {
-			tfh_user_polling_mode(tfh);
-			STAT(intr_mm_lock_failed);
-		}
+		tfh_user_polling_mode(tfh);
 	}
 	return IRQ_HANDLED;
 }
drivers/misc/sgi-gru/gruprocfs.c +0 -1
index 97b8b38ab47df..b8139c27bc7f8 100644
@@ -54,7 +54,6 @@ static int statistics_show(struct seq_file *s, void *p)
 	printstat(s, intr_cbr);
 	printstat(s, intr_tfh);
 	printstat(s, intr_spurious);
-	printstat(s, intr_mm_lock_failed);
 	printstat(s, call_os);
 	printstat(s, call_os_wait_queue);
 	printstat(s, user_flush_tlb);
drivers/misc/sgi-gru/grutables.h +0 -1
index 640daf1994df7..3348552925c61 100644
@@ -182,7 +182,6 @@ struct gru_stats_s {
 	atomic_long_t intr_cbr;
 	atomic_long_t intr_tfh;
 	atomic_long_t intr_spurious;
-	atomic_long_t intr_mm_lock_failed;
 	atomic_long_t call_os;
 	atomic_long_t call_os_wait_queue;
 	atomic_long_t user_flush_tlb;
Signature · 1 line
2.53.0
author maintainer
#1604 lore raw

PATCH 1598/1887 md/raid1: create serial pool adding rdev to array with serialize_policy=1

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Martin Wilck <[email protected]>

[ Upstream commit 140234b2380ffb8ffb0cfc46fee0e822f43adef7 ]

The following bug has been observed with kernel 7.1.3 after adding a new rdev to an existing RAID1 array with serialize_policy enabled:

  Oops: 0002 [#1]
  CPU: 0 UID: 0 PID: 19639 Comm: ext4lazyinit Not tainted 7.1.3-1-default
  RIP: _raw_spin_lock_irqsave+0x27/0x50
  CR2: 0000000000004960
  Call Trace:
   wait_for_serialization+0xb9/0x260 [raid1]
   raid1_make_request+0x762/0xaff [raid1]
   md_handle_request+0x1c9/0x2e0 [md_mod]

The raid1.c code calls wait_for_serialization() if the MD_SERIALIZE_POLICY is set, and wait_for_serialization assumes that rdev->serial is initialized. Normally this will be the case for arrays that have the serialize_policy sysfs attribute set to 1.

But when a new rdev is added to an existing array in bind_rdev_to_array(), the condition at mddev_create_serial_pool() causes creation of rdev->serial to be skipped. Fix it.

Fixes 69b00b5bb235 ("md: introduce a new struct for IO serialization")
---
 drivers/md/md.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
drivers/md/md.c +2 -1
index ce5063114449c..e268eb0be6da9 100644
@@ -213,7 +213,8 @@ void mddev_create_serial_pool(struct mddev *mddev, struct md_rdev *rdev,
 {
 	int ret = 0;
 
-	if (rdev && !rdev_need_serial(rdev) &&
+	if (!mddev->serialize_policy &&
+	    rdev && !rdev_need_serial(rdev) &&
 	    !test_bit(CollisionCheck, &rdev->flags))
 		return;
 
Signature · 1 line
2.53.0
author maintainer
#1605 lore raw

PATCH 1599/1887 x86/ftrace: Remove SYSTEM_BOOTING exceptions

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Peter Zijlstra <[email protected]>

[ Upstream commit eb7d389d5b2b3c453332abc41c3eea73290cc006 ]

Now that text_poke is available before ftrace, remove the SYSTEM_BOOTING exceptions.

Specifically, this cures a W+X case during boot.

Stable-dep-of: 863f3ddd0b8a ("bpf: Fix potential UAF when reading bpf link info")

---
 arch/x86/kernel/alternative.c | 10 ----------
 arch/x86/kernel/ftrace.c      |  3 +--
 2 files changed, 1 insertion(+), 12 deletions(-)
arch/x86/kernel/alternative.c +0 -10
index 3bc6be6404d23..af47ab50e7c1c 100644
@@ -1976,11 +1976,6 @@ void __ref text_poke_queue(void *addr, const void *opcode, size_t len, const voi
 {
 	struct text_poke_loc *tp;
 
-	if (unlikely(system_state == SYSTEM_BOOTING)) {
-		text_poke_early(addr, opcode, len);
-		return;
-	}
-
 	text_poke_flush(addr);
 
 	tp = &tp_vec[tp_vec_nr++];
@@ -2002,11 +1997,6 @@ void __ref text_poke_bp(void *addr, const void *opcode, size_t len, const void *
 {
 	struct text_poke_loc tp;
 
-	if (unlikely(system_state == SYSTEM_BOOTING)) {
-		text_poke_early(addr, opcode, len);
-		return;
-	}
-
 	text_poke_loc_init(&tp, addr, opcode, len, emulate);
 	text_poke_bp_batch(&tp, 1);
 }
arch/x86/kernel/ftrace.c +1 -2
index 48cf91625a169..c20422367f82c 100644
@@ -415,8 +415,7 @@ create_trampoline(struct ftrace_ops *ops, unsigned int *tramp_size)
 	/* ALLOC_TRAMP flags lets us know we created it */
 	ops->flags |= FTRACE_OPS_FL_ALLOC_TRAMP;
 
-	if (likely(system_state != SYSTEM_BOOTING))
-		set_memory_ro((unsigned long)trampoline, npages);
+	set_memory_ro((unsigned long)trampoline, npages);
 	set_memory_x((unsigned long)trampoline, npages);
 	return (unsigned long)trampoline;
 fail:
Signature · 1 line
2.53.0
author maintainer
#1606 lore raw

PATCH 1600/1887 bpf: Retire the struct_ops map kvalue->refcnt.

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Kui-Feng Lee <[email protected]>

[ Upstream commit b671c2067a04c0668df174ff5dfdb573d1f9b074 ]

We have replaced kvalue-refcnt with synchronize_rcu() to wait for an RCU grace period.

Maintenance of kvalue->refcnt was a complicated task, as we had to simultaneously keep track of two reference counts: one for the reference count of bpf_map. When the kvalue->refcnt reaches zero, we also have to reduce the reference count on bpf_map - yet these steps are not performed in an atomic manner and require us to be vigilant when managing them. By eliminating kvalue->refcnt, we can make our maintenance more straightforward as the refcount of bpf_map is now solely managed!

To prevent the trampoline image of a struct_ops from being released while it is still in use, we wait for an RCU grace period. The setsockopt(TCP_CONGESTION, "...") command allows you to change your socket's congestion control algorithm and can result in releasing the old struct_ops implementation. It is fine. However, this function is exposed through bpf_setsockopt(), it may be accessed by BPF programs as well. To ensure that the trampoline image belonging to struct_op can be safely called while its method is in use, the trampoline safeguarde the BPF program with rcu_read_lock(). Doing so prevents any destruction of the associated images before returning from a trampoline and requires us to wait for an RCU grace period.

Stable-dep-of: 863f3ddd0b8a ("bpf: Fix potential UAF when reading bpf link info")

---
 include/linux/bpf.h         |  1 +
 kernel/bpf/bpf_struct_ops.c | 77 +++++++++++++++++++++----------------
 kernel/bpf/syscall.c        |  6 ++-
 3 files changed, 49 insertions(+), 35 deletions(-)
include/linux/bpf.h +1 -0
index 4f09079ecaebd..546d40d82edbc 100644
@@ -1813,6 +1813,7 @@ struct bpf_map *bpf_map_get_with_uref(u32 ufd);
 struct bpf_map *__bpf_map_get(struct fd f);
 void bpf_map_inc(struct bpf_map *map);
 void bpf_map_inc_with_uref(struct bpf_map *map);
+struct bpf_map *__bpf_map_inc_not_zero(struct bpf_map *map, bool uref);
 struct bpf_map * __must_check bpf_map_inc_not_zero(struct bpf_map *map);
 void bpf_map_put_with_uref(struct bpf_map *map);
 void bpf_map_put(struct bpf_map *map);
kernel/bpf/bpf_struct_ops.c +44 -33

139 lines not on this page · raw message

kernel/bpf/syscall.c +4 -2
index e32d9b9de0dcb..f629db72bd1c2 100644
@@ -1298,8 +1298,10 @@ struct bpf_map *bpf_map_get_with_uref(u32 ufd)
 	return map;
 }
 
-/* map_idr_lock should have been held */
-static struct bpf_map *__bpf_map_inc_not_zero(struct bpf_map *map, bool uref)
+/* map_idr_lock should have been held or the map should have been
+ * protected by rcu read lock.
+ */
+struct bpf_map *__bpf_map_inc_not_zero(struct bpf_map *map, bool uref)
 {
 	int refold;
 
Signature · 1 line
2.53.0
author maintainer
#1607 lore raw

PATCH 1601/1887 bpf: Fix potential UAF when reading bpf link info

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Pu Lehui <[email protected]>

[ Upstream commit 863f3ddd0b8ac65abfb50d3be0869268ac0e277b ]

In bpf_link_show_fdinfo and bpf_link_get_info_by_fd, link->prog is accessed without holding any locks. If the prog is concurrently replaced via bpf_link_update, the old prog can be freed, leading to a potential UAF issue.

Fix this by accessing link->prog under RCU protection to safely fetch the pointer and guarantee its lifetime while reading its fields.

---
 kernel/bpf/syscall.c | 32 ++++++++++++++++++++++++--------
 1 file changed, 24 insertions(+), 8 deletions(-)
kernel/bpf/syscall.c +24 -8
index f629db72bd1c2..df9ce9cc7c112 100644
@@ -2851,9 +2851,10 @@ static const char *bpf_link_type_strs[] = {
 static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
 {
 	const struct bpf_link *link = filp->private_data;
-	const struct bpf_prog *prog = link->prog;
+	const struct bpf_prog *prog;
 	enum bpf_link_type type = link->type;
 	char prog_tag[sizeof(prog->tag) * 2 + 1] = { };
+	u32 prog_id = 0;
 
 	if (type < ARRAY_SIZE(bpf_link_type_strs) && bpf_link_type_strs[type]) {
 		seq_printf(m, "link_type:\t%s\n", bpf_link_type_strs[type]);
@@ -2863,12 +2864,21 @@ static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
 	}
 	seq_printf(m, "link_id:\t%u\n", link->id);
 
-	bin2hex(prog_tag, prog->tag, sizeof(prog->tag));
-	seq_printf(m,
-		   "prog_tag:\t%s\n"
-		   "prog_id:\t%u\n",
-		   prog_tag,
-		   prog->aux->id);
+	rcu_read_lock();
+	prog = READ_ONCE(link->prog);
+	if (prog) {
+		bin2hex(prog_tag, prog->tag, sizeof(prog->tag));
+		prog_id = prog->aux->id;
+	}
+	rcu_read_unlock();
+
+	if (prog) {
+		seq_printf(m,
+			   "prog_tag:\t%s\n"
+			   "prog_id:\t%u\n",
+			   prog_tag,
+			   prog_id);
+	}
 	if (link->ops->show_fdinfo)
 		link->ops->show_fdinfo(link, m);
 }
@@ -4355,6 +4365,7 @@ static int bpf_link_get_info_by_fd(struct file *file,
 {
 	struct bpf_link_info __user *uinfo = u64_to_user_ptr(attr->info.info);
 	struct bpf_link_info info;
+	const struct bpf_prog *prog;
 	u32 info_len = attr->info.info_len;
 	int err;
 
@@ -4369,7 +4380,12 @@ static int bpf_link_get_info_by_fd(struct file *file,
 
 	info.type = link->type;
 	info.id = link->id;
-	info.prog_id = link->prog->aux->id;
+
+	rcu_read_lock();
+	prog = READ_ONCE(link->prog);
+	if (prog)
+		info.prog_id = prog->aux->id;
+	rcu_read_unlock();
 
 	if (link->ops->fill_link_info) {
 		err = link->ops->fill_link_info(link, &info);
Signature · 1 line
2.53.0
author maintainer
#1608 lore raw

PATCH 1602/1887 wifi: ath11k: fix stride mismatch in mac_phy_caps_parse()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Jeff Johnson <[email protected]>

[ Upstream commit 7a246c72132eb943b5844ba79dad597b47429dba ]

Currently, in ath11k_wmi_tlv_mac_phy_caps_parse(), kcalloc() sizes the mac_phy_caps buffer as tot_phy_id * len, where len is clamped to min(firmware_len, sizeof(struct wmi_mac_phy_capabilities)). The subsequent memcpy() destination advances by sizeof(full struct) per slot via C pointer arithmetic, not by the clamped len. When firmware sends short TLVs, the second and later slots are written past the end of the allocation.

The reader in ath11k_pull_mac_phy_cap_svc_ready_ext() also indexes the buffer with full-struct pointer arithmetic, so the allocation must match that stride.

Fix by using kzalloc_objs(), which derives the element size from the pointer type, making allocation size and pointer stride provably consistent regardless of what len the firmware provides.

Compile tested only.

---
 drivers/net/wireless/ath/ath11k/wmi.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)
drivers/net/wireless/ath/ath11k/wmi.c +5 -3
index c3f440cae0bfd..b58c84c2f9dd3 100644
@@ -4513,14 +4513,16 @@ static int ath11k_wmi_tlv_mac_phy_caps_parse(struct ath11k_base *soc,
 	if (svc_rdy_ext->n_mac_phy_caps >= svc_rdy_ext->tot_phy_id)
 		return -ENOBUFS;
 
-	len = min_t(u16, len, sizeof(struct wmi_mac_phy_capabilities));
 	if (!svc_rdy_ext->n_mac_phy_caps) {
-		svc_rdy_ext->mac_phy_caps = kcalloc(svc_rdy_ext->tot_phy_id,
-						    len, GFP_ATOMIC);
+		svc_rdy_ext->mac_phy_caps =
+			kcalloc(svc_rdy_ext->tot_phy_id,
+				sizeof(*svc_rdy_ext->mac_phy_caps),
+				GFP_ATOMIC);
 		if (!svc_rdy_ext->mac_phy_caps)
 			return -ENOMEM;
 	}
 
+	len = min_t(u16, len, sizeof(struct wmi_mac_phy_capabilities));
 	memcpy(svc_rdy_ext->mac_phy_caps + svc_rdy_ext->n_mac_phy_caps, ptr, len);
 	svc_rdy_ext->n_mac_phy_caps++;
 	return 0;
Signature · 1 line
2.53.0
author maintainer
#1609 lore raw

PATCH 1603/1887 wifi: ath11k: fix overreads in ath11k_wmi_process_csa_switch_count_event()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Jeff Johnson <[email protected]>

[ Upstream commit 208d7fdb85976a737a715b81d54efaff6703880c ]

There is no policy entry for WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT, so the parse infrastructure does not enforce a minimum length for the event struct. Additionally, the num_vdevs field is taken directly from firmware and used as a loop bound over the vdev_ids array without checking that it fits within the TLV payload. Either condition can cause an out-of-bounds read.

Add a TLV policy entry for WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT so the parse infrastructure enforces a minimum length for the fixed-size event struct. Add a helper ath11k_wmi_tlv_data_len() to recover the payload length of a parsed TLV from the header preceding its data pointer. Use it in ath11k_wmi_process_csa_switch_count_event() to bound num_vdevs before the loop.

Compile tested only.

---
 drivers/net/wireless/ath/ath11k/wmi.c | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)
drivers/net/wireless/ath/ath11k/wmi.c +19 -2
index b58c84c2f9dd3..579cd7a2d4279 100644
@@ -145,6 +145,8 @@ static const struct wmi_tlv_policy wmi_tlv_policies[] = {
 		.min_len = sizeof(struct wmi_per_chain_rssi_stats) },
 	[WMI_TAG_TWT_ADD_DIALOG_COMPLETE_EVENT] = {
 		.min_len = sizeof(struct wmi_twt_add_dialog_event) },
+	[WMI_TAG_PDEV_CSA_SWITCH_COUNT_STATUS_EVENT] = {
+		.min_len = sizeof(struct wmi_pdev_csa_switch_ev) },
 };
 
 #define PRIMAP(_hw_mode_) \
@@ -249,6 +251,13 @@ ath11k_wmi_tlv_parse_alloc(struct ath11k_base *ab, const void *ptr,
 	return tb;
 }
 
+static u32 ath11k_wmi_tlv_data_len(const void *data)
+{
+	const struct wmi_tlv *tlv = (const struct wmi_tlv *)data - 1;
+
+	return FIELD_GET(WMI_TLV_LEN, tlv->header);
+}
+
 static int ath11k_wmi_cmd_send_nowait(struct ath11k_pdev_wmi *wmi, struct sk_buff *skb,
 				      u32 cmd_id)
 {
@@ -7656,15 +7665,23 @@ ath11k_wmi_process_csa_switch_count_event(struct ath11k_base *ab,
 					  const struct wmi_pdev_csa_switch_ev *ev,
 					  const u32 *vdev_ids)
 {
-	int i;
+	u32 vdev_ids_len = ath11k_wmi_tlv_data_len(vdev_ids);
+	u32 num_vdevs = ev->num_vdevs;
 	struct ath11k_vif *arvif;
+	int i;
 
 	/* Finish CSA once the switch count becomes NULL */
 	if (ev->current_switch_count)
 		return;
 
+	if (num_vdevs > vdev_ids_len / sizeof(*vdev_ids)) {
+		ath11k_warn(ab, "csa switch count num_vdevs %u exceeds tlv array length %u\n",
+			    num_vdevs, vdev_ids_len);
+		return;
+	}
+
 	rcu_read_lock();
-	for (i = 0; i < ev->num_vdevs; i++) {
+	for (i = 0; i < num_vdevs; i++) {
 		arvif = ath11k_mac_get_arvif_by_vdev_id(ab, vdev_ids[i]);
 
 		if (!arvif) {
Signature · 1 line
2.53.0
author maintainer
#1610 lore raw

PATCH 1604/1887 spi: davinci: switch to managed controller allocation

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Fan Wu <[email protected]>

[ Upstream commit ea408a05dc8f18b4a184b88d6e19d2fd1acc1527 ]

The controller is allocated with the non-managed spi_alloc_host() while the interrupt is registered with devm_request_threaded_irq(). During removal, spi_bitbang_stop() only unregisters the controller; the subsequent spi_controller_put() then frees the controller together with its embedded davinci_spi devdata, which is the IRQ handler's dev_id. The devm_request_threaded_irq() release action (free_irq()), which drains the handler, does not run until after .remove() returns. A late or latched interrupt can therefore reach davinci_spi_irq() and dereference already-freed memory.

Switch to devm_spi_alloc_host() so that the devres LIFO order releases the controller only after free_irq() has drained the handler, and drop the now-redundant spi_controller_put() from .remove(). The probe error path is simplified to direct returns.

The clock is acquired with devm_clk_get_enabled(), which is registered after the IRQ and thus released before it by the devres LIFO order. Drain the interrupt explicitly with devm_free_irq() before disabling the controller so that a late interrupt cannot access the registers of a clock-gated controller.

This issue was found by an in-house static analysis tool.

Fixes 5b3bb5963ff2 ("spi: davinci: Use devm_*() functions")
Assisted-by Codex:gpt-5.6
---
 drivers/spi/spi-davinci.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
drivers/spi/spi-davinci.c +3 -2
index d112c2cac042b..ce4689e96bad6 100644
@@ -863,7 +863,7 @@ static int davinci_spi_probe(struct platform_device *pdev)
 	int ret = 0;
 	u32 spipc0;
 
-	master = spi_alloc_master(&pdev->dev, sizeof(struct davinci_spi));
+	master = devm_spi_alloc_master(&pdev->dev, sizeof(struct davinci_spi));
 	if (master == NULL) {
 		ret = -ENOMEM;
 		goto err;
@@ -1004,7 +1004,6 @@ static int davinci_spi_probe(struct platform_device *pdev)
 free_clk:
 	clk_disable_unprepare(dspi->clk);
 free_master:
-	spi_master_put(master);
 err:
 	return ret;
 }
@@ -1028,6 +1027,8 @@ static int davinci_spi_remove(struct platform_device *pdev)
 
 	spi_bitbang_stop(&dspi->bitbang);
 
+	devm_free_irq(&pdev->dev, dspi->irq, dspi);
+
 	clk_disable_unprepare(dspi->clk);
 
 	if (dspi->dma_rx) {
Signature · 1 line
2.53.0
author maintainer
#1611 lore raw

PATCH 1605/1887 RDMA/cxgb4: Fix dereg_skb leak and double free in write_tpt_entry()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Leon Romanovsky <[email protected]>

[ Upstream commit 373f3716a2de7adc739269ebb4d87e5bf4dc180c ]

When the device is in the fatal error state, write_tpt_entry() returns -EIO before handing the caller's preallocated skb to the transmit path; its allocation-failure returns do the same. c4iw_dereg_mr() ignores the error and frees mhp, leaking mhp->dereg_skb. c4iw_get_dma_mr() instead frees the skb a second time after dereg_mem() already consumed it, a double free.

Make write_tpt_entry() the sole owner of a non-NULL skb, freeing it on every return preceding handoff to c4iw_ofld_send(): fatal error, tpt and stag allocation failure. c4iw_ofld_send() consumes the skb on success and error alike, so drop the redundant kfree_skb() in c4iw_get_dma_mr() after dereg_mem().

---
 drivers/infiniband/hw/cxgb4/mem.c | 46 ++++++++++++-------------------
 1 file changed, 17 insertions(+), 29 deletions(-)
drivers/infiniband/hw/cxgb4/mem.c +17 -29
index 9c24789bbb54b..9ed43d23c1835 100644
@@ -199,7 +199,8 @@ static int _c4iw_write_mem_dma(struct c4iw_rdev *rdev, u32 addr, u32 len,
 
 	daddr = dma_map_single(&rdev->lldi.pdev->dev, data, len, DMA_TO_DEVICE);
 	if (dma_mapping_error(&rdev->lldi.pdev->dev, daddr))
-		return -1;
+		return _c4iw_write_mem_inline(rdev, addr, len, data, skb,
+					      wr_waitp);
 	save = daddr;
 
 	while (remain > inline_threshold) {
@@ -235,30 +236,12 @@ static int write_adapter_mem(struct c4iw_rdev *rdev, u32 addr, u32 len,
 			     void *data, struct sk_buff *skb,
 			     struct c4iw_wr_wait *wr_waitp)
 {
-	int ret;
-
-	if (!rdev->lldi.ulptx_memwrite_dsgl || !use_dsgl) {
-		ret = _c4iw_write_mem_inline(rdev, addr, len, data, skb,
-					      wr_waitp);
-		goto out;
-	}
-
-	if (len <= inline_threshold) {
-		ret = _c4iw_write_mem_inline(rdev, addr, len, data, skb,
+	if (!rdev->lldi.ulptx_memwrite_dsgl || !use_dsgl ||
+	    len <= inline_threshold)
+		return _c4iw_write_mem_inline(rdev, addr, len, data, skb,
 					      wr_waitp);
-		goto out;
-	}
-
-	ret = _c4iw_write_mem_dma(rdev, addr, len, data, skb, wr_waitp);
-	if (ret) {
-		pr_warn_ratelimited("%s: dma map failure (non fatal)\n",
-				    pci_name(rdev->lldi.pdev));
-		ret = _c4iw_write_mem_inline(rdev, addr, len, data, skb,
-					      wr_waitp);
-	}
-out:
-	return ret;
 
+	return _c4iw_write_mem_dma(rdev, addr, len, data, skb, wr_waitp);
 }
 
 /*
@@ -280,12 +263,16 @@ static int write_tpt_entry(struct c4iw_rdev *rdev, u32 reset_tpt_entry,
 	bool stag_idx_allocated = false;
 	static atomic_t key;
 
-	if (c4iw_fatal_error(rdev))
+	if (c4iw_fatal_error(rdev)) {
+		kfree_skb(skb);
 		return -EIO;
+	}
 
 	tpt = kmalloc(sizeof(*tpt), GFP_KERNEL);
-	if (!tpt)
+	if (!tpt) {
+		kfree_skb(skb);
 		return -ENOMEM;
+	}
 
 	stag_state = stag_state > 0;
 	stag_idx = (*stag) >> 8;
@@ -297,6 +284,7 @@ static int write_tpt_entry(struct c4iw_rdev *rdev, u32 reset_tpt_entry,
 			rdev->stats.stag.fail++;
 			mutex_unlock(&rdev->stats.lock);
 			kfree(tpt);
+			kfree_skb(skb);
 			return -ENOMEM;
 		}
 		mutex_lock(&rdev->stats.lock);
@@ -471,8 +459,10 @@ struct ib_mr *c4iw_get_dma_mr(struct ib_pd *pd, int acc)
 			      FW_RI_STAG_NSMR, mhp->attr.perms,
 			      mhp->attr.mw_bind_enable, 0, 0, ~0ULL, 0, 0, 0,
 			      NULL, mhp->wr_waitp);
-	if (ret)
-		goto err_free_skb;
+	if (ret) {
+		kfree_skb(mhp->dereg_skb);
+		goto err_free_wr_wait;
+	}
 
 	ret = finish_mem_reg(mhp, stag);
 	if (ret)
@@ -481,8 +471,6 @@ struct ib_mr *c4iw_get_dma_mr(struct ib_pd *pd, int acc)
 err_dereg_mem:
 	dereg_mem(&rhp->rdev, mhp->attr.stag, mhp->attr.pbl_size,
 		  mhp->attr.pbl_addr, mhp->dereg_skb, mhp->wr_waitp);
-err_free_skb:
-	kfree_skb(mhp->dereg_skb);
 err_free_wr_wait:
 	c4iw_put_wr_wait(mhp->wr_waitp);
 err_free_mhp:
Signature · 1 line
2.53.0
author maintainer
#1612 lore raw

PATCH 1606/1887 nilfs2: fix BUG in nilfs_copy_dirty_pages() on dirty state mismatch

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Ryusuke Konishi <[email protected]>

[ Upstream commit 66f4ad3ce158902e5f98afea93189972ed8750c2 ]

Syzbot reported a kernel BUG triggered within nilfs_copy_dirty_pages(), which copies dirty DAT file folios/pages to its shadow page cache. The BUG occurs when a retrieved dirty folio/page unexpectedly loses its 'dirty' status.

This issue arises because, since the commit referenced below, the 'dirty' flag of a folio/page can be cleared asynchronously after the filesystem detects metadata corruption and transitions to read-only mode.

Resolve the issue by returning an -EROFS error if the filesystem has transitioned to read-only mode. Also change the behavior to issue a kernel warning only once instead of triggering a kernel BUG when this unexpected 'dirty' state is detected while the filesystem is not in read-only mode.

Reported-by [email protected]
Fixes 8c26c4e2694a ("nilfs2: fix issue with flush kernel thread after remount in RO mode because of driver's internal error or metadata corruption")
---
 fs/nilfs2/page.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)
fs/nilfs2/page.c +15 -2
index bf090240f6ede..89cd1510bf715 100644
@@ -241,6 +241,7 @@ static void nilfs_copy_page(struct page *dst, struct page *src, int copy_dirty)
 int nilfs_copy_dirty_pages(struct address_space *dmap,
 			   struct address_space *smap)
 {
+	struct inode *smap_inode = smap->host;
 	struct pagevec pvec;
 	unsigned int i;
 	pgoff_t index = 0;
@@ -255,8 +256,19 @@ int nilfs_copy_dirty_pages(struct address_space *dmap,
 		struct page *page = pvec.pages[i], *dpage;
 
 		lock_page(page);
-		if (unlikely(!PageDirty(page)))
-			NILFS_PAGE_BUG(page, "inconsistent dirty state");
+		if (unlikely(!PageDirty(page))) {
+			if (WARN_ONCE(!sb_rdonly(smap_inode->i_sb),
+					"inconsistent dirty state\n"))
+				goto unlock_src_page;
+
+			/*
+			 * If the filesystem has been forced to read-only
+			 * due to metadata corruption.
+			 */
+			unlock_page(page);
+			err = -EROFS;
+			break;
+		}
 
 		dpage = grab_cache_page(dmap, page->index);
 		if (unlikely(!dpage)) {
@@ -274,6 +286,7 @@ int nilfs_copy_dirty_pages(struct address_space *dmap,
 
 		unlock_page(dpage);
 		put_page(dpage);
+unlock_src_page:
 		unlock_page(page);
 	}
 	pagevec_release(&pvec);
Signature · 1 line
2.53.0
author maintainer
#1613 lore raw

PATCH 1607/1887 PCI: Initialize temporary device in new_id_store()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Samiullah Khawaja <[email protected]>

[ Upstream commit f45a49a2380a47332817b7248c61a0ebbc6f0d00 ]

When setting new_id of a PCI device driver using sysfs a lockdep splat occurs. This is because new_id_store() builds a temporary pci_dev for pci_match_device(), which calls device_match_driver_override(). That depends on the driver_override.lock added by cb3d1049f4ea ("driver core:
generalize driver_override in struct device").

The new driver_override.lock was not initialized in the temporary pci_dev, resulting in this lockdep splat.

Initialize the temporary pci_dev to fix this.

Repro:

Build with CONFIG_LOCKDEP=y, boot with QEMU, and add a new ID:

  # echo "8086 10f5" > /sys/bus/pci/drivers/e1000e/new_id

INFO: trying to register non-static key.
The code is fine but needs lockdep annotation, or maybe you didn't initialize this object before use? turning off the locking correctness validator.

  CPU: 2 UID: 0 PID: 177 Comm: liveupdate-iomm Not tainted 7.0.0+ #9 PREEMPT(full)
  Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014
  Call Trace:
   <TASK>
   dump_stack_lvl+0x5d/0x80
   register_lock_class+0x77e/0x790
   lock_acquire+0xbf/0x2e0
   pci_match_device+0x24/0x180
   new_id_store+0x189/0x1d0
   kernfs_fop_write_iter+0x14f/0x210
   vfs_write+0x263/0x5e0
   ksys_write+0x79/0xf0
   do_syscall_64+0x117/0xf80
Fixes 10a4206a2401 ("PCI: use generic driver_override infrastructure")
Fixes 8895d3bcb8ba ("PCI: Fail new_id for vendor/device values already built into driver")
[bhelgaas: add commit log details and repro, trim backtrace]

Stable-dep-of: 3ffc4c9690c3 ("PCI: Fix UAF when probe runs concurrent to dyn ID removal")

---
 drivers/pci/pci-driver.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)
drivers/pci/pci-driver.c +8 -1
index fa3165e5b921b..9cbef46dc1af5 100644
@@ -177,6 +177,11 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
 	return NULL;
 }
 
+static void _pci_free_device(struct device *dev)
+{
+	kfree(to_pci_dev(dev));
+}
+
 /**
  * new_id_store - sysfs frontend to pci_add_dynid()
  * @driver: target device driver
@@ -212,11 +217,13 @@ static ssize_t new_id_store(struct device_driver *driver, const char *buf,
 		pdev->subsystem_vendor = subvendor;
 		pdev->subsystem_device = subdevice;
 		pdev->class = class;
+		pdev->dev.release = _pci_free_device;
 
+		device_initialize(&pdev->dev);
 		if (pci_match_device(pdrv, pdev))
 			retval = -EEXIST;
 
-		kfree(pdev);
+		put_device(&pdev->dev);
 
 		if (retval)
 			return retval;
Signature · 1 line
2.53.0
author maintainer
#1614 lore raw

PATCH 1608/1887 PCI: Make pci_match_one_device() match on ID instead of device

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Gary Guo <[email protected]>

[ Upstream commit c967b365d7b51c2297d9be5df39ac3e20638faf4 ]

PCI dynamic ID needs to match IDs against a new ID to see if it already exists. Existing APIs can only match IDs against devices, so the dynamic ID insertion code creates a temporary device only for matching purposes. Rename pci_match_one_device() to pci_match_one_id() so it can be used for this purpose instead; add a pci_id_from_device() helper to make it easy to convert users.

Similarly, convert pci_match_id() to do_pci_match_id(). But keep the existing API because there are many users.

Stable-dep-of: 3ffc4c9690c3 ("PCI: Fix UAF when probe runs concurrent to dyn ID removal")

---
 drivers/pci/pci-driver.c | 38 +++++++++++++++++++++++++----------
 drivers/pci/pci.h        | 43 +++++++++++++++++++++++++++++-----------
 drivers/pci/search.c     |  4 +++-
 3 files changed, 62 insertions(+), 23 deletions(-)
drivers/pci/pci-driver.c +28 -10
index 9cbef46dc1af5..2b5b70e5b393a 100644
@@ -90,6 +90,27 @@ static void pci_free_dynids(struct pci_driver *drv)
 	spin_unlock(&drv->dynids.lock);
 }
 
+/**
+ * do_pci_match_id - See if a PCI ID matches a given pci_id table
+ * @ids: array of PCI device ID structures to search in
+ * @dev_id: the PCI device ID structure to match against.
+ *
+ * Returns the matching pci_device_id structure or %NULL if there is no match.
+ */
+static const struct pci_device_id *
+do_pci_match_id(const struct pci_device_id *ids,
+		const struct pci_device_id *dev_id)
+{
+	if (ids) {
+		while (ids->vendor || ids->subvendor || ids->class_mask) {
+			if (pci_match_one_id(ids, dev_id))
+				return ids;
+			ids++;
+		}
+	}
+	return NULL;
+}
+
 /**
  * pci_match_id - See if a PCI device matches a given pci_id table
  * @ids: array of PCI device ID structures to search in
@@ -105,14 +126,9 @@ static void pci_free_dynids(struct pci_driver *drv)
 const struct pci_device_id *pci_match_id(const struct pci_device_id *ids,
 					 struct pci_dev *dev)
 {
-	if (ids) {
-		while (ids->vendor || ids->subvendor || ids->class_mask) {
-			if (pci_match_one_device(ids, dev))
-				return ids;
-			ids++;
-		}
-	}
-	return NULL;
+	struct pci_device_id dev_id = pci_id_from_device(dev);
+
+	return do_pci_match_id(ids, &dev_id);
 }
 EXPORT_SYMBOL(pci_match_id);
 
@@ -138,15 +154,17 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
 {
 	struct pci_dynid *dynid;
 	const struct pci_device_id *found_id = NULL, *ids;
+	struct pci_device_id dev_id;
 
 	/* When driver_override is set, only bind to the matching driver */
 	if (dev->driver_override && strcmp(dev->driver_override, drv->name))
 		return NULL;
 
+	dev_id = pci_id_from_device(dev);
 	/* Look at the dynamic ids first, before the static ones */
 	spin_lock(&drv->dynids.lock);
 	list_for_each_entry(dynid, &drv->dynids.list, node) {
-		if (pci_match_one_device(&dynid->id, dev)) {
+		if (pci_match_one_id(&dynid->id, &dev_id)) {
 			found_id = &dynid->id;
 			break;
 		}
@@ -156,7 +174,7 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
 	if (found_id)
 		return found_id;
 
-	for (ids = drv->id_table; (found_id = pci_match_id(ids, dev));
+	for (ids = drv->id_table; (found_id = do_pci_match_id(ids, &dev_id));
 	     ids = found_id + 1) {
 		/*
 		 * The match table is split based on driver_override.
drivers/pci/pci.h +31 -12

52 lines not on this page · raw message

drivers/pci/search.c +3 -1
index b4c138a6ec025..376f5cbe08a87 100644
@@ -243,8 +243,10 @@ static int match_pci_dev_by_id(struct device *dev, const void *data)
 {
 	struct pci_dev *pdev = to_pci_dev(dev);
 	const struct pci_device_id *id = data;
+	struct pci_device_id dev_id;
 
-	if (pci_match_one_device(id, pdev))
+	dev_id = pci_id_from_device(pdev);
+	if (pci_match_one_id(id, &dev_id))
 		return 1;
 	return 0;
 }
Signature · 1 line
2.53.0
author maintainer
#1615 lore raw

PATCH 1609/1887 PCI: Fix dyn_id add TOCTOU

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Gary Guo <[email protected]>

[ Upstream commit 04fde70f782b6ce984f9f80c2023786caea28287 ]

Currently there is a TOCTOU issue in new_id_store() as the dyn ID insertion in pci_add_dynid() and the pci_match_device() are in separate critical sections.

Fix this by moving the existing ID check to inside pci_add_dynid() and only check against the static ID table outside the critical section.

Fixes 3853f9123c18 ("PCI: Avoid duplicate IDs in driver dynamic IDs list")

Adapted to this tree: use kzalloc() instead of kzalloc_obj(), and keep the dev->driver_override check to decide whether override-only table entries are matched.

Stable-dep-of: 3ffc4c9690c3 ("PCI: Fix UAF when probe runs concurrent to dyn ID removal")

---
 drivers/pci/pci-driver.c | 142 ++++++++++++++++++++-------------------
 1 file changed, 73 insertions(+), 69 deletions(-)
drivers/pci/pci-driver.c +73 -69
index 2b5b70e5b393a..3a1bb9b43cf78 100644
@@ -29,6 +29,47 @@ struct pci_dynid {
 	struct pci_device_id id;
 };
 
+/**
+ * do_pci_add_dynid - Add a new PCI device ID to this driver and re-probe
+ * @drv: target PCI driver
+ * @id: ID to be added
+ * @check_dup: whether to check if matching ID is already present
+ *
+ * Add a new dynamic PCI device ID to this driver and causes the driver to
+ * probe for all devices again.  @drv must have been registered prior to calling
+ * this function.
+ *
+ * Context: Does GFP_KERNEL allocation.
+ *
+ * Return: 0 on success, -errno on failure.
+ */
+static int do_pci_add_dynid(struct pci_driver *drv,
+			    const struct pci_device_id *id,
+			    bool check_dup)
+{
+	struct pci_dynid *dynid, *existing_dynid;
+
+	dynid = kzalloc(sizeof(*dynid), GFP_KERNEL);
+	if (!dynid)
+		return -ENOMEM;
+
+	dynid->id = *id;
+
+	scoped_guard(spinlock, &drv->dynids.lock) {
+		if (check_dup) {
+			list_for_each_entry(existing_dynid, &drv->dynids.list, node) {
+				if (pci_match_one_id(&existing_dynid->id, id)) {
+					kfree(dynid);
+					return -EEXIST;
+				}
+			}
+		}
+		list_add_tail(&dynid->node, &drv->dynids.list);
+	}
+
+	return driver_attach(&drv->driver);
+}
+
 /**
  * pci_add_dynid - add a new PCI device ID to this driver and re-probe devices
  * @drv: target pci driver
@@ -56,25 +97,17 @@ int pci_add_dynid(struct pci_driver *drv,
 		  unsigned int class, unsigned int class_mask,
 		  unsigned long driver_data)
 {
-	struct pci_dynid *dynid;
-
-	dynid = kzalloc(sizeof(*dynid), GFP_KERNEL);
-	if (!dynid)
-		return -ENOMEM;
+	struct pci_device_id id = {
+		.vendor = vendor,
+		.device = device,
+		.subvendor = subvendor,
+		.subdevice = subdevice,
+		.class = class,
+		.class_mask = class_mask,
+		.driver_data = driver_data,
+	};
 
-	dynid->id.vendor = vendor;
-	dynid->id.device = device;
-	dynid->id.subvendor = subvendor;
-	dynid->id.subdevice = subdevice;
-	dynid->id.class = class;
-	dynid->id.class_mask = class_mask;
-	dynid->id.driver_data = driver_data;
-
-	spin_lock(&drv->dynids.lock);
-	list_add_tail(&dynid->node, &drv->dynids.list);
-	spin_unlock(&drv->dynids.lock);
-
-	return driver_attach(&drv->driver);
+	return do_pci_add_dynid(drv, &id, false);
 }
 EXPORT_SYMBOL_GPL(pci_add_dynid);
 
@@ -94,16 +127,20 @@ static void pci_free_dynids(struct pci_driver *drv)
  * do_pci_match_id - See if a PCI ID matches a given pci_id table
  * @ids: array of PCI device ID structures to search in
  * @dev_id: the PCI device ID structure to match against.
+ * @include_override_only: also match against device ID entries marked as
+ *                         override only.
  *
  * Returns the matching pci_device_id structure or %NULL if there is no match.
  */
 static const struct pci_device_id *
 do_pci_match_id(const struct pci_device_id *ids,
-		const struct pci_device_id *dev_id)
+		const struct pci_device_id *dev_id,
+		bool include_override_only)
 {
 	if (ids) {
 		while (ids->vendor || ids->subvendor || ids->class_mask) {
-			if (pci_match_one_id(ids, dev_id))
+			if ((!ids->override_only || include_override_only) &&
+			    pci_match_one_id(ids, dev_id))
 				return ids;
 			ids++;
 		}
@@ -128,7 +165,7 @@ const struct pci_device_id *pci_match_id(const struct pci_device_id *ids,
 {
 	struct pci_device_id dev_id = pci_id_from_device(dev);
 
-	return do_pci_match_id(ids, &dev_id);
+	return do_pci_match_id(ids, &dev_id, true);
 }
 EXPORT_SYMBOL(pci_match_id);
 
@@ -153,7 +190,7 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
 						    struct pci_dev *dev)
 {
 	struct pci_dynid *dynid;
-	const struct pci_device_id *found_id = NULL, *ids;
+	const struct pci_device_id *found_id = NULL;
 	struct pci_device_id dev_id;
 
 	/* When driver_override is set, only bind to the matching driver */
@@ -174,20 +211,9 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
 	if (found_id)
 		return found_id;
 
-	for (ids = drv->id_table; (found_id = do_pci_match_id(ids, &dev_id));
-	     ids = found_id + 1) {
-		/*
-		 * The match table is split based on driver_override.
-		 * In case override_only was set, enforce driver_override
-		 * matching.
-		 */
-		if (found_id->override_only) {
-			if (dev->driver_override)
-				return found_id;
-		} else {
-			return found_id;
-		}
-	}
+	found_id = do_pci_match_id(drv->id_table, &dev_id, !!dev->driver_override);
+	if (found_id)
+		return found_id;
 
 	/* driver_override will always match, send a dummy id */
 	if (dev->driver_override)
@@ -195,11 +221,6 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
 	return NULL;
 }
 
-static void _pci_free_device(struct device *dev)
-{
-	kfree(to_pci_dev(dev));
-}
-
 /**
  * new_id_store - sysfs frontend to pci_add_dynid()
  * @driver: target device driver
@@ -213,38 +234,22 @@ static ssize_t new_id_store(struct device_driver *driver, const char *buf,
 {
 	struct pci_driver *pdrv = to_pci_driver(driver);
 	const struct pci_device_id *ids = pdrv->id_table;
-	u32 vendor, device, subvendor = PCI_ANY_ID,
-		subdevice = PCI_ANY_ID, class = 0, class_mask = 0;
-	unsigned long driver_data = 0;
-	int fields = 0;
+	struct pci_device_id id = {
+		.subvendor = PCI_ANY_ID,
+		.subdevice = PCI_ANY_ID
+	};
+	int fields;
 	int retval = 0;
 
 	fields = sscanf(buf, "%x %x %x %x %x %x %lx",
-			&vendor, &device, &subvendor, &subdevice,
-			&class, &class_mask, &driver_data);
+			&id.vendor, &id.device, &id.subvendor, &id.subdevice,
+			&id.class, &id.class_mask, &id.driver_data);
 	if (fields < 2)
 		return -EINVAL;
 
 	if (fields != 7) {
-		struct pci_dev *pdev = kzalloc(sizeof(*pdev), GFP_KERNEL);
-		if (!pdev)
-			return -ENOMEM;
-
-		pdev->vendor = vendor;
-		pdev->device = device;
-		pdev->subsystem_vendor = subvendor;
-		pdev->subsystem_device = subdevice;
-		pdev->class = class;
-		pdev->dev.release = _pci_free_device;
-
-		device_initialize(&pdev->dev);
-		if (pci_match_device(pdrv, pdev))
-			retval = -EEXIST;
-
-		put_device(&pdev->dev);
-
-		if (retval)
-			return retval;
+		if (do_pci_match_id(pdrv->id_table, &id, false))
+			return -EEXIST;
 	}
 
 	/* Only accept driver_data values that match an existing id_table
@@ -252,7 +257,7 @@ static ssize_t new_id_store(struct device_driver *driver, const char *buf,
 	if (ids) {
 		retval = -EINVAL;
 		while (ids->vendor || ids->subvendor || ids->class_mask) {
-			if (driver_data == ids->driver_data) {
+			if (id.driver_data == ids->driver_data) {
 				retval = 0;
 				break;
 			}
@@ -262,8 +267,7 @@ static ssize_t new_id_store(struct device_driver *driver, const char *buf,
 			return retval;
 	}
 
-	retval = pci_add_dynid(pdrv, vendor, device, subvendor, subdevice,
-			       class, class_mask, driver_data);
+	retval = do_pci_add_dynid(pdrv, &id, fields != 7);
 	if (retval)
 		return retval;
 	return count;
Signature · 1 line
2.53.0
author maintainer
#1616 lore raw

PATCH 1610/1887 PCI: Fix UAF when probe runs concurrent to dyn ID removal

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Gary Guo <[email protected]>

[ Upstream commit 3ffc4c9690c33ee28cdb3d0182b12f9c623e3acc ]

Dynamic IDs are only guaranteed to be valid when dynids.lock is held, as remove_id_store() can free the node. Thus, make a copy in pci_match_device(). Also, clarify that the id parameter is only valid during probe.

---
 drivers/pci/pci-driver.c | 28 +++++++++++++++-------------
 include/linux/pci.h      |  1 +
 2 files changed, 16 insertions(+), 13 deletions(-)
drivers/pci/pci-driver.c +15 -13
index 3a1bb9b43cf78..1fbf42f2c961a 100644
@@ -180,6 +180,7 @@ static const struct pci_device_id pci_device_id_any = {
  * pci_match_device - See if a device matches a driver's list of IDs
  * @drv: the PCI driver to match against
  * @dev: the PCI device structure to match against
+ * @id_copy: place to store copy of pci_device_id for dynamic ID
  *
  * Used by a driver to check whether a PCI device is in its list of
  * supported devices or in the dynids list, which may have been augmented
@@ -187,9 +188,9 @@ static const struct pci_device_id pci_device_id_any = {
  * structure or %NULL if there is no match.
  */
 static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
-						    struct pci_dev *dev)
+						    struct pci_dev *dev,
+						    struct pci_device_id *id_copy)
 {
-	struct pci_dynid *dynid;
 	const struct pci_device_id *found_id = NULL;
 	struct pci_device_id dev_id;
 
@@ -199,17 +200,16 @@ static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
 
 	dev_id = pci_id_from_device(dev);
 	/* Look at the dynamic ids first, before the static ones */
-	spin_lock(&drv->dynids.lock);
-	list_for_each_entry(dynid, &drv->dynids.list, node) {
-		if (pci_match_one_id(&dynid->id, &dev_id)) {
-			found_id = &dynid->id;
-			break;
+	scoped_guard(spinlock, &drv->dynids.lock) {
+		struct pci_dynid *dynid;
+
+		list_for_each_entry(dynid, &drv->dynids.list, node) {
+			if (pci_match_one_id(&dynid->id, &dev_id)) {
+				*id_copy = dynid->id;
+				return id_copy;
+			}
 		}
 	}
-	spin_unlock(&drv->dynids.lock);
-
-	if (found_id)
-		return found_id;
 
 	found_id = do_pci_match_id(drv->id_table, &dev_id, !!dev->driver_override);
 	if (found_id)
@@ -436,12 +436,13 @@ static int pci_call_probe(struct pci_driver *drv, struct pci_dev *dev,
 static int __pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev)
 {
 	const struct pci_device_id *id;
+	struct pci_device_id id_copy;
 	int error = 0;
 
 	if (drv->probe) {
 		error = -ENODEV;
 
-		id = pci_match_device(drv, pci_dev);
+		id = pci_match_device(drv, pci_dev, &id_copy);
 		if (id)
 			error = pci_call_probe(drv, pci_dev, id);
 	}
@@ -1546,12 +1547,13 @@ static int pci_bus_match(struct device *dev, struct device_driver *drv)
 	struct pci_dev *pci_dev = to_pci_dev(dev);
 	struct pci_driver *pci_drv;
 	const struct pci_device_id *found_id;
+	struct pci_device_id id_copy;
 
 	if (!pci_dev->match_driver)
 		return 0;
 
 	pci_drv = to_pci_driver(drv);
-	found_id = pci_match_device(pci_drv, pci_dev);
+	found_id = pci_match_device(pci_drv, pci_dev, &id_copy);
 	if (found_id)
 		return 1;
 
include/linux/pci.h +1 -0
index 31ac4bb31251b..39e9aea18f5d3 100644
@@ -882,6 +882,7 @@ struct module;
  *		function returns zero when the driver chooses to
  *		take "ownership" of the device or an error code
  *		(negative number) otherwise.
+ *		The pci_device_id parameter is only valid during probe.
  *		The probe function always gets called from process
  *		context, so it can sleep.
  * @remove:	The remove() function gets called whenever a device
Signature · 1 line
2.53.0
author maintainer
#1617 lore raw

PATCH 1611/1887 spi: oc-tiny: switch to managed controller allocation

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Fan Wu <[email protected]>

[ Upstream commit d710f43ce30975d197f73c543bfe47b958d8ba17 ]

The controller is allocated with the non-managed spi_alloc_host() while the interrupt is registered with devm_request_irq(). During removal, spi_bitbang_stop() only unregisters the controller; the subsequent spi_controller_put() then frees the controller together with its embedded driver-private devdata, which is the IRQ handler's dev_id. The devm_request_irq() release action (free_irq()), which drains the handler, does not run until after .remove() returns. A late or latched interrupt can therefore reach tiny_spi_irq() and dereference already-freed memory (e.g. hw->base).

Switch to devm_spi_alloc_host() so that the devres LIFO order releases the controller only after free_irq() has drained the handler, and drop the now-redundant spi_controller_put() from .remove(). The probe error path is simplified to direct returns.

This issue was found by an in-house static analysis tool.

Fixes ce792580ea2c ("spi: add OpenCores tiny SPI driver")
Assisted-by Codex:gpt-5.6
---
 drivers/spi/spi-oc-tiny.c | 24 ++++++++----------------
 1 file changed, 8 insertions(+), 16 deletions(-)
drivers/spi/spi-oc-tiny.c +8 -16
index 38c14c4e4e212..e1c984a80ed25 100644
@@ -213,11 +213,11 @@ static int tiny_spi_probe(struct platform_device *pdev)
 	struct tiny_spi_platform_data *platp = dev_get_platdata(&pdev->dev);
 	struct tiny_spi *hw;
 	struct spi_master *master;
-	int err = -ENODEV;
+	int err;
 
-	master = spi_alloc_master(&pdev->dev, sizeof(struct tiny_spi));
+	master = devm_spi_alloc_master(&pdev->dev, sizeof(struct tiny_spi));
 	if (!master)
-		return err;
+		return -ENOMEM;
 
 	/* setup the master state. */
 	master->bus_num = pdev->id;
@@ -235,10 +235,8 @@ static int tiny_spi_probe(struct platform_device *pdev)
 
 	/* find and map our resources */
 	hw->base = devm_platform_ioremap_resource(pdev, 0);
-	if (IS_ERR(hw->base)) {
-		err = PTR_ERR(hw->base);
-		goto exit;
-	}
+	if (IS_ERR(hw->base))
+		return PTR_ERR(hw->base);
 	/* irq is optional */
 	hw->irq = platform_get_irq(pdev, 0);
 	if (hw->irq >= 0) {
@@ -246,7 +244,7 @@ static int tiny_spi_probe(struct platform_device *pdev)
 		err = devm_request_irq(&pdev->dev, hw->irq, tiny_spi_irq, 0,
 				       pdev->name, hw);
 		if (err)
-			goto exit;
+			return err;
 	}
 	/* find platform data */
 	if (platp) {
@@ -255,29 +253,23 @@ static int tiny_spi_probe(struct platform_device *pdev)
 	} else {
 		err = tiny_spi_of_probe(pdev);
 		if (err)
-			goto exit;
+			return err;
 	}
 
 	/* register our spi controller */
 	err = spi_bitbang_start(&hw->bitbang);
 	if (err)
-		goto exit;
+		return err;
 	dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);
 
 	return 0;
-
-exit:
-	spi_master_put(master);
-	return err;
 }
 
 static int tiny_spi_remove(struct platform_device *pdev)
 {
 	struct tiny_spi *hw = platform_get_drvdata(pdev);
-	struct spi_master *master = hw->bitbang.master;
 
 	spi_bitbang_stop(&hw->bitbang);
-	spi_master_put(master);
 	return 0;
 }
 
Signature · 1 line
2.53.0
author maintainer
#1618 lore raw

PATCH 1612/1887 scsi: ufs: core: Validate string descriptors

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Li Qiang <[email protected]>

[ Upstream commit d96e83d028d7d8762e424e49c671d49ac2ecf14f ]

The string descriptor length includes a two-byte header while the UTF-16 payload starts after it. utf16s_to_utf8s() expects a count of UTF-16 code units, not bytes. Passing the payload byte count can make it read beyond the descriptor buffer.

Validate that the payload has an even byte count, pass a code-unit count to the converter, and allocate sufficient UTF-8 output space.

The raw string buffer starts after the descriptor header but its size is bLength. Copying bLength bytes from that pointer can read beyond the response buffer.

Allocate a zeroed bLength-sized buffer and copy only the UTF-16 payload. This preserves the raw buffer size consumed by the RPMB device-ID ABI while avoiding the overread.

Fixes 4b828fe156a6 ("scsi: ufs: revamp string descriptor reading")
Fixes d794b499f948 ("scsi: ufs: core: fix incorrect buffer duplication in ufshcd_read_string_desc()")
---
 drivers/ufs/core/ufshcd.c | 24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)
drivers/ufs/core/ufshcd.c +19 -5
index 0e9ea032711b9..f441fc0903a8d 100644
@@ -3530,7 +3530,7 @@ int ufshcd_read_string_desc(struct ufs_hba *hba, u8 desc_index,
 {
 	struct uc_string_id *uc_str;
 	u8 *str;
-	int ret;
+	int ret, uc_len;
 
 	if (!buf)
 		return -EINVAL;
@@ -3555,11 +3555,19 @@ int ufshcd_read_string_desc(struct ufs_hba *hba, u8 desc_index,
 		goto out;
 	}
 
+	uc_len = uc_str->len - QUERY_DESC_HDR_SIZE;
+	if (uc_len % sizeof(*uc_str->uc)) {
+		dev_err(hba->dev, "String Desc has an odd UTF-16 payload length\n");
+		str = NULL;
+		ret = -EINVAL;
+		goto out;
+	}
+
 	if (ascii) {
 		ssize_t ascii_len;
 		int i;
-		/* remove header and divide by 2 to move from UTF16 to UTF8 */
-		ascii_len = (uc_str->len - QUERY_DESC_HDR_SIZE) / 2 + 1;
+		/* Allow up to three UTF-8 bytes per UTF-16 code unit plus a NUL. */
+		ascii_len = uc_len / sizeof(*uc_str->uc) * 3 + 1;
 		str = kzalloc(ascii_len, GFP_KERNEL);
 		if (!str) {
 			ret = -ENOMEM;
@@ -3571,7 +3579,7 @@ int ufshcd_read_string_desc(struct ufs_hba *hba, u8 desc_index,
 		 * we need to convert to utf-8 so it can be displayed
 		 */
 		ret = utf16s_to_utf8s(uc_str->uc,
-				      uc_str->len - QUERY_DESC_HDR_SIZE,
+				      uc_len / sizeof(*uc_str->uc),
 				      UTF16_BIG_ENDIAN, str, ascii_len - 1);
 
 		/* replace non-printable or non-ASCII characters with spaces */
@@ -3581,11 +3589,17 @@ int ufshcd_read_string_desc(struct ufs_hba *hba, u8 desc_index,
 		str[ret++] = '\0';
 
 	} else {
-		str = kmemdup(uc_str->uc, uc_str->len, GFP_KERNEL);
+		/*
+		 * Keep the bLength-sized raw output for the RPMB device ID ABI.
+		 * The two bytes beyond the UTF-16 payload are explicitly zeroed
+		 * instead of being read past the descriptor buffer.
+		 */
+		str = kzalloc(uc_str->len, GFP_KERNEL);
 		if (!str) {
 			ret = -ENOMEM;
 			goto out;
 		}
+		memcpy(str, uc_str->uc, uc_len);
 		ret = uc_str->len;
 	}
 out:
Signature · 1 line
2.53.0
author maintainer
#1619 lore raw

PATCH 1613/1887 pinctrl: mediatek: use devm_gpiochip_add_data() for GPIO chip

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Justin Yeh <[email protected]>

[ Upstream commit 9c650317ba553d297f3fc5f0ae40ec53d86f9dab ]

The gpio_chip is allocated with device-managed memory but registered with the non-managed gpiochip_add_data(). This was harmless while the drivers were built-in, but once they can be built as modules and unbound/rmmod'd, devm frees the gpio_chip's memory while it is still registered, causing a use-after-free.

Register it with devm_gpiochip_add_data() so it shares the same device-managed lifecycle, which also lets the manual gpiochip_remove() error paths go away.

Fixes a6df410d420a ("pinctrl: mediatek: Add Pinctrl/GPIO driver for mt8135.")
Fixes 805250982bb5 ("pinctrl: mediatek: add pinctrl-paris that implements the vendor dt-bindings")
Fixes e78d57b2f87c ("pinctrl: mediatek: add pinctrl-moore that implements the generic pinctrl dt-bindings")
---
 drivers/pinctrl/mediatek/pinctrl-moore.c      |  6 ++----
 drivers/pinctrl/mediatek/pinctrl-mtk-common.c | 14 ++++----------
 drivers/pinctrl/mediatek/pinctrl-paris.c      |  2 +-
 3 files changed, 7 insertions(+), 15 deletions(-)
drivers/pinctrl/mediatek/pinctrl-moore.c +2 -4
index 319da66d4f329..e91987d33fc65 100644
@@ -538,7 +538,7 @@ static int mtk_build_gpiochip(struct mtk_pinctrl *hw)
 	chip->ngpio		= hw->soc->npins;
 	chip->of_gpio_n_cells	= 2;
 
-	ret = gpiochip_add_data(chip, hw);
+	ret = devm_gpiochip_add_data(hw->dev, chip, hw);
 	if (ret < 0)
 		return ret;
 
@@ -552,10 +552,8 @@ static int mtk_build_gpiochip(struct mtk_pinctrl *hw)
 	if (!of_find_property(hw->dev->of_node, "gpio-ranges", NULL)) {
 		ret = gpiochip_add_pin_range(chip, dev_name(hw->dev), 0, 0,
 					     chip->ngpio);
-		if (ret < 0) {
-			gpiochip_remove(chip);
+		if (ret < 0)
 			return ret;
-		}
 	}
 
 	return 0;
drivers/pinctrl/mediatek/pinctrl-mtk-common.c +4 -10
index fe44867b4c289..1dada2b64b9e2 100644
@@ -1121,30 +1121,24 @@ int mtk_pctrl_init(struct platform_device *pdev,
 	pctl->chip->parent = &pdev->dev;
 	pctl->chip->base = -1;
 
-	ret = gpiochip_add_data(pctl->chip, pctl);
+	ret = devm_gpiochip_add_data(&pdev->dev, pctl->chip, pctl);
 	if (ret)
 		return -EINVAL;
 
 	/* Register the GPIO to pin mappings. */
 	ret = gpiochip_add_pin_range(pctl->chip, dev_name(&pdev->dev),
 			0, 0, pctl->devdata->npins);
-	if (ret) {
-		ret = -EINVAL;
-		goto chip_error;
-	}
+	if (ret)
+		return -EINVAL;
 
 	/* Only initialize EINT if we have EINT pins */
 	if (data->eint_hw.ap_num > 0) {
 		ret = mtk_eint_init(pctl, pdev);
 		if (ret)
-			goto chip_error;
+			return ret;
 	}
 
 	return 0;
-
-chip_error:
-	gpiochip_remove(pctl->chip);
-	return ret;
 }
 
 int mtk_pctrl_common_probe(struct platform_device *pdev)
drivers/pinctrl/mediatek/pinctrl-paris.c +1 -1
index ab1c34e5a582e..464bedc0fe454 100644
@@ -970,7 +970,7 @@ static int mtk_build_gpiochip(struct mtk_pinctrl *hw)
 	chip->ngpio		= hw->soc->npins;
 	chip->of_gpio_n_cells	= 2;
 
-	ret = gpiochip_add_data(chip, hw);
+	ret = devm_gpiochip_add_data(hw->dev, chip, hw);
 	if (ret < 0)
 		return ret;
 
Signature · 1 line
2.53.0
author maintainer
#1620 lore raw

PATCH 1614/1887 bpf: Fix WARNING in bpf_tracing_link_release

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Leon Hwang <[email protected]>

[ Upstream commit 61aaa8782bec59ecffd22e030f54ef9351bcabf9 ]

The trampoline could be corrupted by the blindly 'tr->flags = BPF_TRAMP_F_TAIL_CALL_CTX' in verifier.

1. A fexit attached to a tail_call_reachable prog. 'tr->flags' became 'BPF_TRAMP_F_CALL_ORIG | BPF_TRAMP_F_TAIL_CALL_CTX'. And, the trampoline would poke the target prog's nop insn using jmp insn instead of call insn.
2. Another fexit loaded with the same tail_call_reachable prog target. 'tr->flags' became 'BPF_TRAMP_F_TAIL_CALL_CTX'.
3. Close the first fexit link. Due to no BPF_TRAMP_F_CALL_ORIG in 'tr->flags', the trampoline will fail to restore the prog's nop insn using call insn.

[    3.410719] WARNING: kernel/bpf/syscall.c:3551 at bpf_tracing_link_release+0x53/0x60, CPU#1: test_progs/98
...
[    3.428793]  bpf_link_free+0x58/0x130
[    3.429293]  bpf_link_release+0x23/0x30

Fix the warning by updating 'tr->flags' with '|=' and lock.

---
 include/linux/bpf.h     | 3 +++
 kernel/bpf/trampoline.c | 7 +++++++
 kernel/bpf/verifier.c   | 2 +-
 3 files changed, 11 insertions(+), 1 deletion(-)
include/linux/bpf.h +3 -0
index 546d40d82edbc..7af010d1830d0 100644
@@ -1025,6 +1025,8 @@ struct bpf_trampoline *bpf_trampoline_get(u64 key,
 void bpf_trampoline_put(struct bpf_trampoline *tr);
 int arch_prepare_bpf_dispatcher(void *image, void *buf, s64 *funcs, int num_funcs);
 
+void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags);
+
 /*
  * When the architecture supports STATIC_CALL replace the bpf_dispatcher_fn
  * indirection with a direct call to the bpf program. If the architecture does
@@ -1132,6 +1134,7 @@ static inline bool bpf_prog_has_trampoline(const struct bpf_prog *prog)
 {
 	return false;
 }
+static inline void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags) {}
 #endif
 
 struct bpf_func_info_aux {
kernel/bpf/trampoline.c +7 -0
index 930914f628c7a..3473e86a112a0 100644
@@ -418,6 +418,13 @@ static struct bpf_tramp_image *bpf_tramp_image_alloc(u64 key)
 	return ERR_PTR(err);
 }
 
+void bpf_trampoline_set_flags(struct bpf_trampoline *tr, u32 flags)
+{
+	mutex_lock(&tr->mutex);
+	tr->flags |= flags;
+	mutex_unlock(&tr->mutex);
+}
+
 static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mutex)
 {
 	struct bpf_tramp_image *im;
kernel/bpf/verifier.c +1 -1
index 1ba6ce6765e0c..b79581a941cd9 100644
@@ -15493,7 +15493,7 @@ static int check_attach_btf_id(struct bpf_verifier_env *env)
 		return -ENOMEM;
 
 	if (tgt_prog && tgt_prog->aux->tail_call_reachable)
-		tr->flags = BPF_TRAMP_F_TAIL_CALL_CTX;
+		bpf_trampoline_set_flags(tr, BPF_TRAMP_F_TAIL_CALL_CTX);
 
 	prog->aux->dst_trampoline = tr;
 	return 0;
Signature · 1 line
2.53.0
author maintainer
#1621 lore raw

PATCH 1615/1887 RDMA/erdma: complete object teardown when the destroy command fails

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Leon Romanovsky <[email protected]>

[ Upstream commit 652befcba956ef357f480525ccbe25c59bc81d4d ]

erdma_destroy_qp(), erdma_destroy_cq(), erdma_dereg_mr(), and erdma_destroy_ah() returned early when erdma_post_cmd_wait() failed, leaking the queue buffers, MTTs, doorbells and the STAG, QPN, CQN and AHN identifiers. A command timeout clears ERDMA_CMDQ_STATE_OK_BIT and permanently disables the command queue, so no retry can succeed; the RDMA core keeps the object after a failed destructor and forced uverbs cleanup then nulls the pointers, making the resources unreachable.

Warn on failure but release every software-owned resource and return success, since during terminal destruction the hardware command result is only diagnostic.

---
 drivers/infiniband/hw/erdma/erdma_verbs.c | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)
drivers/infiniband/hw/erdma/erdma_verbs.c +14 -3
index d8f21f9d9101c..2ddc56335b9a6 100644
@@ -971,8 +971,15 @@ int erdma_dereg_mr(struct ib_mr *ibmr, struct ib_udata *udata)
 		  FIELD_PREP(ERDMA_CMD_MR_KEY_MASK, ibmr->lkey & 0xFF);
 
 	ret = erdma_post_cmd_wait(&dev->cmdq, &req, sizeof(req), NULL, NULL);
+	/*
+	 * A timeout disables the command queue, so retry cannot succeed.  Treat
+	 * terminal command failures as diagnostic; propagating them can make
+	 * forced uverbs cleanup discard the last software resource pointers.
+	 */
 	if (ret)
-		return ret;
+		ibdev_warn_ratelimited(&dev->ibdev,
+				       "failed to deregister MR 0x%x: %d\n",
+				       ibmr->lkey, ret);
 
 	erdma_free_idx(&dev->res_cb[ERDMA_RES_TYPE_STAG_IDX], ibmr->lkey >> 8);
 
@@ -998,7 +1005,9 @@ int erdma_destroy_cq(struct ib_cq *ibcq, struct ib_udata *udata)
 
 	err = erdma_post_cmd_wait(&dev->cmdq, &req, sizeof(req), NULL, NULL);
 	if (err)
-		return err;
+		ibdev_warn_ratelimited(&dev->ibdev,
+				       "failed to destroy CQ %u: %d\n",
+				       cq->cqn, err);
 
 	xa_lock_irqsave(&dev->cq_xa, flags);
 	__xa_erase(&dev->cq_xa, cq->cqn);
@@ -1041,7 +1050,9 @@ int erdma_destroy_qp(struct ib_qp *ibqp, struct ib_udata *udata)
 
 	err = erdma_post_cmd_wait(&dev->cmdq, &req, sizeof(req), NULL, NULL);
 	if (err)
-		return err;
+		ibdev_warn_ratelimited(&dev->ibdev,
+				       "failed to destroy QP %u: %d\n",
+				       QP_ID(qp), err);
 
 	xa_lock_irqsave(&dev->qp_xa, flags);
 	__xa_erase(&dev->qp_xa, QP_ID(qp));
Signature · 1 line
2.53.0
author maintainer
#1622 lore raw

PATCH 1616/1887 ext4: fix ABBA deadlock in ext4_xattr_inode_cache_find()

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Aditya Prakash Srivastava <[email protected]>

[ Upstream commit 03438084a7b8621fb5c762dd3d04cff5f2630fb2 ]

Syzbot/stress-ng reported an ABBA deadlock in ext4 when exercising concurrent xattr workloads (using the ea_inode mount/format option).

The deadlock occurs between the running transaction and the eviction thread:
- Task 1 (stress-ng): Holds a reference to a shared mbcache_entry (ce) and calls ext4_xattr_inode_cache_find() -> ext4_iget() to retrieve the corresponding EA inode. Since the EA inode is currently being evicted, ext4_iget() blocks in __wait_on_freeing_inode() waiting for eviction to complete.
- Task 2 (eviction thread): Currently evicting the same EA inode in ext4_evict_ea_inode(). It calls mb_cache_entry_wait_unused(oe) which blocks waiting for Task 1 to release the reference to the mbcache_entry.

To break this deadlock, implement a new ext4_iget() configuration flag named EXT4_IGET_NOWAIT. When set, perform a non-blocking lookup of the inode via VFS's find_inode_nowait() API.

If the inode is currently being evicted (marked with I_FREEING or I_WILL_FREE) or created (I_CREATING), or if it is not present in the VFS inode cache (cache miss), simply skip it (returning -ENOENT) rather than waiting for eviction/creation to complete, breaking the ABBA cycle.

Since we return -ENOENT immediately on a cache miss, we never attempt to allocate a new inode or call iget_locked(), completely eliminating any TOCTOU race window.

If the returned inode is I_NEW, wait for its initialization to clear via wait_on_new_inode(). If initialization fails and the inode is unhashed during wait_on_new_inode() waking up (e.g., due to an I/O read error in another thread), safely drop the reference and return -ENOENT. This unhashed check is executed unconditionally on all cache-hit pathways to properly handle concurrent initialization failures.

Finally, standard validation checks (including is_bad_inode, EXT4_EA_INODE_FL, file_acl, and xattr flags) are executed as normal inside check_igot_inode() to fully guarantee VFS-layer safety.

In ext4_xattr_inode_cache_find(), invoke ext4_iget() with the new EXT4_IGET_NOWAIT flag to perform the non-blocking cache search.

---
 fs/ext4/ext4.h  |  3 ++-
 fs/ext4/inode.c | 36 +++++++++++++++++++++++++++++++++---
 fs/ext4/xattr.c |  2 +-
 fs/inode.c      |  1 +
 4 files changed, 37 insertions(+), 5 deletions(-)
fs/ext4/ext4.h +2 -1
index 6d3f888f7e556..5ef489c9e73af 100644
@@ -3008,7 +3008,8 @@ typedef enum {
 	EXT4_IGET_SPECIAL =	0x0001, /* OK to iget a system inode */
 	EXT4_IGET_HANDLE = 	0x0002,	/* Inode # is from a handle */
 	EXT4_IGET_BAD =		0x0004, /* Allow to iget a bad inode */
-	EXT4_IGET_EA_INODE =	0x0008	/* Inode should contain an EA value */
+	EXT4_IGET_EA_INODE =	0x0008,	/* Inode should contain an EA value */
+	EXT4_IGET_NOWAIT =	0x0010	/* Non-blocking lookup (skip if freeing) */
 } ext4_iget_flags;
 
 extern struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
fs/ext4/inode.c +33 -3
index 25b3844073d44..2739c9a04c8f8 100644
@@ -4837,6 +4837,21 @@ static int check_igot_inode(struct inode *inode, ext4_iget_flags flags,
 	return -EFSCORRUPTED;
 }
 
+static int ext4_iget_match(struct inode *inode, unsigned long ino,
+			   void *data)
+{
+	if (inode->i_ino != ino)
+		return 0;
+	spin_lock(&inode->i_lock);
+	if (inode->i_state & (I_FREEING | I_WILL_FREE | I_CREATING)) {
+		spin_unlock(&inode->i_lock);
+		return -1;
+	}
+	__iget(inode);
+	spin_unlock(&inode->i_lock);
+	return 1;
+}
+
 struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
 			  ext4_iget_flags flags, const char *function,
 			  unsigned int line)
@@ -4870,9 +4885,24 @@ struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
 		return ERR_PTR(-EFSCORRUPTED);
 	}
 
-	inode = iget_locked(sb, ino);
-	if (!inode)
-		return ERR_PTR(-ENOMEM);
+	if (flags & EXT4_IGET_NOWAIT) {
+		inode = find_inode_nowait(sb, ino, ext4_iget_match, NULL);
+		if (!inode)
+			return ERR_PTR(-ENOENT);
+
+		if (inode->i_state & I_NEW)
+			wait_on_inode(inode);
+
+		if (unlikely(inode_unhashed(inode))) {
+			iput(inode);
+			return ERR_PTR(-ENOENT);
+		}
+	} else {
+		inode = iget_locked(sb, ino);
+		if (!inode)
+			return ERR_PTR(-ENOMEM);
+	}
+
 	if (!(inode->i_state & I_NEW)) {
 		ret = check_igot_inode(inode, flags, function, line);
 		if (ret) {
fs/ext4/xattr.c +1 -1
index a944028502f25..e66275a5d83ff 100644
@@ -1497,7 +1497,7 @@ ext4_xattr_inode_cache_find(struct inode *inode, const void *value,
 
 	while (ce) {
 		ea_inode = ext4_iget(inode->i_sb, ce->e_value,
-				     EXT4_IGET_EA_INODE);
+				     EXT4_IGET_EA_INODE | EXT4_IGET_NOWAIT);
 		if (IS_ERR(ea_inode))
 			goto next_entry;
 		ext4_xattr_inode_set_class(ea_inode);
fs/inode.c +1 -0
index 5c46e7bb931db..99e980ddbdceb 100644
@@ -443,6 +443,7 @@ void __iget(struct inode *inode)
 {
 	atomic_inc(&inode->i_count);
 }
+EXPORT_SYMBOL(__iget);
 
 /*
  * get additional reference to inode; caller must already hold one.
Signature · 1 line
2.53.0
author maintainer
#1623 lore raw

PATCH 1617/1887 bpf: Zero queue and stack outputs on lock failure

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Kumar Kartikeya Dwivedi <[email protected]>

[ Upstream commit 7ac6e1ae41a09f1dd4baeeff1d028ae49ee01232 ]

Queue and stack pop/peek helpers accept an uninitialized output buffer because the verifier expects the helper to initialize it. The empty-map error path clears the buffer, but a failed lock acquisition returns -EBUSY without writing it.

Clear the output before returning -EBUSY so BPF programs cannot observe uninitialized stack contents after a failed helper call.

Fixes a34a9f1a19af ("bpf: Avoid deadlock when using queue and stack maps from NMI")
---
 kernel/bpf/queue_stack_maps.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)
kernel/bpf/queue_stack_maps.c +6 -2
index a8fe640318c6c..a088132163b1e 100644
@@ -103,8 +103,10 @@ static int __queue_map_get(struct bpf_map *map, void *value, bool delete)
 	void *ptr;
 
 	if (in_nmi()) {
-		if (!raw_spin_trylock_irqsave(&qs->lock, flags))
+		if (!raw_spin_trylock_irqsave(&qs->lock, flags)) {
+			memset(value, 0, qs->map.value_size);
 			return -EBUSY;
+		}
 	} else {
 		raw_spin_lock_irqsave(&qs->lock, flags);
 	}
@@ -138,8 +140,10 @@ static int __stack_map_get(struct bpf_map *map, void *value, bool delete)
 	u32 index;
 
 	if (in_nmi()) {
-		if (!raw_spin_trylock_irqsave(&qs->lock, flags))
+		if (!raw_spin_trylock_irqsave(&qs->lock, flags)) {
+			memset(value, 0, qs->map.value_size);
 			return -EBUSY;
+		}
 	} else {
 		raw_spin_lock_irqsave(&qs->lock, flags);
 	}
Signature · 1 line
2.53.0
author maintainer
#1624 lore raw

PATCH 1618/1887 dmaengine: dw-edma: Terminate all descriptors without callbacks

In reply to Greg Kroah-Hartman

6.1-stable review patch. If anyone has any objections, please let me know.

------------------

From: Koichiro Den <[email protected]>

[ Upstream commit 99109a51efd28c9a661fbfb9469b023c517b31d1 ]

The DMA Engine client documentation says in the "Terminate APIs" section of Documentation/driver-api/dmaengine/client.rst:

"No callback functions will be called for any incomplete transfers."

dw-edma instead calls vchan_cookie_complete() when a deferred STOP reaches the interrupt handler. This schedules a callback for the active descriptor and leaves other issued or submitted descriptors queued. A late callback after dmaengine_terminate_sync() can dereference client state that has already been freed, while leftover descriptors may later restart into reused buffers or leak.

Move all issued and submitted descriptors to the terminated list whenever termination completes. For a pending STOP, do this from both the DONE and ABORT paths. Complete their cookies in order without scheduling callbacks.

A STOP can remain pending until the running transfer raises an interrupt. Make device_synchronize() wait for such a pending STOP to complete before releasing terminated descriptors. Reuse it from free_chan_resources(), then release the remaining virt-dma resources. Sleep instead of busy-polling while waiting, and warn if the existing timeout expires.

Fixes e63d79d1ffcd ("dmaengine: Add Synopsys eDMA IP core driver")
---
 drivers/dma/dw-edma/dw-edma-core.c | 90 +++++++++++++++++++++++++-----
 1 file changed, 76 insertions(+), 14 deletions(-)
drivers/dma/dw-edma/dw-edma-core.c +76 -14
index e68cb0ea76977..d46d3f89f9963 100644
@@ -7,6 +7,7 @@
  */
 
 #include <linux/module.h>
+#include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/kernel.h>
 #include <linux/dmaengine.h>
@@ -199,6 +200,35 @@ static int dw_edma_start_transfer(struct dw_edma_chan *chan)
 	return 1;
 }
 
+static void dw_edma_terminate_vdesc(struct virt_dma_desc *vd)
+{
+	list_del(&vd->node);
+	dma_cookie_complete(&vd->tx);
+	vchan_terminate_vdesc(vd);
+}
+
+static void dw_edma_terminate_vdesc_list(struct list_head *head)
+{
+	struct virt_dma_desc *vd, *_vd;
+
+	list_for_each_entry_safe(vd, _vd, head, node)
+		dw_edma_terminate_vdesc(vd);
+}
+
+/* Must be called with vc.lock held. */
+static void dw_edma_terminate_all_descs(struct dw_edma_chan *chan)
+{
+	/*
+	 * This order must not be reversed. Cookies are assigned when
+	 * descriptors are submitted, so desc_issued contains older cookies
+	 * than desc_submitted. Completing desc_submitted first could move
+	 * chan->vc.chan.completed_cookie backwards when desc_issued is
+	 * terminated afterwards.
+	 */
+	dw_edma_terminate_vdesc_list(&chan->vc.desc_issued);
+	dw_edma_terminate_vdesc_list(&chan->vc.desc_submitted);
+}
+
 static int dw_edma_device_config(struct dma_chan *dchan,
 				 struct dma_slave_config *config)
 {
@@ -256,20 +286,22 @@ static int dw_edma_device_terminate_all(struct dma_chan *dchan)
 	struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
 	int err = 0;
 
+	guard(spinlock_irqsave)(&chan->vc.lock);
+
 	if (!chan->configured) {
-		/* Do nothing */
+		dw_edma_terminate_all_descs(chan);
 	} else if (chan->status == EDMA_ST_PAUSE) {
+		dw_edma_terminate_all_descs(chan);
 		chan->status = EDMA_ST_IDLE;
-		chan->configured = false;
 	} else if (chan->status == EDMA_ST_IDLE) {
-		chan->configured = false;
+		dw_edma_terminate_all_descs(chan);
 	} else if (dw_edma_v0_core_ch_status(chan) == DMA_COMPLETE) {
 		/*
 		 * The channel is in a false BUSY state, probably didn't
 		 * receive or lost an interrupt
 		 */
+		dw_edma_terminate_all_descs(chan);
 		chan->status = EDMA_ST_IDLE;
-		chan->configured = false;
 	} else if (chan->request > EDMA_REQ_PAUSE) {
 		err = -EPERM;
 	} else {
@@ -626,8 +658,7 @@ static void dw_edma_done_interrupt(struct dw_edma_chan *chan)
 			break;
 
 		case EDMA_REQ_STOP:
-			list_del(&vd->node);
-			vchan_cookie_complete(vd);
+			dw_edma_terminate_all_descs(chan);
 			chan->request = EDMA_REQ_NONE;
 			chan->status = EDMA_ST_IDLE;
 			break;
@@ -648,7 +679,9 @@ static void dw_edma_abort_interrupt(struct dw_edma_chan *chan)
 
 	spin_lock_irqsave(&chan->vc.lock, flags);
 	vd = vchan_next_desc(&chan->vc);
-	if (vd) {
+	if (vd && chan->request == EDMA_REQ_STOP) {
+		dw_edma_terminate_all_descs(chan);
+	} else if (vd) {
 		dw_hdma_set_callback_result(vd, DMA_TRANS_ABORTED);
 		list_del(&vd->node);
 		vchan_cookie_complete(vd);
@@ -727,21 +760,49 @@ static int dw_edma_alloc_chan_resources(struct dma_chan *dchan)
 	return 0;
 }
 
-static void dw_edma_free_chan_resources(struct dma_chan *dchan)
+static void dw_edma_wait_termination(struct dma_chan *dchan)
 {
+	struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
 	unsigned long timeout = jiffies + msecs_to_jiffies(5000);
-	int ret;
+	bool stopping;
 
+	/*
+	 * A STOP may be deferred to a later interrupt while the channel is still
+	 * running. Wait until that handler completes the termination.
+	 */
 	while (time_before(jiffies, timeout)) {
-		ret = dw_edma_device_terminate_all(dchan);
-		if (!ret)
-			break;
+		scoped_guard(spinlock_irqsave, &chan->vc.lock)
+			stopping = chan->request == EDMA_REQ_STOP;
 
-		if (time_after_eq(jiffies, timeout))
+		if (!stopping)
 			return;
 
-		cpu_relax();
+		fsleep(1000);
 	}
+
+	dev_warn(chan->dw->chip->dev,
+		 "timeout waiting for channel termination\n");
+}
+
+static void dw_edma_device_synchronize(struct dma_chan *dchan)
+{
+	struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+
+	dw_edma_wait_termination(dchan);
+	vchan_synchronize(&chan->vc);
+}
+
+static void dw_edma_free_chan_resources(struct dma_chan *dchan)
+{
+	struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan);
+
+	dw_edma_device_terminate_all(dchan);
+	dw_edma_device_synchronize(dchan);
+
+	scoped_guard(spinlock_irqsave, &chan->vc.lock)
+		chan->configured = false;
+
+	vchan_free_chan_resources(&chan->vc);
 }
 
 static int dw_edma_channel_setup(struct dw_edma *dw, bool write,
@@ -853,6 +914,7 @@ static int dw_edma_channel_setup(struct dw_edma *dw, bool write,
 	dma->device_pause = dw_edma_device_pause;
 	dma->device_resume = dw_edma_device_resume;
 	dma->device_terminate_all = dw_edma_device_terminate_all;
+	dma->device_synchronize = dw_edma_device_synchronize;
 	dma->device_issue_pending = dw_edma_device_issue_pending;
 	dma->device_tx_status = dw_edma_device_tx_status;
 	dma->device_prep_slave_sg = dw_edma_device_prep_slave_sg;
Signature · 1 line
2.53.0