zfcp: fixes for the zFCP device driver
Subsystems (4)
cover zfcp: fixes for the zFCP device driver
Hello James and Martin,
here are some fixes for the zFCP device driver. I am rather certain this won't fit into this rc-cycle anymore.. I just found these last week and with internal reviews and tests I was regrettably not able to post these any sooner.
So please consider them for the next cycle. Or should I not have send them in that case? Sorry, this step of the process was a bit unclear to me.
The first patch is the "most urgent" of the three, although nothing too terrible happens if we hit it.
Reviews are welcome from everyone, obviously.
Benjamin Block (3):
zfcp: fix request object use-after-free in send path causing seqno
errors
zfcp: fix request object use-after-free in send path causing wrong
traces
zfcp: fix GCC compiler warning emitted with -Wmaybe-uninitialized
drivers/s390/scsi/zfcp_erp.c | 7 +++++ drivers/s390/scsi/zfcp_fsf.c | 55 +++++++++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 7 deletions(-)
Signature · 1 line
2.17.1
PATCH 1/3 zfcp: fix request object use-after-free in send path causing seqno errors
With a recent change to our send path for FSF commands we introduced a possible use-after-free of request-objects, that might further lead to zfcp crafting bad requests, which the FCP channel correctly complains about with an error (FSF_PROT_SEQ_NUMB_ERROR). This error is then handled by an adapter-wide recovery.
The following sequence illustrates the possible use-after-free:
Send Path:
int zfcp_fsf_open_port(struct zfcp_erp_action *erp_action)
{
struct zfcp_fsf_req *req;
...
spin_lock_irq(&qdio->req_q_lock);
// ^^^^^^^^^^^^^^^^
// protects QDIO queue during sending
...
req = zfcp_fsf_req_create(qdio,
FSF_QTCB_OPEN_PORT_WITH_DID,
SBAL_SFLAGS0_TYPE_READ,
qdio->adapter->pool.erp_req);
// ^^^^^^^^^^^^^^^^^^^
// allocation of the request-object
...
retval = zfcp_fsf_req_send(req);
...
spin_unlock_irq(&qdio->req_q_lock);
return retval;
}
static int zfcp_fsf_req_send(struct zfcp_fsf_req *req)
{
struct zfcp_adapter *adapter = req->adapter;
struct zfcp_qdio *qdio = adapter->qdio;
...
zfcp_reqlist_add(adapter->req_list, req);
// ^^^^^^^^^^^^^^^^
// add request to our driver-internal hash-table for tracking
// (protected by separate lock req_list->lock)
...
if (zfcp_qdio_send(qdio, &req->qdio_req)) {
// ^^^^^^^^^^^^^^
// hand-off the request to FCP channel;
// the request can complete at any point now
...
}
/* Don't increase for unsolicited status */
if (!zfcp_fsf_req_is_status_read_buffer(req))
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// possible use-after-free
adapter->fsf_req_seq_no++;
// ^^^^^^^^^^^^^^^^
// because of the use-after-free we might
// miss this accounting, and as follow-up
// this results in the FCP channel error
// FSF_PROT_SEQ_NUMB_ERROR
adapter->req_no++;
return 0;
}
static inline bool
zfcp_fsf_req_is_status_read_buffer(struct zfcp_fsf_req *req)
{
return req->qtcb == NULL;
// ^^^^^^^^^
// possible use-after-free
}
Response Path:
void zfcp_fsf_reqid_check(struct zfcp_qdio *qdio, int sbal_idx)
{
...
struct zfcp_fsf_req *fsf_req;
...
for (idx = 0; idx < QDIO_MAX_ELEMENTS_PER_BUFFER; idx++) {
...
fsf_req = zfcp_reqlist_find_rm(adapter->req_list,
req_id);
// ^^^^^^^^^^^^^^^^^^^^
// remove request from our driver-internal
// hash-table (lock req_list->lock)
...
zfcp_fsf_req_complete(fsf_req);
}
}
static void zfcp_fsf_req_complete(struct zfcp_fsf_req *req)
{
...
if (likely(req->status & ZFCP_STATUS_FSFREQ_CLEANUP))
zfcp_fsf_req_free(req);
// ^^^^^^^^^^^^^^^^^
// free memory for request-object
else
complete(&req->completion);
// ^^^^^^^^
// completion notification for code-paths that wait
// synchronous for the completion of the request; in
// those the memory is freed separately
}The result of the use-after-free only affects the send path, and can not lead to any data corruption. In case we miss the sequence-number accounting, because the memory was already re-purposed, the next FSF command will fail with said FCP channel error, and we will recover the whole adapter. This causes no additional errors, but it slows down traffic. There is a slight chance of the same thing happen again recursively after the adapter recovery, but so far this has not been seen.
This was seen under z/VM, where the send path might run on a virtual CPU that gets scheduled away by z/VM, while the return path might still run, and so create the necessary timing. Running with KASAN can also slow down the kernel sufficiently to run into this user-after-free, and then see the report by KASAN.
To fix this, simply pull the test for the sequence-number accounting in front of the hand-off to the FCP channel (this information doesn't change during hand-off), but leave the sequence-number accounting itself where it is.
To make future regressions of the same kind less likely, add comments to all closely related code-paths.
--- drivers/s390/scsi/zfcp_fsf.c | 45 ++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-)
drivers/s390/scsi/zfcp_fsf.c +40 -5
@@ -11,6 +11,7 @@#define pr_fmt (fmt) KMSG_COMPONENT ": " fmt#include <linux/blktrace_api.h> +#include <linux/types.h> #include <linux/slab.h> #include <scsi/fc/fc_els.h> #include "zfcp_ext.h"
@@ -741,6 +742,7 @@ static struct zfcp_fsf_req *zfcp_fsf_req_create(struct zfcp_qdio *qdio,static int zfcp_fsf_req_send (struct zfcp_fsf_req *req) { +const bool is_srb =zfcp_fsf_req_is_status_read_buffer (req);struct zfcp_adapter *adapter = req->adapter;struct zfcp_qdio *qdio = adapter->qdio;int req_id = req->req_id;
@@ -757,8 +759,20 @@ static int zfcp_fsf_req_send(struct zfcp_fsf_req *req)return -EIO ; } +/* +* NOTE: DO NOT TOUCH ASYNC req PAST THIS POINT. +* ONLY TOUCH SYNC req AGAIN ON req->completion. +* +* The request might complete and be freed concurrently at any point +* now. This is not protected by the QDIO-lock (req_q_lock). So any +* uncontrolled access after this might result in an use-after-free bug. +* Only if the request doesn't have ZFCP_STATUS_FSFREQ_CLEANUP set, and +* when it is completed via req->completion, is it safe to use req +* again. +*/ +/* Don't increase for unsolicited status */ -if (!zfcp_fsf_req_is_status_read_buffer (req)) +if (!is_srb) adapter->fsf_req_seq_no++; adapter->req_no++;
@@ -805,6 +819,7 @@ int zfcp_fsf_status_read(struct zfcp_qdio *qdio) retval =zfcp_fsf_req_send (req);if (retval) goto failed_req_send; +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ goto out;
@@ -914,8 +929,10 @@ struct zfcp_fsf_req *zfcp_fsf_abort_fcp_cmnd(struct scsi_cmnd *scmnd) req->qtcb->bottom.support.req_handle = (u64 ) old_req_id;zfcp_fsf_start_timer (req,ZFCP_FSF_SCSI_ER_TIMEOUT ); -if (!zfcp_fsf_req_send (req)) +if (!zfcp_fsf_req_send (req)) { +/* NOTE: DO NOT TOUCH req, UNTIL IT COMPLETES! */ goto out; + } out_error_free:zfcp_fsf_req_free (req);
@@ -1098,6 +1115,7 @@ int zfcp_fsf_send_ct(struct zfcp_fc_wka_port *wka_port, ret =zfcp_fsf_req_send (req);if (ret) goto failed_send; +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ goto out;
@@ -1198,6 +1216,7 @@ int zfcp_fsf_send_els(struct zfcp_adapter *adapter, u32 d_id, ret =zfcp_fsf_req_send (req);if (ret) goto failed_send; +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ goto out;
@@ -1243,6 +1262,7 @@ int zfcp_fsf_exchange_config_data(struct zfcp_erp_action *erp_action)zfcp_fsf_req_free (req); erp_action->fsf_req_id =0 ; } +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ out:spin_unlock_irq (&qdio->req_q_lock);return retval;
@@ -1279,8 +1299,10 @@ int zfcp_fsf_exchange_config_data_sync(struct zfcp_qdio *qdio,zfcp_fsf_start_timer (req,ZFCP_FSF_REQUEST_TIMEOUT ); retval =zfcp_fsf_req_send (req);spin_unlock_irq (&qdio->req_q_lock); -if (!retval) +if (!retval) { +/* NOTE: ONLY TOUCH SYNC req AGAIN ON req->completion. */ wait_for_completion (&req->completion); + }zfcp_fsf_req_free (req);return retval;
@@ -1330,6 +1352,7 @@ int zfcp_fsf_exchange_port_data(struct zfcp_erp_action *erp_action)zfcp_fsf_req_free (req); erp_action->fsf_req_id =0 ; } +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ out:spin_unlock_irq (&qdio->req_q_lock);return retval;
@@ -1372,8 +1395,10 @@ int zfcp_fsf_exchange_port_data_sync(struct zfcp_qdio *qdio, retval =zfcp_fsf_req_send (req);spin_unlock_irq (&qdio->req_q_lock); -if (!retval) +if (!retval) { +/* NOTE: ONLY TOUCH SYNC req AGAIN ON req->completion. */ wait_for_completion (&req->completion); + }zfcp_fsf_req_free (req);
@@ -1493,6 +1518,7 @@ int zfcp_fsf_open_port(struct zfcp_erp_action *erp_action) erp_action->fsf_req_id =0 ;put_device (&port->dev); } +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ out:spin_unlock_irq (&qdio->req_q_lock);return retval;
@@ -1557,6 +1583,7 @@ int zfcp_fsf_close_port(struct zfcp_erp_action *erp_action)zfcp_fsf_req_free (req); erp_action->fsf_req_id =0 ; } +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ out:spin_unlock_irq (&qdio->req_q_lock);return retval;
@@ -1626,6 +1653,7 @@ int zfcp_fsf_open_wka_port(struct zfcp_fc_wka_port *wka_port) retval =zfcp_fsf_req_send (req);if (retval)zfcp_fsf_req_free (req); +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ out:spin_unlock_irq (&qdio->req_q_lock);if (!retval)
@@ -1681,6 +1709,7 @@ int zfcp_fsf_close_wka_port(struct zfcp_fc_wka_port *wka_port) retval =zfcp_fsf_req_send (req);if (retval)zfcp_fsf_req_free (req); +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ out:spin_unlock_irq (&qdio->req_q_lock);if (!retval)
@@ -1776,6 +1805,7 @@ int zfcp_fsf_close_physical_port(struct zfcp_erp_action *erp_action)zfcp_fsf_req_free (req); erp_action->fsf_req_id =0 ; } +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ out:spin_unlock_irq (&qdio->req_q_lock);return retval;
@@ -1899,6 +1929,7 @@ int zfcp_fsf_open_lun(struct zfcp_erp_action *erp_action)zfcp_fsf_req_free (req); erp_action->fsf_req_id =0 ; } +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ out:spin_unlock_irq (&qdio->req_q_lock);return retval;
@@ -1987,6 +2018,7 @@ int zfcp_fsf_close_lun(struct zfcp_erp_action *erp_action)zfcp_fsf_req_free (req); erp_action->fsf_req_id =0 ; } +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ out:spin_unlock_irq (&qdio->req_q_lock);return retval;
@@ -2299,6 +2331,7 @@ int zfcp_fsf_fcp_cmnd(struct scsi_cmnd *scsi_cmnd) retval =zfcp_fsf_req_send (req);if (unlikely (retval)) goto failed_scsi_cmnd; +/* NOTE: DO NOT TOUCH req PAST THIS POINT! */ goto out;
@@ -2373,8 +2406,10 @@ struct zfcp_fsf_req *zfcp_fsf_fcp_task_mgmt(struct scsi_device *sdev,zfcp_fc_fcp_tm (fcp_cmnd, sdev, tm_flags);zfcp_fsf_start_timer (req,ZFCP_FSF_SCSI_ER_TIMEOUT ); -if (!zfcp_fsf_req_send (req)) +if (!zfcp_fsf_req_send (req)) { +/* NOTE: DO NOT TOUCH req, UNTIL IT COMPLETES! */ goto out; + }zfcp_fsf_req_free (req); req =NULL ;
Signature · 1 line
2.17.1
PATCH 2/3 zfcp: fix request object use-after-free in send path causing wrong traces
In reply to Benjamin BlockWhen tracing instances where we open and close WKA ports, we also pass the request-ID of the respective FSF command.
But after successfully sending the FSF command we must not use the request-object anymore, as this might result in an use-after-free (see "zfcp: fix request object use-after-free in send path causing seqno errors" ).
To fix this add a new variable that caches the request-ID before sending the request. This won't change during the hand-off to the FCP channel, and so it's safe to trace this cached request-ID later, instead of using the request object.
--- drivers/s390/scsi/zfcp_fsf.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-)
drivers/s390/scsi/zfcp_fsf.c +8 -2
@@ -1627,6 +1627,7 @@ int zfcp_fsf_open_wka_port(struct zfcp_fc_wka_port *wka_port) {struct zfcp_qdio *qdio = wka_port->adapter->qdio;struct zfcp_fsf_req *req; +unsigned long req_id =0 ;int retval = -EIO ;spin_lock_irq (&qdio->req_q_lock);
@@ -1649,6 +1650,8 @@ int zfcp_fsf_open_wka_port(struct zfcp_fc_wka_port *wka_port)hton24 (req->qtcb->bottom.support.d_id, wka_port->d_id); req->data = wka_port; + req_id = req->req_id; +zfcp_fsf_start_timer (req,ZFCP_FSF_REQUEST_TIMEOUT ); retval =zfcp_fsf_req_send (req);if (retval)
@@ -1657,7 +1660,7 @@ int zfcp_fsf_open_wka_port(struct zfcp_fc_wka_port *wka_port) out:spin_unlock_irq (&qdio->req_q_lock);if (!retval) -zfcp_dbf_rec_run_wka ("fsowp_1" , wka_port, req->req_id); +zfcp_dbf_rec_run_wka ("fsowp_1" , wka_port, req_id);return retval; }
@@ -1683,6 +1686,7 @@ int zfcp_fsf_close_wka_port(struct zfcp_fc_wka_port *wka_port) {struct zfcp_qdio *qdio = wka_port->adapter->qdio;struct zfcp_fsf_req *req; +unsigned long req_id =0 ;int retval = -EIO ;spin_lock_irq (&qdio->req_q_lock);
@@ -1705,6 +1709,8 @@ int zfcp_fsf_close_wka_port(struct zfcp_fc_wka_port *wka_port) req->data = wka_port; req->qtcb->header.port_handle = wka_port->handle; + req_id = req->req_id; +zfcp_fsf_start_timer (req,ZFCP_FSF_REQUEST_TIMEOUT ); retval =zfcp_fsf_req_send (req);if (retval)
@@ -1713,7 +1719,7 @@ int zfcp_fsf_close_wka_port(struct zfcp_fc_wka_port *wka_port) out:spin_unlock_irq (&qdio->req_q_lock);if (!retval) -zfcp_dbf_rec_run_wka ("fscwp_1" , wka_port, req->req_id); +zfcp_dbf_rec_run_wka ("fscwp_1" , wka_port, req_id);return retval; }
Signature · 1 line
2.17.1
PATCH 3/3 zfcp: fix GCC compiler warning emitted with -Wmaybe-uninitialized
In reply to Benjamin BlockGCC v9 emits this warning:
CC drivers/s390/scsi/zfcp_erp.o
drivers/s390/scsi/zfcp_erp.c: In function 'zfcp_erp_action_enqueue':
drivers/s390/scsi/zfcp_erp.c:217:26: warning: 'erp_action' may be used uninitialized in this function [-Wmaybe-uninitialized]
217 | struct zfcp_erp_action *erp_action;
| ^~~~~~~~~~
This is a possible false positive case, as also documented in the GCC documentations:
https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wmaybe-uninitialized
The actual code-sequence is like this:
Various callers can invoke the function below with the argument "want"
being one of:
ZFCP_ERP_ACTION_REOPEN_ADAPTER,
ZFCP_ERP_ACTION_REOPEN_PORT_FORCED,
ZFCP_ERP_ACTION_REOPEN_PORT, or
ZFCP_ERP_ACTION_REOPEN_LUN.
zfcp_erp_action_enqueue(want, ...)
...
need = zfcp_erp_required_act(want, ...)
need = want
...
maybe: need = ZFCP_ERP_ACTION_REOPEN_PORT
maybe: need = ZFCP_ERP_ACTION_REOPEN_ADAPTER
...
return need
...
zfcp_erp_setup_act(need, ...)
struct zfcp_erp_action *erp_action; // <== line 217
...
switch(need) {
case ZFCP_ERP_ACTION_REOPEN_LUN:
...
erp_action = &zfcp_sdev->erp_action;
WARN_ON_ONCE(erp_action->port != port); // <== access
...
break;
case ZFCP_ERP_ACTION_REOPEN_PORT:
case ZFCP_ERP_ACTION_REOPEN_PORT_FORCED:
...
erp_action = &port->erp_action;
WARN_ON_ONCE(erp_action->port != port); // <== access
...
break;
case ZFCP_ERP_ACTION_REOPEN_ADAPTER:
...
erp_action = &adapter->erp_action;
WARN_ON_ONCE(erp_action->port != NULL); // <== access
...
break;
}
...
WARN_ON_ONCE(erp_action->adapter != adapter); // <== accessWhen zfcp_erp_setup_act() is called, 'need' will never be anything else than one of the 4 possible enumeration-names that are used in the switch-case, and 'erp_action' is initialized for every one of them, before it is used. Thus the warning is a false positive, as documented.
We introduce the extra if{} in the beginning to create an extra code-flow, so the compiler can be convinced that the switch-case will never see any other value.
BUG_ON()/BUG() is intentionally not used to not crash anything, should this ever happen anyway - right now it's impossible, as argued above; and it doesn't introduce a 'default:' switch-case to retain warnings should 'enum zfcp_erp_act_type' ever be extended and no explicit case be introduced. See also v5.0 commit 399b6c8bc9f7 ("scsi: zfcp: drop old default switch case which might paper over missing case").
--- drivers/s390/scsi/zfcp_erp.c | 7 +++++++ 1 file changed, 7 insertions(+)
drivers/s390/scsi/zfcp_erp.c +7 -0
@@ -11,6 +11,7 @@#define pr_fmt (fmt) KMSG_COMPONENT ": " fmt#include <linux/kthread.h> +#include <linux/bug.h> #include "zfcp_ext.h" #include "zfcp_reqlist.h"
@@ -217,6 +218,12 @@ static struct zfcp_erp_action *zfcp_erp_setup_act(enum zfcp_erp_act_type need,struct zfcp_erp_action *erp_action;struct zfcp_scsi_dev *zfcp_sdev; +if (WARN_ON_ONCE (need !=ZFCP_ERP_ACTION_REOPEN_LUN && + need !=ZFCP_ERP_ACTION_REOPEN_PORT && + need !=ZFCP_ERP_ACTION_REOPEN_PORT_FORCED && + need !=ZFCP_ERP_ACTION_REOPEN_ADAPTER )) +return NULL ; +switch (need) {case ZFCP_ERP_ACTION_REOPEN_LUN : zfcp_sdev =sdev_to_zfcp (sdev);
Signature · 1 line
2.17.1
Benjamin,
Quoted 3 lines
So please consider them for the next cycle. Or should I not have send them in that case? Sorry, this step of the process was a bit unclear to me.
A fix is a fix. Applied the series to 5.3/scsi-fixes. Thanks!
Signature · 1 line
Martin K. Petersen Oracle Linux Engineering
Quoted 9 lines · Martin K. Petersen
Benjamin,
Quoted 3 lines
So please consider them for the next cycle. Or should I not have send them in that case? Sorry, this step of the process was a bit unclear to me.
A fix is a fix. Applied the series to 5.3/scsi-fixes. Thanks!
Thanks Martin.
Signature · 4 lines
With Best Regards, Benjamin Block / Linux on IBM Z Kernel Development IBM Systems & Technology Group / IBM Deutschland Research & Development GmbH Vorsitz. AufsR.: Matthias Hartmann / Gesch�ftsf�hrung: Dirk Wittkopp Sitz der Gesellschaft: B�blingen / Registergericht: AmtsG Stuttgart, HRB 243294