Hi dri-devel,

udmabuf_create() accepts hugetlb memfds (check_memfd_seals() in
drivers/dma-buf/udmabuf.c:256-272 explicitly allows
is_file_hugepages()), but the buffer fill logic mishandles
offsets that are not aligned to the huge page size, causing the
exported dma_buf to serve data displaced from what the caller
requested — and in a partial-fill case, a mismatch between the
declared buffer size and the actually filled folio array.

== AFFECTED ==

- Linux 6.12.112 (linux-6.12.y stable), commit 7aba70ab
- Linux mainline, HEAD 24a19b953e2a (torvalds/linux), as of this
  report

== ROOT CAUSE ==

memfd_pin_folios() (mm/gup.c:3700-3809) pins the folios
overlapping the requested range and returns, per folio, the
offset of the range start *inside* that folio
(offset_in_folio(), gup.c:3777-3778). For a hugetlb memfd
(huge pages, e.g. 2MB), a range starting mid-folio returns a
large unaligned pgoff.

The udmabuf fill loop (udmabuf.c:288-340) then does:

    for each pinned folio:
        subpgoff = pgoff;                  /* starts unaligned */
        for (; subpgoff < folio_size(folio); subpgoff += PAGE_SIZE) {
                ubuf->folios[upgcnt] = folio;
                ubuf->offsets[upgcnt] = subpgoff;
                ++upgcnt;
                if (++cur_pgcnt >= pgcnt) goto end;
        }
        pgoff = 0;    /* reset only from the SECOND folio on */

Two consequences:

1) The first (huge) folio fills only
   (folio_size - unaligned_pgoff) / PAGE_SIZE slots instead of
   folio_size / PAGE_SIZE — so the accounting between
   cur_pgcnt (pages of the requested range) and upgcnt (slots
   filled) diverges from what the caller sized
   (pgcnt = list[i].size >> PAGE_SHIFT, udmabuf.c:364).

2) Subsequent folios restart their offsets at 0 (the pgoff
   reset at udmabuf.c:334), so the resulting buffer maps the
   folios with displaced data: the first folio serves bytes
   from the requested offset, but later folios serve from their
   own start — the exported dma_buf does not contain the
   contiguous range the caller asked for.

Also, when cur_pgcnt reaches pgcnt early (goto end at
udmabuf.c:324-325), upgcnt can end lower than the pgcnt the
user requested in some unaligned configurations — the declared
buffer size (exp_info.size = pagecount << PAGE_SHIFT,
udmabuf.c:281) then reflects the shortened fill.

There is no alignment check on the requested offsets/sizes
against the huge page size anywhere in udmabuf_create()
(udmabuf.c:342-382) or check_memfd_seals() — is_file_hugepages()
is accepted with no hstate-size validation.

== IMPACT ==

Data integrity: any consumer of the exported dma_buf (GPU
render paths, virtio buffers, cross-process sharing) reads
buffer contents displaced from the producer's layout — silent
data corruption within the buffer the caller created.

Reachability: udmabuf_create is reachable from any process via
/dev/udmabuf (misc device) with a memfd that it creates itself
(memfd_create with MFD_HUGETLB) — no special privileges.

Not a privilege escalation: the defect corrupts/reads the
caller's own buffer data, not kernel memory outside the pinned
folios.

== PROPOSED REMEDIATION ==

Either:

(a) Reject unaligned ranges for hugetlb memfds in
    udmabuf_create(): if is_file_hugepages(memfd), require
    (offset % huge_page_size(memfd)) == 0 for every list entry
    (or round handling per the QEMU/KVM usage expectations);

or (b) handle the first folio's unaligned pgoff consistently:
    count the first folio's contribution correctly so pgcnt and
    upgcnt stay in sync (the fill loop's cur_pgcnt accounting
    against the range pages does not currently match the
    folio-relative subpgoff walk).

Option (b) preserves the ability to share sub-folio ranges.

== NOTES ==

- Found by static analysis of the 6.12.y stable tree,
  cross-verified against mainline. No dynamic PoC built; the
  reproduction is a memfd_create(MFD_HUGETLB) + UDMABUF_CREATE
  with an unaligned offset, then comparing the mmap'd buffer
  content against the memfd layout.
- Happy to test patches and send the formal fix.
- Please credit "kerhunt <[email protected]>".

Best regards,
kerhunt

Reply via email to