From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wr1-f49.google.com (mail-wr1-f49.google.com [209.85.221.49]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8288A367B99 for ; Mon, 5 Oct 2026 06:41:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.49 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791182509; cv=none; b=n5L/6i0W6HvK0PvBDRLf1OpJWSHQDXn6u4XHzV5036iKTqV2LAwOOKdaKQWgMy19+Ab9rfm4k9vwdnylwbPIO6h6253qNbskuNZnFUybcewuYbWwfLvJnMdb2N9ArkOiVMfAFvT6krWocpothHgyu/+P/rl/aUMN/nZHRW40wts= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791182509; c=relaxed/simple; bh=C1s1NOK5capGxkAfXNfN1qEhmcibh/Y6yUVlBOkk+A0=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=thQg55JuNtBzVmRdC+NrVGPMkg7rWG75QrTyN1D8o0nQIh844WNaNujVncSrjEelbD1jYS78D6QN1Jj7pJFuDJnKDqb+X7xChAsL8Hz45wOeym6t6zBdlumvVe9d1m/dmb570IczJSu+VNlEZfAYqq5UyoSPqUfYBG48LhfJBeo= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Kw/UulGx; arc=none smtp.client-ip=209.85.221.49 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Kw/UulGx" Received: by mail-wr1-f49.google.com with SMTP id ffacd0b85a97d-48441a2ba1bso733319f8f.1 for ; Sun, 04 Oct 2026 23:41:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1791182502; x=1791787302; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=1AVlpPfw5UZbso5/hscvA6TJLBv2kCVbh4MVGQA/j1o=; b=Kw/UulGxoq5DQk9vdWRhQn9s8NgLkpBCNe1DDx7Ae/IKmN6mXffezEydooUf/1cnM8 Z1Mu+fhAdbSqSDywEjhr/hP0FgTJTWNncjfAK71/hqZZg4BW00RNtXWzkRKL9bIrX+lH Z1mHYxkfIbhoW4BhGmqYIt+HHwXmttgV+iGXCszH25psjlETHn1ECLzom6EhGgcH33wq +A8vf6yYnKLdGgeCVJYfrORsSdz+Dk6OVKPVQESnxWdAq6bcCkJ1QljNgI8GJI7qhSa3 iEHdF5w2rNQHx/CVflAWQrZolbuSMqcs2d/qPL+w2kNdzjB5GhjfWuKMMnQfX24g5Pcb go2g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1791182502; x=1791787302; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=1AVlpPfw5UZbso5/hscvA6TJLBv2kCVbh4MVGQA/j1o=; b=UzHeS9Gy9OgWA5zbVLKw1et7hlToyy52c8PZZKakrXpSLDcdcOhfYwmI9MMRj0DHcx kTNWUmHll8H7w35cexeQXM3kUr1M526EVj7N2ZIDL0a/l52x0RgP9vUbNDXX8kFIEVz/ C5ebdD/PDzCGlTuq/TcXS3xqWP8rxsn6GUoohrB1+/gLB/kpSoxBHHLDkFYYu7JrNCaL 9bXDjgp3gSKkf/rymTQRyfOQuyhopQuHUmNZMMZojk0bwskF9m2E0PYW/AWgQShRJ7mX 7SzijqNpUJbpHhP8UiJu8FEwChTFIkQPWg5eY0aO4SEoyXpjDJGnadwcNn6TCue5rJ9f jLyw== X-Forwarded-Encrypted: i=1; AKwUvBzDDpOFJ4Emr3fNZ4YWEAO5NRbaySVPeDWYoxDdklVUfE+imLbwli46QFeP6/XWCr4PvW1ky8VtNgB1wg==@vger.kernel.org X-Gm-Message-State: AFuF++knu3YIz10iujiDrhobaqtO5U+McSBqIhEwKGfjcbgXFX2pem+Z l84TrGosU+59gx5hhOptMcND5AN4DxISzyLpq4Xul6mCZvQxXiVvq/AO X-Gm-Gg: AYBFou1tNSsw2gzSPP77Mi7LUyq7EDy5lBj9EjXuvNM+vYFnSzdGK66IOiQNM5Xiqcl ic25RcJWXFHf2oSO3DkwyCHm6hu7X6CCXzKjDZiPsv1t9KvROzZb44JMQwivX3AXQw+l/ZUQ6wS wcBgLa0dyvp+1I/qfPo6poKJcK7GpuHf9uM0BjVTk6fH+NYIQ38topH6Ao05Wz3S35WBY65FSQu AW5xo3vPgroB8nWhIXjVJh5+CIH1c2McTAvDYor9uRj8x5kcjqIbhll8lr///xz4eoWfEgVUuCb 6iWQvHlob92Y0AVMl0ESWZ7rH2fTsKORHRX7/g6YgK1BSZq47inw4v/bwWYmEEP6j0Jxd3muC9/ jie1DUuaeie5czQcIkdGxfjyY/dKmrwRzOEW+g2YFep5ej5wstYBIFFblVsAh/WA8f/YjeyCLY4 FaQF0VbLnE6wWLzF2mm1DgZfpHv1R7fIalv08gKJ4cq7oRHXk784FlRT4DBRMtOGSG4cTfznCVv Zvebyh3up2ACEQCB4m+GaTBuJ3nnI3JjPVI5YE/OBd9HSqY9DBuiKfex25hO3iDcRyBlhazt2it LEStvCMTc4dbEMuv5zGeNqIYLQSSIxhjxw0AxiJBebtSpX85MNtKZ9dj4dV5FbciALUq80fRNpL Zb3x8aoCVisw+ X-Received: by 2002:a05:600c:a013:b0:4a1:69fd:f90a with SMTP id 5b1f17b1804b1-4a169fdfac5mr81732785e9.26.1791182502096; Sun, 04 Oct 2026 23:41:42 -0700 (PDT) Received: from localhost.localdomain (dynamic-2a02-3100-b305-2001-39fa-3d24-821a-4eae.310.pool.telefonica.de. [2a02:3100:b305:2001:39fa:3d24:821a:4eae]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-48c62289b62sm1666871f8f.20.2026.10.04.23.41.41 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 04 Oct 2026 23:41:41 -0700 (PDT) From: Karl Mehltretter To: Sumit Semwal , =?UTF-8?q?Christian=20K=C3=B6nig?= Cc: Karl Mehltretter , Andrew Morton , Jason Gunthorpe , Rob Clark , Jianfeng Liu , Diederik de Haas , Andy Shevchenko , Vinod Koul , Bjorn Andersson , linux-media@vger.kernel.org, dri-devel@lists.freedesktop.org, linaro-mm-sig@lists.linaro.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 2/3] dma-buf: add a warn-only mode to DMABUF_DEBUG Date: Mon, 5 Oct 2026 08:41:32 +0200 Message-Id: <20261005064133.7305-3-kmehltretter@gmail.com> X-Mailer: git-send-email 2.39.5 (Apple Git-154) In-Reply-To: <20261005064133.7305-1-kmehltretter@gmail.com> References: <20261005064133.7305-1-kmehltretter@gmail.com> Precedence: bulk X-Mailing-List: linux-media@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit DMABUF_DEBUG hands importers a copy of the exporter's sg_table without the struct pages. An importer that uses them then fails, often far from the cause and without a message that points at it. Where sg_dma_len() is sg->length, the copy cannot hide the length either. Add DMABUF_DEBUG_WARN. With it the copy keeps the CPU side of the exporter's table: page, offset and length of all orig_nents entries, and the exporter's nents and orig_nents. A table without a CPU side (orig_nents == 0) stays that way. Every entry of the copy is marked with a new dma_flags bit, SG_DMA_DMABUF_DEBUG. sg_page(), sg_nents_for_len() and sg_split() test the bit and print a rate limited message with a stack trace. sg_page() is mostly reached through DMA and scatterlist helpers, so the trace is what names the importer. These CPU-side accesses can continue after the report instead of failing because the fields were cleared. The report is not a WARN(). It does not taint the kernel and does not trigger panic_on_warn. Reads of sg->offset and sg->length that do not go through these helpers are not seen. The option adds a test to every sg_page() and selects NEED_SG_DMA_FLAGS. Strict mode remains the default and continues to remove the CPU-side fields. Assisted-by: LLM Signed-off-by: Karl Mehltretter --- Notes: Tested on v7.3-rc4-70-gfe2ec83746e5 with GCC 15.2.0. Builds, without warnings: - x86_64 with DMABUF_DEBUG_WARN=y, full build - arm64 defconfig with DMABUF_DEBUG_WARN=y: Image, and the msm and rockchip DRM drivers - ARM926 (SAM9X75) with DMABUF_DEBUG_WARN=y and without NEED_SG_DMA_LENGTH, full build - ARM926 with DMABUF_DEBUG=n and without NEED_SG_DMA_FLAGS, full build. dma-buf.o and lib/scatterlist.o disassemble as on the base commit, except for __LINE__ constants. - x86_64 with SG_SPLIT, W=1, the objects touched here: with DMABUF_DEBUG_WARN=y, with DMABUF_DEBUG=y alone, and with DMABUF_DEBUG=n Runtime, both in QEMU with local device models, not on hardware. Zynq with an AXI ADC, IIO DMABUF capture into udmabuf buffers, NEED_SG_DMA_LENGTH=y. The IIO dmaengine buffer calls sg_nents_for_len() on the attachment's table. With DMABUF_DEBUG=y alone the capture fails: DMABUF_ENQUEUE failed: Device or resource busy With DMABUF_DEBUG_WARN=y all 16 blocks arrive with the right data, the kernel is not tainted, and the log has one report: DMA-BUF: importer used the CPU side of an exporter's sg_table CPU: 0 UID: 0 PID: 48 Comm: iio-dmabuf Not tainted 7.3.0-rc4+ #2 VOLUNTARY Hardware name: Xilinx Zynq Platform Call trace: unwind_backtrace from show_stack+0x10/0x14 show_stack from dump_stack_lvl+0x54/0x68 dump_stack_lvl from sg_nents_for_len+0xd8/0xe4 sg_nents_for_len from iio_dmaengine_buffer_submit_block+0x4c/0x33c iio_dmaengine_buffer_submit_block from iio_dma_buffer_submit_block.part.0+0x5c/0x104 iio_dma_buffer_submit_block.part.0 from iio_dma_buffer_enqueue_dmabuf+0x68/0xa0 iio_dma_buffer_enqueue_dmabuf from iio_buffer_chrdev_ioctl+0x520/0x9a4 iio_buffer_chrdev_ioctl from sys_ioctl+0x460/0x914 sys_ioctl from ret_fast_syscall+0x0/0x4c x86_64 with intel-iommu, xHCI and a SUR40, capture into udmabuf buffers. This kernel had a one-line test-only change in sur40 that makes the vb2 queue use the USB controller's DMA device. There was no report during boot. The capture gives one, from sg_page() in iommu_dma_map_sg(): DMA-BUF: importer used the CPU side of an exporter's sg_table CPU: 0 UID: 0 PID: 9 Comm: kworker/0:0 Not tainted 7.3.0-rc4+ #5 PREEMPT(lazy) Workqueue: events_freezable input_dev_poller_work Call Trace: dump_stack_lvl+0x4d/0x70 iommu_dma_map_sg+0x4c0/0x1010 __dma_map_sg_attrs+0x254/0x3b0 dma_map_sg_attrs+0xe/0x20 usb_hcd_map_urb_for_dma+0x7e0/0x1620 usb_hcd_submit_urb+0x162/0x1af0 usb_sg_wait+0x17c/0x550 sur40_poll+0xb3e/0xff0 input_dev_poller_work+0x54/0x90 process_one_work+0x692/0xf90 [...] The capture did not finish in this setup. The guest stalled in xhci_queue_bulk_tx() after the report, so this run only shows the report. The SG_DMA_* defines move up in scatterlist.h because sg_page() needs the new one. drivers/dma-buf/Kconfig | 23 +++++++++++++++++++ drivers/dma-buf/dma-buf.c | 44 ++++++++++++++++++++++++++++++++++++- include/linux/scatterlist.h | 26 +++++++++++++++++++--- lib/scatterlist.c | 22 +++++++++++++++++++ lib/sg_split.c | 2 ++ 5 files changed, 113 insertions(+), 4 deletions(-) diff --git a/drivers/dma-buf/Kconfig b/drivers/dma-buf/Kconfig index e4f078a326a4..06177465091a 100644 --- a/drivers/dma-buf/Kconfig +++ b/drivers/dma-buf/Kconfig @@ -49,6 +49,29 @@ config DMABUF_DEBUG exporters. Specifically it validates that importers do not peek at the underlying struct page when they import a buffer. +config DMABUF_DEBUG_WARN + bool "Warn instead of hiding the pages from DMA-BUF importers" + depends on DMABUF_DEBUG + select NEED_SG_DMA_FLAGS + help + DMABUF_DEBUG normally hands importers a copy of the exporter's + sg_table without the struct page pointers, so that an importer which + uses them fails early. + + With this option the copy keeps the pages, offsets and lengths and is + only marked. sg_page() and the helpers built on it, sg_nents_for_len() + and sg_split() then print a rate limited message with a stack trace + when they are used on such a table. The access itself continues + instead of failing because the fields were cleared. The message is + not a WARN(): it does not taint the kernel or trigger panic_on_warn. + + Importers that read sg->offset or sg->length directly are not + noticed. The option adds a test to every sg_page() call. It selects + NEED_SG_DMA_FLAGS, which adds dma_flags and may increase the size of + struct scatterlist. + + If unsure, say N. + config DMABUF_KUNIT_TEST tristate "KUnit tests for DMA-BUF" if !KUNIT_ALL_TESTS depends on KUNIT diff --git a/drivers/dma-buf/dma-buf.c b/drivers/dma-buf/dma-buf.c index b3d311acb883..bd82b6b7f5ff 100644 --- a/drivers/dma-buf/dma-buf.c +++ b/drivers/dma-buf/dma-buf.c @@ -57,6 +57,7 @@ struct dma_buf_sg_table_wrapper { struct sg_table *original; struct sg_table wrapper; + unsigned int alloc_nents; }; static inline int is_dma_buf_file(struct file *); @@ -874,11 +875,42 @@ void dma_buf_put(struct dma_buf *dmabuf) } EXPORT_SYMBOL_NS_GPL(dma_buf_put, "DMA_BUF"); +#ifdef CONFIG_DMABUF_DEBUG_WARN +/* + * Warn mode: the importer also gets the CPU side of the exporter's table, + * marked so that sg_page() and friends can report who uses it. + */ +static void dma_buf_wrap_cpu_side(struct sg_table *to, struct sg_table *from) +{ + struct scatterlist *to_sg, *from_sg; + int i; + + for_each_sgtable_sg(to, to_sg, i) + to_sg->dma_flags |= SG_DMA_DMABUF_DEBUG; + + to_sg = to->sgl; + for_each_sgtable_sg(from, from_sg, i) { + sg_assign_page(to_sg, sg_page(from_sg)); + to_sg->offset = from_sg->offset; + to_sg->length = from_sg->length; + to_sg = sg_next(to_sg); + } + + to->nents = from->nents; + to->orig_nents = from->orig_nents; +} +#else +static void dma_buf_wrap_cpu_side(struct sg_table *to, struct sg_table *from) +{ +} +#endif + static int dma_buf_wrap_sg_table(struct sg_table **sg_table) { struct scatterlist *to_sg, *from_sg; struct sg_table *from = *sg_table; struct dma_buf_sg_table_wrapper *to; + unsigned int nents = from->nents; int i, ret; if (!IS_ENABLED(CONFIG_DMABUF_DEBUG)) @@ -888,14 +920,21 @@ static int dma_buf_wrap_sg_table(struct sg_table **sg_table) * To catch abuse of the underlying struct page by importers copy the * sg_table without copying the page_link and give only the copy back to * the importer. + * + * With DMABUF_DEBUG_WARN the copy keeps the CPU side, which can have + * more entries than the DMA side. */ to = kzalloc_obj(*to); if (!to) return -ENOMEM; - ret = sg_alloc_table(&to->wrapper, from->nents, GFP_KERNEL); + if (IS_ENABLED(CONFIG_DMABUF_DEBUG_WARN)) + nents = max(nents, from->orig_nents); + + ret = sg_alloc_table(&to->wrapper, nents, GFP_KERNEL); if (ret) goto free_to; + to->alloc_nents = nents; to_sg = to->wrapper.sgl; for_each_sgtable_dma_sg(from, from_sg, i) { @@ -910,6 +949,7 @@ static int dma_buf_wrap_sg_table(struct sg_table **sg_table) #endif to_sg = sg_next(to_sg); } + dma_buf_wrap_cpu_side(&to->wrapper, from); to->original = from; *sg_table = &to->wrapper; @@ -929,6 +969,8 @@ static void dma_buf_unwrap_sg_table(struct sg_table **sg_table) copy = container_of(*sg_table, typeof(*copy), wrapper); *sg_table = copy->original; + /* sg_free_table() needs the number of allocated entries */ + copy->wrapper.orig_nents = copy->alloc_nents; sg_free_table(©->wrapper); kfree(copy); } diff --git a/include/linux/scatterlist.h b/include/linux/scatterlist.h index 6de1a2434299..5dc8c134ca4c 100644 --- a/include/linux/scatterlist.h +++ b/include/linux/scatterlist.h @@ -21,6 +21,13 @@ struct scatterlist { #endif }; +/* Bits in dma_flags, see below. sg_page() needs SG_DMA_DMABUF_DEBUG. */ +#ifdef CONFIG_NEED_SG_DMA_FLAGS +#define SG_DMA_BUS_ADDRESS (1 << 0) +#define SG_DMA_SWIOTLB (1 << 1) +#define SG_DMA_DMABUF_DEBUG (1 << 2) +#endif + /* * These macros should be used after a dma_map_sg call has been done * to get bus addresses of each of the SG entries and their lengths. @@ -188,11 +195,27 @@ static inline void sg_set_folio(struct scatterlist *sg, struct folio *folio, sg->length = len; } +#ifdef CONFIG_DMABUF_DEBUG_WARN +void sg_dmabuf_cpu_access_warn(void); + +/* Report use of the CPU side of a table that a DMA-BUF importer was given */ +static inline void sg_dmabuf_cpu_access_check(struct scatterlist *sg) +{ + if (unlikely(sg->dma_flags & SG_DMA_DMABUF_DEBUG)) + sg_dmabuf_cpu_access_warn(); +} +#else +static inline void sg_dmabuf_cpu_access_check(struct scatterlist *sg) +{ +} +#endif + static inline struct page *sg_page(struct scatterlist *sg) { #ifdef CONFIG_DEBUG_SG BUG_ON(sg_is_chain(sg)); #endif + sg_dmabuf_cpu_access_check(sg); return (struct page *)((sg)->page_link & ~SG_PAGE_LINK_MASK); } @@ -303,9 +326,6 @@ static inline void sg_unmark_end(struct scatterlist *sg) */ #ifdef CONFIG_NEED_SG_DMA_FLAGS -#define SG_DMA_BUS_ADDRESS (1 << 0) -#define SG_DMA_SWIOTLB (1 << 1) - /** * sg_dma_is_bus_address - Return whether a given segment was marked * as a bus address diff --git a/lib/scatterlist.c b/lib/scatterlist.c index 6ea40d2e6247..55a3697df881 100644 --- a/lib/scatterlist.c +++ b/lib/scatterlist.c @@ -12,6 +12,27 @@ #include #include #include +#include +#include + +#ifdef CONFIG_DMABUF_DEBUG_WARN +/* + * Not a WARN(): a wrong importer must not taint the kernel or trigger + * panic_on_warn. sg_page() is mostly reached through DMA or scatterlist + * helpers, so the stack trace is what identifies the importer. + */ +void sg_dmabuf_cpu_access_warn(void) +{ + static DEFINE_RATELIMIT_STATE(rs, DEFAULT_RATELIMIT_INTERVAL, 1); + + if (!__ratelimit(&rs)) + return; + + pr_warn("DMA-BUF: importer used the CPU side of an exporter's sg_table\n"); + dump_stack_lvl(KERN_WARNING); +} +EXPORT_SYMBOL(sg_dmabuf_cpu_access_warn); +#endif /** * sg_nents - return total count of entries in scatterlist @@ -54,6 +75,7 @@ int sg_nents_for_len(struct scatterlist *sg, u64 len) return 0; for (nents = 0, total = 0; sg; sg = sg_next(sg)) { + sg_dmabuf_cpu_access_check(sg); nents++; total += sg->length; if (total >= len) diff --git a/lib/sg_split.c b/lib/sg_split.c index 24e8f5e48e63..cbbc7f9a206d 100644 --- a/lib/sg_split.c +++ b/lib/sg_split.c @@ -33,6 +33,8 @@ static int sg_calculate_split(struct scatterlist *in, int nents, int nb_splits, } for_each_sg(in, sg, nents, i) { + if (!mapped) + sg_dmabuf_cpu_access_check(sg); sglen = mapped ? sg_dma_len(sg) : sg->length; if (skip > sglen) { skip -= sglen; -- 2.53.0