Skip to content

Commit 101c268

Browse files
djbwweiny2
authored andcommitted
cxl/port: Fix use-after-free, permit out-of-order decoder shutdown
In support of investigating an initialization failure report [1], cxl_test was updated to register mock memory-devices after the mock root-port/bus device had been registered. That led to cxl_test crashing with a use-after-free bug with the following signature: cxl_port_attach_region: cxl region3: cxl_host_bridge.0:port3 decoder3.0 add: mem0:decoder7.0 @ 0 next: cxl_switch_uport.0 nr_eps: 1 nr_targets: 1 cxl_port_attach_region: cxl region3: cxl_host_bridge.0:port3 decoder3.0 add: mem4:decoder14.0 @ 1 next: cxl_switch_uport.0 nr_eps: 2 nr_targets: 1 cxl_port_setup_targets: cxl region3: cxl_switch_uport.0:port6 target[0] = cxl_switch_dport.0 for mem0:decoder7.0 @ 0 1) cxl_port_setup_targets: cxl region3: cxl_switch_uport.0:port6 target[1] = cxl_switch_dport.4 for mem4:decoder14.0 @ 1 [..] cxld_unregister: cxl decoder14.0: cxl_region_decode_reset: cxl_region region3: mock_decoder_reset: cxl_port port3: decoder3.0 reset 2) mock_decoder_reset: cxl_port port3: decoder3.0: out of order reset, expected decoder3.1 cxl_endpoint_decoder_release: cxl decoder14.0: [..] cxld_unregister: cxl decoder7.0: 3) cxl_region_decode_reset: cxl_region region3: Oops: general protection fault, probably for non-canonical address 0x6b6b6b6b6b6b6bc3: 0000 [#1] PREEMPT SMP PTI [..] RIP: 0010:to_cxl_port+0x8/0x60 [cxl_core] [..] Call Trace: <TASK> cxl_region_decode_reset+0x69/0x190 [cxl_core] cxl_region_detach+0xe8/0x210 [cxl_core] cxl_decoder_kill_region+0x27/0x40 [cxl_core] cxld_unregister+0x5d/0x60 [cxl_core] At 1) a region has been established with 2 endpoint decoders (7.0 and 14.0). Those endpoints share a common switch-decoder in the topology (3.0). At teardown, 2), decoder14.0 is the first to be removed and hits the "out of order reset case" in the switch decoder. The effect though is that region3 cleanup is aborted leaving it in-tact and referencing decoder14.0. At 3) the second attempt to teardown region3 trips over the stale decoder14.0 object which has long since been deleted. The fix here is to recognize that the CXL specification places no mandate on in-order shutdown of switch-decoders, the driver enforces in-order allocation, and hardware enforces in-order commit. So, rather than fail and leave objects dangling, always remove them. In support of making cxl_region_decode_reset() always succeed, cxl_region_invalidate_memregion() failures are turned into warnings. Crashing the kernel is ok there since system integrity is at risk if caches cannot be managed around physical address mutation events like CXL region destruction. A new device_for_each_child_reverse_from() is added to cleanup port->commit_end after all dependent decoders have been disabled. In other words if decoders are allocated 0->1->2 and disabled 1->2->0 then port->commit_end only decrements from 2 after 2 has been disabled, and it decrements all the way to zero since 1 was disabled previously. Link: http://lore.kernel.org/[email protected] [1] Cc: [email protected] Fixes: 176baef ("cxl/hdm: Commit decoder state to hardware") Reviewed-by: Jonathan Cameron <[email protected]> Cc: Greg Kroah-Hartman <[email protected]> Cc: Davidlohr Bueso <[email protected]> Cc: Dave Jiang <[email protected]> Cc: Alison Schofield <[email protected]> Cc: Ira Weiny <[email protected]> Cc: Zijun Hu <[email protected]> Signed-off-by: Dan Williams <[email protected]> Reviewed-by: Ira Weiny <[email protected]> Link: https://patch.msgid.link/172964782781.81806.17902885593105284330.stgit@dwillia2-xfh.jf.intel.com Signed-off-by: Ira Weiny <[email protected]>
1 parent 48f62d3 commit 101c268

File tree

6 files changed

+100
-53
lines changed

6 files changed

+100
-53
lines changed

drivers/base/core.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4037,6 +4037,41 @@ int device_for_each_child_reverse(struct device *parent, void *data,
40374037
}
40384038
EXPORT_SYMBOL_GPL(device_for_each_child_reverse);
40394039

4040+
/**
4041+
* device_for_each_child_reverse_from - device child iterator in reversed order.
4042+
* @parent: parent struct device.
4043+
* @from: optional starting point in child list
4044+
* @fn: function to be called for each device.
4045+
* @data: data for the callback.
4046+
*
4047+
* Iterate over @parent's child devices, starting at @from, and call @fn
4048+
* for each, passing it @data. This helper is identical to
4049+
* device_for_each_child_reverse() when @from is NULL.
4050+
*
4051+
* @fn is checked each iteration. If it returns anything other than 0,
4052+
* iteration stop and that value is returned to the caller of
4053+
* device_for_each_child_reverse_from();
4054+
*/
4055+
int device_for_each_child_reverse_from(struct device *parent,
4056+
struct device *from, const void *data,
4057+
int (*fn)(struct device *, const void *))
4058+
{
4059+
struct klist_iter i;
4060+
struct device *child;
4061+
int error = 0;
4062+
4063+
if (!parent->p)
4064+
return 0;
4065+
4066+
klist_iter_init_node(&parent->p->klist_children, &i,
4067+
(from ? &from->p->knode_parent : NULL));
4068+
while ((child = prev_device(&i)) && !error)
4069+
error = fn(child, data);
4070+
klist_iter_exit(&i);
4071+
return error;
4072+
}
4073+
EXPORT_SYMBOL_GPL(device_for_each_child_reverse_from);
4074+
40404075
/**
40414076
* device_find_child - device iterator for locating a particular device.
40424077
* @parent: parent struct device

drivers/cxl/core/hdm.c

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,44 @@ static int cxl_decoder_commit(struct cxl_decoder *cxld)
712712
return 0;
713713
}
714714

715-
static int cxl_decoder_reset(struct cxl_decoder *cxld)
715+
static int commit_reap(struct device *dev, const void *data)
716+
{
717+
struct cxl_port *port = to_cxl_port(dev->parent);
718+
struct cxl_decoder *cxld;
719+
720+
if (!is_switch_decoder(dev) && !is_endpoint_decoder(dev))
721+
return 0;
722+
723+
cxld = to_cxl_decoder(dev);
724+
if (port->commit_end == cxld->id &&
725+
((cxld->flags & CXL_DECODER_F_ENABLE) == 0)) {
726+
port->commit_end--;
727+
dev_dbg(&port->dev, "reap: %s commit_end: %d\n",
728+
dev_name(&cxld->dev), port->commit_end);
729+
}
730+
731+
return 0;
732+
}
733+
734+
void cxl_port_commit_reap(struct cxl_decoder *cxld)
735+
{
736+
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
737+
738+
lockdep_assert_held_write(&cxl_region_rwsem);
739+
740+
/*
741+
* Once the highest committed decoder is disabled, free any other
742+
* decoders that were pinned allocated by out-of-order release.
743+
*/
744+
port->commit_end--;
745+
dev_dbg(&port->dev, "reap: %s commit_end: %d\n", dev_name(&cxld->dev),
746+
port->commit_end);
747+
device_for_each_child_reverse_from(&port->dev, &cxld->dev, NULL,
748+
commit_reap);
749+
}
750+
EXPORT_SYMBOL_NS_GPL(cxl_port_commit_reap, CXL);
751+
752+
static void cxl_decoder_reset(struct cxl_decoder *cxld)
716753
{
717754
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
718755
struct cxl_hdm *cxlhdm = dev_get_drvdata(&port->dev);
@@ -721,14 +758,14 @@ static int cxl_decoder_reset(struct cxl_decoder *cxld)
721758
u32 ctrl;
722759

723760
if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
724-
return 0;
761+
return;
725762

726-
if (port->commit_end != id) {
763+
if (port->commit_end == id)
764+
cxl_port_commit_reap(cxld);
765+
else
727766
dev_dbg(&port->dev,
728767
"%s: out of order reset, expected decoder%d.%d\n",
729768
dev_name(&cxld->dev), port->id, port->commit_end);
730-
return -EBUSY;
731-
}
732769

733770
down_read(&cxl_dpa_rwsem);
734771
ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(id));
@@ -741,7 +778,6 @@ static int cxl_decoder_reset(struct cxl_decoder *cxld)
741778
writel(0, hdm + CXL_HDM_DECODER0_BASE_LOW_OFFSET(id));
742779
up_read(&cxl_dpa_rwsem);
743780

744-
port->commit_end--;
745781
cxld->flags &= ~CXL_DECODER_F_ENABLE;
746782

747783
/* Userspace is now responsible for reconfiguring this decoder */
@@ -751,8 +787,6 @@ static int cxl_decoder_reset(struct cxl_decoder *cxld)
751787
cxled = to_cxl_endpoint_decoder(&cxld->dev);
752788
cxled->state = CXL_DECODER_STATE_MANUAL;
753789
}
754-
755-
return 0;
756790
}
757791

758792
static int cxl_setup_hdm_decoder_from_dvsec(

drivers/cxl/core/region.c

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,8 @@ static int cxl_region_invalidate_memregion(struct cxl_region *cxlr)
232232
"Bypassing cpu_cache_invalidate_memregion() for testing!\n");
233233
return 0;
234234
} else {
235-
dev_err(&cxlr->dev,
236-
"Failed to synchronize CPU cache state\n");
235+
dev_WARN(&cxlr->dev,
236+
"Failed to synchronize CPU cache state\n");
237237
return -ENXIO;
238238
}
239239
}
@@ -242,19 +242,17 @@ static int cxl_region_invalidate_memregion(struct cxl_region *cxlr)
242242
return 0;
243243
}
244244

245-
static int cxl_region_decode_reset(struct cxl_region *cxlr, int count)
245+
static void cxl_region_decode_reset(struct cxl_region *cxlr, int count)
246246
{
247247
struct cxl_region_params *p = &cxlr->params;
248-
int i, rc = 0;
248+
int i;
249249

250250
/*
251-
* Before region teardown attempt to flush, and if the flush
252-
* fails cancel the region teardown for data consistency
253-
* concerns
251+
* Before region teardown attempt to flush, evict any data cached for
252+
* this region, or scream loudly about missing arch / platform support
253+
* for CXL teardown.
254254
*/
255-
rc = cxl_region_invalidate_memregion(cxlr);
256-
if (rc)
257-
return rc;
255+
cxl_region_invalidate_memregion(cxlr);
258256

259257
for (i = count - 1; i >= 0; i--) {
260258
struct cxl_endpoint_decoder *cxled = p->targets[i];
@@ -277,23 +275,17 @@ static int cxl_region_decode_reset(struct cxl_region *cxlr, int count)
277275
cxl_rr = cxl_rr_load(iter, cxlr);
278276
cxld = cxl_rr->decoder;
279277
if (cxld->reset)
280-
rc = cxld->reset(cxld);
281-
if (rc)
282-
return rc;
278+
cxld->reset(cxld);
283279
set_bit(CXL_REGION_F_NEEDS_RESET, &cxlr->flags);
284280
}
285281

286282
endpoint_reset:
287-
rc = cxled->cxld.reset(&cxled->cxld);
288-
if (rc)
289-
return rc;
283+
cxled->cxld.reset(&cxled->cxld);
290284
set_bit(CXL_REGION_F_NEEDS_RESET, &cxlr->flags);
291285
}
292286

293287
/* all decoders associated with this region have been torn down */
294288
clear_bit(CXL_REGION_F_NEEDS_RESET, &cxlr->flags);
295-
296-
return 0;
297289
}
298290

299291
static int commit_decoder(struct cxl_decoder *cxld)
@@ -409,16 +401,8 @@ static ssize_t commit_store(struct device *dev, struct device_attribute *attr,
409401
* still pending.
410402
*/
411403
if (p->state == CXL_CONFIG_RESET_PENDING) {
412-
rc = cxl_region_decode_reset(cxlr, p->interleave_ways);
413-
/*
414-
* Revert to committed since there may still be active
415-
* decoders associated with this region, or move forward
416-
* to active to mark the reset successful
417-
*/
418-
if (rc)
419-
p->state = CXL_CONFIG_COMMIT;
420-
else
421-
p->state = CXL_CONFIG_ACTIVE;
404+
cxl_region_decode_reset(cxlr, p->interleave_ways);
405+
p->state = CXL_CONFIG_ACTIVE;
422406
}
423407
}
424408

@@ -2054,13 +2038,7 @@ static int cxl_region_detach(struct cxl_endpoint_decoder *cxled)
20542038
get_device(&cxlr->dev);
20552039

20562040
if (p->state > CXL_CONFIG_ACTIVE) {
2057-
/*
2058-
* TODO: tear down all impacted regions if a device is
2059-
* removed out of order
2060-
*/
2061-
rc = cxl_region_decode_reset(cxlr, p->interleave_ways);
2062-
if (rc)
2063-
goto out;
2041+
cxl_region_decode_reset(cxlr, p->interleave_ways);
20642042
p->state = CXL_CONFIG_ACTIVE;
20652043
}
20662044

drivers/cxl/cxl.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ struct cxl_decoder {
359359
struct cxl_region *region;
360360
unsigned long flags;
361361
int (*commit)(struct cxl_decoder *cxld);
362-
int (*reset)(struct cxl_decoder *cxld);
362+
void (*reset)(struct cxl_decoder *cxld);
363363
};
364364

365365
/*
@@ -730,6 +730,7 @@ static inline bool is_cxl_root(struct cxl_port *port)
730730
int cxl_num_decoders_committed(struct cxl_port *port);
731731
bool is_cxl_port(const struct device *dev);
732732
struct cxl_port *to_cxl_port(const struct device *dev);
733+
void cxl_port_commit_reap(struct cxl_decoder *cxld);
733734
struct pci_bus;
734735
int devm_cxl_register_pci_bus(struct device *host, struct device *uport_dev,
735736
struct pci_bus *bus);

include/linux/device.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,9 @@ int device_for_each_child(struct device *dev, void *data,
10781078
int (*fn)(struct device *dev, void *data));
10791079
int device_for_each_child_reverse(struct device *dev, void *data,
10801080
int (*fn)(struct device *dev, void *data));
1081+
int device_for_each_child_reverse_from(struct device *parent,
1082+
struct device *from, const void *data,
1083+
int (*fn)(struct device *, const void *));
10811084
struct device *device_find_child(struct device *dev, void *data,
10821085
int (*match)(struct device *dev, void *data));
10831086
struct device *device_find_child_by_name(struct device *parent,

tools/testing/cxl/test/cxl.c

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -693,26 +693,22 @@ static int mock_decoder_commit(struct cxl_decoder *cxld)
693693
return 0;
694694
}
695695

696-
static int mock_decoder_reset(struct cxl_decoder *cxld)
696+
static void mock_decoder_reset(struct cxl_decoder *cxld)
697697
{
698698
struct cxl_port *port = to_cxl_port(cxld->dev.parent);
699699
int id = cxld->id;
700700

701701
if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
702-
return 0;
702+
return;
703703

704704
dev_dbg(&port->dev, "%s reset\n", dev_name(&cxld->dev));
705-
if (port->commit_end != id) {
705+
if (port->commit_end == id)
706+
cxl_port_commit_reap(cxld);
707+
else
706708
dev_dbg(&port->dev,
707709
"%s: out of order reset, expected decoder%d.%d\n",
708710
dev_name(&cxld->dev), port->id, port->commit_end);
709-
return -EBUSY;
710-
}
711-
712-
port->commit_end--;
713711
cxld->flags &= ~CXL_DECODER_F_ENABLE;
714-
715-
return 0;
716712
}
717713

718714
static void default_mock_decoder(struct cxl_decoder *cxld)

0 commit comments

Comments
 (0)