[PATCH v5 4/4] cxl/port: Bound switch decoder target array access by nr_targets
Alison Schofield <[email protected]>
| Newsgroups | org.kernel.vger.linux-cxl |
|---|---|
| Message-ID | <d7577652cc6d414e1de0ee4045d4d6b2808172ce.1786143520.git.alison.schofield@intel.com> |
Switch decoder target lookups walk the target array using interleave
ways as the bound. The array is allocated with nr_targets entries, so a
decoder whose interleave ways exceeds nr_targets reads and writes past
the end of it. That happens during dport activation, during decoder
target population, and when userspace reads the target_list attribute.
Bounding by nr_targets alone is not correct either. Commit d6488fee6647
("cxl/port: Fix decoder initialization when nr_targets > interleave_ways")
moved target population to interleave ways precisely so that targets a
decoder does not use are left unpopulated. Neither field alone is a safe
bound, so add a helper that returns the minimum of the two and use it at
every walk of the target array.
Valid configurations keep interleave ways within nr_targets, so this is
not a behavior change for them.
Fixes: 40ba17afdfab ("cxl/acpi: Introduce cxl_decoder objects")
Fixes: d6488fee6647 ("cxl/port: Fix decoder initialization when nr_targets > interleave_ways")
Signed-off-by: Alison Schofield <[email protected]>
---
drivers/cxl/core/port.c | 25 ++++++++++++++++++-------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/drivers/cxl/core/port.c b/drivers/cxl/core/port.c
index 28eccfdd75b8..757b48584ac2 100644
--- a/drivers/cxl/core/port.c
+++ b/drivers/cxl/core/port.c
@@ -147,20 +147,29 @@ static ssize_t target_type_show(struct device *dev,
}
static DEVICE_ATTR_RO(target_type);
+/*
+ * Interleave ways selects how many targets a decoder uses, but the target
+ * array is only nr_targets long. Bound array access by both.
+ */
+static int cxlsd_nr_used_targets(struct cxl_switch_decoder *cxlsd)
+{
+ return min(cxlsd->cxld.interleave_ways, cxlsd->nr_targets);
+}
+
static ssize_t emit_target_list(struct cxl_switch_decoder *cxlsd, char *buf)
{
- struct cxl_decoder *cxld = &cxlsd->cxld;
+ int nr_used = cxlsd_nr_used_targets(cxlsd);
ssize_t offset = 0;
int i, rc = 0;
- for (i = 0; i < cxld->interleave_ways; i++) {
+ for (i = 0; i < nr_used; i++) {
struct cxl_dport *dport = cxlsd->target[i];
struct cxl_dport *next = NULL;
if (!dport)
break;
- if (i + 1 < cxld->interleave_ways)
+ if (i + 1 < nr_used)
next = cxlsd->target[i + 1];
rc = sysfs_emit_at(buf, offset, "%d%s", dport->port_id,
next ? "," : "");
@@ -1603,7 +1612,7 @@ static int update_decoder_targets(struct device *dev, void *data)
struct cxl_dport *dport = data;
struct cxl_switch_decoder *cxlsd;
struct cxl_decoder *cxld;
- int i;
+ int i, nr_used;
if (!is_switch_decoder(dev))
return 0;
@@ -1611,8 +1620,9 @@ static int update_decoder_targets(struct device *dev, void *data)
cxlsd = to_cxl_switch_decoder(dev);
cxld = &cxlsd->cxld;
guard(rwsem_write)(&cxl_rwsem.region);
+ nr_used = cxlsd_nr_used_targets(cxlsd);
- for (i = 0; i < cxld->interleave_ways; i++) {
+ for (i = 0; i < nr_used; i++) {
if (cxld->target_map[i] == dport->port_id) {
cxlsd->target[i] = dport;
dev_dbg(dev, "dport%d found in target list, index %d\n",
@@ -1910,7 +1920,7 @@ static int decoder_populate_targets(struct cxl_switch_decoder *cxlsd,
struct cxl_port *port)
{
struct cxl_decoder *cxld = &cxlsd->cxld;
- int i;
+ int i, nr_used;
device_lock_assert(&port->dev);
@@ -1918,7 +1928,8 @@ static int decoder_populate_targets(struct cxl_switch_decoder *cxlsd,
return 0;
guard(rwsem_write)(&cxl_rwsem.region);
- for (i = 0; i < cxlsd->cxld.interleave_ways; i++) {
+ nr_used = cxlsd_nr_used_targets(cxlsd);
+ for (i = 0; i < nr_used; i++) {
struct cxl_dport *dport = find_dport(port, cxld->target_map[i]);
if (!dport) {
--
2.37.3