IB/srpt: Fix login-related race conditions
Make sure that sport->mutex is not released between the duplicate channel check, adding a channel to the channel list and performing the sport enabled check. Avoid that srpt_disconnect_ch() can be invoked concurrently with the ib_send_cm_rep() call by srpt_cm_req_recv(). Signed-off-by: Bart Van Assche <bart.vanassche@wdc.com> Signed-off-by: Doug Ledford <dledford@redhat.com>
This commit is contained in:
parent
63d370a6a9
commit
db7683d7de
@ -2040,7 +2040,7 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id,
|
|||||||
struct srpt_rdma_ch *ch;
|
struct srpt_rdma_ch *ch;
|
||||||
char i_port_id[36];
|
char i_port_id[36];
|
||||||
u32 it_iu_len;
|
u32 it_iu_len;
|
||||||
int i, ret = 0;
|
int i, ret;
|
||||||
|
|
||||||
WARN_ON_ONCE(irqs_disabled());
|
WARN_ON_ONCE(irqs_disabled());
|
||||||
|
|
||||||
@ -2063,69 +2063,43 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id,
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = -ENOMEM;
|
||||||
rsp = kzalloc(sizeof(*rsp), GFP_KERNEL);
|
rsp = kzalloc(sizeof(*rsp), GFP_KERNEL);
|
||||||
rej = kzalloc(sizeof(*rej), GFP_KERNEL);
|
rej = kzalloc(sizeof(*rej), GFP_KERNEL);
|
||||||
rep_param = kzalloc(sizeof(*rep_param), GFP_KERNEL);
|
rep_param = kzalloc(sizeof(*rep_param), GFP_KERNEL);
|
||||||
|
if (!rsp || !rej || !rep_param)
|
||||||
if (!rsp || !rej || !rep_param) {
|
|
||||||
ret = -ENOMEM;
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
|
||||||
|
|
||||||
|
ret = -EINVAL;
|
||||||
if (it_iu_len > srp_max_req_size || it_iu_len < 64) {
|
if (it_iu_len > srp_max_req_size || it_iu_len < 64) {
|
||||||
rej->reason = cpu_to_be32(
|
rej->reason = cpu_to_be32(
|
||||||
SRP_LOGIN_REJ_REQ_IT_IU_LENGTH_TOO_LARGE);
|
SRP_LOGIN_REJ_REQ_IT_IU_LENGTH_TOO_LARGE);
|
||||||
ret = -EINVAL;
|
pr_err("rejected SRP_LOGIN_REQ because its length (%d bytes) is out of range (%d .. %d)\n",
|
||||||
pr_err("rejected SRP_LOGIN_REQ because its"
|
|
||||||
" length (%d bytes) is out of range (%d .. %d)\n",
|
|
||||||
it_iu_len, 64, srp_max_req_size);
|
it_iu_len, 64, srp_max_req_size);
|
||||||
goto reject;
|
goto reject;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sport->enabled) {
|
if (!sport->enabled) {
|
||||||
rej->reason = cpu_to_be32(
|
rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
|
||||||
SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
|
pr_info("rejected SRP_LOGIN_REQ because target port %s_%d has not yet been enabled\n",
|
||||||
ret = -EINVAL;
|
sport->sdev->device->name, param->port);
|
||||||
pr_err("rejected SRP_LOGIN_REQ because the target port"
|
|
||||||
" has not yet been enabled\n");
|
|
||||||
goto reject;
|
goto reject;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((req->req_flags & SRP_MTCH_ACTION) == SRP_MULTICHAN_SINGLE) {
|
|
||||||
struct srpt_rdma_ch *ch2;
|
|
||||||
|
|
||||||
rsp->rsp_flags = SRP_LOGIN_RSP_MULTICHAN_NO_CHAN;
|
|
||||||
|
|
||||||
mutex_lock(&sport->mutex);
|
|
||||||
list_for_each_entry(ch2, &nexus->ch_list, list) {
|
|
||||||
if (srpt_disconnect_ch(ch2) < 0)
|
|
||||||
continue;
|
|
||||||
pr_info("Relogin - closed existing channel %s\n",
|
|
||||||
ch2->sess_name);
|
|
||||||
rsp->rsp_flags = SRP_LOGIN_RSP_MULTICHAN_TERMINATED;
|
|
||||||
}
|
|
||||||
mutex_unlock(&sport->mutex);
|
|
||||||
} else {
|
|
||||||
rsp->rsp_flags = SRP_LOGIN_RSP_MULTICHAN_MAINTAINED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*(__be64 *)req->target_port_id != cpu_to_be64(srpt_service_guid)
|
if (*(__be64 *)req->target_port_id != cpu_to_be64(srpt_service_guid)
|
||||||
|| *(__be64 *)(req->target_port_id + 8) !=
|
|| *(__be64 *)(req->target_port_id + 8) !=
|
||||||
cpu_to_be64(srpt_service_guid)) {
|
cpu_to_be64(srpt_service_guid)) {
|
||||||
rej->reason = cpu_to_be32(
|
rej->reason = cpu_to_be32(
|
||||||
SRP_LOGIN_REJ_UNABLE_ASSOCIATE_CHANNEL);
|
SRP_LOGIN_REJ_UNABLE_ASSOCIATE_CHANNEL);
|
||||||
ret = -ENOMEM;
|
pr_err("rejected SRP_LOGIN_REQ because it has an invalid target port identifier.\n");
|
||||||
pr_err("rejected SRP_LOGIN_REQ because it"
|
|
||||||
" has an invalid target port identifier.\n");
|
|
||||||
goto reject;
|
goto reject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = -ENOMEM;
|
||||||
ch = kzalloc(sizeof(*ch), GFP_KERNEL);
|
ch = kzalloc(sizeof(*ch), GFP_KERNEL);
|
||||||
if (!ch) {
|
if (!ch) {
|
||||||
rej->reason = cpu_to_be32(
|
rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
|
||||||
SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
|
pr_err("rejected SRP_LOGIN_REQ because out of memory.\n");
|
||||||
pr_err("rejected SRP_LOGIN_REQ because no memory.\n");
|
|
||||||
ret = -ENOMEM;
|
|
||||||
goto reject;
|
goto reject;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2153,8 +2127,11 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id,
|
|||||||
srpt_alloc_ioctx_ring(ch->sport->sdev, ch->rq_size,
|
srpt_alloc_ioctx_ring(ch->sport->sdev, ch->rq_size,
|
||||||
sizeof(*ch->ioctx_ring[0]),
|
sizeof(*ch->ioctx_ring[0]),
|
||||||
ch->max_rsp_size, DMA_TO_DEVICE);
|
ch->max_rsp_size, DMA_TO_DEVICE);
|
||||||
if (!ch->ioctx_ring)
|
if (!ch->ioctx_ring) {
|
||||||
|
pr_err("rejected SRP_LOGIN_REQ because creating a new QP SQ ring failed.\n");
|
||||||
|
rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
|
||||||
goto free_ch;
|
goto free_ch;
|
||||||
|
}
|
||||||
|
|
||||||
INIT_LIST_HEAD(&ch->free_list);
|
INIT_LIST_HEAD(&ch->free_list);
|
||||||
for (i = 0; i < ch->rq_size; i++) {
|
for (i = 0; i < ch->rq_size; i++) {
|
||||||
@ -2176,20 +2153,10 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret = srpt_create_ch_ib(ch);
|
ret = srpt_create_ch_ib(ch);
|
||||||
if (ret) {
|
|
||||||
rej->reason = cpu_to_be32(
|
|
||||||
SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
|
|
||||||
pr_err("rejected SRP_LOGIN_REQ because creating"
|
|
||||||
" a new RDMA channel failed.\n");
|
|
||||||
goto free_recv_ring;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = srpt_ch_qp_rtr(ch, ch->qp);
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
|
rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
|
||||||
pr_err("rejected SRP_LOGIN_REQ because enabling"
|
pr_err("rejected SRP_LOGIN_REQ because creating a new RDMA channel failed.\n");
|
||||||
" RTR failed (error code = %d)\n", ret);
|
goto free_recv_ring;
|
||||||
goto destroy_ib;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
srpt_format_guid(ch->sess_name, sizeof(ch->sess_name),
|
srpt_format_guid(ch->sess_name, sizeof(ch->sess_name),
|
||||||
@ -2214,11 +2181,51 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id,
|
|||||||
TARGET_PROT_NORMAL,
|
TARGET_PROT_NORMAL,
|
||||||
i_port_id + 2, ch, NULL);
|
i_port_id + 2, ch, NULL);
|
||||||
if (IS_ERR_OR_NULL(ch->sess)) {
|
if (IS_ERR_OR_NULL(ch->sess)) {
|
||||||
pr_info("Rejected login because no ACL has been configured yet for initiator %s.\n",
|
ret = PTR_ERR(ch->sess);
|
||||||
ch->sess_name);
|
pr_info("Rejected login for initiator %s: ret = %d.\n",
|
||||||
rej->reason = cpu_to_be32((PTR_ERR(ch->sess) == -ENOMEM) ?
|
ch->sess_name, ret);
|
||||||
|
rej->reason = cpu_to_be32(ret == -ENOMEM ?
|
||||||
SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES :
|
SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES :
|
||||||
SRP_LOGIN_REJ_CHANNEL_LIMIT_REACHED);
|
SRP_LOGIN_REJ_CHANNEL_LIMIT_REACHED);
|
||||||
|
goto reject;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&sport->mutex);
|
||||||
|
|
||||||
|
if ((req->req_flags & SRP_MTCH_ACTION) == SRP_MULTICHAN_SINGLE) {
|
||||||
|
struct srpt_rdma_ch *ch2;
|
||||||
|
|
||||||
|
rsp->rsp_flags = SRP_LOGIN_RSP_MULTICHAN_NO_CHAN;
|
||||||
|
|
||||||
|
list_for_each_entry(ch2, &nexus->ch_list, list) {
|
||||||
|
if (srpt_disconnect_ch(ch2) < 0)
|
||||||
|
continue;
|
||||||
|
pr_info("Relogin - closed existing channel %s\n",
|
||||||
|
ch2->sess_name);
|
||||||
|
rsp->rsp_flags = SRP_LOGIN_RSP_MULTICHAN_TERMINATED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rsp->rsp_flags = SRP_LOGIN_RSP_MULTICHAN_MAINTAINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
list_add_tail_rcu(&ch->list, &nexus->ch_list);
|
||||||
|
|
||||||
|
if (!sport->enabled) {
|
||||||
|
rej->reason = cpu_to_be32(
|
||||||
|
SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
|
||||||
|
pr_info("rejected SRP_LOGIN_REQ because target %s_%d is not enabled\n",
|
||||||
|
sdev->device->name, param->port);
|
||||||
|
mutex_unlock(&sport->mutex);
|
||||||
|
goto reject;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_unlock(&sport->mutex);
|
||||||
|
|
||||||
|
ret = srpt_ch_qp_rtr(ch, ch->qp);
|
||||||
|
if (ret) {
|
||||||
|
rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
|
||||||
|
pr_err("rejected SRP_LOGIN_REQ because enabling RTR failed (error code = %d)\n",
|
||||||
|
ret);
|
||||||
goto destroy_ib;
|
goto destroy_ib;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2231,8 +2238,8 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id,
|
|||||||
rsp->max_it_iu_len = req->req_it_iu_len;
|
rsp->max_it_iu_len = req->req_it_iu_len;
|
||||||
rsp->max_ti_iu_len = req->req_it_iu_len;
|
rsp->max_ti_iu_len = req->req_it_iu_len;
|
||||||
ch->max_ti_iu_len = it_iu_len;
|
ch->max_ti_iu_len = it_iu_len;
|
||||||
rsp->buf_fmt = cpu_to_be16(SRP_BUF_FORMAT_DIRECT
|
rsp->buf_fmt = cpu_to_be16(SRP_BUF_FORMAT_DIRECT |
|
||||||
| SRP_BUF_FORMAT_INDIRECT);
|
SRP_BUF_FORMAT_INDIRECT);
|
||||||
rsp->req_lim_delta = cpu_to_be32(ch->rq_size);
|
rsp->req_lim_delta = cpu_to_be32(ch->rq_size);
|
||||||
atomic_set(&ch->req_lim, ch->rq_size);
|
atomic_set(&ch->req_lim, ch->rq_size);
|
||||||
atomic_set(&ch->req_lim_delta, 0);
|
atomic_set(&ch->req_lim_delta, 0);
|
||||||
@ -2248,24 +2255,30 @@ static int srpt_cm_req_recv(struct ib_cm_id *cm_id,
|
|||||||
rep_param->responder_resources = 4;
|
rep_param->responder_resources = 4;
|
||||||
rep_param->initiator_depth = 4;
|
rep_param->initiator_depth = 4;
|
||||||
|
|
||||||
ret = ib_send_cm_rep(cm_id, rep_param);
|
/*
|
||||||
if (ret) {
|
* Hold the sport mutex while accepting a connection to avoid that
|
||||||
pr_err("sending SRP_LOGIN_REQ response failed"
|
* srpt_disconnect_ch() is invoked concurrently with this code.
|
||||||
" (error code = %d)\n", ret);
|
*/
|
||||||
goto release_channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex_lock(&sport->mutex);
|
mutex_lock(&sport->mutex);
|
||||||
list_add_tail_rcu(&ch->list, &nexus->ch_list);
|
if (sport->enabled && ch->state == CH_CONNECTING)
|
||||||
|
ret = ib_send_cm_rep(cm_id, rep_param);
|
||||||
|
else
|
||||||
|
ret = -EINVAL;
|
||||||
mutex_unlock(&sport->mutex);
|
mutex_unlock(&sport->mutex);
|
||||||
|
|
||||||
goto out;
|
switch (ret) {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case -EINVAL:
|
||||||
|
goto reject;
|
||||||
|
default:
|
||||||
|
rej->reason = cpu_to_be32(SRP_LOGIN_REJ_INSUFFICIENT_RESOURCES);
|
||||||
|
pr_err("sending SRP_LOGIN_REQ response failed (error code = %d)\n",
|
||||||
|
ret);
|
||||||
|
goto reject;
|
||||||
|
}
|
||||||
|
|
||||||
release_channel:
|
goto out;
|
||||||
srpt_disconnect_ch(ch);
|
|
||||||
transport_deregister_session_configfs(ch->sess);
|
|
||||||
transport_deregister_session(ch->sess);
|
|
||||||
ch->sess = NULL;
|
|
||||||
|
|
||||||
destroy_ib:
|
destroy_ib:
|
||||||
srpt_destroy_ch_ib(ch);
|
srpt_destroy_ch_ib(ch);
|
||||||
@ -2280,13 +2293,18 @@ free_ring:
|
|||||||
ch->sport->sdev, ch->rq_size,
|
ch->sport->sdev, ch->rq_size,
|
||||||
ch->max_rsp_size, DMA_TO_DEVICE);
|
ch->max_rsp_size, DMA_TO_DEVICE);
|
||||||
free_ch:
|
free_ch:
|
||||||
|
cm_id->context = NULL;
|
||||||
kfree(ch);
|
kfree(ch);
|
||||||
|
ch = NULL;
|
||||||
|
|
||||||
|
WARN_ON_ONCE(ret == 0);
|
||||||
|
|
||||||
reject:
|
reject:
|
||||||
|
pr_info("Rejecting login with reason %#x\n", be32_to_cpu(rej->reason));
|
||||||
rej->opcode = SRP_LOGIN_REJ;
|
rej->opcode = SRP_LOGIN_REJ;
|
||||||
rej->tag = req->tag;
|
rej->tag = req->tag;
|
||||||
rej->buf_fmt = cpu_to_be16(SRP_BUF_FORMAT_DIRECT
|
rej->buf_fmt = cpu_to_be16(SRP_BUF_FORMAT_DIRECT |
|
||||||
| SRP_BUF_FORMAT_INDIRECT);
|
SRP_BUF_FORMAT_INDIRECT);
|
||||||
|
|
||||||
ib_send_cm_rej(cm_id, IB_CM_REJ_CONSUMER_DEFINED, NULL, 0,
|
ib_send_cm_rej(cm_id, IB_CM_REJ_CONSUMER_DEFINED, NULL, 0,
|
||||||
(void *)rej, sizeof(*rej));
|
(void *)rej, sizeof(*rej));
|
||||||
@ -2329,17 +2347,26 @@ static void srpt_cm_rtu_recv(struct srpt_rdma_ch *ch)
|
|||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (srpt_set_ch_state(ch, CH_LIVE)) {
|
ret = srpt_ch_qp_rts(ch, ch->qp);
|
||||||
ret = srpt_ch_qp_rts(ch, ch->qp);
|
if (ret < 0) {
|
||||||
|
pr_err("%s-%d: QP transition to RTS failed\n", ch->sess_name,
|
||||||
if (ret == 0) {
|
ch->qp->qp_num);
|
||||||
/* Trigger wait list processing. */
|
srpt_close_ch(ch);
|
||||||
ret = srpt_zerolength_write(ch);
|
return;
|
||||||
WARN_ONCE(ret < 0, "%d\n", ret);
|
|
||||||
} else {
|
|
||||||
srpt_close_ch(ch);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Trigger wait list processing. */
|
||||||
|
ret = srpt_zerolength_write(ch);
|
||||||
|
WARN_ONCE(ret < 0, "%d\n", ret);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note: calling srpt_close_ch() if the transition to the LIVE state
|
||||||
|
* fails is not necessary since that means that that function has
|
||||||
|
* already been invoked from another thread.
|
||||||
|
*/
|
||||||
|
if (!srpt_set_ch_state(ch, CH_LIVE))
|
||||||
|
pr_err("%s-%d: channel transition to LIVE state failed\n",
|
||||||
|
ch->sess_name, ch->qp->qp_num);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user