From: Dave Jiang <dave.jiang@intel.com>
To: Li Ming <ming.li@zohomail.com>, linux-cxl@vger.kernel.org
Cc: dave@stgolabs.net, jic23@kernel.org, alison.schofield@intel.com,
icheng@nvidia.com
Subject: Re: [PATCH v2 2/2] cxl/core: Hold the dport host lock across dport lookup and use
Date: Tue, 29 Sep 2026 08:57:22 -0700 [thread overview]
Message-ID: <0731555d-21b3-49ac-bd25-d2b40e675e69@intel.com> (raw)
In-Reply-To: <0c68324c-cfc7-46b2-9344-24ae40e88923@zohomail.com>
On 9/29/26 2:21 AM, Li Ming wrote:
>
> 在 2026/9/29 07:03, Dave Jiang 写道:
>> Callers of cxl_pci_find_port() and cxl_mem_find_port() can be handed a
>> 'struct cxl_dport' that is already freed, and all four dereference it
>> without the lock that would prevent that.
>>
>> A dport is kzalloc()'d into a devres group on its host device, so it dies
>> when that host unbinds. The host is &port->dev for a switch port and
>> port->uport_dev for the root port.
>
> In unbinding case, seems like CXL driver needs to check port's driver before accessing a dport of the port. I guess the dport in devm_cxl_enumerate_ports() and add_port_attach_ep() also needs to be protected by port's lock? Like cxl_add_ep() in devm_cxl_enumerate_ports(), it is invoked without port's lock, the dport could be freed during the function calling.
I'll take a look.
>
> BTW, just wondering whether adding a dedicated "struct device" to dport makes more sense? use get_device()/put_device() to prevent UAF of dports.
Do you think using the dport->dport_dev is not sufficient? I fear how much messier the whole thing would be if we add a 'struct device' to dport now.
DJ
>
>>
>> Both helpers return the dport through an output parameter. The caller's
>> port reference pins struct cxl_port, not the dport allocation, and the
>> lookup itself is a bare xa_load() under no lock.
>>
>> Add cxl_port_dport_host() to name the device that pins a dport, and
>> cxl_pci_find_dport() / cxl_mem_find_dport() to look one up with that
>> device's lock held and asserted. Convert the four callers to use them and
>> bail if the dport is gone. cxl_port_add_dport() already follows this rule.
>>
>> cxl_mem_probe() chose its endpoint devm host from dport->rch, which meant
>> dereferencing the dport before taking any lock. Use cxl_port_dport_host():
>> rch dports only ever land on the root port, so it selects the same device
>> without needing the dport.
>>
>> Fixes: 733b57f262b0 ("cxl/pci: Early setup RCH dport component registers from RCRB")
>> Signed-off-by: Dave Jiang <dave.jiang@intel.com>
>> Assisted-by: LLM
>> ---
>> v2:
>> - Reword the cxl_port_dport_host() comment. (Jonathan)
>> - Drop the note on stale cxlsd->target[] pointers, fixed by patch 1. (Jonathan)
>>
>> Found by review, not from a failure report.
>>
>> Untested: two of the four converted sites add a new lock acquisition, and
>> neither executed under test.
>>
>> cxl_pci_setup_regs() new guard() sits behind is_cxl_restricted(),
>> which needs a PCI_EXP_TYPE_RC_END device
>> cxl_handle_rdport_errors() needs an RCH dport AER error; cxl_test has no
>> injection path
>>
>> Only cxl_pci_setup_regs() introduces a lock order lockdep has not already
>> seen: device_lock(&pdev->dev) from probe, then the dport host lock.
>>
>> cxl_handle_rdport_errors() nests its new host lock inside the
>> device_lock(&cxlmd->dev) that core/ras.c:274 and :298 hold, but
>> cxl_mem_probe() takes that same pair in that same order and ran clean under
>> PROVE_LOCKING, so the edge itself is covered. What is untested there is the
>> path, not the ordering.
>>
>> Reaching either needs a cxl-type3 attached directly to a host bridge so
>> pci_pcie_type() is PCI_EXP_TYPE_RC_END, which run_qemu.sh does not generate.
>>
>> Tested in QEMU with cxl_test, PROVE_LOCKING and KASAN on, 8 module
>> load/unload cycles with concurrent sysfs readers. No lockdep splat, no
>> device_lock_assert() firing, enumeration unchanged (15 memdev, 12 port,
>> 192 decoder before and after).
>>
>> cxl_mem_find_dport() ran for 15 memdevs on each of 9 loads, covering both
>> cxl_port_dport_host() branches. The rch branch is covered too: "cxl_mem
>> mem2: endpoint9 added to root3" shows the new selection picking the same
>> parent dport->rch did.
>> ---
>> drivers/cxl/core/core.h | 6 +-----
>> drivers/cxl/core/pci.c | 8 +++++++-
>> drivers/cxl/core/port.c | 36 ++++++++++++++++++++++++++++++++++++
>> drivers/cxl/core/ras_rch.c | 12 +++++++++++-
>> drivers/cxl/cxl.h | 16 ++++++++++++++++
>> drivers/cxl/mem.c | 14 +++++++++-----
>> drivers/cxl/pci.c | 11 ++++++-----
>> 7 files changed, 86 insertions(+), 17 deletions(-)
>>
>> diff --git a/drivers/cxl/core/core.h b/drivers/cxl/core/core.h
>> index 35eaf636adc9..0a39e500029a 100644
>> --- a/drivers/cxl/core/core.h
>> +++ b/drivers/cxl/core/core.h
>> @@ -178,11 +178,7 @@ static inline struct device *port_to_host(struct cxl_port *port)
>> static inline struct device *dport_to_host(struct cxl_dport *dport)
>> {
>> - struct cxl_port *port = dport->port;
>> -
>> - if (is_cxl_root(port))
>> - return port->uport_dev;
>> - return &port->dev;
>> + return cxl_port_dport_host(dport->port);
>> }
>> #ifdef CONFIG_CXL_RAS
>> void cxl_ras_init(void);
>> diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c
>> index 9d807c1a002c..8a236e22bb3f 100644
>> --- a/drivers/cxl/core/pci.c
>> +++ b/drivers/cxl/core/pci.c
>> @@ -734,10 +734,16 @@ int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type,
>> if (rc && type == CXL_REGLOC_RBI_COMPONENT && is_cxl_restricted(pdev)) {
>> struct cxl_dport *dport;
>> struct cxl_port *port __free(put_cxl_port) =
>> - cxl_pci_find_port(pdev, &dport);
>> + cxl_pci_find_port(pdev, NULL);
>> if (!port)
>> return -EPROBE_DEFER;
>> + guard(device)(cxl_port_dport_host(port));
>> +
>> + dport = cxl_pci_find_dport(pdev, port);
>> + if (!dport)
>> + return -EPROBE_DEFER;
>> +
>> rc = cxl_rcrb_get_comp_regs(pdev, map, dport);
>> if (rc)
>> return rc;
>> diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
>> index 6ff3353865e3..336d7c0f5d98 100644
>> --- a/drivers/cxl/core/port.c
>> +++ b/drivers/cxl/core/port.c
>> @@ -1428,6 +1428,10 @@ static struct cxl_port *__find_cxl_port_by_dport(struct cxl_find_port_ctx *ctx)
>> *
>> * Return a 'struct cxl_port' with an elevated reference if found. Use
>> * __free(put_cxl_port) to release.
>> + *
>> + * The port reference does not pin @dport, which is a devm allocation of
>> + * cxl_port_dport_host(). Pass NULL and use cxl_pci_find_dport() or
>> + * cxl_mem_find_dport() under that device's lock instead.
>> */
>> static struct cxl_port *find_cxl_port_by_dport(struct device *dport_dev,
>> struct cxl_dport **dport)
>> @@ -1943,6 +1947,38 @@ struct cxl_port *cxl_mem_find_port(struct cxl_memdev *cxlmd,
>> }
>> EXPORT_SYMBOL_NS_GPL(cxl_mem_find_port, "CXL");
>> +/**
>> + * cxl_pci_find_dport - find the dport of @port that @pdev is below
>> + * @pdev: PCI device below the dport
>> + * @port: port to search, typically from cxl_pci_find_port()
>> + *
>> + * Context: Caller must hold the cxl_port_dport_host() lock of @port, and
>> + * must not use the result after dropping it.
>> + */
>> +struct cxl_dport *cxl_pci_find_dport(struct pci_dev *pdev,
>> + struct cxl_port *port)
>> +{
>> + device_lock_assert(cxl_port_dport_host(port));
>> + return cxl_find_dport_by_dev(port, pdev->dev.parent);
>> +}
>> +EXPORT_SYMBOL_NS_GPL(cxl_pci_find_dport, "CXL");
>> +
>> +/**
>> + * cxl_mem_find_dport - find the dport of @port that @cxlmd is below
>> + * @cxlmd: memdev below the dport
>> + * @port: port to search, typically from cxl_mem_find_port()
>> + *
>> + * Context: Caller must hold the cxl_port_dport_host() lock of @port, and
>> + * must not use the result after dropping it.
>> + */
>> +struct cxl_dport *cxl_mem_find_dport(struct cxl_memdev *cxlmd,
>> + struct cxl_port *port)
>> +{
>> + device_lock_assert(cxl_port_dport_host(port));
>> + return cxl_find_dport_by_dev(port, grandparent(&cxlmd->dev));
>> +}
>> +EXPORT_SYMBOL_NS_GPL(cxl_mem_find_dport, "CXL");
>> +
>> static int decoder_populate_targets(struct cxl_switch_decoder *cxlsd,
>> struct cxl_port *port)
>> {
>> diff --git a/drivers/cxl/core/ras_rch.c b/drivers/cxl/core/ras_rch.c
>> index e0e01aa5eba6..827a2f64d892 100644
>> --- a/drivers/cxl/core/ras_rch.c
>> +++ b/drivers/cxl/core/ras_rch.c
>> @@ -118,10 +118,20 @@ void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds)
>> int severity;
>> struct cxl_port *port __free(put_cxl_port) =
>> - cxl_pci_find_port(pdev, &dport);
>> + cxl_pci_find_port(pdev, NULL);
>> if (!port)
>> return;
>> + /*
>> + * The dport may have been freed since the lookup above, so take the
>> + * lock that pins it and find it again.
>> + */
>> + guard(device)(cxl_port_dport_host(port));
>> +
>> + dport = cxl_pci_find_dport(pdev, port);
>> + if (!dport)
>> + return;
>> +
>> if (!cxl_rch_get_aer_info(dport->regs.dport_aer, &aer_regs))
>> return;
>> diff --git a/drivers/cxl/cxl.h b/drivers/cxl/cxl.h
>> index cab8ce39f465..f08de094f412 100644
>> --- a/drivers/cxl/cxl.h
>> +++ b/drivers/cxl/cxl.h
>> @@ -701,6 +701,18 @@ static inline bool is_cxl_root(struct cxl_port *port)
>> return port->uport_dev == port->dev.parent;
>> }
>> +/*
>> + * Return the owner device of the devm allocations of @port's dports. They
>> + * are freed when that device unbinds, so hold device_lock() on the returned
>> + * device across any lookup and use of a 'struct cxl_dport'.
>> + */
>> +static inline struct device *cxl_port_dport_host(struct cxl_port *port)
>> +{
>> + if (is_cxl_root(port))
>> + return port->uport_dev;
>> + return &port->dev;
>> +}
>> +
>> /* Address translation functions exported to cxl_translate test module only */
>> int cxl_validate_translation_params(u8 eiw, u16 eig, int pos);
>> u64 cxl_calculate_hpa_offset(u64 dpa_offset, int pos, u8 eiw, u16 eig);
>> @@ -747,6 +759,10 @@ DEFINE_FREE(put_cxl_dax_region, struct cxl_dax_region *, if (!IS_ERR_OR_NULL(_T)
>> int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd);
>> void cxl_bus_rescan(void);
>> void cxl_bus_drain(void);
>> +struct cxl_dport *cxl_pci_find_dport(struct pci_dev *pdev,
>> + struct cxl_port *port);
>> +struct cxl_dport *cxl_mem_find_dport(struct cxl_memdev *cxlmd,
>> + struct cxl_port *port);
>> struct cxl_port *cxl_pci_find_port(struct pci_dev *pdev,
>> struct cxl_dport **dport);
>> struct cxl_port *cxl_mem_find_port(struct cxl_memdev *cxlmd,
>> diff --git a/drivers/cxl/mem.c b/drivers/cxl/mem.c
>> index 798e5c369cfc..91137487ac1f 100644
>> --- a/drivers/cxl/mem.c
>> +++ b/drivers/cxl/mem.c
>> @@ -133,7 +133,7 @@ static int cxl_mem_probe(struct device *dev)
>> return rc;
>> struct cxl_port *parent_port __free(put_cxl_port) =
>> - cxl_mem_find_port(cxlmd, &dport);
>> + cxl_mem_find_port(cxlmd, NULL);
>> if (!parent_port) {
>> dev_err(dev, "CXL port topology not found\n");
>> return -ENXIO;
>> @@ -148,10 +148,7 @@ static int cxl_mem_probe(struct device *dev)
>> }
>> }
>> - if (dport->rch)
>> - endpoint_parent = parent_port->uport_dev;
>> - else
>> - endpoint_parent = &parent_port->dev;
>> + endpoint_parent = cxl_port_dport_host(parent_port);
>> scoped_guard(device, endpoint_parent) {
>> if (!endpoint_parent->driver) {
>> @@ -160,6 +157,13 @@ static int cxl_mem_probe(struct device *dev)
>> return -ENXIO;
>> }
>> + /* @dport is only valid while @endpoint_parent stays bound */
>> + dport = cxl_mem_find_dport(cxlmd, parent_port);
>> + if (!dport) {
>> + dev_err(dev, "CXL topology dport not found\n");
>> + return -ENXIO;
>> + }
>> +
>> rc = devm_cxl_add_endpoint(endpoint_parent, cxlmd, dport);
>> if (rc)
>> return rc;
>> diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c
>> index c7c91e8dc51d..6e1d7ee94c19 100644
>> --- a/drivers/cxl/pci.c
>> +++ b/drivers/cxl/pci.c
>> @@ -706,22 +706,23 @@ static ssize_t rcd_pcie_cap_emit(struct device *dev, u16 offset, char *buf, size
>> struct device *root_dev;
>> struct cxl_dport *dport;
>> struct cxl_port *root __free(put_cxl_port) =
>> - cxl_mem_find_port(cxlmd, &dport);
>> + cxl_mem_find_port(cxlmd, NULL);
>> if (!root)
>> return -ENXIO;
>> - root_dev = root->uport_dev;
>> + root_dev = cxl_port_dport_host(root);
>> if (!root_dev)
>> return -ENXIO;
>> - if (!dport->regs.rcd_pcie_cap)
>> - return -ENXIO;
>> -
>> guard(device)(root_dev);
>> if (!root_dev->driver)
>> return -ENXIO;
>> + dport = cxl_mem_find_dport(cxlmd, root);
>> + if (!dport || !dport->regs.rcd_pcie_cap)
>> + return -ENXIO;
>> +
>> switch (width) {
>> case 2:
>> return sysfs_emit(buf, "%#x\n",
next prev parent reply other threads:[~2026-09-29 15:57 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-28 23:03 [PATCH v2 0/2] cxl: dport fixes from sashiko reports Dave Jiang
2026-09-28 23:03 ` [PATCH v2 1/2] cxl/port: Clear cached dport pointers when a dport is removed Dave Jiang
2026-09-29 2:57 ` Li Ming
2026-09-29 16:45 ` Gregory Price
2026-09-28 23:03 ` [PATCH v2 2/2] cxl/core: Hold the dport host lock across dport lookup and use Dave Jiang
2026-09-29 9:21 ` Li Ming
2026-09-29 15:57 ` Dave Jiang [this message]
2026-09-30 5:14 ` Li Ming
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=0731555d-21b3-49ac-bd25-d2b40e675e69@intel.com \
--to=dave.jiang@intel.com \
--cc=alison.schofield@intel.com \
--cc=dave@stgolabs.net \
--cc=icheng@nvidia.com \
--cc=jic23@kernel.org \
--cc=linux-cxl@vger.kernel.org \
--cc=ming.li@zohomail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.