[PATCH v7 19/19] s390/dasd: Read cached unit address and LSS in the CCW build path
Stefan Haberland <[email protected]> Sat, 1 Aug 2026 20:00:33 +0200
| Newsgroups | org.kernel.vger.linux-s390 |
|---|---|
| Message-ID | <[email protected]> |
The CCW build path (prefix_LRE, the full-track prefix and dso_ras) read the base address and LSS straight from conf.ned. That buffer is freed and reallocated by the reload worker (do_reload_device - dasd_eckd_read_conf - dasd_eckd_clear_conf_data), so a configuration change concurrent with I/O can free conf.ned while a request is being built. Use-after-free reported by KASAN in prefix_LRE. Read the cached copies instead. The unit address is already kept in uid.real_unit_addr, and the LSS is now cached in ned_lss. Both are refreshed under the ccwdev lock in dasd_eckd_generate_uid whenever the configuration is (re)read. Also fix for prepare for read subsystem data (prssd) users. Reviewed-by: Jan Höppner <[email protected]> Signed-off-by: Stefan Haberland <[email protected]> --- drivers/s390/block/dasd_eckd.c | 26 ++++++++++++++++---------- drivers/s390/block/dasd_eckd.h | 8 ++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index c634876b9807..ad58feb531b9 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -588,8 +588,9 @@ static int prefix_LRE(struct ccw1 *ccw, struct PFX_eckd_data *pfxdata, return -EINVAL; } pfxdata->format = format; - pfxdata->base_address = basepriv->conf.ned->unit_addr; - pfxdata->base_lss = basepriv->conf.ned->ID; + /* cached copies - conf.ned may be freed under us by the reload worker */ + pfxdata->base_address = READ_ONCE(basepriv->ned_ua); + pfxdata->base_lss = READ_ONCE(basepriv->ned_lss); pfxdata->validity.define_extent = 1; /* private uid is kept up to date, conf_data may be outdated */ @@ -806,6 +807,9 @@ static int dasd_eckd_generate_uid(struct dasd_device *device) return -ENODEV; spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); create_uid(&private->conf, &private->uid); + /* cache LSS and unit address for the lockless CCW-build path */ + WRITE_ONCE(private->ned_lss, private->conf.ned->ID); + WRITE_ONCE(private->ned_ua, private->conf.ned->unit_addr); spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); return 0; } @@ -1631,8 +1635,8 @@ static int dasd_eckd_read_vol_info(struct dasd_device *device) prssdp = cqr->data; prssdp->order = PSF_ORDER_PRSSD; prssdp->suborder = PSF_SUBORDER_VSQ; /* Volume Storage Query */ - prssdp->lss = private->conf.ned->ID; - prssdp->volume = private->conf.ned->unit_addr; + prssdp->lss = READ_ONCE(private->ned_lss); + prssdp->volume = READ_ONCE(private->ned_ua); ccw = cqr->cpaddr; ccw->cmd_code = DASD_ECKD_CCW_PSF; @@ -4247,8 +4251,9 @@ dasd_eckd_dso_ras(struct dasd_device *device, struct dasd_block *block, if (!req && features->feature[56] & 0x01 && !copy_relation) ras_data->op_flags.guarantee_init = 1; - ras_data->lss = private->conf.ned->ID; - ras_data->dev_addr = private->conf.ned->unit_addr; + /* cached copies - conf.ned may be freed under us by the reload worker */ + ras_data->lss = READ_ONCE(private->ned_lss); + ras_data->dev_addr = READ_ONCE(private->ned_ua); ras_data->nr_exts = nr_exts; if (by_extent) { @@ -4819,8 +4824,9 @@ static int prepare_itcw(struct itcw *itcw, lredata = &pfxdata->locate_record; pfxdata->format = 1; /* PFX with LRE */ - pfxdata->base_address = basepriv->conf.ned->unit_addr; - pfxdata->base_lss = basepriv->conf.ned->ID; + /* cached copies - conf.ned may be freed under us by the reload worker */ + pfxdata->base_address = READ_ONCE(basepriv->ned_ua); + pfxdata->base_lss = READ_ONCE(basepriv->ned_lss); pfxdata->validity.define_extent = 1; /* private uid is kept up to date, conf_data may be outdated */ @@ -6901,8 +6907,8 @@ static int dasd_eckd_query_host_access(struct dasd_device *device, prssdp->order = PSF_ORDER_PRSSD; prssdp->suborder = PSF_SUBORDER_QHA; /* query host access */ /* LSS and Volume that will be queried */ - prssdp->lss = private->conf.ned->ID; - prssdp->volume = private->conf.ned->unit_addr; + prssdp->lss = READ_ONCE(private->ned_lss); + prssdp->volume = READ_ONCE(private->ned_ua); /* all other bytes of prssdp must be zero */ ccw = cqr->cpaddr; diff --git a/drivers/s390/block/dasd_eckd.h b/drivers/s390/block/dasd_eckd.h index 30745f62402b..8e6f09e9ca7e 100644 --- a/drivers/s390/block/dasd_eckd.h +++ b/drivers/s390/block/dasd_eckd.h @@ -736,6 +736,14 @@ struct dasd_eckd_private { /* alias management */ struct dasd_uid uid; + /* + * Cached copies of conf.ned->ID (the LSS) and conf.ned->unit_addr, + * refreshed under ccwdev_lock. Kept outside uid because create_uid() + * memsets uid before repopulating it, which would expose a transient + * zero to the lockless CCW-build readers. + */ + __u8 ned_lss; + __u8 ned_ua; struct alias_pav_group *pavgroup; struct alias_lcu *lcu; int count; -- 2.53.0