VYPR
Unrated severityNVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-45975

CVE-2026-45975

Description

In the Linux kernel, the following vulnerability has been resolved:

ublk: use READ_ONCE() to read struct ublksrv_ctrl_cmd

struct ublksrv_ctrl_cmd is part of the io_uring_sqe, which may lie in userspace-mapped memory. It's racy to access its fields with normal loads, as userspace may write to them concurrently. Use READ_ONCE() to copy the ublksrv_ctrl_cmd from the io_uring_sqe to the stack. Use the local copy in place of the one in the io_uring_sqe.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A race condition in the Linux kernel's ublk driver due to unsynchronized reads from userspace-mapped memory is fixed by using READ_ONCE() to safely copy the control command.

Vulnerability

In the Linux kernel's ublk driver, the struct ublksrv_ctrl_cmd is stored in the io_uring submission queue entry (SQE), which resides in userspace-mapped memory. Without proper synchronization, normal C loads can race with concurrent writes from userspace, leading to a time-of-check-time-of-use (TOCTOU) vulnerability. This affects kernel versions prior to the commit ce63eda3e6d36e2c253febee1c8421ecbd1a680e [1].

Exploitation

An attacker needs local access and the ability to submit io_uring requests to a vulnerable kernel. By carefully timing userspace writes to the io_uring SQE during the kernel's execution of the ublk control command handler, they can modify the command fields after the kernel's initial validation but before those fields are used. This requires precise race window exploitation.

Impact

Successful exploitation allows the attacker to alter the ublksrv_ctrl_cmd data after validation, potentially causing the kernel to process maliciously crafted commands. This can lead to information disclosure, denial of service, or privilege escalation, depending on the specific control command manipulated.

Mitigation

The fix, introduced in commit ce63eda3e6d36e2c253febee1c8421ecbd1a680e [1], uses READ_ONCE() to copy the ublksrv_ctrl_cmd into a local stack variable, ensuring consistent reads. Users should update their Linux kernel to a version that includes this patch. No workarounds are available.

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

4
ed9f54cc1e33

ublk: use READ_ONCE() to read struct ublksrv_ctrl_cmd

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitCaleb Sander MateosJan 30, 2026Fixed in 7.0via kernel-cna
1 file changed · +31 26
  • drivers/block/ublk_drv.c+31 26 modified
    diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
    index 01088194c8d35d..8122b012a7ae8d 100644
    --- a/drivers/block/ublk_drv.c
    +++ b/drivers/block/ublk_drv.c
    @@ -4731,12 +4731,11 @@ static int ublk_ctrl_del_dev(struct ublk_device **p_ub, bool wait)
     	return 0;
     }
     
    -static inline void ublk_ctrl_cmd_dump(struct io_uring_cmd *cmd)
    +static inline void ublk_ctrl_cmd_dump(u32 cmd_op,
    +				      const struct ublksrv_ctrl_cmd *header)
     {
    -	const struct ublksrv_ctrl_cmd *header = io_uring_sqe_cmd(cmd->sqe);
    -
     	pr_devel("%s: cmd_op %x, dev id %d qid %d data %llx buf %llx len %u\n",
    -			__func__, cmd->cmd_op, header->dev_id, header->queue_id,
    +			__func__, cmd_op, header->dev_id, header->queue_id,
     			header->data[0], header->addr, header->len);
     }
     
    @@ -5119,9 +5118,8 @@ exit:
     }
     
     static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
    -		struct io_uring_cmd *cmd)
    +		u32 cmd_op, struct ublksrv_ctrl_cmd *header)
     {
    -	struct ublksrv_ctrl_cmd *header = (struct ublksrv_ctrl_cmd *)io_uring_sqe_cmd(cmd->sqe);
     	bool unprivileged = ub->dev_info.flags & UBLK_F_UNPRIVILEGED_DEV;
     	void __user *argp = (void __user *)(unsigned long)header->addr;
     	char *dev_path = NULL;
    @@ -5137,7 +5135,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
     		 * know if the specified device is created as unprivileged
     		 * mode.
     		 */
    -		if (_IOC_NR(cmd->cmd_op) != UBLK_CMD_GET_DEV_INFO2)
    +		if (_IOC_NR(cmd_op) != UBLK_CMD_GET_DEV_INFO2)
     			return 0;
     	}
     
    @@ -5158,7 +5156,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
     		return PTR_ERR(dev_path);
     
     	ret = -EINVAL;
    -	switch (_IOC_NR(cmd->cmd_op)) {
    +	switch (_IOC_NR(cmd_op)) {
     	case UBLK_CMD_GET_DEV_INFO:
     	case UBLK_CMD_GET_DEV_INFO2:
     	case UBLK_CMD_GET_QUEUE_AFFINITY:
    @@ -5188,7 +5186,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
     		header->addr += header->dev_path_len;
     	}
     	pr_devel("%s: dev id %d cmd_op %x uid %d gid %d path %s ret %d\n",
    -			__func__, ub->ub_number, cmd->cmd_op,
    +			__func__, ub->ub_number, cmd_op,
     			ub->dev_info.owner_uid, ub->dev_info.owner_gid,
     			dev_path, ret);
     exit:
    @@ -5212,7 +5210,9 @@ static bool ublk_ctrl_uring_cmd_may_sleep(u32 cmd_op)
     static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		unsigned int issue_flags)
     {
    -	const struct ublksrv_ctrl_cmd *header = io_uring_sqe_cmd(cmd->sqe);
    +	/* May point to userspace-mapped memory */
    +	const struct ublksrv_ctrl_cmd *ub_src = io_uring_sqe_cmd(cmd->sqe);
    +	struct ublksrv_ctrl_cmd header;
     	struct ublk_device *ub = NULL;
     	u32 cmd_op = cmd->cmd_op;
     	int ret = -EINVAL;
    @@ -5224,31 +5224,37 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     	if (!(issue_flags & IO_URING_F_SQE128))
     		return -EINVAL;
     
    -	ublk_ctrl_cmd_dump(cmd);
    +	header.dev_id = READ_ONCE(ub_src->dev_id);
    +	header.queue_id = READ_ONCE(ub_src->queue_id);
    +	header.len = READ_ONCE(ub_src->len);
    +	header.addr = READ_ONCE(ub_src->addr);
    +	header.data[0] = READ_ONCE(ub_src->data[0]);
    +	header.dev_path_len = READ_ONCE(ub_src->dev_path_len);
    +	ublk_ctrl_cmd_dump(cmd_op, &header);
     
     	ret = ublk_check_cmd_op(cmd_op);
     	if (ret)
     		goto out;
     
     	if (cmd_op == UBLK_U_CMD_GET_FEATURES) {
    -		ret = ublk_ctrl_get_features(header);
    +		ret = ublk_ctrl_get_features(&header);
     		goto out;
     	}
     
     	if (_IOC_NR(cmd_op) != UBLK_CMD_ADD_DEV) {
     		ret = -ENODEV;
    -		ub = ublk_get_device_from_id(header->dev_id);
    +		ub = ublk_get_device_from_id(header.dev_id);
     		if (!ub)
     			goto out;
     
    -		ret = ublk_ctrl_uring_cmd_permission(ub, cmd);
    +		ret = ublk_ctrl_uring_cmd_permission(ub, cmd_op, &header);
     		if (ret)
     			goto put_dev;
     	}
     
     	switch (_IOC_NR(cmd_op)) {
     	case UBLK_CMD_START_DEV:
    -		ret = ublk_ctrl_start_dev(ub, header);
    +		ret = ublk_ctrl_start_dev(ub, &header);
     		break;
     	case UBLK_CMD_STOP_DEV:
     		ublk_ctrl_stop_dev(ub);
    @@ -5256,10 +5262,10 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		break;
     	case UBLK_CMD_GET_DEV_INFO:
     	case UBLK_CMD_GET_DEV_INFO2:
    -		ret = ublk_ctrl_get_dev_info(ub, header);
    +		ret = ublk_ctrl_get_dev_info(ub, &header);
     		break;
     	case UBLK_CMD_ADD_DEV:
    -		ret = ublk_ctrl_add_dev(header);
    +		ret = ublk_ctrl_add_dev(&header);
     		break;
     	case UBLK_CMD_DEL_DEV:
     		ret = ublk_ctrl_del_dev(&ub, true);
    @@ -5268,26 +5274,26 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		ret = ublk_ctrl_del_dev(&ub, false);
     		break;
     	case UBLK_CMD_GET_QUEUE_AFFINITY:
    -		ret = ublk_ctrl_get_queue_affinity(ub, header);
    +		ret = ublk_ctrl_get_queue_affinity(ub, &header);
     		break;
     	case UBLK_CMD_GET_PARAMS:
    -		ret = ublk_ctrl_get_params(ub, header);
    +		ret = ublk_ctrl_get_params(ub, &header);
     		break;
     	case UBLK_CMD_SET_PARAMS:
    -		ret = ublk_ctrl_set_params(ub, header);
    +		ret = ublk_ctrl_set_params(ub, &header);
     		break;
     	case UBLK_CMD_START_USER_RECOVERY:
    -		ret = ublk_ctrl_start_recovery(ub, header);
    +		ret = ublk_ctrl_start_recovery(ub, &header);
     		break;
     	case UBLK_CMD_END_USER_RECOVERY:
    -		ret = ublk_ctrl_end_recovery(ub, header);
    +		ret = ublk_ctrl_end_recovery(ub, &header);
     		break;
     	case UBLK_CMD_UPDATE_SIZE:
    -		ublk_ctrl_set_size(ub, header);
    +		ublk_ctrl_set_size(ub, &header);
     		ret = 0;
     		break;
     	case UBLK_CMD_QUIESCE_DEV:
    -		ret = ublk_ctrl_quiesce_dev(ub, header);
    +		ret = ublk_ctrl_quiesce_dev(ub, &header);
     		break;
     	case UBLK_CMD_TRY_STOP_DEV:
     		ret = ublk_ctrl_try_stop_dev(ub);
    @@ -5302,7 +5308,7 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		ublk_put_device(ub);
      out:
     	pr_devel("%s: cmd done ret %d cmd_op %x, dev id %d qid %d\n",
    -			__func__, ret, cmd->cmd_op, header->dev_id, header->queue_id);
    +			__func__, ret, cmd_op, header.dev_id, header.queue_id);
     	return ret;
     }
     
    -- 
    cgit 1.3-korg
    
    
    
ce63eda3e6d3

ublk: use READ_ONCE() to read struct ublksrv_ctrl_cmd

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitCaleb Sander MateosJan 30, 2026Fixed in 6.19.4via kernel-cna
1 file changed · +31 26
  • drivers/block/ublk_drv.c+31 26 modified
    diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
    index 0ce0e537fb8504..06e0790150d1d1 100644
    --- a/drivers/block/ublk_drv.c
    +++ b/drivers/block/ublk_drv.c
    @@ -3311,12 +3311,11 @@ static int ublk_ctrl_del_dev(struct ublk_device **p_ub, bool wait)
     	return 0;
     }
     
    -static inline void ublk_ctrl_cmd_dump(struct io_uring_cmd *cmd)
    +static inline void ublk_ctrl_cmd_dump(u32 cmd_op,
    +				      const struct ublksrv_ctrl_cmd *header)
     {
    -	const struct ublksrv_ctrl_cmd *header = io_uring_sqe_cmd(cmd->sqe);
    -
     	pr_devel("%s: cmd_op %x, dev id %d qid %d data %llx buf %llx len %u\n",
    -			__func__, cmd->cmd_op, header->dev_id, header->queue_id,
    +			__func__, cmd_op, header->dev_id, header->queue_id,
     			header->data[0], header->addr, header->len);
     }
     
    @@ -3685,9 +3684,8 @@ exit:
     }
     
     static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
    -		struct io_uring_cmd *cmd)
    +		u32 cmd_op, struct ublksrv_ctrl_cmd *header)
     {
    -	struct ublksrv_ctrl_cmd *header = (struct ublksrv_ctrl_cmd *)io_uring_sqe_cmd(cmd->sqe);
     	bool unprivileged = ub->dev_info.flags & UBLK_F_UNPRIVILEGED_DEV;
     	void __user *argp = (void __user *)(unsigned long)header->addr;
     	char *dev_path = NULL;
    @@ -3703,7 +3701,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
     		 * know if the specified device is created as unprivileged
     		 * mode.
     		 */
    -		if (_IOC_NR(cmd->cmd_op) != UBLK_CMD_GET_DEV_INFO2)
    +		if (_IOC_NR(cmd_op) != UBLK_CMD_GET_DEV_INFO2)
     			return 0;
     	}
     
    @@ -3724,7 +3722,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
     		return PTR_ERR(dev_path);
     
     	ret = -EINVAL;
    -	switch (_IOC_NR(cmd->cmd_op)) {
    +	switch (_IOC_NR(cmd_op)) {
     	case UBLK_CMD_GET_DEV_INFO:
     	case UBLK_CMD_GET_DEV_INFO2:
     	case UBLK_CMD_GET_QUEUE_AFFINITY:
    @@ -3753,7 +3751,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
     		header->addr += header->dev_path_len;
     	}
     	pr_devel("%s: dev id %d cmd_op %x uid %d gid %d path %s ret %d\n",
    -			__func__, ub->ub_number, cmd->cmd_op,
    +			__func__, ub->ub_number, cmd_op,
     			ub->dev_info.owner_uid, ub->dev_info.owner_gid,
     			dev_path, ret);
     exit:
    @@ -3777,7 +3775,9 @@ static bool ublk_ctrl_uring_cmd_may_sleep(u32 cmd_op)
     static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		unsigned int issue_flags)
     {
    -	const struct ublksrv_ctrl_cmd *header = io_uring_sqe_cmd(cmd->sqe);
    +	/* May point to userspace-mapped memory */
    +	const struct ublksrv_ctrl_cmd *ub_src = io_uring_sqe_cmd(cmd->sqe);
    +	struct ublksrv_ctrl_cmd header;
     	struct ublk_device *ub = NULL;
     	u32 cmd_op = cmd->cmd_op;
     	int ret = -EINVAL;
    @@ -3789,41 +3789,47 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     	if (!(issue_flags & IO_URING_F_SQE128))
     		return -EINVAL;
     
    -	ublk_ctrl_cmd_dump(cmd);
    +	header.dev_id = READ_ONCE(ub_src->dev_id);
    +	header.queue_id = READ_ONCE(ub_src->queue_id);
    +	header.len = READ_ONCE(ub_src->len);
    +	header.addr = READ_ONCE(ub_src->addr);
    +	header.data[0] = READ_ONCE(ub_src->data[0]);
    +	header.dev_path_len = READ_ONCE(ub_src->dev_path_len);
    +	ublk_ctrl_cmd_dump(cmd_op, &header);
     
     	ret = ublk_check_cmd_op(cmd_op);
     	if (ret)
     		goto out;
     
     	if (cmd_op == UBLK_U_CMD_GET_FEATURES) {
    -		ret = ublk_ctrl_get_features(header);
    +		ret = ublk_ctrl_get_features(&header);
     		goto out;
     	}
     
     	if (_IOC_NR(cmd_op) != UBLK_CMD_ADD_DEV) {
     		ret = -ENODEV;
    -		ub = ublk_get_device_from_id(header->dev_id);
    +		ub = ublk_get_device_from_id(header.dev_id);
     		if (!ub)
     			goto out;
     
    -		ret = ublk_ctrl_uring_cmd_permission(ub, cmd);
    +		ret = ublk_ctrl_uring_cmd_permission(ub, cmd_op, &header);
     		if (ret)
     			goto put_dev;
     	}
     
     	switch (_IOC_NR(cmd_op)) {
     	case UBLK_CMD_START_DEV:
    -		ret = ublk_ctrl_start_dev(ub, header);
    +		ret = ublk_ctrl_start_dev(ub, &header);
     		break;
     	case UBLK_CMD_STOP_DEV:
     		ret = ublk_ctrl_stop_dev(ub);
     		break;
     	case UBLK_CMD_GET_DEV_INFO:
     	case UBLK_CMD_GET_DEV_INFO2:
    -		ret = ublk_ctrl_get_dev_info(ub, header);
    +		ret = ublk_ctrl_get_dev_info(ub, &header);
     		break;
     	case UBLK_CMD_ADD_DEV:
    -		ret = ublk_ctrl_add_dev(header);
    +		ret = ublk_ctrl_add_dev(&header);
     		break;
     	case UBLK_CMD_DEL_DEV:
     		ret = ublk_ctrl_del_dev(&ub, true);
    @@ -3832,26 +3838,26 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		ret = ublk_ctrl_del_dev(&ub, false);
     		break;
     	case UBLK_CMD_GET_QUEUE_AFFINITY:
    -		ret = ublk_ctrl_get_queue_affinity(ub, header);
    +		ret = ublk_ctrl_get_queue_affinity(ub, &header);
     		break;
     	case UBLK_CMD_GET_PARAMS:
    -		ret = ublk_ctrl_get_params(ub, header);
    +		ret = ublk_ctrl_get_params(ub, &header);
     		break;
     	case UBLK_CMD_SET_PARAMS:
    -		ret = ublk_ctrl_set_params(ub, header);
    +		ret = ublk_ctrl_set_params(ub, &header);
     		break;
     	case UBLK_CMD_START_USER_RECOVERY:
    -		ret = ublk_ctrl_start_recovery(ub, header);
    +		ret = ublk_ctrl_start_recovery(ub, &header);
     		break;
     	case UBLK_CMD_END_USER_RECOVERY:
    -		ret = ublk_ctrl_end_recovery(ub, header);
    +		ret = ublk_ctrl_end_recovery(ub, &header);
     		break;
     	case UBLK_CMD_UPDATE_SIZE:
    -		ublk_ctrl_set_size(ub, header);
    +		ublk_ctrl_set_size(ub, &header);
     		ret = 0;
     		break;
     	case UBLK_CMD_QUIESCE_DEV:
    -		ret = ublk_ctrl_quiesce_dev(ub, header);
    +		ret = ublk_ctrl_quiesce_dev(ub, &header);
     		break;
     	default:
     		ret = -EOPNOTSUPP;
    @@ -3863,7 +3869,7 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		ublk_put_device(ub);
      out:
     	pr_devel("%s: cmd done ret %d cmd_op %x, dev id %d qid %d\n",
    -			__func__, ret, cmd->cmd_op, header->dev_id, header->queue_id);
    +			__func__, ret, cmd_op, header.dev_id, header.queue_id);
     	return ret;
     }
     
    -- 
    cgit 1.3-korg
    
    
    
ce63eda3e6d3

ublk: use READ_ONCE() to read struct ublksrv_ctrl_cmd

1 file changed · +31 26
  • drivers/block/ublk_drv.c+31 26 modified
    diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
    index 0ce0e537fb8504..06e0790150d1d1 100644
    --- a/drivers/block/ublk_drv.c
    +++ b/drivers/block/ublk_drv.c
    @@ -3311,12 +3311,11 @@ static int ublk_ctrl_del_dev(struct ublk_device **p_ub, bool wait)
     	return 0;
     }
     
    -static inline void ublk_ctrl_cmd_dump(struct io_uring_cmd *cmd)
    +static inline void ublk_ctrl_cmd_dump(u32 cmd_op,
    +				      const struct ublksrv_ctrl_cmd *header)
     {
    -	const struct ublksrv_ctrl_cmd *header = io_uring_sqe_cmd(cmd->sqe);
    -
     	pr_devel("%s: cmd_op %x, dev id %d qid %d data %llx buf %llx len %u\n",
    -			__func__, cmd->cmd_op, header->dev_id, header->queue_id,
    +			__func__, cmd_op, header->dev_id, header->queue_id,
     			header->data[0], header->addr, header->len);
     }
     
    @@ -3685,9 +3684,8 @@ exit:
     }
     
     static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
    -		struct io_uring_cmd *cmd)
    +		u32 cmd_op, struct ublksrv_ctrl_cmd *header)
     {
    -	struct ublksrv_ctrl_cmd *header = (struct ublksrv_ctrl_cmd *)io_uring_sqe_cmd(cmd->sqe);
     	bool unprivileged = ub->dev_info.flags & UBLK_F_UNPRIVILEGED_DEV;
     	void __user *argp = (void __user *)(unsigned long)header->addr;
     	char *dev_path = NULL;
    @@ -3703,7 +3701,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
     		 * know if the specified device is created as unprivileged
     		 * mode.
     		 */
    -		if (_IOC_NR(cmd->cmd_op) != UBLK_CMD_GET_DEV_INFO2)
    +		if (_IOC_NR(cmd_op) != UBLK_CMD_GET_DEV_INFO2)
     			return 0;
     	}
     
    @@ -3724,7 +3722,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
     		return PTR_ERR(dev_path);
     
     	ret = -EINVAL;
    -	switch (_IOC_NR(cmd->cmd_op)) {
    +	switch (_IOC_NR(cmd_op)) {
     	case UBLK_CMD_GET_DEV_INFO:
     	case UBLK_CMD_GET_DEV_INFO2:
     	case UBLK_CMD_GET_QUEUE_AFFINITY:
    @@ -3753,7 +3751,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
     		header->addr += header->dev_path_len;
     	}
     	pr_devel("%s: dev id %d cmd_op %x uid %d gid %d path %s ret %d\n",
    -			__func__, ub->ub_number, cmd->cmd_op,
    +			__func__, ub->ub_number, cmd_op,
     			ub->dev_info.owner_uid, ub->dev_info.owner_gid,
     			dev_path, ret);
     exit:
    @@ -3777,7 +3775,9 @@ static bool ublk_ctrl_uring_cmd_may_sleep(u32 cmd_op)
     static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		unsigned int issue_flags)
     {
    -	const struct ublksrv_ctrl_cmd *header = io_uring_sqe_cmd(cmd->sqe);
    +	/* May point to userspace-mapped memory */
    +	const struct ublksrv_ctrl_cmd *ub_src = io_uring_sqe_cmd(cmd->sqe);
    +	struct ublksrv_ctrl_cmd header;
     	struct ublk_device *ub = NULL;
     	u32 cmd_op = cmd->cmd_op;
     	int ret = -EINVAL;
    @@ -3789,41 +3789,47 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     	if (!(issue_flags & IO_URING_F_SQE128))
     		return -EINVAL;
     
    -	ublk_ctrl_cmd_dump(cmd);
    +	header.dev_id = READ_ONCE(ub_src->dev_id);
    +	header.queue_id = READ_ONCE(ub_src->queue_id);
    +	header.len = READ_ONCE(ub_src->len);
    +	header.addr = READ_ONCE(ub_src->addr);
    +	header.data[0] = READ_ONCE(ub_src->data[0]);
    +	header.dev_path_len = READ_ONCE(ub_src->dev_path_len);
    +	ublk_ctrl_cmd_dump(cmd_op, &header);
     
     	ret = ublk_check_cmd_op(cmd_op);
     	if (ret)
     		goto out;
     
     	if (cmd_op == UBLK_U_CMD_GET_FEATURES) {
    -		ret = ublk_ctrl_get_features(header);
    +		ret = ublk_ctrl_get_features(&header);
     		goto out;
     	}
     
     	if (_IOC_NR(cmd_op) != UBLK_CMD_ADD_DEV) {
     		ret = -ENODEV;
    -		ub = ublk_get_device_from_id(header->dev_id);
    +		ub = ublk_get_device_from_id(header.dev_id);
     		if (!ub)
     			goto out;
     
    -		ret = ublk_ctrl_uring_cmd_permission(ub, cmd);
    +		ret = ublk_ctrl_uring_cmd_permission(ub, cmd_op, &header);
     		if (ret)
     			goto put_dev;
     	}
     
     	switch (_IOC_NR(cmd_op)) {
     	case UBLK_CMD_START_DEV:
    -		ret = ublk_ctrl_start_dev(ub, header);
    +		ret = ublk_ctrl_start_dev(ub, &header);
     		break;
     	case UBLK_CMD_STOP_DEV:
     		ret = ublk_ctrl_stop_dev(ub);
     		break;
     	case UBLK_CMD_GET_DEV_INFO:
     	case UBLK_CMD_GET_DEV_INFO2:
    -		ret = ublk_ctrl_get_dev_info(ub, header);
    +		ret = ublk_ctrl_get_dev_info(ub, &header);
     		break;
     	case UBLK_CMD_ADD_DEV:
    -		ret = ublk_ctrl_add_dev(header);
    +		ret = ublk_ctrl_add_dev(&header);
     		break;
     	case UBLK_CMD_DEL_DEV:
     		ret = ublk_ctrl_del_dev(&ub, true);
    @@ -3832,26 +3838,26 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		ret = ublk_ctrl_del_dev(&ub, false);
     		break;
     	case UBLK_CMD_GET_QUEUE_AFFINITY:
    -		ret = ublk_ctrl_get_queue_affinity(ub, header);
    +		ret = ublk_ctrl_get_queue_affinity(ub, &header);
     		break;
     	case UBLK_CMD_GET_PARAMS:
    -		ret = ublk_ctrl_get_params(ub, header);
    +		ret = ublk_ctrl_get_params(ub, &header);
     		break;
     	case UBLK_CMD_SET_PARAMS:
    -		ret = ublk_ctrl_set_params(ub, header);
    +		ret = ublk_ctrl_set_params(ub, &header);
     		break;
     	case UBLK_CMD_START_USER_RECOVERY:
    -		ret = ublk_ctrl_start_recovery(ub, header);
    +		ret = ublk_ctrl_start_recovery(ub, &header);
     		break;
     	case UBLK_CMD_END_USER_RECOVERY:
    -		ret = ublk_ctrl_end_recovery(ub, header);
    +		ret = ublk_ctrl_end_recovery(ub, &header);
     		break;
     	case UBLK_CMD_UPDATE_SIZE:
    -		ublk_ctrl_set_size(ub, header);
    +		ublk_ctrl_set_size(ub, &header);
     		ret = 0;
     		break;
     	case UBLK_CMD_QUIESCE_DEV:
    -		ret = ublk_ctrl_quiesce_dev(ub, header);
    +		ret = ublk_ctrl_quiesce_dev(ub, &header);
     		break;
     	default:
     		ret = -EOPNOTSUPP;
    @@ -3863,7 +3869,7 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		ublk_put_device(ub);
      out:
     	pr_devel("%s: cmd done ret %d cmd_op %x, dev id %d qid %d\n",
    -			__func__, ret, cmd->cmd_op, header->dev_id, header->queue_id);
    +			__func__, ret, cmd_op, header.dev_id, header.queue_id);
     	return ret;
     }
     
    -- 
    cgit 1.3-korg
    
    
    
ed9f54cc1e33

ublk: use READ_ONCE() to read struct ublksrv_ctrl_cmd

1 file changed · +31 26
  • drivers/block/ublk_drv.c+31 26 modified
    diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c
    index 01088194c8d35d..8122b012a7ae8d 100644
    --- a/drivers/block/ublk_drv.c
    +++ b/drivers/block/ublk_drv.c
    @@ -4731,12 +4731,11 @@ static int ublk_ctrl_del_dev(struct ublk_device **p_ub, bool wait)
     	return 0;
     }
     
    -static inline void ublk_ctrl_cmd_dump(struct io_uring_cmd *cmd)
    +static inline void ublk_ctrl_cmd_dump(u32 cmd_op,
    +				      const struct ublksrv_ctrl_cmd *header)
     {
    -	const struct ublksrv_ctrl_cmd *header = io_uring_sqe_cmd(cmd->sqe);
    -
     	pr_devel("%s: cmd_op %x, dev id %d qid %d data %llx buf %llx len %u\n",
    -			__func__, cmd->cmd_op, header->dev_id, header->queue_id,
    +			__func__, cmd_op, header->dev_id, header->queue_id,
     			header->data[0], header->addr, header->len);
     }
     
    @@ -5119,9 +5118,8 @@ exit:
     }
     
     static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
    -		struct io_uring_cmd *cmd)
    +		u32 cmd_op, struct ublksrv_ctrl_cmd *header)
     {
    -	struct ublksrv_ctrl_cmd *header = (struct ublksrv_ctrl_cmd *)io_uring_sqe_cmd(cmd->sqe);
     	bool unprivileged = ub->dev_info.flags & UBLK_F_UNPRIVILEGED_DEV;
     	void __user *argp = (void __user *)(unsigned long)header->addr;
     	char *dev_path = NULL;
    @@ -5137,7 +5135,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
     		 * know if the specified device is created as unprivileged
     		 * mode.
     		 */
    -		if (_IOC_NR(cmd->cmd_op) != UBLK_CMD_GET_DEV_INFO2)
    +		if (_IOC_NR(cmd_op) != UBLK_CMD_GET_DEV_INFO2)
     			return 0;
     	}
     
    @@ -5158,7 +5156,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
     		return PTR_ERR(dev_path);
     
     	ret = -EINVAL;
    -	switch (_IOC_NR(cmd->cmd_op)) {
    +	switch (_IOC_NR(cmd_op)) {
     	case UBLK_CMD_GET_DEV_INFO:
     	case UBLK_CMD_GET_DEV_INFO2:
     	case UBLK_CMD_GET_QUEUE_AFFINITY:
    @@ -5188,7 +5186,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub,
     		header->addr += header->dev_path_len;
     	}
     	pr_devel("%s: dev id %d cmd_op %x uid %d gid %d path %s ret %d\n",
    -			__func__, ub->ub_number, cmd->cmd_op,
    +			__func__, ub->ub_number, cmd_op,
     			ub->dev_info.owner_uid, ub->dev_info.owner_gid,
     			dev_path, ret);
     exit:
    @@ -5212,7 +5210,9 @@ static bool ublk_ctrl_uring_cmd_may_sleep(u32 cmd_op)
     static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		unsigned int issue_flags)
     {
    -	const struct ublksrv_ctrl_cmd *header = io_uring_sqe_cmd(cmd->sqe);
    +	/* May point to userspace-mapped memory */
    +	const struct ublksrv_ctrl_cmd *ub_src = io_uring_sqe_cmd(cmd->sqe);
    +	struct ublksrv_ctrl_cmd header;
     	struct ublk_device *ub = NULL;
     	u32 cmd_op = cmd->cmd_op;
     	int ret = -EINVAL;
    @@ -5224,31 +5224,37 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     	if (!(issue_flags & IO_URING_F_SQE128))
     		return -EINVAL;
     
    -	ublk_ctrl_cmd_dump(cmd);
    +	header.dev_id = READ_ONCE(ub_src->dev_id);
    +	header.queue_id = READ_ONCE(ub_src->queue_id);
    +	header.len = READ_ONCE(ub_src->len);
    +	header.addr = READ_ONCE(ub_src->addr);
    +	header.data[0] = READ_ONCE(ub_src->data[0]);
    +	header.dev_path_len = READ_ONCE(ub_src->dev_path_len);
    +	ublk_ctrl_cmd_dump(cmd_op, &header);
     
     	ret = ublk_check_cmd_op(cmd_op);
     	if (ret)
     		goto out;
     
     	if (cmd_op == UBLK_U_CMD_GET_FEATURES) {
    -		ret = ublk_ctrl_get_features(header);
    +		ret = ublk_ctrl_get_features(&header);
     		goto out;
     	}
     
     	if (_IOC_NR(cmd_op) != UBLK_CMD_ADD_DEV) {
     		ret = -ENODEV;
    -		ub = ublk_get_device_from_id(header->dev_id);
    +		ub = ublk_get_device_from_id(header.dev_id);
     		if (!ub)
     			goto out;
     
    -		ret = ublk_ctrl_uring_cmd_permission(ub, cmd);
    +		ret = ublk_ctrl_uring_cmd_permission(ub, cmd_op, &header);
     		if (ret)
     			goto put_dev;
     	}
     
     	switch (_IOC_NR(cmd_op)) {
     	case UBLK_CMD_START_DEV:
    -		ret = ublk_ctrl_start_dev(ub, header);
    +		ret = ublk_ctrl_start_dev(ub, &header);
     		break;
     	case UBLK_CMD_STOP_DEV:
     		ublk_ctrl_stop_dev(ub);
    @@ -5256,10 +5262,10 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		break;
     	case UBLK_CMD_GET_DEV_INFO:
     	case UBLK_CMD_GET_DEV_INFO2:
    -		ret = ublk_ctrl_get_dev_info(ub, header);
    +		ret = ublk_ctrl_get_dev_info(ub, &header);
     		break;
     	case UBLK_CMD_ADD_DEV:
    -		ret = ublk_ctrl_add_dev(header);
    +		ret = ublk_ctrl_add_dev(&header);
     		break;
     	case UBLK_CMD_DEL_DEV:
     		ret = ublk_ctrl_del_dev(&ub, true);
    @@ -5268,26 +5274,26 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		ret = ublk_ctrl_del_dev(&ub, false);
     		break;
     	case UBLK_CMD_GET_QUEUE_AFFINITY:
    -		ret = ublk_ctrl_get_queue_affinity(ub, header);
    +		ret = ublk_ctrl_get_queue_affinity(ub, &header);
     		break;
     	case UBLK_CMD_GET_PARAMS:
    -		ret = ublk_ctrl_get_params(ub, header);
    +		ret = ublk_ctrl_get_params(ub, &header);
     		break;
     	case UBLK_CMD_SET_PARAMS:
    -		ret = ublk_ctrl_set_params(ub, header);
    +		ret = ublk_ctrl_set_params(ub, &header);
     		break;
     	case UBLK_CMD_START_USER_RECOVERY:
    -		ret = ublk_ctrl_start_recovery(ub, header);
    +		ret = ublk_ctrl_start_recovery(ub, &header);
     		break;
     	case UBLK_CMD_END_USER_RECOVERY:
    -		ret = ublk_ctrl_end_recovery(ub, header);
    +		ret = ublk_ctrl_end_recovery(ub, &header);
     		break;
     	case UBLK_CMD_UPDATE_SIZE:
    -		ublk_ctrl_set_size(ub, header);
    +		ublk_ctrl_set_size(ub, &header);
     		ret = 0;
     		break;
     	case UBLK_CMD_QUIESCE_DEV:
    -		ret = ublk_ctrl_quiesce_dev(ub, header);
    +		ret = ublk_ctrl_quiesce_dev(ub, &header);
     		break;
     	case UBLK_CMD_TRY_STOP_DEV:
     		ret = ublk_ctrl_try_stop_dev(ub);
    @@ -5302,7 +5308,7 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd,
     		ublk_put_device(ub);
      out:
     	pr_devel("%s: cmd done ret %d cmd_op %x, dev id %d qid %d\n",
    -			__func__, ret, cmd->cmd_op, header->dev_id, header->queue_id);
    +			__func__, ret, cmd_op, header.dev_id, header.queue_id);
     	return ret;
     }
     
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Missing READ_ONCE() when reading struct ublksrv_ctrl_cmd from userspace-mapped io_uring SQE memory, allowing a TOCTOU race condition."

Attack vector

An attacker with access to the ublk control uring command interface can concurrently modify fields of `struct ublksrv_ctrl_cmd` (such as `dev_id`, `addr`, `len`, `data[0]`, `queue_id`, `dev_path_len`) while the kernel reads them, because the structure resides in userspace-mapped memory. Because the kernel used plain C loads without `READ_ONCE()`, a malicious userspace thread could alter these fields between validation and use, potentially causing the kernel to operate on a different device ID, address, or length than was originally checked [patch_id=2660803]. This is a classic time-of-check/time-of-use (TOCTOU) race condition affecting the io_uring command path.

Affected code

The vulnerability is in `drivers/block/ublk_drv.c` in the function `ublk_ctrl_uring_cmd()` and its callees. The `struct ublksrv_ctrl_cmd` is read directly from the io_uring SQE via `io_uring_sqe_cmd(cmd->sqe)`, which may point to userspace-mapped memory [patch_id=2660803].

What the fix does

The patch introduces a local stack copy of `struct ublksrv_ctrl_cmd` named `header` and populates each field using `READ_ONCE()` from the userspace-mapped source (`ub_src`). All subsequent code uses this local copy instead of dereferencing the shared SQE pointer. This eliminates the TOCTOU race because the kernel now operates on a snapshot taken atomically per-field, preventing concurrent userspace writes from changing values after validation [patch_id=2660803]. The helper functions `ublk_ctrl_cmd_dump()` and `ublk_ctrl_uring_cmd_permission()` are also refactored to accept the local copy rather than re-reading from the SQE.

Preconditions

  • authAttacker must be able to submit io_uring commands to the ublk control device
  • configThe io_uring SQE must be in userspace-mapped memory (the default configuration)
  • inputAttacker must be able to concurrently write to the SQE memory from another thread or process

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.