mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	target: Fix race for SCF_COMPARE_AND_WRITE_POST checking
This patch addresses a race + use after free where the first stage of COMPARE_AND_WRITE in compare_and_write_callback() is rescheduled after the backend sends the secondary WRITE, resulting in second stage compare_and_write_post() callback completing in target_complete_ok_work() before the first can return. Because current code depends on checking se_cmd->se_cmd_flags after return from se_cmd->transport_complete_callback(), this results in first stage having SCF_COMPARE_AND_WRITE_POST set, which incorrectly falls through into second stage CAW processing code, eventually triggering a NULL pointer dereference due to use after free. To address this bug, pass in a new *post_ret parameter into se_cmd->transport_complete_callback(), and depend upon this value instead of ->se_cmd_flags to determine when to return or fall through into ->queue_status() code for CAW. Cc: Sagi Grimberg <sagig@mellanox.com> Cc: <stable@vger.kernel.org> # v3.12+ Signed-off-by: Nicholas Bellinger <nab@linux-iscsi.org>
This commit is contained in:
		
							parent
							
								
									ca82c2bded
								
							
						
					
					
						commit
						057085e522
					
				
					 3 changed files with 18 additions and 11 deletions
				
			
		| 
						 | 
					@ -371,7 +371,8 @@ sbc_setup_write_same(struct se_cmd *cmd, unsigned char *flags, struct sbc_ops *o
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static sense_reason_t xdreadwrite_callback(struct se_cmd *cmd, bool success)
 | 
					static sense_reason_t xdreadwrite_callback(struct se_cmd *cmd, bool success,
 | 
				
			||||||
 | 
										   int *post_ret)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	unsigned char *buf, *addr;
 | 
						unsigned char *buf, *addr;
 | 
				
			||||||
	struct scatterlist *sg;
 | 
						struct scatterlist *sg;
 | 
				
			||||||
| 
						 | 
					@ -437,7 +438,8 @@ sbc_execute_rw(struct se_cmd *cmd)
 | 
				
			||||||
			       cmd->data_direction);
 | 
								       cmd->data_direction);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static sense_reason_t compare_and_write_post(struct se_cmd *cmd, bool success)
 | 
					static sense_reason_t compare_and_write_post(struct se_cmd *cmd, bool success,
 | 
				
			||||||
 | 
										     int *post_ret)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct se_device *dev = cmd->se_dev;
 | 
						struct se_device *dev = cmd->se_dev;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -447,8 +449,10 @@ static sense_reason_t compare_and_write_post(struct se_cmd *cmd, bool success)
 | 
				
			||||||
	 * sent to the backend driver.
 | 
						 * sent to the backend driver.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	spin_lock_irq(&cmd->t_state_lock);
 | 
						spin_lock_irq(&cmd->t_state_lock);
 | 
				
			||||||
	if ((cmd->transport_state & CMD_T_SENT) && !cmd->scsi_status)
 | 
						if ((cmd->transport_state & CMD_T_SENT) && !cmd->scsi_status) {
 | 
				
			||||||
		cmd->se_cmd_flags |= SCF_COMPARE_AND_WRITE_POST;
 | 
							cmd->se_cmd_flags |= SCF_COMPARE_AND_WRITE_POST;
 | 
				
			||||||
 | 
							*post_ret = 1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	spin_unlock_irq(&cmd->t_state_lock);
 | 
						spin_unlock_irq(&cmd->t_state_lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*
 | 
						/*
 | 
				
			||||||
| 
						 | 
					@ -460,7 +464,8 @@ static sense_reason_t compare_and_write_post(struct se_cmd *cmd, bool success)
 | 
				
			||||||
	return TCM_NO_SENSE;
 | 
						return TCM_NO_SENSE;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static sense_reason_t compare_and_write_callback(struct se_cmd *cmd, bool success)
 | 
					static sense_reason_t compare_and_write_callback(struct se_cmd *cmd, bool success,
 | 
				
			||||||
 | 
											 int *post_ret)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct se_device *dev = cmd->se_dev;
 | 
						struct se_device *dev = cmd->se_dev;
 | 
				
			||||||
	struct scatterlist *write_sg = NULL, *sg;
 | 
						struct scatterlist *write_sg = NULL, *sg;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1658,7 +1658,7 @@ bool target_stop_cmd(struct se_cmd *cmd, unsigned long *flags)
 | 
				
			||||||
void transport_generic_request_failure(struct se_cmd *cmd,
 | 
					void transport_generic_request_failure(struct se_cmd *cmd,
 | 
				
			||||||
		sense_reason_t sense_reason)
 | 
							sense_reason_t sense_reason)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int ret = 0;
 | 
						int ret = 0, post_ret = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pr_debug("-----[ Storage Engine Exception for cmd: %p ITT: 0x%08llx"
 | 
						pr_debug("-----[ Storage Engine Exception for cmd: %p ITT: 0x%08llx"
 | 
				
			||||||
		" CDB: 0x%02x\n", cmd, cmd->tag, cmd->t_task_cdb[0]);
 | 
							" CDB: 0x%02x\n", cmd, cmd->tag, cmd->t_task_cdb[0]);
 | 
				
			||||||
| 
						 | 
					@ -1680,7 +1680,7 @@ void transport_generic_request_failure(struct se_cmd *cmd,
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	if ((cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE) &&
 | 
						if ((cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE) &&
 | 
				
			||||||
	     cmd->transport_complete_callback)
 | 
						     cmd->transport_complete_callback)
 | 
				
			||||||
		cmd->transport_complete_callback(cmd, false);
 | 
							cmd->transport_complete_callback(cmd, false, &post_ret);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (sense_reason) {
 | 
						switch (sense_reason) {
 | 
				
			||||||
	case TCM_NON_EXISTENT_LUN:
 | 
						case TCM_NON_EXISTENT_LUN:
 | 
				
			||||||
| 
						 | 
					@ -2068,11 +2068,13 @@ static void target_complete_ok_work(struct work_struct *work)
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	if (cmd->transport_complete_callback) {
 | 
						if (cmd->transport_complete_callback) {
 | 
				
			||||||
		sense_reason_t rc;
 | 
							sense_reason_t rc;
 | 
				
			||||||
 | 
							bool caw = (cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE);
 | 
				
			||||||
 | 
							bool zero_dl = !(cmd->data_length);
 | 
				
			||||||
 | 
							int post_ret = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		rc = cmd->transport_complete_callback(cmd, true);
 | 
							rc = cmd->transport_complete_callback(cmd, true, &post_ret);
 | 
				
			||||||
		if (!rc && !(cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE_POST)) {
 | 
							if (!rc && !post_ret) {
 | 
				
			||||||
			if ((cmd->se_cmd_flags & SCF_COMPARE_AND_WRITE) &&
 | 
								if (caw && zero_dl)
 | 
				
			||||||
			    !cmd->data_length)
 | 
					 | 
				
			||||||
				goto queue_rsp;
 | 
									goto queue_rsp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -474,7 +474,7 @@ struct se_cmd {
 | 
				
			||||||
	struct completion	cmd_wait_comp;
 | 
						struct completion	cmd_wait_comp;
 | 
				
			||||||
	const struct target_core_fabric_ops *se_tfo;
 | 
						const struct target_core_fabric_ops *se_tfo;
 | 
				
			||||||
	sense_reason_t		(*execute_cmd)(struct se_cmd *);
 | 
						sense_reason_t		(*execute_cmd)(struct se_cmd *);
 | 
				
			||||||
	sense_reason_t (*transport_complete_callback)(struct se_cmd *, bool);
 | 
						sense_reason_t (*transport_complete_callback)(struct se_cmd *, bool, int *);
 | 
				
			||||||
	void			*protocol_data;
 | 
						void			*protocol_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	unsigned char		*t_task_cdb;
 | 
						unsigned char		*t_task_cdb;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue