|
| 1 | +swiotlb: Fix double-allocation of slots due to broken alignment handling |
| 2 | + |
| 3 | +jira LE-1907 |
| 4 | +cve CVE-2024-35814 |
| 5 | +Rebuild_History Non-Buildable kernel-4.18.0-553.16.1.el8_10 |
| 6 | +commit-author Will Deacon < [email protected]> |
| 7 | +commit 04867a7a33324c9c562ee7949dbcaab7aaad1fb4 |
| 8 | +Empty-Commit: Cherry-Pick Conflicts during history rebuild. |
| 9 | +Will be included in final tarball splat. Ref for failed cherry-pick at: |
| 10 | +ciq/ciq_backports/kernel-4.18.0-553.16.1.el8_10/04867a7a.failed |
| 11 | + |
| 12 | +Commit bbb73a103fbb ("swiotlb: fix a braino in the alignment check fix"), |
| 13 | +which was a fix for commit 0eee5ae10256 ("swiotlb: fix slot alignment |
| 14 | +checks"), causes a functional regression with vsock in a virtual machine |
| 15 | +using bouncing via a restricted DMA SWIOTLB pool. |
| 16 | + |
| 17 | +When virtio allocates the virtqueues for the vsock device using |
| 18 | +dma_alloc_coherent(), the SWIOTLB search can return page-unaligned |
| 19 | +allocations if 'area->index' was left unaligned by a previous allocation |
| 20 | +from the buffer: |
| 21 | + |
| 22 | + # Final address in brackets is the SWIOTLB address returned to the caller |
| 23 | + | virtio-pci 0000:00:07.0: orig_addr 0x0 alloc_size 0x2000, iotlb_align_mask 0x800 stride 0x2: got slot 1645-1649/7168 (0x98326800) |
| 24 | + | virtio-pci 0000:00:07.0: orig_addr 0x0 alloc_size 0x2000, iotlb_align_mask 0x800 stride 0x2: got slot 1649-1653/7168 (0x98328800) |
| 25 | + | virtio-pci 0000:00:07.0: orig_addr 0x0 alloc_size 0x2000, iotlb_align_mask 0x800 stride 0x2: got slot 1653-1657/7168 (0x9832a800) |
| 26 | + |
| 27 | +This ends badly (typically buffer corruption and/or a hang) because |
| 28 | +swiotlb_alloc() is expecting a page-aligned allocation and so blindly |
| 29 | +returns a pointer to the 'struct page' corresponding to the allocation, |
| 30 | +therefore double-allocating the first half (2KiB slot) of the 4KiB page. |
| 31 | + |
| 32 | +Fix the problem by treating the allocation alignment separately to any |
| 33 | +additional alignment requirements from the device, using the maximum |
| 34 | +of the two as the stride to search the buffer slots and taking care |
| 35 | +to ensure a minimum of page-alignment for buffers larger than a page. |
| 36 | + |
| 37 | +This also resolves swiotlb allocation failures occuring due to the |
| 38 | +inclusion of ~PAGE_MASK in 'iotlb_align_mask' for large allocations and |
| 39 | +resulting in alignment requirements exceeding swiotlb_max_mapping_size(). |
| 40 | + |
| 41 | +Fixes: bbb73a103fbb ("swiotlb: fix a braino in the alignment check fix") |
| 42 | +Fixes: 0eee5ae10256 ("swiotlb: fix slot alignment checks") |
| 43 | + Signed-off-by: Will Deacon < [email protected]> |
| 44 | + Reviewed-by: Michael Kelley < [email protected]> |
| 45 | + Reviewed-by: Petr Tesarik < [email protected]> |
| 46 | + Tested-by: Nicolin Chen < [email protected]> |
| 47 | + Tested-by: Michael Kelley < [email protected]> |
| 48 | + Signed-off-by: Christoph Hellwig < [email protected]> |
| 49 | +(cherry picked from commit 04867a7a33324c9c562ee7949dbcaab7aaad1fb4) |
| 50 | + Signed-off-by: Jonathan Maple < [email protected]> |
| 51 | + |
| 52 | +# Conflicts: |
| 53 | +# kernel/dma/swiotlb.c |
| 54 | +diff --cc kernel/dma/swiotlb.c |
| 55 | +index c0e227dcb45e,980a7ec70418..000000000000 |
| 56 | +--- a/kernel/dma/swiotlb.c |
| 57 | ++++ b/kernel/dma/swiotlb.c |
| 58 | +@@@ -588,21 -923,88 +588,21 @@@ static unsigned int wrap_area_index(str |
| 59 | + } |
| 60 | + |
| 61 | + /* |
| 62 | + - * Track the total used slots with a global atomic value in order to have |
| 63 | + - * correct information to determine the high water mark. The mem_used() |
| 64 | + - * function gives imprecise results because there's no locking across |
| 65 | + - * multiple areas. |
| 66 | + - */ |
| 67 | + -#ifdef CONFIG_DEBUG_FS |
| 68 | + -static void inc_used_and_hiwater(struct io_tlb_mem *mem, unsigned int nslots) |
| 69 | + -{ |
| 70 | + - unsigned long old_hiwater, new_used; |
| 71 | + - |
| 72 | + - new_used = atomic_long_add_return(nslots, &mem->total_used); |
| 73 | + - old_hiwater = atomic_long_read(&mem->used_hiwater); |
| 74 | + - do { |
| 75 | + - if (new_used <= old_hiwater) |
| 76 | + - break; |
| 77 | + - } while (!atomic_long_try_cmpxchg(&mem->used_hiwater, |
| 78 | + - &old_hiwater, new_used)); |
| 79 | + -} |
| 80 | + - |
| 81 | + -static void dec_used(struct io_tlb_mem *mem, unsigned int nslots) |
| 82 | + -{ |
| 83 | + - atomic_long_sub(nslots, &mem->total_used); |
| 84 | + -} |
| 85 | + - |
| 86 | + -#else /* !CONFIG_DEBUG_FS */ |
| 87 | + -static void inc_used_and_hiwater(struct io_tlb_mem *mem, unsigned int nslots) |
| 88 | + -{ |
| 89 | + -} |
| 90 | + -static void dec_used(struct io_tlb_mem *mem, unsigned int nslots) |
| 91 | + -{ |
| 92 | + -} |
| 93 | + -#endif /* CONFIG_DEBUG_FS */ |
| 94 | + - |
| 95 | + -#ifdef CONFIG_SWIOTLB_DYNAMIC |
| 96 | + -#ifdef CONFIG_DEBUG_FS |
| 97 | + -static void inc_transient_used(struct io_tlb_mem *mem, unsigned int nslots) |
| 98 | + -{ |
| 99 | + - atomic_long_add(nslots, &mem->transient_nslabs); |
| 100 | + -} |
| 101 | + - |
| 102 | + -static void dec_transient_used(struct io_tlb_mem *mem, unsigned int nslots) |
| 103 | + -{ |
| 104 | + - atomic_long_sub(nslots, &mem->transient_nslabs); |
| 105 | + -} |
| 106 | + - |
| 107 | + -#else /* !CONFIG_DEBUG_FS */ |
| 108 | + -static void inc_transient_used(struct io_tlb_mem *mem, unsigned int nslots) |
| 109 | + -{ |
| 110 | + -} |
| 111 | + -static void dec_transient_used(struct io_tlb_mem *mem, unsigned int nslots) |
| 112 | + -{ |
| 113 | + -} |
| 114 | + -#endif /* CONFIG_DEBUG_FS */ |
| 115 | + -#endif /* CONFIG_SWIOTLB_DYNAMIC */ |
| 116 | + - |
| 117 | + -/** |
| 118 | + - * swiotlb_search_pool_area() - search one memory area in one pool |
| 119 | + - * @dev: Device which maps the buffer. |
| 120 | + - * @pool: Memory pool to be searched. |
| 121 | + - * @area_index: Index of the IO TLB memory area to be searched. |
| 122 | + - * @orig_addr: Original (non-bounced) IO buffer address. |
| 123 | + - * @alloc_size: Total requested size of the bounce buffer, |
| 124 | + - * including initial alignment padding. |
| 125 | + - * @alloc_align_mask: Required alignment of the allocated buffer. |
| 126 | + - * |
| 127 | + - * Find a suitable sequence of IO TLB entries for the request and allocate |
| 128 | + - * a buffer from the given IO TLB memory area. |
| 129 | + - * This function takes care of locking. |
| 130 | + - * |
| 131 | + - * Return: Index of the first allocated slot, or -1 on error. |
| 132 | + + * Find a suitable number of IO TLB entries size that will fit this request and |
| 133 | + + * allocate a buffer from that IO TLB pool. |
| 134 | + */ |
| 135 | + -static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool, |
| 136 | + - int area_index, phys_addr_t orig_addr, size_t alloc_size, |
| 137 | + +static int swiotlb_do_find_slots(struct device *dev, int area_index, |
| 138 | + + phys_addr_t orig_addr, size_t alloc_size, |
| 139 | + unsigned int alloc_align_mask) |
| 140 | + { |
| 141 | + - struct io_tlb_area *area = pool->areas + area_index; |
| 142 | + + struct io_tlb_mem *mem = dev->dma_io_tlb_mem; |
| 143 | + + struct io_tlb_area *area = mem->areas + area_index; |
| 144 | + unsigned long boundary_mask = dma_get_seg_boundary(dev); |
| 145 | + dma_addr_t tbl_dma_addr = |
| 146 | + - phys_to_dma_unencrypted(dev, pool->start) & boundary_mask; |
| 147 | + + phys_to_dma_unencrypted(dev, mem->start) & boundary_mask; |
| 148 | + unsigned long max_slots = get_max_slots(boundary_mask); |
| 149 | + unsigned int iotlb_align_mask = |
| 150 | +- dma_get_min_align_mask(dev) | alloc_align_mask; |
| 151 | ++ dma_get_min_align_mask(dev) & ~(IO_TLB_SIZE - 1); |
| 152 | + unsigned int nslots = nr_slots(alloc_size), stride; |
| 153 | + unsigned int offset = swiotlb_align_offset(dev, orig_addr); |
| 154 | + unsigned int index, slots_checked, count = 0, i; |
| 155 | +@@@ -611,36 -1013,38 +611,48 @@@ |
| 156 | + unsigned int slot_index; |
| 157 | + |
| 158 | + BUG_ON(!nslots); |
| 159 | + - BUG_ON(area_index >= pool->nareas); |
| 160 | + + BUG_ON(area_index >= mem->nareas); |
| 161 | + |
| 162 | + /* |
| 163 | +- * For allocations of PAGE_SIZE or larger only look for page aligned |
| 164 | +- * allocations. |
| 165 | ++ * For mappings with an alignment requirement don't bother looping to |
| 166 | ++ * unaligned slots once we found an aligned one. |
| 167 | + */ |
| 168 | +- if (alloc_size >= PAGE_SIZE) |
| 169 | +- iotlb_align_mask |= ~PAGE_MASK; |
| 170 | +- iotlb_align_mask &= ~(IO_TLB_SIZE - 1); |
| 171 | ++ stride = get_max_slots(max(alloc_align_mask, iotlb_align_mask)); |
| 172 | + |
| 173 | + /* |
| 174 | +- * For mappings with an alignment requirement don't bother looping to |
| 175 | +- * unaligned slots once we found an aligned one. |
| 176 | ++ * For allocations of PAGE_SIZE or larger only look for page aligned |
| 177 | ++ * allocations. |
| 178 | + */ |
| 179 | +- stride = (iotlb_align_mask >> IO_TLB_SHIFT) + 1; |
| 180 | ++ if (alloc_size >= PAGE_SIZE) |
| 181 | ++ stride = umax(stride, PAGE_SHIFT - IO_TLB_SHIFT + 1); |
| 182 | + |
| 183 | + spin_lock_irqsave(&area->lock, flags); |
| 184 | + - if (unlikely(nslots > pool->area_nslabs - area->used)) |
| 185 | + + if (unlikely(nslots > mem->area_nslabs - area->used)) |
| 186 | + goto not_found; |
| 187 | + |
| 188 | + - slot_base = area_index * pool->area_nslabs; |
| 189 | + + slot_base = area_index * mem->area_nslabs; |
| 190 | + index = area->index; |
| 191 | + |
| 192 | +++<<<<<<< HEAD |
| 193 | + + for (slots_checked = 0; slots_checked < mem->area_nslabs; ) { |
| 194 | + + slot_index = slot_base + index; |
| 195 | + + |
| 196 | + + if (orig_addr && |
| 197 | + + (slot_addr(tbl_dma_addr, slot_index) & |
| 198 | + + iotlb_align_mask) != (orig_addr & iotlb_align_mask)) { |
| 199 | + + index = wrap_area_index(mem, index + 1); |
| 200 | +++======= |
| 201 | ++ for (slots_checked = 0; slots_checked < pool->area_nslabs; ) { |
| 202 | ++ phys_addr_t tlb_addr; |
| 203 | ++ |
| 204 | ++ slot_index = slot_base + index; |
| 205 | ++ tlb_addr = slot_addr(tbl_dma_addr, slot_index); |
| 206 | ++ |
| 207 | ++ if ((tlb_addr & alloc_align_mask) || |
| 208 | ++ (orig_addr && (tlb_addr & iotlb_align_mask) != |
| 209 | ++ (orig_addr & iotlb_align_mask))) { |
| 210 | ++ index = wrap_area_index(pool, index + 1); |
| 211 | +++>>>>>>> 04867a7a3332 (swiotlb: Fix double-allocation of slots due to broken alignment handling) |
| 212 | + slots_checked++; |
| 213 | + continue; |
| 214 | + } |
| 215 | +* Unmerged path kernel/dma/swiotlb.c |
0 commit comments