get_user_pages and Page Pinning

get_user_pages() (universally abbreviated GUP) is how kernel code reaches into a user process’s address space and obtains the actual struct pages behind a range of user virtual addresses — resolving each address through the process’s page tables, faulting pages in if needed, and taking a reference so they stay resident. Drivers, Direct I/O, RDMA, and io_uring all use it so that hardware or kernel code can touch a user buffer’s physical pages directly, bypassing the page tables that the CPU would otherwise use. GUP is notorious: holding a plain reference does not tell the rest of the memory-management subsystem that the page’s data is being read or written behind its back, which collided with writeback, copy-on-write, and filesystem layout changes and produced real security bugs. The fix was a distinct concept — a pin (FOLL_PIN, via the pin_user_pages() family, merged in Linux 5.6, early 2020 (Corbet, LWN)) — that is trackable and that the fault and writeback paths can interrogate. This note explains the GUP family, the FOLL_GET vs FOLL_PIN vs FOLL_LONGTERM distinction, how a pin is counted, the COW/long-term-pin hazards, and how pins are released. (API shapes are read from pinned v6.12 mm.h; the design rationale is the in-tree pin_user_pages.rst.)

Mental Model — A Reference vs a Pin

A normal user page lives under the kernel’s full control: reclaim can move it, compaction can migrate it to defragment memory, writeback can write it back and mark it clean, COW can replace it with a private copy. GUP hands a physical struct page to something that will touch the page outside that control — a DMA engine, an RDMA NIC, an io_uring fixed buffer. Two strengths of “hold on to this page” exist:

  • A reference (FOLL_GET) — _refcount++. Says “don’t free this frame.” Says nothing about whether the data is being accessed. Indistinguishable from the hundreds of other reasons a page is referenced.
  • A pin (FOLL_PIN) — a trackable reference whose presence the kernel can query with folio_maybe_dma_pinned(). Says “this frame’s data is owned by an out-of-band accessor; be careful before unmapping, migrating, or writing it back.”
flowchart TB
  U["User VA range<br/>(start, nr_pages)"]
  U -->|"walk page tables,<br/>fault in if absent"| PG["struct page[] returned"]
  PG --> Q{"Why are you holding it?"}
  Q -->|"manipulate struct page only<br/>(short, no data access)"| GET["FOLL_GET<br/>_refcount += 1<br/>(get_user_pages*)"]
  Q -->|"access page DATA<br/>(DMA / DIO / RDMA)"| PIN["FOLL_PIN<br/>(pin_user_pages*)"]
  PIN -->|"short term (DIO)"| P1["FOLL_PIN"]
  PIN -->|"long term (RDMA)"| P2["FOLL_PIN | FOLL_LONGTERM"]
  GET -->|"release"| PUT["put_page()"]
  PIN -->|"release"| UNPIN["unpin_user_pages()"]

The GUP decision tree. What it shows: GUP resolves a user VA range to physical struct pages; the caller then declares intent by choosing a wrapper. If you only manipulate page metadata, take a plain reference (get_user_pages*, FOLL_GET, release with put_page()). If you access the page’s data out of band, take a pin (pin_user_pages*, FOLL_PIN, release with unpin_user_pages()); add FOLL_LONGTERM if the pin outlives a single I/O. The insight: the wrapper you call is a contract about intent — get and pin are mutually exclusive for a given call site, and mixing the release functions (e.g. put_page() on a pinned page) corrupts the counts.

The API Family

GUP comes in matched get_* and pin_* variants. From v6.12 include/linux/mm.h:

long get_user_pages(unsigned long start, unsigned long nr_pages,
		    unsigned int gup_flags, struct page **pages);
long pin_user_pages(unsigned long start, unsigned long nr_pages,
		    unsigned int gup_flags, struct page **pages);
 
long get_user_pages_remote(struct mm_struct *mm, unsigned long start,
		    unsigned long nr_pages, unsigned int gup_flags,
		    struct page **pages, int *locked);
long pin_user_pages_remote(struct mm_struct *mm, unsigned long start,
		    unsigned long nr_pages, unsigned int gup_flags,
		    struct page **pages, int *locked);
 
int  get_user_pages_fast(unsigned long start, int nr_pages,
		    unsigned int gup_flags, struct page **pages);
int  pin_user_pages_fast(unsigned long start, int nr_pages,
		    unsigned int gup_flags, struct page **pages);
  • *_user_pages operates on current’s mm; *_user_pages_remote takes an explicit struct mm_struct * (e.g. ptrace, process_vm_readv).
  • *_user_pages_fast is the lockless fast path: it walks the page tables without taking mmap_lock, using a GUP-fast scheme that races safely against page-table teardown, and only falls back to the slow locked path on a miss.
  • start/nr_pages describe the user VA range; the resolved struct page pointers come back in the caller-provided pages[] array; the return value is the number of pages actually pinned (which may be fewer than requested).

The vmas parameter, and how its removal was dated

Older prototypes carried a sixth argument — a trailing struct vm_area_struct **vmas out-parameter, filled in with the VMA covering each returned page. It is absent from every v6.12 signature above, and the removal can be dated exactly by fetching include/linux/mm.h at successive tags and comparing:

Tagget_user_pages() prototypevmas present?
v6.3(start, nr_pages, gup_flags, **pages, **vmas)yes
v6.4(start, nr_pages, gup_flags, **pages, **vmas)yes
v6.5(start, nr_pages, gup_flags, **pages)no
v6.6(start, nr_pages, gup_flags, **pages)no
v6.12(start, nr_pages, gup_flags, **pages)no

Dating an API change by existence-checking. What it shows: the exact release boundary at which the vmas out-parameter disappeared from the whole GUP family (Lorenzo Stoakes’ cleanup series). The insight to take: it was removed in Linux 6.5, not “some time in the 6.x series” — and the technique generalises: fetching one file at successive tags and diffing pins a change to a release far more reliably than reading changelogs. Any tutorial or driver still showing a vmas argument is describing a pre-6.5 kernel and will not compile against anything current.

The FOLL_* flags

A GUP call is steered by a gup_flags bitmask. The flags live in two places, and the split is the API’s main safety mechanism: the ones a caller may set are a public enum in include/linux/mm_types.h, and the ones only GUP itself may set are a separate enum in mm/internal.h, guarded at runtime by a WARN_ON_ONCE(gup_flags & INTERNAL_GUP_FLAGS) in is_valid_gup_args().

FlagBitDeclared inCallable?Meaning
FOLL_WRITE1 << 0mm_types.hyesrequire the PTE to be writable; breaks COW and faults in for write
FOLL_GET1 << 1mm_types.hset for youtake a plain +1 reference — get_user_pages*() sets it when pages is non-NULL
FOLL_DUMP1 << 2mm_types.hyeserror on a hole that would read as zero (used by core dumping)
FOLL_FORCE1 << 3mm_types.hyesread/write ignoring VMA permissions; ptrace-style access only
FOLL_NOWAIT1 << 4mm_types.hyesstart any needed disk I/O and return without waiting
FOLL_NOFAULT1 << 5mm_types.hyesnever fault a page in; fail instead
FOLL_HWPOISON1 << 6mm_types.hyescheck whether the page is hardware-poisoned
FOLL_ANON1 << 7mm_types.hyesrefuse file mappings
FOLL_LONGTERM1 << 8mm_types.hyesthe pin is held for an indefinite period, “often under userspace control”
FOLL_SPLIT_PMD1 << 9mm_types.hyessplit a huge PMD before returning
FOLL_PCI_P2PDMA1 << 10mm_types.hyespermit returning PCI peer-to-peer DMA pages (otherwise -EREMOTEIO)
FOLL_INTERRUPTIBLE1 << 11mm_types.hyesallow generic signals to interrupt a fault wait
FOLL_HONOR_NUMA_FAULT1 << 12mm_types.hyesalways trigger NUMA hinting faults
FOLL_TOUCH1 << 16mm/internal.hnomark the page accessed
FOLL_TRIED1 << 17mm/internal.hnothis is a retry; a previous pass started I/O
FOLL_REMOTE1 << 18mm/internal.hnooperating on a non-current task/mm; set by the *_remote() wrappers
FOLL_PIN1 << 19mm/internal.hnotake a trackable pin; set only by the pin_user_pages*() wrappers
FOLL_FAST_ONLY1 << 20mm/internal.hnodo not fall back from GUP-fast to the slow path
FOLL_UNLOCKABLE1 << 21mm/internal.hnoGUP is allowed to drop mmap_lock
FOLL_MADV_POPULATE1 << 22mm/internal.hnoVMA checks compatible with MADV_POPULATE_(READ|WRITE)

The FOLL_* flag space at Linux v6.12. What it shows: every flag, its bit, where it is declared, and — the column that matters — whether a caller outside mm/gup.c may set it. The insight to take: the public/internal split is not stylistic. FOLL_PIN is deliberately unreachable from a call site so that choosing between a get and a pin can only be expressed by choosing a wrapper function, which the compiler and reviewer can see. FOLL_LONGTERM is the one exception the doc argues for explicitly, to avoid “creating a large number of wrapper functions to cover all combinations”.

Note the gap between bit 12 and bit 16: it is deliberate headroom, so that a new public flag can be added without renumbering the internal set.

Neither FOLL_GET nor FOLL_PIN is meant to be written at a call site, though for different reasons. FOLL_PIN is genuinely private — it lives in mm/internal.h, is a member of INTERNAL_GUP_FLAGS, and a caller that sets it trips a WARN_ON_ONCE; the pin_user_pages*() wrappers OR it in and validate usage. FOLL_GET is technically visible in mm_types.h, but the get_user_pages*() wrappers set it for you whenever you pass a non-NULL pages array, so writing it explicitly is redundant. FOLL_LONGTERM, by contrast, is allowed at the call site, to avoid a combinatorial explosion of wrapper functions. FOLL_PIN and FOLL_GET are mutually exclusive per call, though the same struct page may simultaneously carry pins and gets from different call sites (pin_user_pages.rst).

The Two Walks — GUP-Fast and the Slow Path

Every GUP call is, underneath, a page-table walk that converts user virtual addresses to struct page pointers. There are two implementations of that walk with very different costs, and understanding which one runs — and why the fast one can silently decline — is most of what separates a driver that performs from one that does not.

The slow path (__get_user_pages_locked() → __get_user_pages()) takes mmap_lock for reading, looks up the VMA for each address, checks permissions, and — crucially — can fault pages in that are not currently present, sleeping on disk I/O if necessary. It is the general, always-correct implementation.

GUP-fast (gup_fast()) does none of that. It takes no locks at all; instead it disables local interrupts and walks the page tables speculatively:

/* v6.12 mm/gup.c, gup_fast() */
if (gup_flags & FOLL_PIN) {
	seq = raw_read_seqcount(&current->mm->write_protect_seq);
	if (seq & 1)
		return 0;
}
/*
 * Disable interrupts. The nested form is used, in order to allow full,
 * general purpose use of this routine.
 *
 * With interrupts disabled, we block page table pages from being freed
 * from under us. See struct mmu_table_batch comments in
 * include/asm-generic/tlb.h for more details.
 *
 * We do not adopt an rcu_read_lock() here as we also want to block IPIs
 * that come from THPs splitting.
 */
local_irq_save(flags);
gup_fast_pgd_range(start, end, gup_flags, pages, &nr_pinned);
local_irq_restore(flags);

The local_irq_save() is doing two jobs at once and the comment names both. Page-table pages are freed through the deferred TLB-batching machinery, which finishes its work with an inter-processor interrupt (IPI) to every CPU; with interrupts off on this CPU, that IPI cannot land, so no page-table page the walk is standing on can be freed underneath it. The same mechanism blocks the IPIs that a transparent-huge-page split sends. Disabling interrupts is therefore acting as a very cheap read-side lock against both hazards, at the cost of a hard rule: GUP-fast may never sleep, never fault, and must bail out rather than block.

The seqcount check is the second safety mechanism and it exists specifically for pins. mm->write_protect_seq is bumped by fork() while it write-protects the parent’s PTEs for copy-on-write. If GUP-fast reads an odd sequence number a fork is in progress, so it gives up immediately; and after the walk it re-checks:

/* v6.12 mm/gup.c, gup_fast(), after the walk */
if (gup_flags & FOLL_PIN) {
	if (read_seqcount_retry(&current->mm->write_protect_seq, seq)) {
		gup_fast_unpin_user_pages(pages, nr_pinned);
		return 0;
	} else {
		sanity_check_pinned_pages(pages, nr_pinned);
	}
}

The comment is explicit about why: “When pinning pages for DMA there could be a concurrent write protect from fork() via copy_page_range(), in this case always fail GUP-fast.” A pin taken concurrently with a fork is exactly the race that produced CVE-2020-29374, so the fast path refuses to participate and hands the work to the locked slow path, which serialises against fork properly.

gup_fast_fallback() glues the two together, and its structure is the important part:

/* v6.12 mm/gup.c, gup_fast_fallback() */
if (gup_flags & FOLL_PIN)
	mm_set_has_pinned_flag(&current->mm->flags);
...
nr_pinned = gup_fast(start, end, gup_flags, pages);
if (nr_pinned == nr_pages || gup_flags & FOLL_FAST_ONLY)
	return nr_pinned;
 
/* Slow path: try to get the remaining pages with get_user_pages */
start += nr_pinned << PAGE_SHIFT;
pages += nr_pinned;
ret = __gup_longterm_locked(current->mm, start, nr_pages - nr_pinned,
			    pages, &locked,
			    gup_flags | FOLL_TOUCH | FOLL_UNLOCKABLE);

Two things to take from it. First, the fallback is partial, not all-or-nothing: GUP-fast resolves as many leading pages as it can, and the slow path is asked only for the remainder, starting where the fast walk stopped. A request for 512 pages where page 300 happens to be swapped out costs one fast walk plus one slow walk of 212 pages, not two full walks. Second, mm_set_has_pinned_flag() sets MMF_HAS_PINNED on the mm before anything is pinned, and it is never cleared — it is a sticky “this address space has taken a pin at least once” marker that fork() later consults as a cheap first test before doing the expensive per-folio pin check.

flowchart TB
  CALL["pin_user_pages_fast(start, nr_pages, flags, pages)"] --> SETF["mm_set_has_pinned_flag()<br/>MMF_HAS_PINNED set on the mm<br/>(sticky, never cleared)"]
  SETF --> VAL["validate flags, untagged_addr(),<br/>overflow + access_ok checks"]
  VAL --> SEQ{"FOLL_PIN and<br/>write_protect_seq is odd?<br/>(a fork() is mid-flight)"}
  SEQ -->|yes| GIVEUP["return 0 — do not even try"]
  SEQ -->|no| IRQ["local_irq_save()<br/>blocks page-table-free IPIs<br/>and THP-split IPIs"]
  IRQ --> WALK["gup_fast_pgd_range()<br/>lockless pgd → p4d → pud → pmd → pte<br/>NO mmap_lock, NO faulting, NO sleeping"]
  WALK --> IRQR["local_irq_restore()"]
  IRQR --> RETRY{"FOLL_PIN and<br/>read_seqcount_retry()?<br/>(fork raced us)"}
  RETRY -->|yes| UNDO["gup_fast_unpin_user_pages()<br/>undo every pin, return 0"]
  RETRY -->|no| CNT{"nr_pinned == nr_pages?"}
  UNDO --> SLOW
  GIVEUP --> SLOW
  CNT -->|yes| DONE["done — never took a lock"]
  CNT -->|"no (page absent,<br/>not present, needs fault)"| FAST1{"FOLL_FAST_ONLY?"}
  FAST1 -->|yes| PARTIAL["return the partial count"]
  FAST1 -->|no| SLOW["__gup_longterm_locked()<br/>+ FOLL_TOUCH | FOLL_UNLOCKABLE<br/>advance past pages already done"]
  SLOW --> LOCK["mmap_read_lock(mm)"]
  LOCK --> GUP["__get_user_pages()<br/>VMA lookup, permission checks,<br/>handle_mm_fault() — MAY SLEEP"]
  GUP --> LT{"FOLL_LONGTERM?"}
  LT -->|no| DONE2["return nr_pinned"]
  LT -->|yes| MIG["check_and_migrate_movable_pages()<br/>migrate out of CMA / ZONE_MOVABLE"]
  MIG -->|"-EAGAIN: pages moved,<br/>all unpinned"| SLOW
  MIG -->|"0: all longterm-pinnable"| DONE2

GUP’s fast path, slow path, and the long-term migration loop at v6.12. What it shows: the full control flow of a *_user_pages_fast() call — the two seqcount checks that make GUP-fast safe against a concurrent fork(), the interrupt disable that substitutes for a lock, the partial-progress fallback, and the retry loop that FOLL_LONGTERM adds on top. The insight to take: the fast path is not merely “the same walk without a lock” — it is a walk that has surrendered the right to sleep, to fault, and to race with fork, and every one of those surrenders is a way for it to return fewer pages than you asked for. Code that treats a short return from pin_user_pages_fast() as an error, rather than looping, is the single most common GUP bug.

What FOLL_LONGTERM adds

FOLL_LONGTERM is not a stronger pin; it is a promise about duration that triggers extra work up front. __gup_longterm_locked() wraps the ordinary path in a loop:

/* v6.12 mm/gup.c, __gup_longterm_locked() */
if (!(gup_flags & FOLL_LONGTERM))
	return __get_user_pages_locked(mm, start, nr_pages, pages,
				       locked, gup_flags);
 
flags = memalloc_pin_save();
do {
	nr_pinned_pages = __get_user_pages_locked(mm, start, nr_pages,
						  pages, locked, gup_flags);
	if (nr_pinned_pages <= 0) {
		rc = nr_pinned_pages;
		break;
	}
	/* FOLL_LONGTERM implies FOLL_PIN */
	rc = check_and_migrate_movable_pages(nr_pinned_pages, pages);
} while (rc == -EAGAIN);
memalloc_pin_restore(flags);

memalloc_pin_save() puts the task into a scope in which allocations avoid movable memory, so that any page faulted in during this walk is born somewhere it can legally be pinned forever. Then check_and_migrate_movable_pages() inspects what was actually obtained. A folio is long-term-pinnable only if it passes folio_is_longterm_pinnable():

/* v6.12 include/linux/mm.h */
static inline bool folio_is_longterm_pinnable(struct folio *folio)
{
#ifdef CONFIG_CMA
	int mt = folio_migratetype(folio);
	if (mt == MIGRATE_CMA || mt == MIGRATE_ISOLATE)
		return false;
#endif
	/* The zero page can be "pinned" but gets special handling. */
	if (is_zero_folio(folio))
		return true;
	/* Coherent device memory must always allow eviction. */
	if (folio_is_device_coherent(folio))
		return false;
	/* Otherwise, non-movable zone folios can be pinned. */
	return !folio_is_zone_movable(folio);
}

Anything that fails — a CMA page, an isolated page, a ZONE_MOVABLE page, coherent device memory — is collected, every pin in the range is dropped, the offending folios are migrated elsewhere with migrate_pages(..., MR_LONGTERM_PIN), and the whole call returns -EAGAIN so the outer loop re-pins the range from scratch. That is why a FOLL_LONGTERM pin of a large buffer can be dramatically more expensive than a short-term one on its first attempt, and why it can fail with -ENOMEM when migration has nowhere to move the pages to. It is also the reason long-term pinning interacts so badly with compaction and memory hotplug: the kernel’s answer to “you want to hold this movable page forever” is to make it non-movable first.

How a Pin Is Counted — GUP_PIN_COUNTING_BIAS

The design constraints (from the doc) were: an actual per-page count is required (multiple processes may pin/unpin); false positives (“looks pinned but isn’t”) are acceptable but false negatives are not; and struct page may not grow — every field is already used (see struct page Anatomy). The solution overloads _refcount. Rather than splitting the field into bitfields, a pin adds a medium-large constant:

/* v6.12 include/linux/mm.h */
#define GUP_PIN_COUNTING_BIAS (1U << 10)   /* = 1024 */

A FOLL_GET reference adds 1; a FOLL_PIN pin adds 1024. Because the bias is a power of two, the low bits keep counting ordinary references and the upper bits count pins, so the two are separable by subtraction. folio_maybe_dma_pinned() then answers “is this DMA-pinned?”:

/* v6.12 include/linux/mm.h */
static inline bool folio_maybe_dma_pinned(struct folio *folio)
{
	if (folio_test_large(folio))
		return atomic_read(&folio->_pincount) > 0;   /* exact for large folios */
	return ((unsigned int)folio_ref_count(folio)) >= GUP_PIN_COUNTING_BIAS;
}

The trade-offs, straight from the source and doc:

  • Fuzzy for small folios. If a small page genuinely accumulates ≥1024 ordinary references, folio_maybe_dma_pinned() returns a false positive — accepted, because callers must handle it gracefully and 1024 plain refs is rare. false is never fuzzy (“definitely not pinned”); only true is.
  • Limited counter range. _refcount is 32-bit signed → 31 usable bits; subtracting the 10-bit bias leaves 31 − 10 == 21 bits for a pin counter that increments 10 bits at a time. With compound pages this overflowed: pinning a huge page bumps the head page’s refcount once per (head + tail) subpage, and “refcount overflows were seen in some huge page stress tests” (pin_user_pages.rst).
  • Large folios bypass the bias entirely. They store the pin count in a dedicated _pincount field in the tail-page area of the folio, so the result is exact and the overflow/false-positive problems vanish.
  • The zero page is special-cased. FOLL_PIN on the shared zero page only pretends to pin — it touches neither refcount nor pincount (the page is permanent), and unpin is likewise a no-op.

The actual arithmetic, in one function

All of the above is implemented in a single place, try_grab_folio(), and reading it settles every question about what a pin costs:

/* v6.12 mm/gup.c, try_grab_folio() — abridged */
if (flags & FOLL_GET)
	folio_ref_add(folio, refs);                    /* +1 per page */
else if (flags & FOLL_PIN) {
	/* Don't take a pin on the zero page - it's not going anywhere
	 * and it is used in a *lot* of places. */
	if (is_zero_folio(folio))
		return 0;                              /* no-op */
 
	if (folio_test_large(folio)) {
		folio_ref_add(folio, refs);            /* +1 per page  */
		atomic_add(refs, &folio->_pincount);   /* +1 pincount  */
	} else {
		folio_ref_add(folio, refs * GUP_PIN_COUNTING_BIAS);  /* +1024 */
	}
	node_stat_mod_folio(folio, NR_FOLL_PIN_ACQUIRED, refs);
}

The large-folio branch is the detail most summaries get wrong: a pin on a large folio adds +1 to _refcount and +1 to _pincount — it does not add 1024. The bias trick is used only where there is no _pincount field to use. gup_put_folio() mirrors this exactly on release, subtracting from _pincount for large folios and multiplying by the bias for small ones, which is why the acquire and release functions must be matched.

FOLL_GET (get_user_pages*)FOLL_PIN, small folioFOLL_PIN, large folioFOLL_PIN, zero page
_refcount delta per page+1+1024 (GUP_PIN_COUNTING_BIAS)+10
_pincount delta—— (field not present)+10
Acquire viaget_user_pages*()pin_user_pages*()pin_user_pages*()pin_user_pages*()
Release viaput_page() / folio_put()unpin_user_page*()unpin_user_page*()unpin_user_page*() (no-op)
Visible to folio_maybe_dma_pinned()?noyes, fuzzily (≥ 1024 refs)yes, exactlyn/a (returns “pinnable”)
False positives possible?n/ayes — 1024 plain refs looks like a pinnon/a
NR_FOLL_PIN_ACQUIRED bumped?noyesyesno
Counter headroom31 bits21 bits (31 − 10)full _pincountunlimited
Blocks eager COW at fork()?noyesyesno (page is permanently shared and read-only)

Pin versus get, exactly. What it shows: the four distinct behaviours hiding behind two API families, and what each costs in the struct page/folio counters. The insight to take: FOLL_GET and FOLL_PIN are not “weak and strong versions of the same thing” — they write to different fields by different amounts, and the only reason put_page() on a pinned page is catastrophic is arithmetic: it subtracts 1 where 1024 was added, leaving a folio that is permanently 1023 references above zero and therefore never reclaimable.

stateDiagram-v2
  [*] --> Unreferenced: page exists, nobody holds it
  Unreferenced --> Referenced: get_user_pages*()<br/>_refcount += 1
  Referenced --> Unreferenced: put_page()<br/>_refcount -= 1
  Unreferenced --> PinnedSmall: pin_user_pages*() on a SMALL folio<br/>_refcount += 1024
  PinnedSmall --> Unreferenced: unpin_user_page*()<br/>_refcount -= 1024
  Unreferenced --> PinnedLarge: pin_user_pages*() on a LARGE folio<br/>_refcount += 1, _pincount += 1
  PinnedLarge --> Unreferenced: unpin_user_page*()<br/>_pincount -= 1, _refcount -= 1
  PinnedSmall --> Leaked: put_page() by mistake<br/>_refcount -= 1 (should be 1024)
  PinnedLarge --> Leaked: put_page() by mistake<br/>_pincount never decremented
  Referenced --> Corrupt: unpin_user_page() by mistake<br/>_refcount -= 1024 on a +1 reference
  Leaked --> [*]: frame never reclaimable<br/>folio_maybe_dma_pinned() stays true forever
  Corrupt --> [*]: refcount underflow, premature free,<br/>use-after-free of live DMA memory
  note right of PinnedSmall
    folio_maybe_dma_pinned() is FUZZY here:
    1024 ordinary refs look identical to one pin.
    False positives are accepted by design;
    false negatives would be a correctness bug.
  end note
  note right of PinnedLarge
    Exact: _pincount lives in the folio's
    tail-page area, so no bias and no
    false positives.
  end note

The refcount lifecycle of a page under FOLL_GET versus FOLL_PIN. What it shows: the legal transitions in the upper half and the two mismatched-release accidents in the lower half, with the exact counter deltas on every edge. The insight to take: the two error states fail in opposite and equally bad directions — a missed unpin leaks the frame silently and permanently (nothing ever reports an error; the page simply never comes back), while an over-release frees memory a device is still writing into. Neither is detected by the allocator, which is why unpin_user_pages() versus put_page() is treated as a correctness invariant rather than a style preference.

Two /proc/vmstat counters — nr_foll_pin_acquired and nr_foll_pin_released — track logical pins acquired/released since boot; under normal conditions they are equal except during transitions or while long-term RDMA pins are held. The doc is precise about the granularity mismatch that makes them balance: for a huge page, pin_user_pages() counts the head page once per subpage, while unpinning happens at PAGE_SIZE granularity — so pin_user_pages(huge_page) followed by a loop of unpin_user_page() over each constituent page leaves the two counters equal.

Releasing Pins — unpin, Don’t put_page

Pinned pages must be released with the unpin_* family, not put_page():

/* v6.12 include/linux/mm.h */
void unpin_user_page(struct page *page);
void unpin_user_pages(struct page **pages, unsigned long npages);
void unpin_user_pages_dirty_lock(struct page **pages, unsigned long npages, bool make_dirty);
void unpin_user_page_range_dirty_lock(struct page *page, unsigned long npages, bool make_dirty);
void unpin_folio(struct folio *folio);
void unpin_user_folio(struct folio *folio, unsigned long npages);

unpin_user_pages_dirty_lock(..., make_dirty=true) is the idiom after a write into the pages (e.g. an inbound DMA / RDMA receive): it marks them dirty so writeback eventually persists the new contents, then drops the pin. Calling put_page() on a pinned page subtracts 1 instead of 1024, corrupting the accounting and producing a stuck false-positive pin; calling unpin_user_page() on a merely get-referenced page does the reverse. The release function must match the acquire wrapper.

The Five Cases — Which Flags to Use

The doc enumerates five caller categories (pin_user_pages.rst):

  1. Direct I/O (DIO) — short-lived DMA buffers; no synchronization with folio_mkclean()/munmap(). → FOLL_PIN (via pin_user_pages*).
  2. RDMA — long-lived DMA buffers held indefinitely. → FOLL_PIN | FOLL_LONGTERM. (DAX pages cannot take longterm pins — “pinning” there would lock down filesystem blocks, unsupported.)
  3. MMU-notifier registration — driver pins via get_user_pages*, registers an MMU notifier, and unpins on an “invalidate range” callback; or, with replayable-fault hardware, avoids pinning entirely. Because it synchronizes properly with mm/filesystem, → neither flag.
  4. struct page manipulation only — touches metadata, not the data the page tracks. → plain get_user_pages*, neither flag.
  5. Pin in order to write the page’s data — even without DMA/DIO, the bare pattern “pin, write data, unpin” needs FOLL_PIN. The doc gives the explicit correct/incorrect pair: pin_user_pages() → write → unpin_user_pages() is correct; get_user_pages() → write → put_page() is incorrect.

As a decision table:

#CaseData accessed out of band?Synchronises with mm/FS?DurationFlagsAcquireReleaseReal examples
1Direct I/OyesnoshortFOLL_PINpin_user_pages*()unpin_user_pages_dirty_lock()Direct IO and O_DIRECT, io_uring fixed buffers
2RDMAyesnoindefiniteFOLL_PIN | FOLL_LONGTERMpin_user_pages*() + flagunpin_user_pages()InfiniBand/RoCE memory regions, VFIO Framework DMA mappings
3MMU-notifier registrationyesyes (notifier)anyneitherget_user_pages*()put_page() on invalidateGPU drivers with replayable faults, HMM
4struct page manipulation onlynon/ashortneitherget_user_pages*()put_page()page-migration helpers, process_vm_readv
5Pin in order to write the datayesnoshortFOLL_PINpin_user_pages*()unpin_user_pages*()any “pin, write, unpin” loop, even with no DMA at all

The five caller categories from Documentation/core-api/pin_user_pages.rst (credited by the doc to Jan Kara, Vlastimil Babka and others). What it shows: the two questions that decide everything — does something touch the page’s data outside the kernel’s control, and does the driver synchronise with the mm and filesystem by some other means. The insight to take: Case 3 is the one worth designing toward. Registering an MMU notifier (or using hardware with replayable page faults) means the driver can be told to let go, so it needs no pin at all — the doc calls the fault-capable variant “ideal”. Every other case is a negotiation about how long the kernel must tolerate not being able to move a page.

Case 5 is the trap, because it contains no DMA and no I/O and therefore looks like Case 4. The doc gives the correct and incorrect forms side by side, and the difference is only which two function names you use:

/* Correct */                    /* INCORRECT */
pin_user_pages();                get_user_pages();
/* write to the page's data */   /* write to the page's data */
unpin_user_pages();              put_page();

The mental ladder of restriction: FOLL_GET (metadata only, data untouched) → FOLL_PIN (short-term, data accessed) → FOLL_LONGTERM (long-term, data accessed; FOLL_PIN is a prerequisite). The doc phrases it as a progression: “FOLL_PIN is a replacement for FOLL_GET… As such, FOLL_PIN is a ‘more severe’ form of pinning. And finally, FOLL_LONGTERM is an even more restrictive case that has FOLL_PIN as a prerequisite.”

Why FOLL_PIN Exists — The Hazards

The writeback / COW data hazard. Before pins, GUP just bumped _refcount, indistinguishable from any other reference. Two classes of bug followed (LWN 807108): (1) the kernel believes a page’s contents are stable and writes it back / marks it clean, while a device is still DMA-ing fresh data into it — lost or torn writes; (2) on persistent-memory/DAX, a pin deprives the filesystem of the ability to change file layout for those blocks. The “whole point” of marking folios as DMA-pinned, per the doc, is so code like folio_mkclean() and filesystem writeback can ask folio_maybe_dma_pinned() and make an informed decision before unmapping or cleaning a page it cannot safely touch.

The fork / “COW can break either way” hazard. This one produced a security CVE. When a process pins an anonymous page for DMA and then fork()s, the classic copy-on-write scheme write-protects the page in both parent and child and shares it. Whichever side writes first “breaks COW” and gets a fresh private copy — but the other side (which may be the one whose page the device is still DMA-ing into, or out of) can end up holding the new copy or the old, depending on who faulted first. The result, in the words of the fixing commit 17839856fd58 (“gup: document and work around ‘COW can break either way’ issue”, May 2020): “the get_user_pages() call might result in a page pointer that is no longer associated with the original VM, and is associated with — and controlled by — another VM having taken it over instead” — reported by Jann Horn, later assigned CVE-2020-29374 (Babka, “Patching until the COWs come home”, LWN).

sequenceDiagram
  autonumber
  participant D as DMA engine / NIC
  participant P as Parent process
  participant K as Kernel (mm)
  participant C as Child process
  Note over P,K: Page X is an anonymous page in the parent's heap
  P->>K: pin_user_pages(X) for DMA
  K-->>D: device given the physical address of X
  Note over D: NIC will keep writing into X
  P->>K: fork()
  rect rgb(250, 235, 235)
    Note over K: PRE-5.6 BEHAVIOUR — the bug
    K->>K: write-protect X in BOTH parent and child<br/>share the frame (classic COW)
    C->>K: child writes to X first
    K->>K: COW breaks — but which side gets the NEW copy?
    K-->>P: parent may be left holding a DIFFERENT frame
    Note over D,P: the device keeps writing to the ORIGINAL frame,<br/>which the CHILD now owns and controls<br/>= CVE-2020-29374
  end
  rect rgb(232, 244, 236)
    Note over K: v6.12 BEHAVIOUR — the fix
    K->>K: copy_present_ptes(): is this anon folio maybe-pinned?<br/>folio_needs_cow_for_dma() = MMF_HAS_PINNED && folio_maybe_dma_pinned()
    K->>C: YES → copy_present_page(): allocate a fresh frame<br/>for the child EAGERLY, right now
    Note over P,D: parent keeps the original frame — the pin<br/>the page tables and the device all still agree
  end

The “COW can break either way” hazard and its fix. What it shows: the same fork() on a pinned anonymous page, before and after the 2020 fix. In the red path, the frame the device is writing to can end up owned by the child; in the green path the kernel detects the possible pin during fork() and copies for the child immediately rather than lazily. The insight to take: this is the concrete reason a pin must be trackable. fork() has to ask “is this folio maybe-DMA-pinned?” at PTE-copy time, and a plain FOLL_GET reference cannot answer that question — it is indistinguishable from the page cache holding a reference, or an rmap walk, or anything else. FOLL_PIN exists so that this one if can be written.

The resolution is eager copy at fork for maybe-pinned anonymous pages. Rather than lazily sharing a possibly-pinned page, fork() copies it immediately for the child, so the pinned original stays exclusively with the parent and the device’s DMA can never be silently redirected to a page the child now owns. This is visible in v6.12 mm/memory.c, where copying a present anonymous PTE attempts the normal rmap-dup and, if the folio “may have been pinned,” falls into copy_present_page():

/* v6.12 mm/memory.c, copy_present_ptes() */
folio_get(folio);
if (folio_test_anon(folio)) {
	/*
	 * If this page may have been pinned by the parent process,
	 * copy the page immediately for the child so that we'll always
	 * guarantee the pinned page won't be randomly replaced in the
	 * future.
	 */
	if (unlikely(folio_try_dup_anon_rmap_pte(folio, page, src_vma))) {
		/* Page may be pinned, we have to copy. */
		folio_put(folio);
		err = copy_present_page(dst_vma, src_vma, dst_pte, src_pte,
					addr, rss, prealloc, page);
		...
	}
	...
}

The decision uses folio_needs_cow_for_dma(), which is true only if the mm has the MMF_HAS_PINNED flag set (the address space has ever taken a pin) and folio_maybe_dma_pinned() says the folio is pinned — held under the PT lock and write_protect_seq (v6.12 include/linux/mm.h). This is exactly why a trackable pin (rather than a plain reference) is load-bearing: fork must be able to ask “is this maybe-DMA-pinned?” to know whether to break COW eagerly.

Failure Modes and Diagnostics

  • Mismatched release — put_page() on a FOLL_PIN page (or unpin_user_page() on a FOLL_GET page) corrupts the count by ±1023, leaving a phantom pin (frame never reclaimable) or a premature free.
  • Forgetting make_dirty — unpinning written-into pages without unpin_user_pages_dirty_lock(..., true) can drop the dirty bit and lose the data DMA’d in.
  • FOLL_LONGTERM on DAX — fails by design; DAX pages have no separate page cache to pin against.
  • Long-term pins defeat compaction/migration/hotplug — a longterm-pinned page cannot be migrated, so the allocator often migrates such pinned allocations out of ZONE_MOVABLE / CMA into non-movable memory up front; pinning huge swaths of movable memory long-term undermines compaction and memory hotplug.
  • Diagnostics — dump_page() reports the exact pincount for large folios; /proc/vmstat exposes nr_foll_pin_acquired / nr_foll_pin_released; tools/testing/selftests/mm/gup_test.c exercises the wrappers (./gup_test -a / -b).

Alternatives and When to Choose Them

Pinning is a blunt instrument: it takes a page out of the memory manager’s control for as long as you hold it. Several designs achieve “hardware may touch this user buffer” with less collateral damage, and the kernel documentation is unambiguous that avoiding a pin entirely is the preferred outcome where the hardware allows it.

ApproachWhat holds the page stillCan the kernel reclaim/migrate?CostWhen to choose it
FOLL_PIN (short-term)pin count on the foliono, while pinned — but pins are briefone page-table walk per I/ODirect I/O, per-request DMA: the buffer is pinned and released within one operation
FOLL_PIN | FOLL_LONGTERMpin count, plus migration out of movable zones up frontnever, for the life of the pinan extra migration pass; can fail -ENOMEMRDMA memory regions, VFIO Framework DMA maps — buffers registered once and used for the process lifetime
MMU notifiers (Case 3)nothing — the driver is told to let goyesdriver must implement invalidate_range and quiesce the deviceGPU and accelerator drivers where the device can be stopped
Replayable device faults (ATS/PRI, HMM)nothing at allyesrequires IOMMU + device support for page-faultingthe doc calls this “ideal” — the device faults like a CPU and no pinning is needed
dma-bufexporter-owned buffer, not user memoryexporter’s choicecross-driver plumbingsharing a buffer between devices/subsystems rather than pinning a user allocation
hugetlbfspages reserved out of the movable pool at bootalready non-movablewasted memory if over-reservedvery large, permanently-mapped DMA regions; sidesteps the longterm-migration pass entirely
mlock() / MAP_LOCKEDkeeps the page residentyes — mlocked pages are still migratablea RLIMIT_MEMLOCK chargelatency/paging guarantees only; this is not a substitute for pinning

mlock() is not pinning, and confusing the two is a real bug class.

mlock() places a folio on the unevictable LRU so reclaim will not swap it out. It does not stop the page from moving. The kernel documentation states the design goal outright: “We want to be able to migrate unevictable folios between nodes for memory defragmentation, workload management and memory hotplug”, and describes the 5.18 rework that revived mlock_count specifically “without preventing the migration of mlocked pages” (Documentation/mm/unevictable-lru.rst, v6.12). A driver that mlocks a buffer and then hands its physical addresses to a DMA engine has guaranteed only that the data is in RAM — not that it is in that RAM. Compaction can move it and the device will keep writing to the old frame.

The decision, in order. First ask whether the device can page-fault. If it can (modern GPUs, accelerators with ATS/PRI, anything driven through HMM), register an MMU notifier and pin nothing — this is Case 3 with the “ideal” variant, and it leaves the memory manager fully in control. If it cannot but the driver can be told to stop and restart, still register an MMU notifier and unpin on the invalidate callback. Only if neither is possible does the buffer need a real pin, and then the question is duration: if the pin lives and dies inside one I/O, plain FOLL_PIN costs little and the memory manager gets the page back promptly. If the buffer is registered once and used for the process lifetime, FOLL_LONGTERM is required, and you should expect — and budget for — the up-front migration pass and the possibility that it fails.

The scaling caveat on long-term pins. Every long-term-pinned page is permanently non-movable, which means it is a permanent hole in compaction’s working area and a permanent obstruction to memory hotplug. A host that long-term-pins a large fraction of its RAM (a typical RDMA or VFIO-passthrough workload does exactly this) will progressively lose the ability to allocate huge pages, because compaction cannot assemble contiguous runs around the pinned frames. Where the workload is known in advance, reserving hugetlbfs pages at boot is strictly better: the memory is non-movable from the start, so nothing is lost to pinning that was not already committed.

Production Notes

Short returns are normal, and treating them as errors is the classic bug. get_user_pages*() and pin_user_pages*() return the number of pages actually obtained, which may legitimately be fewer than requested — GUP-fast bailing out mid-range, a fork racing the seqcount check, a VMA boundary, a signal with FOLL_INTERRUPTIBLE. The contract is a loop: advance start and pages by the returned count and call again until the range is complete or an error (negative return) occurs. Note the asymmetry in gup_fast_fallback()’s error handling, which the source comments on directly: if the slow path fails after GUP-fast already pinned some pages, the function returns the partial count rather than the error, because “the caller has to unpin the pages we already pinned so returning -errno is not an option.” A caller that checks only for ret < 0 will silently leak those pins.

RLIMIT_MEMLOCK is the operational limit you will hit first. Long-term pinning subsystems charge pinned pages against the process’s locked-memory limit. On most distributions the default is small (64 KB historically, 8 MB on many modern systems), and the characteristic symptom is an RDMA registration or a VFIO DMA map failing with -ENOMEM or -EPERM at a size that looks trivially small. ulimit -l shows it; for containers it is set via the runtime’s --ulimit memlock (Docker) or the pod’s security context. Raising it is not a workaround for a bug — it is the required configuration for any long-term-pinning workload. See VFIO Framework for how VFIO accounts locked_vm per container.

Diagnosing a suspected pin leak. The two /proc/vmstat counters are the first stop:

grep nr_foll_pin /proc/vmstat
# nr_foll_pin_acquired 1048576
# nr_foll_pin_released 1048576

A persistent and growing gap between acquired and released, measured while no long-term pins should be outstanding, is a leak. Because these are logical pin counts (a huge page counts once per subpage on acquire, and is released at PAGE_SIZE granularity), the two balance exactly under correct use. For a specific page, dump_page() reports the exact pincount for large folios — and note the asymmetry that makes small folios harder to diagnose: folio_maybe_dma_pinned() returning true on a small folio might mean one pin or might mean 1024 ordinary references, so a small-folio “pin” observed in a crash dump is a hypothesis, not a finding. tools/testing/selftests/mm/gup_test.c exercises the wrappers (./gup_test -a for PIN_FAST_BENCHMARK, -b for PIN_BASIC_TEST) and is the reference for how the API is supposed to behave.

Pinning malloc’d memory is the most common real-world mistake. Application code very often does buf = malloc(n) and then registers buf with io_uring, an RDMA verb, or a VFIO device — which pins the underlying frames. Nothing in glibc’s allocator knows this happened. The consequences follow directly from the two mechanisms being mutually blind:

  • free() on a pinned buffer is a use-after-free with extra steps. The chunk goes back into a bin, where the allocator may split it, coalesce it with neighbours, hand it to an unrelated malloc, or (if it was an IS_MMAPPED chunk) munmap it outright — while a device is still writing to the frames.
  • malloc_trim() or a MADV_DONTNEED under the allocator can drop pages out from under the pin’s mapping, leaving the process’s page tables and the device’s view disagreeing.
  • fork() becomes expensive and, historically, dangerous. A pinned anonymous page forces eager copy-at-fork (see the next section); a process that pins a large buffer and then forks pays a full memcpy of it.
  • The pinned region is permanently non-movable, so pinning a buffer that glibc happened to carve out of the middle of the heap fragments compaction’s view of that whole region.

The practical discipline: allocate DMA buffers once, at startup, never free them, and prefer a dedicated allocation (mmap(MAP_ANONYMOUS) directly, hugetlbfs, or a purpose-built pool) over general-purpose malloc memory so that the allocator cannot recycle a region a device still owns. This is why RDMA and io_uring applications register a fixed buffer pool rather than pinning per-request allocations, and it is the single most useful thing to take from reading this note alongside ptmalloc and glibc malloc.

FOLL_LONGTERM on DAX fails by design, and the error is not a bug. DAX pages have no separate page cache, so pinning them would mean locking down filesystem blocks, which is “not (yet) supported in that way” per the in-tree documentation. A persistent-memory-backed file registered as an RDMA memory region will fail; the answer is a different buffer, not a workaround.

Version skew to watch for in existing code and documentation. The vmas out-parameter disappeared in 6.5, so driver code or tutorials carrying a sixth argument will not build against anything current. FOLL_PIN itself only arrived in 5.6 (early 2020), so pre-5.6 drivers use get_user_pages() for DMA buffers — which was the accepted idiom at the time and is now a latent correctness bug in exactly the ways this note describes. When auditing an out-of-tree driver, “does it call get_user_pages() and then write to the data?” is a high-yield question.

See Also

  • struct page Anatomy — why the pin count had to be overloaded onto _refcount (small folios) or stored in _pincount (large folios); the size budget that forbade a new field.

  • Copy-on-Write and fork — the COW mechanism whose interaction with pinned pages produced CVE-2020-29374 and the eager-copy-at-fork fix.

  • Folios and the Folio Conversion — large folios’ dedicated _pincount that makes pin tracking exact.

  • Memory Compaction and Page Migration — operations that long-term pins block, motivating migration out of movable zones.

  • Linux Page Table Hierarchy — the page-table walk GUP performs to resolve user VAs to struct pages.

  • The Page Fault Handler — GUP faults pages in via the same machinery when they are not yet present. The consumers of pinning — these are the subsystems that actually call pin_user_pages*(), and they are where the trade-offs above become concrete. Each is a distinct concept with its own note; GUP is the shared primitive underneath, not a replacement for any of them:

  • The DMA API — the layer above GUP that turns pinned struct pages into device-addressable DMA addresses

  • Scatter-Gather DMA and dma-buf — assembling pinned pages into scatter-gather lists, and the buffer-sharing alternative to pinning user memory

  • The IOMMU and DMA Remapping and IOMMU Groups and Device Isolation — why a pinned physical address is still not what the device sees

  • VFIO Framework — userspace device assignment; the largest consumer of FOLL_LONGTERM pins, and where RLIMIT_MEMLOCK accounting lives

  • Direct IO and O_DIRECT — Case 1 in practice: pin, submit, unpin-with-dirty per request

  • io_uring as a Syscall Batching Mechanism — registered/fixed buffers are pinned once and reused, which is precisely why they are faster

Across the userspace boundary:

  • ptmalloc and glibc malloc — the mirror image of this note. ptmalloc is userspace obtaining address space from the kernel and sub-dividing it into chunks and bins; GUP is the kernel reaching back into that address space to take the physical frames and hold them still. Neither mechanism can see the other, which is the origin of the “never free() a pinned buffer” discipline.
  • Anonymous vs File-Backed Memory — which kind of page you are pinning changes everything about writeback and COW interactions
  • Shared Memory via mmap — shared mappings pinned by one process are visible to others
  • The Unevictable LRU and mlock — the mechanism that keeps pages resident but, importantly, still movable
  • hugetlbfs and Reserved Huge Pages and Transparent Huge Pages — huge pages under pinning: exact _pincount, but FOLL_SPLIT_PMD and long-term pins interact badly with THP
  • MOC: Linux Memory Management MOC (§15, advanced/cross-cutting MM facilities).