[PATCH] hw/cxl: bound Set Feature cleanup to written extent

Jia Jia posted 1 patch 1 month ago
Failed in applying to current master (apply log)
hw/cxl/cxl-mailbox-utils.c |  11 +---
tests/qtest/cxl-test.c     | 119 ++++++++++++++++++++++++++++++++++++-
2 files changed, 121 insertions(+), 9 deletions(-)
[PATCH] hw/cxl: bound Set Feature cleanup to written extent
Posted by Jia Jia 1 month ago
cmd_features_set_feature() validates each fragment against the target
write-attribute buffer, but it tracks data_size as the sum of every
fragment copied so far.

That becomes a problem at transfer completion. Cleanup uses data_size as
its memset() length, so a sequence of legal partial transfers can keep
rewriting the same small range while growing data_size far beyond the
feature buffer that is actually being updated.

On an ASan build, repeatedly sending 2-byte rank_sparing fragments at
offset 0 and then finishing aborts the host with:

  ERROR: AddressSanitizer: heap-buffer-overflow
  WRITE of size 1144
      #0 __interceptor_memset
      #1 cmd_features_set_feature ../hw/cxl/cxl-mailbox-utils.c:1961
      #2 cxl_process_cci_message ../hw/cxl/cxl-mailbox-utils.c:4642
      #3 mailbox_reg_write ../hw/cxl/cxl-device-utils.c:209

Fix this by tracking the maximum written extent instead of the cumulative
number of copied bytes. That keeps cleanup bounded to the actual
write-attribute window without changing the rest of the transfer flow.

Add a qtest that confirms a multipart bank_sparing transfer does not
clobber adjacent rank_sparing state during cleanup.

Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3461
Signed-off-by: Jia Jia <physicalmtea@gmail.com>

---
 hw/cxl/cxl-mailbox-utils.c |  11 +---
 tests/qtest/cxl-test.c     | 119 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 121 insertions(+), 9 deletions(-)

diff --git a/hw/cxl/cxl-mailbox-utils.c b/hw/cxl/cxl-mailbox-utils.c
index ce139e30eb..8346177b79 100644
--- a/hw/cxl/cxl-mailbox-utils.c
+++ b/hw/cxl/cxl-mailbox-utils.c
@@ -1769,7 +1769,6 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd,
         memcpy((uint8_t *)&ct3d->patrol_scrub_wr_attrs + hdr->offset,
                ps_write_attrs,
                bytes_to_copy);
-        set_feat_info->data_size += bytes_to_copy;
 
         if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
             data_transfer_flag ==  CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) {
@@ -1796,7 +1795,6 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd,
         memcpy((uint8_t *)&ct3d->ecs_wr_attrs + hdr->offset,
                ecs_write_attrs,
                bytes_to_copy);
-        set_feat_info->data_size += bytes_to_copy;
 
         if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
             data_transfer_flag ==  CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) {
@@ -1820,7 +1818,6 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd,
         }
         memcpy((uint8_t *)&ct3d->soft_ppr_wr_attrs + hdr->offset,
                sppr_write_attrs, bytes_to_copy);
-        set_feat_info->data_size += bytes_to_copy;
 
         if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
             data_transfer_flag ==  CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) {
@@ -1842,7 +1839,6 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd,
         }
         memcpy((uint8_t *)&ct3d->hard_ppr_wr_attrs + hdr->offset,
                hppr_write_attrs, bytes_to_copy);
-        set_feat_info->data_size += bytes_to_copy;
 
         if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
             data_transfer_flag ==  CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) {
@@ -1864,7 +1860,6 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd,
         }
         memcpy((uint8_t *)&ct3d->cacheline_sparing_wr_attrs + hdr->offset,
                mem_sparing_write_attrs, bytes_to_copy);
-        set_feat_info->data_size += bytes_to_copy;
 
         if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
             data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) {
@@ -1885,7 +1880,6 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd,
         }
         memcpy((uint8_t *)&ct3d->row_sparing_wr_attrs + hdr->offset,
                mem_sparing_write_attrs, bytes_to_copy);
-        set_feat_info->data_size += bytes_to_copy;
 
         if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
             data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) {
@@ -1906,7 +1900,6 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd,
         }
         memcpy((uint8_t *)&ct3d->bank_sparing_wr_attrs + hdr->offset,
                mem_sparing_write_attrs, bytes_to_copy);
-        set_feat_info->data_size += bytes_to_copy;
 
         if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
             data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) {
@@ -1927,7 +1920,6 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd,
         }
         memcpy((uint8_t *)&ct3d->rank_sparing_wr_attrs + hdr->offset,
                mem_sparing_write_attrs, bytes_to_copy);
-        set_feat_info->data_size += bytes_to_copy;
 
         if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
             data_transfer_flag == CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER) {
@@ -1938,6 +1930,9 @@ static CXLRetCode cmd_features_set_feature(const struct cxl_cmd *cmd,
         return CXL_MBOX_UNSUPPORTED;
     }
 
+    set_feat_info->data_size = MAX(set_feat_info->data_size,
+                                   (size_t)end_offset);
+
     if (data_transfer_flag == CXL_SET_FEATURE_FLAG_FULL_DATA_TRANSFER ||
         data_transfer_flag ==  CXL_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER ||
         data_transfer_flag ==  CXL_SET_FEATURE_FLAG_ABORT_DATA_TRANSFER) {
diff --git a/tests/qtest/cxl-test.c b/tests/qtest/cxl-test.c
index a9fcd98736..ed222aca4b 100644
--- a/tests/qtest/cxl-test.c
+++ b/tests/qtest/cxl-test.c
@@ -99,6 +99,21 @@ typedef struct QEMU_PACKED CXLSetFeatureInHeaderTest {
     uint8_t rsvd[9];
 } CXLSetFeatureInHeaderTest;
 
+typedef struct QEMU_PACKED CXLGetFeatureInHeaderTest {
+    uint8_t uuid[16];
+    uint16_t offset;
+    uint16_t count;
+    uint8_t selection;
+} CXLGetFeatureInHeaderTest;
+
+enum {
+    CXL_TEST_GET_FEATURE_SEL_CURRENT_VALUE = 0,
+    CXL_TEST_SET_FEATURE_FLAG_FULL_DATA_TRANSFER = 0,
+    CXL_TEST_SET_FEATURE_FLAG_INITIATE_DATA_TRANSFER = 1,
+    CXL_TEST_SET_FEATURE_FLAG_CONTINUE_DATA_TRANSFER = 2,
+    CXL_TEST_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER = 3,
+};
+
 static void cxl_basic_hb(void)
 {
     qtest_start("-machine q35,cxl=on");
@@ -177,15 +192,37 @@ static uint16_t cxl_test_t3d_mailbox_errno(void)
 
 static void cxl_test_fill_set_feature_header(CXLSetFeatureInHeaderTest *hdr,
                                              const uint8_t uuid[16],
+                                             uint32_t flags,
                                              uint16_t offset,
                                              uint8_t version)
 {
     memset(hdr, 0, sizeof(*hdr));
     memcpy(hdr->uuid, uuid, 16);
+    hdr->flags = cpu_to_le32(flags);
     hdr->offset = cpu_to_le16(offset);
     hdr->version = version;
 }
 
+static void cxl_test_fill_get_feature_header(CXLGetFeatureInHeaderTest *hdr,
+                                             const uint8_t uuid[16],
+                                             uint16_t offset,
+                                             uint16_t count)
+{
+    memset(hdr, 0, sizeof(*hdr));
+    memcpy(hdr->uuid, uuid, 16);
+    hdr->offset = cpu_to_le16(offset);
+    hdr->count = cpu_to_le16(count);
+    hdr->selection = CXL_TEST_GET_FEATURE_SEL_CURRENT_VALUE;
+}
+
+static void cxl_test_t3d_submit_get_feature(const void *payload, size_t len)
+{
+    memwrite(cxl_test_t3d_payload_base(), payload, len);
+    writeq(cxl_test_t3d_mailbox_base() + A_CXL_DEV_MAILBOX_CMD,
+           ((uint64_t)len << 16) | (0x05 << 8) | 0x01);
+    writel(cxl_test_t3d_mailbox_base() + A_CXL_DEV_MAILBOX_CTRL, 1);
+}
+
 static void cxl_t3d_set_feature_rejects_oversized_rank_sparing(void)
 {
     static const uint8_t rank_sparing_uuid[16] = {
@@ -203,7 +240,7 @@ static void cxl_t3d_set_feature_rejects_oversized_rank_sparing(void)
     qtest_start(cmdline->str);
     cxl_test_t3d_enable_bar2();
 
-    cxl_test_fill_set_feature_header(hdr, rank_sparing_uuid, 0,
+    cxl_test_fill_set_feature_header(hdr, rank_sparing_uuid, 0, 0,
                                      CXL_MEMDEV_SPARING_SET_FEATURE_VERSION);
     memset(payload + sizeof(*hdr), 0x41,
            sizeof(payload) - sizeof(*hdr));
@@ -215,6 +252,84 @@ static void cxl_t3d_set_feature_rejects_oversized_rank_sparing(void)
     rmdir(tmpfs);
 }
 
+static void cxl_t3d_set_feature_cleanup_stays_within_feature_state(void)
+{
+    static const uint8_t bank_sparing_uuid[16] = {
+        0x36, 0x96, 0xb7, 0x78, 0xac, 0x90, 0x64, 0x4b,
+        0xa4, 0xef, 0xfa, 0xac, 0x5d, 0x18, 0xa8, 0x63,
+    };
+    static const uint8_t rank_sparing_uuid[16] = {
+        0x34, 0xdb, 0xaf, 0xf5, 0x05, 0x52, 0x42, 0x81,
+        0x8f, 0x76, 0xda, 0x0b, 0x5e, 0x7a, 0x76, 0xa7,
+    };
+    g_autoptr(GString) cmdline = g_string_new(NULL);
+    g_autofree const char *tmpfs = NULL;
+    uint8_t set_payload[sizeof(CXLSetFeatureInHeaderTest) + 2] = { 0 };
+    uint8_t get_payload[sizeof(CXLGetFeatureInHeaderTest)] = { 0 };
+    uint8_t out[2];
+    CXLSetFeatureInHeaderTest *set_hdr = (void *)set_payload;
+    CXLGetFeatureInHeaderTest *get_hdr = (void *)get_payload;
+    int i;
+
+    tmpfs = g_dir_make_tmp("cxl-test-XXXXXX", NULL);
+    g_string_printf(cmdline, QEMU_T3D_DIRECT_PMEM, tmpfs, tmpfs);
+
+    qtest_start(cmdline->str);
+    cxl_test_t3d_enable_bar2();
+
+    cxl_test_fill_set_feature_header(
+        set_hdr, rank_sparing_uuid,
+        CXL_TEST_SET_FEATURE_FLAG_FULL_DATA_TRANSFER, 0,
+        CXL_MEMDEV_SPARING_SET_FEATURE_VERSION);
+    set_payload[sizeof(*set_hdr)] = 0x34;
+    set_payload[sizeof(*set_hdr) + 1] = 0x12;
+    cxl_test_t3d_submit_set_feature(set_payload, sizeof(set_payload));
+    g_assert_cmphex(cxl_test_t3d_mailbox_errno(), ==, CXL_MBOX_SUCCESS);
+
+    cxl_test_fill_set_feature_header(set_hdr, bank_sparing_uuid,
+                                     CXL_TEST_SET_FEATURE_FLAG_INITIATE_DATA_TRANSFER,
+                                     0,
+                                     CXL_MEMDEV_SPARING_SET_FEATURE_VERSION);
+    set_payload[sizeof(*set_hdr)] = 0x78;
+    set_payload[sizeof(*set_hdr) + 1] = 0x56;
+    cxl_test_t3d_submit_set_feature(set_payload, sizeof(set_payload));
+    g_assert_cmphex(cxl_test_t3d_mailbox_errno(), ==, CXL_MBOX_SUCCESS);
+
+    /* Repeating the same 2-byte fragment must not clobber adjacent state. */
+    for (i = 0; i < 2; i++) {
+        cxl_test_fill_set_feature_header(
+            set_hdr, bank_sparing_uuid,
+            CXL_TEST_SET_FEATURE_FLAG_CONTINUE_DATA_TRANSFER,
+            0, CXL_MEMDEV_SPARING_SET_FEATURE_VERSION);
+        set_payload[sizeof(*set_hdr)] = 0xaa;
+        set_payload[sizeof(*set_hdr) + 1] = 0xbb;
+        cxl_test_t3d_submit_set_feature(set_payload, sizeof(set_payload));
+        g_assert_cmphex(cxl_test_t3d_mailbox_errno(), ==, CXL_MBOX_SUCCESS);
+    }
+
+    cxl_test_fill_set_feature_header(set_hdr, bank_sparing_uuid,
+                                     CXL_TEST_SET_FEATURE_FLAG_FINISH_DATA_TRANSFER,
+                                     0,
+                                     CXL_MEMDEV_SPARING_SET_FEATURE_VERSION);
+    set_payload[sizeof(*set_hdr)] = 0xcc;
+    set_payload[sizeof(*set_hdr) + 1] = 0xdd;
+    cxl_test_t3d_submit_set_feature(set_payload, sizeof(set_payload));
+    g_assert_cmphex(cxl_test_t3d_mailbox_errno(), ==, CXL_MBOX_SUCCESS);
+
+    cxl_test_fill_get_feature_header(get_hdr, rank_sparing_uuid,
+                                     offsetof(CXLMemSparingReadAttrs, op_mode),
+                                     2);
+    cxl_test_t3d_submit_get_feature(get_payload, sizeof(get_payload));
+    g_assert_cmphex(cxl_test_t3d_mailbox_errno(), ==, CXL_MBOX_SUCCESS);
+
+    memread(cxl_test_t3d_payload_base(), out, sizeof(out));
+    g_assert_cmphex(out[0], ==, 0x34);
+    g_assert_cmphex(out[1], ==, 0x12);
+
+    qtest_end();
+    rmdir(tmpfs);
+}
+
 static void cxl_t3d_deprecated(void)
 {
     g_autoptr(GString) cmdline = g_string_new(NULL);
@@ -337,6 +452,8 @@ int main(int argc, char **argv)
         qtest_add_func("/pci/cxl/type3_device_vmem_lsa", cxl_t3d_volatile_lsa);
         qtest_add_func("/pci/cxl/type3_device_set_feature_rank_sparing_bounds",
                        cxl_t3d_set_feature_rejects_oversized_rank_sparing);
+        qtest_add_func("/pci/cxl/type3_device_set_feature_cleanup_bounds",
+                       cxl_t3d_set_feature_cleanup_stays_within_feature_state);
         qtest_add_func("/pci/cxl/rp_x2_type3_x2", cxl_1pxb_2rp_2t3d);
         qtest_add_func("/pci/cxl/pxb_x2_root_port_x4_type3_x4",
                        cxl_2pxb_4rp_4t3d);
-- 
2.34.1