nvme: avoid possible double fetch in handling CQE
authorLalithambika Krishnakumar <lalithambika.krishnakumar@intel.com>
Wed, 23 Dec 2020 22:09:00 +0000 (14:09 -0800)
committerChristoph Hellwig <hch@lst.de>
Wed, 6 Jan 2021 09:30:37 +0000 (10:30 +0100)
While handling the completion queue, keep a local copy of the command id
from the DMA-accessible completion entry. This silences a time-of-check
to time-of-use (TOCTOU) warning from KF/x[1], with respect to a
Thunderclap[2] vulnerability analysis. The double-read impact appears
benign.

There may be a theoretical window for @command_id to be used as an
adversary-controlled array-index-value for mounting a speculative
execution attack, but that mitigation is saved for a potential follow-on.
A man-in-the-middle attack on the data payload is out of scope for this
analysis and is hopefully mitigated by filesystem integrity mechanisms.

[1] https://github.com/intel/kernel-fuzzer-for-xen-project
[2] http://thunderclap.io/thunderclap-paper-ndss2019.pdf
Signed-off-by: Lalithambika Krishna Kumar <lalithambika.krishnakumar@intel.com>
Signed-off-by: Christoph Hellwig <hch@lst.de>
drivers/nvme/host/pci.c

index 553871e..50d9a20 100644 (file)
@@ -967,6 +967,7 @@ static inline struct blk_mq_tags *nvme_queue_tagset(struct nvme_queue *nvmeq)
 static inline void nvme_handle_cqe(struct nvme_queue *nvmeq, u16 idx)
 {
        struct nvme_completion *cqe = &nvmeq->cqes[idx];
+       __u16 command_id = READ_ONCE(cqe->command_id);
        struct request *req;
 
        /*
@@ -975,17 +976,17 @@ static inline void nvme_handle_cqe(struct nvme_queue *nvmeq, u16 idx)
         * aborts.  We don't even bother to allocate a struct request
         * for them but rather special case them here.
         */
-       if (unlikely(nvme_is_aen_req(nvmeq->qid, cqe->command_id))) {
+       if (unlikely(nvme_is_aen_req(nvmeq->qid, command_id))) {
                nvme_complete_async_event(&nvmeq->dev->ctrl,
                                cqe->status, &cqe->result);
                return;
        }
 
-       req = blk_mq_tag_to_rq(nvme_queue_tagset(nvmeq), cqe->command_id);
+       req = blk_mq_tag_to_rq(nvme_queue_tagset(nvmeq), command_id);
        if (unlikely(!req)) {
                dev_warn(nvmeq->dev->ctrl.device,
                        "invalid id %d completed on queue %d\n",
-                       cqe->command_id, le16_to_cpu(cqe->sq_id));
+                       command_id, le16_to_cpu(cqe->sq_id));
                return;
        }