Thank you for your contribution! Sashiko AI review found 15 potential issue(s) to consider: - [High] Calling `cma_init_reserved_mem` on a `no-map` region causes a boot-time kernel panic. - [High] Memory allocated with `kvmalloc_array()` is incorrectly freed with `kfree()`. - [High] Calling `dma_map_sgtable()` with an SG list containing a NULL page maps garbage physical addresses. - [High] Direct invocation of system PM callbacks (`pm_generic_freeze`/`thaw`) bypasses the PM core. - [High] 0-sized chunks generate an integer underflow that permanently breaks VPR buffer allocation. - [High] Pointer arithmetic on `struct page` arrays across MAX_ORDER / memory sections causes panics on SPARSEMEM. - [High] Direct map corruption when returning partially invalidated pages to the CMA allocator. - [High] Integer overflow in allocation size allows a zero-sized allocation that permanently bricks the heap. - [High] Shrinking VPR restores linear mapping before the hardware protection is removed, risking speculative SErrors. - [Medium] Memory and reference leaks on the error path of `tegra_vpr_add_heap()`. - [Medium] Missing lock acquisition when traversing `vpr->buffers` list in `tegra_vpr_debugfs_show_chunks`. - [Medium] Dereferencing `dev->driver->pm` without checking if `dev->driver` is valid. - [Low] Off-by-one error in VPR end_page calculation permanently wastes the last page. - [Low] Use of dynamically sized Variable Length Arrays (VLAs) via `DECLARE_BITMAP`. - [Low] The commit message claims userspace processes are frozen during resize, but the code does not implement this. --
commit 08e43ed119b9e546aa530f96844c1805cc1597a2 Author: Thierry Reding <[email protected]> Subject: dma-buf: heaps: Add support for Tegra VPR This commit introduces DMA-BUF heap support for the NVIDIA Tegra Video Protection Region (VPR). It adds initialization for CMA memory during boot and a heap driver for dynamically allocating and resizing chunks of this protected memory. > Unfortunately, resizing the VPR is slightly tricky because the GPU found > on Tegra SoCs must be in reset during the VPR resize operation. This is > currently implemented by freezing all userspace processes and calling > invoking the GPU's freeze() implementation, resizing and the thawing the > GPU and userspace processes. This is quite heavy-handed, so eventually [Severity: Low] The commit message states that userspace processes are frozen during resize, but this doesn't seem to be implemented in the code (perhaps removed in v4?). Could the commit message be updated to reflect the current behavior? > diff --git a/drivers/dma-buf/heaps/tegra-vpr-init.c > b/drivers/dma-buf/heaps/tegra-vpr-init.c > --- /dev/null > +++ b/drivers/dma-buf/heaps/tegra-vpr-init.c > @@ -0,0 +1,133 @@ [ ... ] > +static int __init tegra_vpr_node_init(unsigned long offset, > + struct reserved_mem *rmem) > +{ > + struct cma *cma; > + int err; > + > + if (!IS_ALIGNED(rmem->base, SZ_1M)) { > + pr_err("%s: base is not aligned to 1 MiB\n", rmem->name); > + return -EINVAL; > + } > + > + if (!IS_ALIGNED(rmem->size, SZ_1M)) { > + pr_err("%s: size is not aligned to 1 MiB\n", rmem->name); > + return -EINVAL; > + } > + > + err = cma_init_reserved_mem(rmem->base, rmem->size, 0, rmem->name, > + &cma); [Severity: High] Can this cause a panic during early boot if the firmware configures a fixed VPR using the no-map property? A no-map region is removed from the linear memory map (no struct page allocation), but cma_init_reserved_mem() is called unconditionally. When CMA later tries to return pages to the buddy allocator via pfn_to_page(), won't it access invalid pointers? [ ... ] > +static int tegra_vpr_device_init(struct reserved_mem *rmem, struct device > *dev) > +{ > + const struct dev_pm_ops *pm = dev->driver->pm; [Severity: Medium] Is it safe to blindly dereference dev->driver here? If this is called on a device node without a bound driver, dev->driver would be NULL, causing a panic. Should we verify dev->driver is valid first? > diff --git a/drivers/dma-buf/heaps/tegra-vpr.c > b/drivers/dma-buf/heaps/tegra-vpr.c > --- /dev/null > +++ b/drivers/dma-buf/heaps/tegra-vpr.c > @@ -0,0 +1,1210 @@ [ ... ] > +static int tegra_vpr_resize(struct tegra_vpr *vpr) > +{ > + struct tegra_vpr_device *node; > + phys_addr_t base, size; > + int err, status = 0; > + > + err = tegra_vpr_get_extents(vpr, &base, &size); > + if (err < 0) { > + pr_err("%s(): failed to get VPR extents: %d\n", __func__, err); > + return err; > + } > + > + list_for_each_entry(node, &vpr->devices, node) { > + err = pm_generic_freeze(node->dev); [ ... ] > + list_for_each_entry_continue_reverse(node, &vpr->devices, node) { > + err = pm_generic_thaw(node->dev); [Severity: High] Does calling pm_generic_freeze() and pm_generic_thaw() directly bypass the PM core's synchronization? Normally these are coordinated under device_lock. If a device's driver is concurrently unbinding, dev->driver might become NULL during the callback, which could lead to a kernel panic. Should this be coordinated with the PM core instead? [ ... ] > +static bool tegra_vpr_chunk_overlaps(struct tegra_vpr_chunk *chunk, > + unsigned int start, unsigned int limit) > +{ > + unsigned int first = chunk->offset; > + unsigned int last = chunk->offset + chunk->num_pages - 1; [Severity: High] Could chunk->num_pages be 0 for trailing chunks if the total VPR size doesn't evenly divide into num_chunks? Looking at tegra_vpr_setup_chunks(): size = min_t(size_t, size, max_size); end = start + size - 1; If the remaining size is 0, this would cause chunk->num_pages to be 0, making the calculation underflow to UINT_MAX. This would cause the 0-sized chunk to falsely overlap all future allocations and break the VPR heap. [ ... ] > +static int tegra_vpr_activate_chunks(struct tegra_vpr *vpr, > + struct tegra_vpr_buffer *buffer) > +{ > + DECLARE_BITMAP(dirty, vpr->num_chunks); [Severity: Low] Since vpr->num_chunks is evaluated at runtime, doesn't DECLARE_BITMAP create a Variable Length Array (VLA) on the stack? The kernel globally forbids VLAs and this will likely cause a build failure with -Werror=vla. > + unsigned int i, bottom, top; > + int err = 0, ret; > + > + bitmap_zero(dirty, vpr->num_chunks); > + > + /* activate any inactive chunks that overlap this buffer */ > + for_each_set_bit(i, buffer->chunks, vpr->num_chunks) { > + struct tegra_vpr_chunk *chunk = &vpr->chunks[i]; > + > + if (chunk->active) > + continue; > + > + err = tegra_vpr_chunk_alloc(chunk); > + if (err < 0) > + goto deactivate; > + > + err = tegra_vpr_chunk_activate(chunk); > + if (err < 0) { > + tegra_vpr_chunk_release(chunk); > + goto deactivate; > + } [Severity: High] If set_direct_map_invalid_noflush() fails partially, tegra_vpr_chunk_activate() returns an error, and the caller immediately invokes tegra_vpr_chunk_release(). Will this return partially invalidated pages back to the CMA allocator without restoring the linear mapping? Subsequent allocations of these pages could trigger direct map faults. [ ... ] > +static int tegra_vpr_find_free_region(struct tegra_vpr *vpr, > + unsigned int num_pages, > + unsigned long align) > +{ > + return tegra_vpr_find_free_region_in_range(vpr, 0, vpr->num_pages - 1, > + num_pages, align); > +} [Severity: Low] Is there an off-by-one error here? tegra_vpr_find_free_region_in_range() expects end_page as an exclusive upper bound, but passing vpr->num_pages - 1 means an allocation perfectly reaching the end of the region will be incorrectly rejected. [ ... ] > +static struct tegra_vpr_buffer * > +tegra_vpr_buffer_allocate(struct tegra_vpr *vpr, size_t size) > +{ [ ... ] > + buffer->pages = kvmalloc_array(buffer->num_pages, > + sizeof(*buffer->pages), > + GFP_KERNEL); > + if (!buffer->pages) { > + err = -ENOMEM; > + goto free; > + } > + > + for (i = 0; i < buffer->num_pages; i++) > + buffer->pages[i] = &vpr->start_page[pageno + i]; [Severity: High] Is it safe to perform direct pointer arithmetic on struct page arrays across potentially massive VPR regions? On platforms using the SPARSEMEM memory model without VMEMMAP, struct page objects are only contiguous within a single memory section. A 2 GiB VPR region crosses many section boundaries, so this arithmetic could point into unmapped memory and crash the kernel when accessed. [ ... ] > +static void tegra_vpr_buffer_release(struct tegra_vpr_buffer *buffer) > +{ [ ... ] > + kfree(buffer->pages); > + kfree(buffer); > +} [Severity: High] buffer->pages is allocated using kvmalloc_array(), which may fall back to vmalloc space. Shouldn't this be freed with kvfree()? Calling kfree() on a vmalloc'd pointer will cause a SLUB panic or memory corruption. [ ... ] > +static int tegra_vpr_attach(struct dma_buf *buf, > + struct dma_buf_attachment *attachment) > +{ [ ... ] > + } else { > + if (sg_alloc_table(&attach->sgt, 1, GFP_KERNEL)) { > + err = -ENOMEM; > + goto free; > + } > + > + sg_set_page(attach->sgt.sgl, NULL, buffer->size, 0); > + sg_dma_address(attach->sgt.sgl) = buffer->start; > + sg_dma_len(attach->sgt.sgl) = buffer->size; > + } [Severity: High] For static VPR setups, the SG list is populated with a NULL page. When tegra_vpr_map_dma_buf() later calls dma_map_sgtable(), the IOMMU DMA mapping code relies on sg_phys(), which calculates page_to_phys(NULL) + offset. This usually results in physical address 0. The IOMMU will be programmed to map address 0 instead of the actual VPR hardware address, breaking DMA. [ ... ] > +static void tegra_vpr_recycle(struct tegra_vpr *vpr) > +{ > + DECLARE_BITMAP(dirty, vpr->num_chunks); > + unsigned int i; > + int err; [Severity: Low] Here is another DECLARE_BITMAP using vpr->num_chunks that creates a VLA. [ ... ] > + err = tegra_vpr_chunk_deactivate(chunk); [ ... ] > + if (!bitmap_empty(dirty, vpr->num_chunks)) { > + err = tegra_vpr_resize(vpr); [Severity: High] tegra_vpr_chunk_deactivate() is called before tegra_vpr_resize(). This restores the CPU's linear mapping to the memory while the hardware firewall is still active. Does this inverted ordering create a race window where speculative CPU accesses to the direct map could trigger a fatal bus fault / SError before the hardware firewall is shrunk? [ ... ] > +static struct dma_buf *tegra_vpr_allocate(struct dma_heap *heap, > + unsigned long len, u32 fd_flags, > + u64 heap_flags) > +{ > + struct tegra_vpr *vpr = dma_heap_get_drvdata(heap); > + struct tegra_vpr_buffer *buffer, *entry; > + size_t size = ALIGN(len, vpr->align); [Severity: High] Could ALIGN(len, vpr->align) overflow to 0 if an unprivileged user requests a massive len? A 0-sized allocation might succeed and eventually underflow vpr->last when it computes vpr->last = limit - 1 with limit = 0. This would permanently corrupt the heap tracking bounds and break future clustered allocations. [ ... ] > +static void tegra_vpr_debugfs_show_chunks(struct tegra_vpr *vpr, > + struct seq_file *s) > +{ > + struct tegra_vpr_buffer *buffer; > + unsigned int i; > + char buf[16]; > + > + for (i = 0; i < vpr->num_chunks; i++) { > + const struct tegra_vpr_chunk *chunk = &vpr->chunks[i]; > + > + string_get_size(chunk->size, 1, STRING_UNITS_2, buf, > + sizeof(buf)); > + seq_printf(s, " %pap-%pap (%s) (%s, %u buffers)\n", > + &chunk->start, &chunk->limit, buf, > + chunk->active ? "active" : "inactive", > + chunk->num_buffers); > + } > + > + list_for_each_entry(buffer, &vpr->buffers, list) { [Severity: Medium] This iterates over the vpr->buffers linked list without holding vpr->lock. If a user reads this debugfs file concurrently with heap allocations or frees, could this race with list mutations and cause use-after-free or list corruption panics? [ ... ] > +static int tegra_vpr_add_heap(struct reserved_mem *rmem, > + struct device_node *np) > +{ [ ... ] > + vpr->dev_node = of_node_get(np); [ ... ] > + if (vpr->resizable) > + err = tegra_vpr_setup_chunks(vpr, rmem->name); > + else > + err = tegra_vpr_setup_static(vpr); > + > + if (err < 0) > + goto free; [ ... ] > +free: > + bitmap_free(vpr->bitmap); > + kfree(vpr); > + return err; > +} [Severity: Medium] There appear to be multiple leaks in the error paths here. If tegra_vpr_setup_chunks() fails, it jumps to free without calling of_node_put(vpr->dev_node). Additionally, if dma_heap_add() fails, the cleanup path calls tegra_vpr_free_chunks(), which correctly frees the chunks array but forgets to free the vpr->active bitmap allocated during setup. -- Sashiko AI review ยท https://sashiko.dev/#/patchset/[email protected]?part=9
