iscsi-target: deadlock because of iscsit_get_tpg()
Subsystems (2)
Hello,
Our tool reports a potential double lock because of quite strange code in iscsit_get_tpg().
drivers/target/iscsi/iscsi_target_tpg.c:
int iscsit_get_tpg(
struct iscsi_portal_group *tpg)
{
int ret;
ret = mutex_lock_interruptible(&tpg->tpg_access_lock);
return ((ret != 0) || signal_pending(current)) ? -1 : 0;
}If mutex_lock_interruptible() successfully acquires the mutex, but there is a pending signal, the function returns error, but it leaves the mutex held. Callers do not expect such behaviour that can lead to a deadlock.
Why the check for pending signal is needed here?
Found by Linux Driver Verification project (linuxtesting.org).
Similar dangerous pattern presents in a couple of other places:
drivers/target/iscsi/iscsi_target.c:
int iscsit_access_np(struct iscsi_np *np, struct iscsi_portal_group *tpg) {
...
ret = down_interruptible(&tpg->np_login_sem); if ((ret != 0) || signal_pending(current)) return -1;
drivers/target/target_core_sbc.c:
static sense_reason_tsbc_compare_and_write(struct se_cmd *cmd)
{
...
rc = down_interruptible(&dev->caw_sem);
if ((rc != 0) || signal_pending(current)) {
cmd->transport_complete_callback = NULL;
return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
}--
Alexey Khoroshilov
Linux Verification Center, ISPRAS
Hi Alexey,
Quoted 15 lines · Alexey Khoroshilov
Hello,
Our tool reports a potential double lock because of quite strange code in
iscsit_get_tpg().
drivers/target/iscsi/iscsi_target_tpg.c:
int iscsit_get_tpg(
structiscsi_portal_group*tpg){ int ret; ret = mutex_lock_interruptible(&tpg->tpg_access_lock); return ((ret != 0) || signal_pending(current)) ? -1 : 0; }
Quoted 6 lines
If
mutex_lock_interruptible()successfully acquires the mutex, but there is a pending signal, the function returns error, but it leaves the mutex held. Callers do not expect such behaviour that can lead to a deadlock.Why the check for pending signal is needed here?
Once upon a time, this was an un-interruptible lock. The signal pending was a check to allow userspace to break out of the operation with SIGINT.
AFAICT, there's no reason why this is necessary anymore.
Quoted 19 lines
Found by Linux Driver Verification project (linuxtesting.org).
Similar dangerous pattern presents in a couple of other places:
drivers/target/iscsi/iscsi_target.c:
intiscsit_access_np(struct iscsi_np *np, struct iscsi_portal_group *tpg){
...ret = down_interruptible(&tpg->np_login_sem); if ((ret != 0) || signal_pending(current)) return -1;
drivers/target/target_core_sbc.c:
staticsense_reason_tsbc_compare_and_write(struct se_cmd *cmd)
{
...
Quoted 7 lines
rc = down_interruptible(&dev->caw_sem);
if ((rc != 0) || signal_pending(current)) {
cmd->transport_complete_callback = NULL;
return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE;
}Ditto on these.
Applying the following patch to target-pending/master.
Thank you,
--nab
drivers/target/iscsi/iscsi_target.c +1 -1
@@ -230,7 +230,7 @@ int iscsit_access_np(struct iscsi_np *np, struct iscsi_portal_group *tpg)* Here we serialize access across the TIQN+TPG Tuple. */ ret =down_interruptible (&tpg->np_login_sem); -if ((ret !=0 ) ||signal_pending (current)) +if (ret !=0 )return -1 ;spin_lock_bh (&tpg->tpg_state_lock);
drivers/target/iscsi/iscsi_target_tpg.c +1 -4
@@ -161,10 +161,7 @@ struct iscsi_portal_group *iscsit_get_tpg_from_np(int iscsit_get_tpg (struct iscsi_portal_group *tpg) { -int ret; - - ret =mutex_lock_interruptible (&tpg->tpg_access_lock); -return ((ret !=0 ) ||signal_pending (current)) ?-1 :0 ; +return mutex_lock_interruptible (&tpg->tpg_access_lock); }void iscsit_put_tpg (struct iscsi_portal_group *tpg)
drivers/target/target_core_sbc.c +1 -1
@@ -568,7 +568,7 @@ sbc_compare_and_write(struct se_cmd *cmd)* comparision using SGLs at cmd->t_bidi_data_sg.. */ rc =down_interruptible (&dev->caw_sem); -if ((rc !=0 ) ||signal_pending (current)) { +if (rc !=0 ) { cmd->transport_complete_callback =NULL ;return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE ; }