diff options
| author | Jakub Kicinski <kuba@kernel.org> | 2026-08-06 11:51:42 -0700 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-08-13 11:00:14 -0700 |
| commit | 3da8c3c8b8fa99505624b65ef590482f48e766b6 (patch) | |
| tree | a4e8435b0e16ba34a7fe90a6b7e355390cc33fb4 /drivers/s390 | |
| parent | 3205699d79f262412c1be7fc1c04066610d3cd52 (diff) | |
| parent | 3aa1dcaa4f6f5ae08936491e08bd456f331f2d40 (diff) | |
| download | linux-next-3da8c3c8b8fa99505624b65ef590482f48e766b6.tar.gz linux-next-3da8c3c8b8fa99505624b65ef590482f48e766b6.zip | |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net
Cross-merge networking fixes after downstream PR (net-7.2-rc8).
No conflicts.
Adjacent changes:
drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
5f3a13e0bb5e ("net: ngbe: fix NULL pointer dereference in non-MSI-X interrupt enabling")
d661abdc30c2 ("net: ngbe: correct misleading interrupt comment")
drivers/net/ipvlan/ipvlan_main.c
e16e960d55a4 ("ipvlan: inherit needed_headroom and needed_tailroom from phy_dev")
00a40d809207 ("ipvlan: Support per-netns netdev unregistration.")
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Diffstat (limited to 'drivers/s390')
| -rw-r--r-- | drivers/s390/cio/vfio_ccw_async.c | 16 | ||||
| -rw-r--r-- | drivers/s390/cio/vfio_ccw_chp.c | 31 | ||||
| -rw-r--r-- | drivers/s390/cio/vfio_ccw_cp.c | 74 | ||||
| -rw-r--r-- | drivers/s390/cio/vfio_ccw_cp.h | 10 | ||||
| -rw-r--r-- | drivers/s390/cio/vfio_ccw_drv.c | 21 | ||||
| -rw-r--r-- | drivers/s390/cio/vfio_ccw_fsm.c | 8 | ||||
| -rw-r--r-- | drivers/s390/cio/vfio_ccw_ops.c | 39 | ||||
| -rw-r--r-- | drivers/s390/cio/vfio_ccw_private.h | 10 | ||||
| -rw-r--r-- | drivers/s390/crypto/zcrypt_ccamisc.c | 20 | ||||
| -rw-r--r-- | drivers/s390/crypto/zcrypt_ep11misc.c | 16 | ||||
| -rw-r--r-- | drivers/s390/crypto/zcrypt_msgtype6.c | 366 |
11 files changed, 454 insertions, 157 deletions
diff --git a/drivers/s390/cio/vfio_ccw_async.c b/drivers/s390/cio/vfio_ccw_async.c index 420d89ba7f83..4aff0b58fa5d 100644 --- a/drivers/s390/cio/vfio_ccw_async.c +++ b/drivers/s390/cio/vfio_ccw_async.c @@ -8,6 +8,7 @@ */ #include <linux/vfio.h> +#include <linux/nospec.h> #include "vfio_ccw_private.h" @@ -24,11 +25,20 @@ static ssize_t vfio_ccw_async_region_read(struct vfio_ccw_private *private, return -EINVAL; mutex_lock(&private->io_mutex); + + if (i >= private->num_regions) { + ret = -EINVAL; + goto out_unlock; + } + + i = array_index_nospec(i, private->num_regions); region = private->region[i].data; if (copy_to_user(buf, (void *)region + pos, count)) ret = -EFAULT; else ret = count; + +out_unlock: mutex_unlock(&private->io_mutex); return ret; } @@ -48,6 +58,12 @@ static ssize_t vfio_ccw_async_region_write(struct vfio_ccw_private *private, if (!mutex_trylock(&private->io_mutex)) return -EAGAIN; + if (i >= private->num_regions) { + ret = -EINVAL; + goto out_unlock; + } + + i = array_index_nospec(i, private->num_regions); region = private->region[i].data; if (copy_from_user((void *)region + pos, buf, count)) { ret = -EFAULT; diff --git a/drivers/s390/cio/vfio_ccw_chp.c b/drivers/s390/cio/vfio_ccw_chp.c index 38c176cf6295..7708eb4d6de0 100644 --- a/drivers/s390/cio/vfio_ccw_chp.c +++ b/drivers/s390/cio/vfio_ccw_chp.c @@ -9,6 +9,7 @@ */ #include <linux/slab.h> +#include <linux/nospec.h> #include <linux/vfio.h> #include "vfio_ccw_private.h" @@ -26,6 +27,13 @@ static ssize_t vfio_ccw_schib_region_read(struct vfio_ccw_private *private, return -EINVAL; mutex_lock(&private->io_mutex); + + if (i >= private->num_regions) { + ret = -EINVAL; + goto out; + } + + i = array_index_nospec(i, private->num_regions); region = private->region[i].data; if (cio_update_schib(sch)) { @@ -85,19 +93,30 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private, loff_t pos = *ppos & VFIO_CCW_OFFSET_MASK; struct ccw_crw_region *region; struct vfio_ccw_crw *crw; + unsigned long flags; int ret; if (pos + count > sizeof(*region)) return -EINVAL; + mutex_lock(&private->io_mutex); + if (i >= private->num_regions) { + ret = -EINVAL; + goto out; + } + + i = array_index_nospec(i, private->num_regions); + region = private->region[i].data; + + spin_lock_irqsave(&private->crw_lock, flags); crw = list_first_entry_or_null(&private->crw, struct vfio_ccw_crw, next); if (crw) list_del(&crw->next); - mutex_lock(&private->io_mutex); - region = private->region[i].data; + /* Drop CRW lock while copying to userspace */ + spin_unlock_irqrestore(&private->crw_lock, flags); if (crw) memcpy(®ion->crw, &crw->crw, sizeof(region->crw)); @@ -108,14 +127,16 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private, ret = count; region->crw = 0; - - mutex_unlock(&private->io_mutex); - kfree(crw); /* Notify the guest if more CRWs are on our queue */ + spin_lock_irqsave(&private->crw_lock, flags); if (!list_empty(&private->crw) && private->crw_trigger) eventfd_signal(private->crw_trigger); + spin_unlock_irqrestore(&private->crw_lock, flags); + +out: + mutex_unlock(&private->io_mutex); return ret; } diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c index 7561aa7d3e01..58722c4baa25 100644 --- a/drivers/s390/cio/vfio_ccw_cp.c +++ b/drivers/s390/cio/vfio_ccw_cp.c @@ -233,6 +233,7 @@ static void convert_ccw0_to_ccw1(struct ccw1 *source, unsigned long len) } #define idal_is_2k(_cp) (!(_cp)->orb.cmd.c64 || (_cp)->orb.cmd.i2k) +#define get_idaw_size(_cp) ((_cp)->orb.cmd.c64 ? sizeof(u64) : sizeof(u32)) /* * Helpers to operate ccwchain. @@ -332,6 +333,7 @@ static struct ccwchain *ccwchain_alloc(struct channel_program *cp, int len) goto out_err; list_add_tail(&chain->next, &cp->ccwchain_list); + cp->ccwchain_count++; return chain; @@ -376,11 +378,9 @@ static void ccwchain_cda_free(struct ccwchain *chain, int idx) static int ccwchain_calc_length(u64 iova, struct channel_program *cp) { struct ccw1 *ccw = cp->guest_cp; - int cnt = 0; - - do { - cnt++; + int cnt; + for (cnt = 1; cnt <= CCWCHAIN_LEN_MAX; cnt++, ccw++) { /* * We want to keep counting if the current CCW has the * command-chaining flag enabled, or if it is a TIC CCW @@ -390,15 +390,10 @@ static int ccwchain_calc_length(u64 iova, struct channel_program *cp) * after the TIC, depending on the results of its operation. */ if (!ccw_is_chain(ccw) && !is_tic_within_range(ccw, iova, cnt)) - break; - - ccw++; - } while (cnt < CCWCHAIN_LEN_MAX + 1); - - if (cnt == CCWCHAIN_LEN_MAX + 1) - cnt = -EINVAL; + return cnt; + } - return cnt; + return -EINVAL; } static int tic_target_chain_exists(struct ccw1 *tic, struct channel_program *cp) @@ -441,6 +436,10 @@ static int ccwchain_handle_ccw(dma32_t cda, struct channel_program *cp) if (len < 0) return len; + /* Limit number of chains in a single channel program */ + if (cp->ccwchain_count >= CCWCHAIN_COUNT_MAX) + return -EINVAL; + /* Need alloc a new chain for this one. */ chain = ccwchain_alloc(cp, len); if (!chain) @@ -455,9 +454,6 @@ static int ccwchain_handle_ccw(dma32_t cda, struct channel_program *cp) /* Loop for tics on this new chain. */ ret = ccwchain_loop_tic(chain, cp); - if (ret) - ccwchain_free(chain); - return ret; } @@ -486,6 +482,23 @@ static int ccwchain_loop_tic(struct ccwchain *chain, struct channel_program *cp) return 0; } +static int ccwchain_build_ccws(dma32_t cda, struct channel_program *cp) +{ + struct ccwchain *chain, *temp; + int ret; + + ret = ccwchain_handle_ccw(cda, cp); + + if (ret) { + /* Cleanup if an error occurred */ + list_for_each_entry_safe(chain, temp, &cp->ccwchain_list, next) { + ccwchain_free(chain); + } + } + + return ret; +} + static int ccwchain_fetch_tic(struct ccw1 *ccw, struct channel_program *cp) { @@ -511,7 +524,8 @@ static dma64_t *get_guest_idal(struct ccw1 *ccw, struct channel_program *cp, int &container_of(cp, struct vfio_ccw_private, cp)->vdev; dma64_t *idaws; dma32_t *idaws_f1; - int idal_len = idaw_nr * sizeof(*idaws); + u64 first_idaw; + int idal_len = idaw_nr * get_idaw_size(cp); int idaw_size = idal_is_2k(cp) ? PAGE_SIZE / 2 : PAGE_SIZE; int idaw_mask = ~(idaw_size - 1); int i, ret; @@ -527,6 +541,18 @@ static dma64_t *get_guest_idal(struct ccw1 *ccw, struct channel_program *cp, int kfree(idaws); return ERR_PTR(ret); } + + idaws_f1 = (dma32_t *)idaws; + if (cp->orb.cmd.c64) + first_idaw = dma64_to_u64(idaws[0]); + else + first_idaw = dma32_to_u32(idaws_f1[0]); + + /* Unexpected mismatch from earlier read */ + if (first_idaw != cp->guest_iova) { + kfree(idaws); + return ERR_PTR(-EINVAL); + } } else { /* Fabricate an IDAL based off CCW data address */ if (cp->orb.cmd.c64) { @@ -568,7 +594,7 @@ static int ccw_count_idaws(struct ccw1 *ccw, struct vfio_device *vdev = &container_of(cp, struct vfio_ccw_private, cp)->vdev; u64 iova; - int size = cp->orb.cmd.c64 ? sizeof(u64) : sizeof(u32); + int size = get_idaw_size(cp); int ret; int bytes = 1; @@ -592,6 +618,9 @@ static int ccw_count_idaws(struct ccw1 *ccw, iova = dma32_to_u32(ccw->cda); } + /* Save the read address for later */ + cp->guest_iova = iova; + /* Format-1 IDAWs operate on 2K each */ if (!cp->orb.cmd.c64) return idal_2k_nr_words((void *)iova, bytes); @@ -731,11 +760,12 @@ int cp_init(struct channel_program *cp, union orb *orb) vdev->dev, "Prefetching channel program even though prefetch not specified in ORB"); + cp->ccwchain_count = 0; INIT_LIST_HEAD(&cp->ccwchain_list); memcpy(&cp->orb, orb, sizeof(*orb)); /* Build a ccwchain for the first CCW segment */ - ret = ccwchain_handle_ccw(orb->cmd.cpa, cp); + ret = ccwchain_build_ccws(orb->cmd.cpa, cp); if (!ret) cp->initialized = true; @@ -947,17 +977,23 @@ void cp_update_scsw(struct channel_program *cp, union scsw *scsw) */ bool cp_iova_pinned(struct channel_program *cp, u64 iova, u64 length) { + struct vfio_ccw_private *private = + container_of(cp, struct vfio_ccw_private, cp); struct ccwchain *chain; int i; if (!cp->initialized) return false; + mutex_lock(&private->io_mutex); list_for_each_entry(chain, &cp->ccwchain_list, next) { for (i = 0; i < chain->ch_len; i++) - if (page_array_iova_pinned(&chain->ch_pa[i], iova, length)) + if (page_array_iova_pinned(&chain->ch_pa[i], iova, length)) { + mutex_unlock(&private->io_mutex); return true; + } } + mutex_unlock(&private->io_mutex); return false; } diff --git a/drivers/s390/cio/vfio_ccw_cp.h b/drivers/s390/cio/vfio_ccw_cp.h index fc31eb699807..9af98ff12d67 100644 --- a/drivers/s390/cio/vfio_ccw_cp.h +++ b/drivers/s390/cio/vfio_ccw_cp.h @@ -23,11 +23,19 @@ */ #define CCWCHAIN_LEN_MAX 256 +/* + * Maximum number of chains + */ +#define CCWCHAIN_COUNT_MAX 16 + /** * struct channel_program - manage information for channel program * @ccwchain_list: list head of ccwchains * @orb: orb for the currently processed ssch request * @initialized: whether this instance is actually initialized + * @guest_cp: copy of guest channel program + * @ccwchain_count: number of channel program segments (linked by TIC) + * @guest_iova: first data address of a guest channel program * * @ccwchain_list is the head of a ccwchain list, that contents the * translated result of the guest channel program that pointed out by @@ -38,6 +46,8 @@ struct channel_program { union orb orb; bool initialized; struct ccw1 *guest_cp; + unsigned int ccwchain_count; + u64 guest_iova; }; int cp_init(struct channel_program *cp, union orb *orb); diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c index 1a095085bc72..ab6b518cc353 100644 --- a/drivers/s390/cio/vfio_ccw_drv.c +++ b/drivers/s390/cio/vfio_ccw_drv.c @@ -91,6 +91,7 @@ void vfio_ccw_sch_io_todo(struct work_struct *work) is_final = !(scsw_actl(&irb->scsw) & (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT)); + mutex_lock(&private->io_mutex); if (scsw_is_solicited(&irb->scsw)) { cp_update_scsw(&private->cp, &irb->scsw); if (is_final && private->state == VFIO_CCW_STATE_CP_PENDING) { @@ -98,9 +99,7 @@ void vfio_ccw_sch_io_todo(struct work_struct *work) cp_is_finished = true; } } - mutex_lock(&private->io_mutex); memcpy(private->io_region->irb_area, irb, sizeof(*irb)); - mutex_unlock(&private->io_mutex); /* * Reset to IDLE only if processing of a channel program @@ -110,6 +109,7 @@ void vfio_ccw_sch_io_todo(struct work_struct *work) */ if (cp_is_finished) private->state = VFIO_CCW_STATE_IDLE; + mutex_unlock(&private->io_mutex); if (private->io_trigger) eventfd_signal(private->io_trigger); @@ -118,11 +118,25 @@ void vfio_ccw_sch_io_todo(struct work_struct *work) void vfio_ccw_crw_todo(struct work_struct *work) { struct vfio_ccw_private *private; + unsigned long flags; private = container_of(work, struct vfio_ccw_private, crw_work); + spin_lock_irqsave(&private->crw_lock, flags); if (!list_empty(&private->crw) && private->crw_trigger) eventfd_signal(private->crw_trigger); + spin_unlock_irqrestore(&private->crw_lock, flags); +} + +void vfio_ccw_notoper_todo(struct work_struct *work) +{ + struct vfio_ccw_private *private; + + private = container_of(work, struct vfio_ccw_private, notoper_work); + + mutex_lock(&private->io_mutex); + cp_free(&private->cp); + mutex_unlock(&private->io_mutex); } /* @@ -275,6 +289,7 @@ static void vfio_ccw_queue_crw(struct vfio_ccw_private *private, unsigned int rsid) { struct vfio_ccw_crw *crw; + unsigned long flags; /* * If unable to allocate a CRW, just drop the event and @@ -292,7 +307,9 @@ static void vfio_ccw_queue_crw(struct vfio_ccw_private *private, crw->crw.erc = erc; crw->crw.rsid = rsid; + spin_lock_irqsave(&private->crw_lock, flags); list_add_tail(&crw->next, &private->crw); + spin_unlock_irqrestore(&private->crw_lock, flags); queue_work(vfio_ccw_work_q, &private->crw_work); } diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c index 4d7988ea47ef..5fd94e9d5c61 100644 --- a/drivers/s390/cio/vfio_ccw_fsm.c +++ b/drivers/s390/cio/vfio_ccw_fsm.c @@ -170,8 +170,8 @@ static void fsm_notoper(struct vfio_ccw_private *private, css_sched_sch_todo(sch, SCH_TODO_UNREG); private->state = VFIO_CCW_STATE_NOT_OPER; - /* This is usually handled during CLOSE event */ - cp_free(&private->cp); + /* This routine could be called from IRQ context, so defer */ + queue_work(vfio_ccw_work_q, &private->notoper_work); } /* @@ -410,7 +410,11 @@ static void fsm_close(struct vfio_ccw_private *private, private->state = VFIO_CCW_STATE_STANDBY; spin_unlock_irq(&sch->lock); + + mutex_lock(&private->io_mutex); cp_free(&private->cp); + mutex_unlock(&private->io_mutex); + return; err_unlock: diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c index 45ec722d25ea..5ce91285c7d5 100644 --- a/drivers/s390/cio/vfio_ccw_ops.c +++ b/drivers/s390/cio/vfio_ccw_ops.c @@ -54,6 +54,8 @@ static int vfio_ccw_mdev_init_dev(struct vfio_device *vdev) INIT_LIST_HEAD(&private->crw); INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo); INIT_WORK(&private->crw_work, vfio_ccw_crw_todo); + INIT_WORK(&private->notoper_work, vfio_ccw_notoper_todo); + spin_lock_init(&private->crw_lock); private->cp.guest_cp = kzalloc_objs(struct ccw1, CCWCHAIN_LEN_MAX); if (!private->cp.guest_cp) @@ -130,11 +132,28 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev) struct vfio_ccw_private *private = container_of(vdev, struct vfio_ccw_private, vdev); struct vfio_ccw_crw *crw, *temp; + unsigned long flags; + /* + * Ensure these work items are fully drained, so none can + * fire after being released. + * + * notoper_work should have nothing to do here, because only + * open devices could have channel_program resources in use + * and those would be released during close. Nevertheless, + * call flush here as well to be certain anything that was + * allocated is freed. + */ + cancel_work_sync(&private->io_work); + cancel_work_sync(&private->crw_work); + flush_work(&private->notoper_work); + + spin_lock_irqsave(&private->crw_lock, flags); list_for_each_entry_safe(crw, temp, &private->crw, next) { list_del(&crw->next); kfree(crw); } + spin_unlock_irqrestore(&private->crw_lock, flags); kmem_cache_free(vfio_ccw_crw_region, private->crw_region); kmem_cache_free(vfio_ccw_schib_region, private->schib_region); @@ -202,6 +221,19 @@ static void vfio_ccw_mdev_close_device(struct vfio_device *vdev) container_of(vdev, struct vfio_ccw_private, vdev); vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_CLOSE); + + /* + * Ensure these work items are drained, in the event the + * device is re-opened instead of released. + * + * notoper_work needs to be given a chance to run if it + * is queued, so any memory associated with the channel + * program can be returned. + */ + cancel_work_sync(&private->io_work); + cancel_work_sync(&private->crw_work); + flush_work(&private->notoper_work); + vfio_ccw_unregister_dev_regions(private); } @@ -243,6 +275,7 @@ static ssize_t vfio_ccw_mdev_read(struct vfio_device *vdev, return vfio_ccw_mdev_read_io_region(private, buf, count, ppos); default: index -= VFIO_CCW_NUM_REGIONS; + index = array_index_nospec(index, private->num_regions); return private->region[index].ops->read(private, buf, count, ppos); } @@ -295,6 +328,7 @@ static ssize_t vfio_ccw_mdev_write(struct vfio_device *vdev, return vfio_ccw_mdev_write_io_region(private, buf, count, ppos); default: index -= VFIO_CCW_NUM_REGIONS; + index = array_index_nospec(index, private->num_regions); return private->region[index].ops->write(private, buf, count, ppos); } @@ -338,11 +372,8 @@ static int vfio_ccw_mdev_ioctl_get_region_info(struct vfio_device *vdev, VFIO_CCW_NUM_REGIONS + private->num_regions) return -EINVAL; - info->index = array_index_nospec(info->index, - VFIO_CCW_NUM_REGIONS + - private->num_regions); - i = info->index - VFIO_CCW_NUM_REGIONS; + i = array_index_nospec(i, private->num_regions); info->offset = VFIO_CCW_INDEX_TO_OFFSET(info->index); info->size = private->region[i].size; diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h index 0501d4bbcdbd..3bd0171d38d0 100644 --- a/drivers/s390/cio/vfio_ccw_private.h +++ b/drivers/s390/cio/vfio_ccw_private.h @@ -88,7 +88,8 @@ struct vfio_ccw_parent { * @state: internal state of the device * @completion: synchronization helper of the I/O completion * @io_region: MMIO region to input/output I/O arguments/results - * @io_mutex: protect against concurrent update of I/O regions + * @io_mutex: protect against concurrent update of I/O resources + * and @cp lifecycle * @region: additional regions for other subchannel operations * @cmd_region: MMIO region for asynchronous I/O commands other than START * @schib_region: MMIO region for SCHIB information @@ -97,11 +98,14 @@ struct vfio_ccw_parent { * @cp: channel program for the current I/O operation * @irb: irb info received from interrupt * @scsw: scsw info + * @crw_lock: serialization of CRW list information + * @crw: list of Channel Report Word elements * @io_trigger: eventfd ctx for signaling userspace I/O results * @crw_trigger: eventfd ctx for signaling userspace CRW information * @req_trigger: eventfd ctx for signaling userspace to return device * @io_work: work for deferral process of I/O handling * @crw_work: work for deferral process of CRW handling + * @notoper_work: work for deferred processing in not-operational state */ struct vfio_ccw_private { struct vfio_device vdev; @@ -118,6 +122,8 @@ struct vfio_ccw_private { struct channel_program cp; struct irb irb; union scsw scsw; + + spinlock_t crw_lock; struct list_head crw; struct eventfd_ctx *io_trigger; @@ -125,11 +131,13 @@ struct vfio_ccw_private { struct eventfd_ctx *req_trigger; struct work_struct io_work; struct work_struct crw_work; + struct work_struct notoper_work; } __aligned(8); int vfio_ccw_sch_quiesce(struct subchannel *sch); void vfio_ccw_sch_io_todo(struct work_struct *work); void vfio_ccw_crw_todo(struct work_struct *work); +void vfio_ccw_notoper_todo(struct work_struct *work); extern struct mdev_driver vfio_ccw_mdev_driver; diff --git a/drivers/s390/crypto/zcrypt_ccamisc.c b/drivers/s390/crypto/zcrypt_ccamisc.c index 86d2ee78c9f4..d4ce6352b5b2 100644 --- a/drivers/s390/crypto/zcrypt_ccamisc.c +++ b/drivers/s390/crypto/zcrypt_ccamisc.c @@ -15,6 +15,7 @@ #include <linux/module.h> #include <linux/slab.h> #include <linux/random.h> +#include <linux/align.h> #include <asm/zcrypt.h> #include <asm/pkey.h> @@ -267,6 +268,10 @@ EXPORT_SYMBOL(cca_check_sececckeytoken); * block, reply CPRB and reply param block and fill in values * for the common fields. Returns 0 on success or errno value * on failure. + * It is guaranteed that request and a possible param block + * are aligned to a 4 byte boundary. Furthermore if a param + * block is used, the memory allocated for this is rounded up to + * the next multiple of 4 bytes. */ static int alloc_and_prep_cprbmem(size_t paramblen, u8 **p_cprb_mem, @@ -275,7 +280,8 @@ static int alloc_and_prep_cprbmem(size_t paramblen, u32 xflags) { u8 *cprbmem = NULL; - size_t cprbplusparamblen = sizeof(struct CPRBX) + paramblen; + size_t cprbplusparamblen = + ALIGN(sizeof(struct CPRBX), 4) + ALIGN(paramblen, 4); size_t len = 2 * cprbplusparamblen; struct CPRBX *preqcblk, *prepcblk; @@ -302,10 +308,10 @@ static int alloc_and_prep_cprbmem(size_t paramblen, memcpy(preqcblk->func_id, "T2", 2); preqcblk->rpl_msgbl = cprbplusparamblen; if (paramblen) { - preqcblk->req_parmb = - ((u8 __user *)preqcblk) + sizeof(struct CPRBX); - preqcblk->rpl_parmb = - ((u8 __user *)prepcblk) + sizeof(struct CPRBX); + preqcblk->req_parmb = ((u8 __user *)preqcblk) + + ALIGN(sizeof(struct CPRBX), 4); + preqcblk->rpl_parmb = ((u8 __user *)prepcblk) + + ALIGN(sizeof(struct CPRBX), 4); } *p_cprb_mem = cprbmem; @@ -323,8 +329,10 @@ static int alloc_and_prep_cprbmem(size_t paramblen, */ static void free_cprbmem(void *mem, size_t paramblen, bool scrub, u32 xflags) { + size_t cprblen = ALIGN(sizeof(struct CPRBX), 4) + ALIGN(paramblen, 4); + if (mem && scrub) - memzero_explicit(mem, 2 * (sizeof(struct CPRBX) + paramblen)); + memzero_explicit(mem, 2 * cprblen); if (xflags & ZCRYPT_XFLAG_NOMEMALLOC) mempool_free(mem, cprb_mempool); diff --git a/drivers/s390/crypto/zcrypt_ep11misc.c b/drivers/s390/crypto/zcrypt_ep11misc.c index 3dda9589f2b9..2d900ffc5068 100644 --- a/drivers/s390/crypto/zcrypt_ep11misc.c +++ b/drivers/s390/crypto/zcrypt_ep11misc.c @@ -14,6 +14,7 @@ #include <linux/module.h> #include <linux/random.h> #include <linux/slab.h> +#include <linux/align.h> #include <asm/zcrypt.h> #include <asm/pkey.h> #include <crypto/aes.h> @@ -355,21 +356,24 @@ EXPORT_SYMBOL(ep11_check_aes_key); /* * Allocate and prepare ep11 cprb plus additional payload. + * It is guaranteed that the memory is aligned to a 4 byte boundary. + * Furthermore the memory allocation is rounded up to the next + * multiple of 4 bytes (with taking the payload_len into account). */ static void *alloc_cprbmem(size_t payload_len, u32 xflags) { - size_t len = sizeof(struct ep11_cprb) + payload_len; + size_t memlen = ALIGN(sizeof(struct ep11_cprb) + payload_len, 4); struct ep11_cprb *cprb = NULL; if (xflags & ZCRYPT_XFLAG_NOMEMALLOC) { - if (len <= CPRB_MEMPOOL_ITEM_SIZE) + if (memlen <= CPRB_MEMPOOL_ITEM_SIZE) cprb = mempool_alloc_preallocated(cprb_mempool); } else { - cprb = kmalloc(len, GFP_KERNEL); + cprb = kmalloc(memlen, GFP_KERNEL); } if (!cprb) return NULL; - memset(cprb, 0, len); + memset(cprb, 0, memlen); cprb->cprb_len = sizeof(struct ep11_cprb); cprb->cprb_ver_id = 0x04; @@ -385,8 +389,10 @@ static void *alloc_cprbmem(size_t payload_len, u32 xflags) */ static void free_cprbmem(void *mem, size_t payload_len, bool scrub, u32 xflags) { + size_t memlen = ALIGN(sizeof(struct ep11_cprb) + payload_len, 4); + if (mem && scrub) - memzero_explicit(mem, sizeof(struct ep11_cprb) + payload_len); + memzero_explicit(mem, memlen); if (xflags & ZCRYPT_XFLAG_NOMEMALLOC) mempool_free(mem, cprb_mempool); diff --git a/drivers/s390/crypto/zcrypt_msgtype6.c b/drivers/s390/crypto/zcrypt_msgtype6.c index 40f72cdf284d..3df1d676de5d 100644 --- a/drivers/s390/crypto/zcrypt_msgtype6.c +++ b/drivers/s390/crypto/zcrypt_msgtype6.c @@ -19,6 +19,7 @@ #include <linux/slab.h> #include <linux/atomic.h> #include <linux/uaccess.h> +#include <linux/unaligned.h> #include "ap_bus.h" #include "zcrypt_api.h" @@ -34,6 +35,9 @@ #define CEXXC_RESPONSE_TYPE_XCRB 1 #define CEXXC_RESPONSE_TYPE_EP11 2 +/* smallest possible EP11 payload size */ +#define MIN_EP11_PAYLOAD_SIZE 5 + MODULE_AUTHOR("IBM Corporation"); MODULE_DESCRIPTION("Cryptographic Coprocessor (message type 6), " \ "Copyright IBM Corp. 2001, 2023"); @@ -342,49 +346,39 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg, }; } __packed * msg = ap_msg->msg; - int rcblen = CEIL4(xcrb->request_control_blk_length); - int req_sumlen, resp_sumlen; - char *req_data = ap_msg->msg + sizeof(struct type6_hdr) + rcblen; - char *function_code; - - if (CEIL4(xcrb->request_control_blk_length) < - xcrb->request_control_blk_length) - return -EINVAL; /* overflow after alignment*/ + size_t req_cblen, rep_cblen, req_sumlen, rep_sumlen; + char *function_code, *req_data; - /* length checks */ - ap_msg->len = sizeof(struct type6_hdr) + - CEIL4(xcrb->request_control_blk_length) + - xcrb->request_data_length; + /* request length and overflow checks */ + if (xcrb->request_control_blk_length < sizeof(struct CPRBX)) + return -EINVAL; + req_cblen = CEIL4((size_t)xcrb->request_control_blk_length); + if (req_cblen > U32_MAX) + return -EINVAL; + req_sumlen = req_cblen + xcrb->request_data_length; + if (req_sumlen > U32_MAX) + return -EINVAL; + ap_msg->len = sizeof(struct type6_hdr) + req_sumlen; if (ap_msg->len > ap_msg->bufsize) return -EINVAL; - - /* - * Overflow check - * sum must be greater (or equal) than the largest operand - */ - req_sumlen = CEIL4(xcrb->request_control_blk_length) + - xcrb->request_data_length; - if ((CEIL4(xcrb->request_control_blk_length) <= - xcrb->request_data_length) ? + if (req_cblen <= xcrb->request_data_length ? req_sumlen < xcrb->request_data_length : - req_sumlen < CEIL4(xcrb->request_control_blk_length)) { + req_sumlen < req_cblen) { return -EINVAL; } - if (CEIL4(xcrb->reply_control_blk_length) < - xcrb->reply_control_blk_length) - return -EINVAL; /* overflow after alignment*/ - - /* - * Overflow check - * sum must be greater (or equal) than the largest operand - */ - resp_sumlen = CEIL4(xcrb->reply_control_blk_length) + - xcrb->reply_data_length; - if ((CEIL4(xcrb->reply_control_blk_length) <= - xcrb->reply_data_length) ? - resp_sumlen < xcrb->reply_data_length : - resp_sumlen < CEIL4(xcrb->reply_control_blk_length)) { + /* reply length and overflow checks */ + if (xcrb->reply_control_blk_length < sizeof(struct CPRBX)) + return -EINVAL; + rep_cblen = CEIL4((size_t)xcrb->reply_control_blk_length); + if (rep_cblen > U32_MAX) + return -EINVAL; + rep_sumlen = rep_cblen + xcrb->reply_data_length; + if (rep_sumlen > U32_MAX) + return -EINVAL; + if (rep_cblen <= xcrb->reply_data_length ? + rep_sumlen < xcrb->reply_data_length : + rep_sumlen < rep_cblen) { return -EINVAL; } @@ -393,7 +387,7 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg, memcpy(msg->hdr.agent_id, &xcrb->agent_ID, sizeof(xcrb->agent_ID)); msg->hdr.tocardlen1 = xcrb->request_control_blk_length; if (xcrb->request_data_length) { - msg->hdr.offset2 = msg->hdr.offset1 + rcblen; + msg->hdr.offset2 = msg->hdr.offset1 + req_cblen; msg->hdr.tocardlen2 = xcrb->request_data_length; } msg->hdr.fromcardlen1 = xcrb->reply_control_blk_length; @@ -404,8 +398,12 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg, xcrb->request_control_blk_addr, xcrb->request_control_blk_length)) return -EFAULT; - if (msg->cprbx.cprb_len + sizeof(msg->hdr.function_code) > - xcrb->request_control_blk_length) + /* pad tail with 0 up to req_cblen */ + if (xcrb->request_control_blk_length < req_cblen) + memset(msg->userdata + xcrb->request_control_blk_length, + 0, req_cblen - xcrb->request_control_blk_length); + /* copy subfunction code into AP msg type 6 function code field */ + if (msg->cprbx.cprb_len > req_cblen - sizeof(msg->hdr.function_code)) return -EINVAL; function_code = ((unsigned char *)&msg->cprbx) + msg->cprbx.cprb_len; memcpy(msg->hdr.function_code, function_code, @@ -437,20 +435,70 @@ static int xcrb_msg_to_type6cprb_msgx(bool userspace, struct ap_message *ap_msg, } /* copy data block */ - if (xcrb->request_data_length && - z_copy_from_user(userspace, req_data, xcrb->request_data_address, - xcrb->request_data_length)) - return -EFAULT; + if (xcrb->request_data_length) { + req_data = ap_msg->msg + sizeof(struct type6_hdr) + req_cblen; + if (z_copy_from_user(userspace, req_data, + xcrb->request_data_address, + xcrb->request_data_length)) + return -EFAULT; + } return 0; } +/* + * Simple asn1 int reader/decoder helper function + * Returns number of bytes processed or < 0 on failure + * Only accepts int length values of 1, 2 or 4. + */ +static inline int asn1_int_decode(const u8 *buf, size_t intlen, u32 *u) +{ + switch (intlen) { + case 1: + *u = (u32)(*buf); + return 1; + case 2: + *u = (u32)get_unaligned_be16(buf); + return 2; + case 4: + *u = (u32)get_unaligned_be32(buf); + return 4; + default: + return -EINVAL; + } +} + +/* + * Simple asn1 length parse helper function + * Returns number of bytes processed or < 0 on failure + * Only accepts length encoded within the length octet + * or for long form 1, 2 or 4 octet length bytes. + */ +static inline int asn1_length_decode(const u8 *buf, size_t buflen, u32 *u) +{ + int i; + + if (buflen < 1) + return -EINVAL; + + if (*buf < 128) { + *u = (u32)(*buf & 0x7F); + return 1; + } + + i = *buf & 0x7F; + if (--buflen < i) + return -EINVAL; + i = asn1_int_decode(++buf, i, u); + + return i < 0 ? i : i + 1; +} + static int xcrb_msg_to_type6_ep11cprb_msgx(bool userspace, struct ap_message *ap_msg, struct ep11_urb *xcrb, unsigned int *fcode, unsigned int *domain) { - unsigned int lfmt; static struct type6_hdr static_type6_ep11_hdr = { .type = 0x06, .rqid = {0x00, 0x01}, @@ -462,34 +510,32 @@ static int xcrb_msg_to_type6_ep11cprb_msgx(bool userspace, struct ap_message *ap struct { struct type6_hdr hdr; union { - struct { - struct ep11_cprb cprbx; - unsigned char pld_tag; /* fixed value 0x30 */ - unsigned char pld_lenfmt; /* length format */ - } __packed; + struct ep11_cprb cprbx; DECLARE_FLEX_ARRAY(u8, userdata); }; } __packed * msg = ap_msg->msg; - struct pld_hdr { - unsigned char func_tag; /* fixed value 0x4 */ - unsigned char func_len; /* fixed value 0x4 */ - unsigned int func_val; /* function ID */ - unsigned char dom_tag; /* fixed value 0x4 */ - unsigned char dom_len; /* fixed value 0x4 */ - unsigned int dom_val; /* domain id */ - } __packed * payload_hdr = NULL; + size_t req_len, rep_len, pld_len; + unsigned char *pld; + int offs = 0, i; + unsigned int u; - if (CEIL4(xcrb->req_len) < xcrb->req_len) - return -EINVAL; /* overflow after alignment*/ - - /* length checks */ - ap_msg->len = sizeof(struct type6_hdr) + CEIL4(xcrb->req_len); + /* request length and overflow checks */ + if (xcrb->req_len < sizeof(struct ep11_cprb) + MIN_EP11_PAYLOAD_SIZE) + return -EINVAL; + req_len = CEIL4(xcrb->req_len); + if (req_len < xcrb->req_len || req_len > U32_MAX) + return -EINVAL; + ap_msg->len = sizeof(struct type6_hdr) + req_len; if (ap_msg->len > ap_msg->bufsize) return -EINVAL; - if (CEIL4(xcrb->resp_len) < xcrb->resp_len) - return -EINVAL; /* overflow after alignment*/ + /* reply length and overflow checks */ + if (xcrb->resp_len < sizeof(struct ep11_cprb)) + return -EINVAL; + rep_len = CEIL4(xcrb->resp_len); + if (rep_len < xcrb->resp_len || rep_len > U32_MAX) + return -EINVAL; /* prepare type6 header */ msg->hdr = static_type6_ep11_hdr; @@ -498,26 +544,55 @@ static int xcrb_msg_to_type6_ep11cprb_msgx(bool userspace, struct ap_message *ap /* Import CPRB data from the ioctl input parameter */ if (z_copy_from_user(userspace, msg->userdata, - (char __force __user *)xcrb->req, xcrb->req_len)) { + (char __force __user *)xcrb->req, xcrb->req_len)) return -EFAULT; - } + /* pad tail with 0 up to req_len */ + if (xcrb->req_len < req_len) + memset(msg->userdata + xcrb->req_len, 0, + req_len - xcrb->req_len); + + pld = msg->userdata + sizeof(struct ep11_cprb); + pld_len = msg->cprbx.payload_len; + if (pld_len != xcrb->req_len - sizeof(struct ep11_cprb)) + return -EINVAL; + /* + * At this point pld_len is always >= MIN_EP11_PAYLOAD_SIZE + * and the smallest supported asn1 payload is: + * payload tag (1 octet) + * payload length (1-5 octets) + * function tag (1 octet) + * function length (1-5 octets) + * function value (1-4 octets) + */ - if ((msg->pld_lenfmt & 0x80) == 0x80) { /*ext.len.fmt 2 or 3*/ - switch (msg->pld_lenfmt & 0x03) { - case 1: - lfmt = 2; - break; - case 2: - lfmt = 3; - break; - default: - return -EINVAL; - } - } else { - lfmt = 1; /* length format #1 */ - } - payload_hdr = (struct pld_hdr *)((&msg->pld_lenfmt) + lfmt); - *fcode = payload_hdr->func_val & 0xFFFF; + /* payload tag */ + if (pld[offs++] != 0x30) + return -EINVAL; + /* payload length field */ + i = asn1_length_decode(pld + offs, pld_len - offs, &u); + if (i < 0) + return -EINVAL; + offs += i; + if (offs >= pld_len || u > pld_len - offs) + return -EINVAL; + /* function tag */ + if (pld[offs++] != 0x04) + return -EINVAL; + /* function length */ + if (offs >= pld_len) + return -EINVAL; + i = asn1_length_decode(pld + offs, pld_len - offs, &u); + if (i < 0) + return -EINVAL; + offs += i; + if (offs >= pld_len || u > pld_len - offs) + return -EINVAL; + /* function value */ + i = asn1_int_decode(pld + offs, u, &u); + if (i < 0) + return -EINVAL; + offs += i; + *fcode = 0xFFFF & u; /* enable special processing based on the cprbs flags special bit */ if (msg->cprbx.flags & 0x20) @@ -1160,6 +1235,28 @@ int prep_ep11_ap_msg(bool userspace, struct ep11_urb *xcrb, } /* + * Simple asn1 int writer/encoder helper function + * Returns number of bytes processed or < 0 on failure + * Only accepts int length values of 1, 2 or 4. + */ +static inline int asn1_int_encode(u8 *buf, size_t intlen, u32 u) +{ + switch (intlen) { + case 1: + *buf = (u8)u; + return 1; + case 2: + put_unaligned_be16((u16)u, buf); + return 2; + case 4: + put_unaligned_be32((u32)u, buf); + return 4; + default: + return -EINVAL; + } +} + +/* * The request distributor calls this function if it picked the CEX4P * device to handle a send_ep11_cprb request. * @zq: pointer to zcrypt_queue structure that identifies the @@ -1171,51 +1268,94 @@ static long zcrypt_msgtype6_send_ep11_cprb(bool userspace, struct zcrypt_queue * struct ap_message *ap_msg) { int rc; - unsigned int lfmt; struct ap_response_type *resp_type = &ap_msg->response; struct { struct type6_hdr hdr; struct ep11_cprb cprbx; - unsigned char pld_tag; /* fixed value 0x30 */ - unsigned char pld_lenfmt; /* payload length format */ } __packed * msg = ap_msg->msg; - struct pld_hdr { - unsigned char func_tag; /* fixed value 0x4 */ - unsigned char func_len; /* fixed value 0x4 */ - unsigned int func_val; /* function ID */ - unsigned char dom_tag; /* fixed value 0x4 */ - unsigned char dom_len; /* fixed value 0x4 */ - unsigned int dom_val; /* domain id */ - } __packed * payload_hdr = NULL; /* * The target domain field within the cprb body/payload block will be * replaced by the usage domain for non-management commands only. * Therefore we check the first bit of the 'flags' parameter for * management command indication. - * 0 - non management command - * 1 - management command */ - if (!((msg->cprbx.flags & 0x80) == 0x80)) { - msg->cprbx.target_id = (unsigned int) - AP_QID_QUEUE(zq->queue->qid); - - if ((msg->pld_lenfmt & 0x80) == 0x80) { /*ext.len.fmt 2 or 3*/ - switch (msg->pld_lenfmt & 0x03) { - case 1: - lfmt = 2; - break; - case 2: - lfmt = 3; - break; - default: + if (!(msg->cprbx.flags & 0x80)) { + int i, offs = 0; + size_t pld_len; + u8 *pld; + u32 u; + + /* update target field in ep11_cprb */ + msg->cprbx.target_id = (u32)AP_QID_QUEUE(zq->queue->qid); + + /* ptr and length to payload */ + pld = ap_msg->msg + + sizeof(struct type6_hdr) + sizeof(struct ep11_cprb); + pld_len = msg->cprbx.payload_len; + if (pld_len < MIN_EP11_PAYLOAD_SIZE) + return -EINVAL; + + /* + * Parse the asn1 payload, at least we have + * pld tag (1 octet) + * payload length (1-5 octets) + * function tag (1 octet) + * function length (1-5 octets) + * function value (1-4 octets) + * ----- optional fields ----- + * domain tag (1 octet) + * domain length (1-5 octets) + * domain value (1-4 octets) + * ... maybe much more data ... + */ + + /* payload tag */ + if (pld[offs++] != 0x30) + return -EINVAL; + /* payload length field */ + i = asn1_length_decode(pld + offs, pld_len - offs, &u); + if (i < 0) + return -EINVAL; + offs += i; + if (offs >= pld_len || u > pld_len - offs) + return -EINVAL; + /* function tag */ + if (pld[offs++] != 0x04) + return -EINVAL; + /* function length */ + if (offs >= pld_len) + return -EINVAL; + i = asn1_length_decode(pld + offs, pld_len - offs, &u); + if (i < 0) + return -EINVAL; + offs += i; + if (u > pld_len - offs) + return -EINVAL; + /* skip over the function value */ + offs += u; + /* is there some payload left which could hold a domain value ? */ + if (offs < pld_len && pld_len - offs >= 3) { + /* domain tag */ + if (pld[offs++] != 0x04) return -EINVAL; - } - } else { - lfmt = 1; /* length format #1 */ + /* domain length */ + i = asn1_length_decode(pld + offs, pld_len - offs, &u); + if (i < 0) + return -EINVAL; + offs += i; + if (offs >= pld_len || u > pld_len - offs) + return -EINVAL; + /* + * pld[offs] is now at the start of the domain value + * with the value sprawled in u octets. + */ + i = asn1_int_encode(pld + offs, u, + AP_QID_QUEUE(zq->queue->qid)); + if (i < 0) + return -EINVAL; + offs += i; } - payload_hdr = (struct pld_hdr *)((&msg->pld_lenfmt) + lfmt); - payload_hdr->dom_val = AP_QID_QUEUE(zq->queue->qid); } /* |
