iwlwifi: fw: harden page loading code
authorLuca Coelho <luciano.coelho@intel.com>
Tue, 13 Feb 2018 09:09:40 +0000 (11:09 +0200)
committerLuca Coelho <luciano.coelho@intel.com>
Thu, 26 Apr 2018 12:18:07 +0000 (15:18 +0300)
The page loading code trusts the data provided in the firmware images
a bit too much and may cause a buffer overflow or copy unknown data if
the block sizes don't match what we expect.

To prevent potential problems, harden the code by checking if the
sizes we are copying are what we expect.

Cc: stable@vger.kernel.org
Signed-off-by: Luca Coelho <luciano.coelho@intel.com>
drivers/net/wireless/intel/iwlwifi/fw/paging.c

index 1fec8e3..6afcfd1 100644 (file)
@@ -8,6 +8,7 @@
  * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved.
  * Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH
  * Copyright(c) 2016 - 2017 Intel Deutschland GmbH
+ * Copyright(c) 2018        Intel Corporation
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of version 2 of the GNU General Public License as
@@ -30,6 +31,7 @@
  * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved.
  * Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH
  * Copyright(c) 2016 - 2017 Intel Deutschland GmbH
+ * Copyright(c) 2018        Intel Corporation
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -163,7 +165,7 @@ static int iwl_alloc_fw_paging_mem(struct iwl_fw_runtime *fwrt,
 static int iwl_fill_paging_mem(struct iwl_fw_runtime *fwrt,
                               const struct fw_img *image)
 {
-       int sec_idx, idx;
+       int sec_idx, idx, ret;
        u32 offset = 0;
 
        /*
@@ -190,17 +192,23 @@ static int iwl_fill_paging_mem(struct iwl_fw_runtime *fwrt,
         */
        if (sec_idx >= image->num_sec - 1) {
                IWL_ERR(fwrt, "Paging: Missing CSS and/or paging sections\n");
-               iwl_free_fw_paging(fwrt);
-               return -EINVAL;
+               ret = -EINVAL;
+               goto err;
        }
 
        /* copy the CSS block to the dram */
        IWL_DEBUG_FW(fwrt, "Paging: load paging CSS to FW, sec = %d\n",
                     sec_idx);
 
+       if (image->sec[sec_idx].len > fwrt->fw_paging_db[0].fw_paging_size) {
+               IWL_ERR(fwrt, "CSS block is larger than paging size\n");
+               ret = -EINVAL;
+               goto err;
+       }
+
        memcpy(page_address(fwrt->fw_paging_db[0].fw_paging_block),
               image->sec[sec_idx].data,
-              fwrt->fw_paging_db[0].fw_paging_size);
+              image->sec[sec_idx].len);
        dma_sync_single_for_device(fwrt->trans->dev,
                                   fwrt->fw_paging_db[0].fw_paging_phys,
                                   fwrt->fw_paging_db[0].fw_paging_size,
@@ -221,6 +229,14 @@ static int iwl_fill_paging_mem(struct iwl_fw_runtime *fwrt,
        for (idx = 1; idx < fwrt->num_of_paging_blk; idx++) {
                struct iwl_fw_paging *block = &fwrt->fw_paging_db[idx];
 
+               if (block->fw_paging_size > image->sec[sec_idx].len - offset) {
+                       IWL_ERR(fwrt,
+                               "Paging: paging size is larger than remaining data in block %d\n",
+                               idx);
+                       ret = -EINVAL;
+                       goto err;
+               }
+
                memcpy(page_address(block->fw_paging_block),
                       image->sec[sec_idx].data + offset,
                       block->fw_paging_size);
@@ -231,19 +247,32 @@ static int iwl_fill_paging_mem(struct iwl_fw_runtime *fwrt,
 
                IWL_DEBUG_FW(fwrt,
                             "Paging: copied %d paging bytes to block %d\n",
-                            fwrt->fw_paging_db[idx].fw_paging_size,
-                            idx);
+                            block->fw_paging_size, idx);
 
-               offset += fwrt->fw_paging_db[idx].fw_paging_size;
+               offset += block->fw_paging_size;
+
+               if (offset > image->sec[sec_idx].len) {
+                       IWL_ERR(fwrt,
+                               "Paging: offset goes over section size\n");
+                       ret = -EINVAL;
+                       goto err;
+               }
        }
 
        /* copy the last paging block */
        if (fwrt->num_of_pages_in_last_blk > 0) {
                struct iwl_fw_paging *block = &fwrt->fw_paging_db[idx];
 
+               if (image->sec[sec_idx].len - offset > block->fw_paging_size) {
+                       IWL_ERR(fwrt,
+                               "Paging: last block is larger than paging size\n");
+                       ret = -EINVAL;
+                       goto err;
+               }
+
                memcpy(page_address(block->fw_paging_block),
                       image->sec[sec_idx].data + offset,
-                      FW_PAGING_SIZE * fwrt->num_of_pages_in_last_blk);
+                      image->sec[sec_idx].len - offset);
                dma_sync_single_for_device(fwrt->trans->dev,
                                           block->fw_paging_phys,
                                           block->fw_paging_size,
@@ -255,6 +284,10 @@ static int iwl_fill_paging_mem(struct iwl_fw_runtime *fwrt,
        }
 
        return 0;
+
+err:
+       iwl_free_fw_paging(fwrt);
+       return ret;
 }
 
 static int iwl_save_fw_paging(struct iwl_fw_runtime *fwrt,