hulk inclusion category: bugfix bugzilla: https://atomgit.com/src-openeuler/kernel/issues/21064 -------------------------------- Monitoring groups beyond the intPARTID count are given request PARTIDs of their own, and reqpartid_map[] records which internal PARTID each request PARTID uses. The map is indexed from req2intpartid(), which runs from mpam_thread_switch() on every context switch of a labelled task, so it runs once per switch on every CPU and takes no resctrl, MSC or MPAM SRCU lock. mpam_resctrl_exit() frees the map through reqpartid_exit(), and the caller disables the static key that guards mpam_thread_switch() only afterwards. In the interval a task holding a request PARTID above the intPARTID count switches on another CPU, indexes the freed array, and reads whatever memory occupies it, which is a use-after-free. Publish the array through RCU: build it, install it with rcu_assign_pointer(), and on teardown clear the pointer, wait for a grace period with synchronize_rcu() and then free. req2intpartid() reads the pointer under rcu_read_lock() and returns the request PARTID unchanged when the pointer is already NULL, which keeps a task that is still scheduled in from following a stale mapping. Disabling the static key alone does not retire readers that are already inside mpam_thread_switch(). The writers, resctrl_arch_rmid_expand() and resctrl_arch_rmid_reclaim(), run under rdtgroup_mutex and update the array in place, so only the pointer needs RCU treatment; they reach it through reqpartid_map_writable(). mpam_sync_config() returns with the SRCU read lock held when mpam_apply_config() fails. mpam_apply_config() discards that failure today, so the path is dead; it becomes reachable as soon as the failure is reported. Release the lock before returning. Only systems that build a request PARTID map, meaning systems with the Narrow-PARTID feature and more monitoring groups than intPARTIDs, reach the freed array, and only through the error teardown in mpam_disable_thread(). Fixes: d181cc9412cc ("arm_mpam: Implement dynamic reqPARTID allocation for monitoring groups") Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com> --- drivers/platform/mpam/mpam_resctrl.c | 74 ++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/drivers/platform/mpam/mpam_resctrl.c b/drivers/platform/mpam/mpam_resctrl.c index 1c3df3ad8dba..ad2848e322c6 100644 --- a/drivers/platform/mpam/mpam_resctrl.c +++ b/drivers/platform/mpam/mpam_resctrl.c @@ -310,10 +310,24 @@ static u32 req_pmg2rmid(u32 reqpartid, u8 pmg) return (reqpartid << pmg_shift) | (pmg & pmg_mask); } -static u32 *reqpartid_map; +/* + * Maps a request PARTID to its internal PARTID. + * + * Most users of req2intpartid() run under rdtgroup_mutex, but two do not: + * mpam_resctrl_exit() drops the map from the error-disable path without + * taking that lock, and resctrl_arch_rmid_idx_decode() is called from + * __rmid_entry() during RMID initialisation, also without it. Those can + * therefore race with the teardown, so the array is only replaced and + * freed after an RCU grace period. The pointer is NULL before the map is + * built and after mpam_resctrl_exit(). + */ +static u32 __rcu *reqpartid_map; u32 req2intpartid(u32 reqpartid) { + u32 intpartid; + u32 __rcu *map; + /* * Directly return intPartid in case that mpam_reset_ris() access * NULL pointer. @@ -321,7 +335,17 @@ u32 req2intpartid(u32 reqpartid) if (reqpartid < resctrl_arch_get_num_closid(NULL)) return reqpartid; - return reqpartid_map[reqpartid]; + rcu_read_lock(); + map = rcu_dereference(reqpartid_map); + /* + * The map may already have been dropped by the error-disable path + * while this reader is still in flight. Fall back to the identity + * mapping. + */ + intpartid = map ? map[reqpartid] : reqpartid; + rcu_read_unlock(); + + return intpartid; } static u32 partid2closid(u32 partid) @@ -1393,28 +1417,46 @@ static int mpam_resctrl_resource_init(struct mpam_resctrl_res *res) static int reqpartid_init(void) { + u32 *map; int req_num, idx; req_num = get_num_reqpartid(); - reqpartid_map = kcalloc(req_num, sizeof(u32), GFP_KERNEL); - if (!reqpartid_map) + map = kcalloc(req_num, sizeof(u32), GFP_KERNEL); + if (!map) return -ENOMEM; for (idx = 0; idx < req_num; idx++) - reqpartid_map[idx] = idx; + map[idx] = idx; + + RCU_INIT_POINTER(reqpartid_map, map); return 0; } void reqpartid_exit(void) { - kfree(reqpartid_map); + u32 *old = rcu_dereference_protected(reqpartid_map, 1); + + RCU_INIT_POINTER(reqpartid_map, NULL); + synchronize_rcu(); + kfree(old); +} + +/* + * The mapping itself is only ever updated under rdtgroup_mutex, and the + * pointer is only replaced during setup and teardown. Callers of this + * helper are those write paths; the scheduler goes through + * req2intpartid() instead. + */ +static u32 *reqpartid_map_writable(void) +{ + return rcu_dereference_protected(reqpartid_map, 1); } void update_rmid_entries_for_reqpartid(u32 reqpartid) { int pmg; - u32 intpartid = reqpartid_map[reqpartid]; + u32 intpartid = reqpartid_map_writable()[reqpartid]; u32 closid = partid2closid(intpartid); for (pmg = 0; pmg <= mpam_pmg_max; pmg++) @@ -1431,8 +1473,10 @@ static int mpam_sync_config(u32 reqpartid) list_for_each_entry_rcu(class, &mpam_classes, classes_list) { list_for_each_entry(comp, &class->components, class_list) { err = mpam_apply_config(comp, reqpartid, NULL, true); - if (err) + if (err) { + srcu_read_unlock(&mpam_srcu, idx); return err; + } } } srcu_read_unlock(&mpam_srcu, idx); @@ -1442,20 +1486,21 @@ static int mpam_sync_config(u32 reqpartid) int resctrl_arch_rmid_expand(u32 closid) { + u32 *map = reqpartid_map_writable(); int i; for (i = resctrl_arch_get_num_closid(NULL); i < get_num_reqpartid(); i++) { - if (reqpartid_map[i] >= resctrl_arch_get_num_closid(NULL)) { + if (map[i] >= resctrl_arch_get_num_closid(NULL)) { if (cdp_enabled) { - reqpartid_map[i] = resctrl_get_config_index(closid, CDP_DATA); + map[i] = resctrl_get_config_index(closid, CDP_DATA); mpam_sync_config(i); - reqpartid_map[i + 1] = resctrl_get_config_index(closid, CDP_CODE); + map[i + 1] = resctrl_get_config_index(closid, CDP_CODE); mpam_sync_config(i + 1); } else { - reqpartid_map[i] = resctrl_get_config_index(closid, CDP_NONE); + map[i] = resctrl_get_config_index(closid, CDP_NONE); mpam_sync_config(i); } @@ -1472,6 +1517,7 @@ void resctrl_arch_rmid_reclaim(u32 closid, u32 rmid) int pmg; u32 intpartid; int reqpartid = rmid2reqpartid(rmid); + u32 *map = reqpartid_map_writable(); if (reqpartid < resctrl_arch_get_num_closid(NULL)) return; @@ -1489,9 +1535,9 @@ void resctrl_arch_rmid_reclaim(u32 closid, u32 rmid) } if (pmg > mpam_pmg_max) { - reqpartid_map[reqpartid] = reqpartid; + map[reqpartid] = reqpartid; if (cdp_enabled) - reqpartid_map[reqpartid + 1] = reqpartid + 1; + map[reqpartid + 1] = reqpartid + 1; update_rmid_entries_for_reqpartid(reqpartid); } -- 2.34.1