swiotlb: Validate bounce size in the sync/unmap path
The size of the buffer being bounced is not checked if it happens to be larger than the size of the mapped buffer. Because the size can be controlled by a device, as it's the case with virtio devices, this can lead to memory corruption. This patch saves the remaining buffer memory for each slab and uses that information for validation in the sync/unmap paths before swiotlb_bounce is called. Validating this argument is important under the threat models of AMD SEV-SNP and Intel TDX, where the HV is considered untrusted. Signed-off-by: Martin Radev <martin.b.radev@gmail.com> Signed-off-by: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
This commit is contained in:
parent
3d2d861eb0
commit
daf9514fd5
@ -99,6 +99,11 @@ static unsigned int max_segment;
|
||||
#define INVALID_PHYS_ADDR (~(phys_addr_t)0)
|
||||
static phys_addr_t *io_tlb_orig_addr;
|
||||
|
||||
/*
|
||||
* The mapped buffer's size should be validated during a sync operation.
|
||||
*/
|
||||
static size_t *io_tlb_orig_size;
|
||||
|
||||
/*
|
||||
* Protect the above data structures in the map and unmap calls
|
||||
*/
|
||||
@ -247,9 +252,16 @@ int __init swiotlb_init_with_tbl(char *tlb, unsigned long nslabs, int verbose)
|
||||
panic("%s: Failed to allocate %zu bytes align=0x%lx\n",
|
||||
__func__, alloc_size, PAGE_SIZE);
|
||||
|
||||
alloc_size = PAGE_ALIGN(io_tlb_nslabs * sizeof(size_t));
|
||||
io_tlb_orig_size = memblock_alloc(alloc_size, PAGE_SIZE);
|
||||
if (!io_tlb_orig_size)
|
||||
panic("%s: Failed to allocate %zu bytes align=0x%lx\n",
|
||||
__func__, alloc_size, PAGE_SIZE);
|
||||
|
||||
for (i = 0; i < io_tlb_nslabs; i++) {
|
||||
io_tlb_list[i] = IO_TLB_SEGSIZE - io_tlb_offset(i);
|
||||
io_tlb_orig_addr[i] = INVALID_PHYS_ADDR;
|
||||
io_tlb_orig_size[i] = 0;
|
||||
}
|
||||
io_tlb_index = 0;
|
||||
no_iotlb_memory = false;
|
||||
@ -370,7 +382,7 @@ swiotlb_late_init_with_tbl(char *tlb, unsigned long nslabs)
|
||||
* between io_tlb_start and io_tlb_end.
|
||||
*/
|
||||
io_tlb_list = (unsigned int *)__get_free_pages(GFP_KERNEL,
|
||||
get_order(io_tlb_nslabs * sizeof(int)));
|
||||
get_order(io_tlb_nslabs * sizeof(int)));
|
||||
if (!io_tlb_list)
|
||||
goto cleanup3;
|
||||
|
||||
@ -381,9 +393,18 @@ swiotlb_late_init_with_tbl(char *tlb, unsigned long nslabs)
|
||||
if (!io_tlb_orig_addr)
|
||||
goto cleanup4;
|
||||
|
||||
io_tlb_orig_size = (size_t *)
|
||||
__get_free_pages(GFP_KERNEL,
|
||||
get_order(io_tlb_nslabs *
|
||||
sizeof(size_t)));
|
||||
if (!io_tlb_orig_size)
|
||||
goto cleanup5;
|
||||
|
||||
|
||||
for (i = 0; i < io_tlb_nslabs; i++) {
|
||||
io_tlb_list[i] = IO_TLB_SEGSIZE - io_tlb_offset(i);
|
||||
io_tlb_orig_addr[i] = INVALID_PHYS_ADDR;
|
||||
io_tlb_orig_size[i] = 0;
|
||||
}
|
||||
io_tlb_index = 0;
|
||||
no_iotlb_memory = false;
|
||||
@ -396,6 +417,10 @@ swiotlb_late_init_with_tbl(char *tlb, unsigned long nslabs)
|
||||
|
||||
return 0;
|
||||
|
||||
cleanup5:
|
||||
free_pages((unsigned long)io_tlb_orig_addr, get_order(io_tlb_nslabs *
|
||||
sizeof(phys_addr_t)));
|
||||
|
||||
cleanup4:
|
||||
free_pages((unsigned long)io_tlb_list, get_order(io_tlb_nslabs *
|
||||
sizeof(int)));
|
||||
@ -411,6 +436,8 @@ void __init swiotlb_exit(void)
|
||||
return;
|
||||
|
||||
if (late_alloc) {
|
||||
free_pages((unsigned long)io_tlb_orig_size,
|
||||
get_order(io_tlb_nslabs * sizeof(size_t)));
|
||||
free_pages((unsigned long)io_tlb_orig_addr,
|
||||
get_order(io_tlb_nslabs * sizeof(phys_addr_t)));
|
||||
free_pages((unsigned long)io_tlb_list, get_order(io_tlb_nslabs *
|
||||
@ -420,6 +447,8 @@ void __init swiotlb_exit(void)
|
||||
} else {
|
||||
memblock_free_late(__pa(io_tlb_orig_addr),
|
||||
PAGE_ALIGN(io_tlb_nslabs * sizeof(phys_addr_t)));
|
||||
memblock_free_late(__pa(io_tlb_orig_size),
|
||||
PAGE_ALIGN(io_tlb_nslabs * sizeof(size_t)));
|
||||
memblock_free_late(__pa(io_tlb_list),
|
||||
PAGE_ALIGN(io_tlb_nslabs * sizeof(int)));
|
||||
memblock_free_late(io_tlb_start,
|
||||
@ -608,9 +637,10 @@ phys_addr_t swiotlb_tbl_map_single(struct device *dev, phys_addr_t orig_addr,
|
||||
* This is needed when we sync the memory. Then we sync the buffer if
|
||||
* needed.
|
||||
*/
|
||||
for (i = 0; i < nr_slots(alloc_size + offset); i++)
|
||||
for (i = 0; i < nr_slots(alloc_size + offset); i++) {
|
||||
io_tlb_orig_addr[index + i] = slot_addr(orig_addr, i);
|
||||
|
||||
io_tlb_orig_size[index+i] = alloc_size - (i << IO_TLB_SHIFT);
|
||||
}
|
||||
tlb_addr = slot_addr(io_tlb_start, index) + offset;
|
||||
if (!(attrs & DMA_ATTR_SKIP_CPU_SYNC) &&
|
||||
(dir == DMA_TO_DEVICE || dir == DMA_BIDIRECTIONAL))
|
||||
@ -618,6 +648,17 @@ phys_addr_t swiotlb_tbl_map_single(struct device *dev, phys_addr_t orig_addr,
|
||||
return tlb_addr;
|
||||
}
|
||||
|
||||
static void validate_sync_size_and_truncate(struct device *hwdev, size_t orig_size, size_t *size)
|
||||
{
|
||||
if (*size > orig_size) {
|
||||
/* Warn and truncate mapping_size */
|
||||
dev_WARN_ONCE(hwdev, 1,
|
||||
"Attempt for buffer overflow. Original size: %zu. Mapping size: %zu.\n",
|
||||
orig_size, *size);
|
||||
*size = orig_size;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* tlb_addr is the physical address of the bounce buffer to unmap.
|
||||
*/
|
||||
@ -631,6 +672,8 @@ void swiotlb_tbl_unmap_single(struct device *hwdev, phys_addr_t tlb_addr,
|
||||
int index = (tlb_addr - offset - io_tlb_start) >> IO_TLB_SHIFT;
|
||||
phys_addr_t orig_addr = io_tlb_orig_addr[index];
|
||||
|
||||
validate_sync_size_and_truncate(hwdev, io_tlb_orig_size[index], &mapping_size);
|
||||
|
||||
/*
|
||||
* First, sync the memory before unmapping the entry
|
||||
*/
|
||||
@ -658,6 +701,7 @@ void swiotlb_tbl_unmap_single(struct device *hwdev, phys_addr_t tlb_addr,
|
||||
for (i = index + nslots - 1; i >= index; i--) {
|
||||
io_tlb_list[i] = ++count;
|
||||
io_tlb_orig_addr[i] = INVALID_PHYS_ADDR;
|
||||
io_tlb_orig_size[i] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -677,11 +721,14 @@ void swiotlb_tbl_sync_single(struct device *hwdev, phys_addr_t tlb_addr,
|
||||
enum dma_sync_target target)
|
||||
{
|
||||
int index = (tlb_addr - io_tlb_start) >> IO_TLB_SHIFT;
|
||||
size_t orig_size = io_tlb_orig_size[index];
|
||||
phys_addr_t orig_addr = io_tlb_orig_addr[index];
|
||||
|
||||
if (orig_addr == INVALID_PHYS_ADDR)
|
||||
return;
|
||||
|
||||
validate_sync_size_and_truncate(hwdev, orig_size, &size);
|
||||
|
||||
switch (target) {
|
||||
case SYNC_FOR_CPU:
|
||||
if (likely(dir == DMA_FROM_DEVICE || dir == DMA_BIDIRECTIONAL))
|
||||
|
Loading…
Reference in New Issue
Block a user