CVE-2025-38242 (GCVE-0-2025-38242)
Vulnerability from cvelistv5
Published
2025-07-09 10:42
Modified
2026-08-05 12:00
Summary
In the Linux kernel, the following vulnerability has been resolved: mm: userfaultfd: fix race of userfaultfd_move and swap cache This commit fixes two kinds of races, they may have different results: Barry reported a BUG_ON in commit c50f8e6053b0, we may see the same BUG_ON if the filemap lookup returned NULL and folio is added to swap cache after that. If another kind of race is triggered (folio changed after lookup) we may see RSS counter is corrupted: [ 406.893936] BUG: Bad rss-counter state mm:ffff0000c5a9ddc0 type:MM_ANONPAGES val:-1 [ 406.894071] BUG: Bad rss-counter state mm:ffff0000c5a9ddc0 type:MM_SHMEMPAGES val:1 Because the folio is being accounted to the wrong VMA. I'm not sure if there will be any data corruption though, seems no. The issues above are critical already. On seeing a swap entry PTE, userfaultfd_move does a lockless swap cache lookup, and tries to move the found folio to the faulting vma. Currently, it relies on checking the PTE value to ensure that the moved folio still belongs to the src swap entry and that no new folio has been added to the swap cache, which turns out to be unreliable. While working and reviewing the swap table series with Barry, following existing races are observed and reproduced [1]: In the example below, move_pages_pte is moving src_pte to dst_pte, where src_pte is a swap entry PTE holding swap entry S1, and S1 is not in the swap cache: CPU1 CPU2 userfaultfd_move move_pages_pte() entry = pte_to_swp_entry(orig_src_pte); // Here it got entry = S1 ... < interrupted> ... <swapin src_pte, alloc and use folio A> // folio A is a new allocated folio // and get installed into src_pte <frees swap entry S1> // src_pte now points to folio A, S1 // has swap count == 0, it can be freed // by folio_swap_swap or swap // allocator's reclaim. <try to swap out another folio B> // folio B is a folio in another VMA. <put folio B to swap cache using S1 > // S1 is freed, folio B can use it // for swap out with no problem. ... folio = filemap_get_folio(S1) // Got folio B here !!! ... < interrupted again> ... <swapin folio B and free S1> // Now S1 is free to be used again. <swapout src_pte & folio A using S1> // Now src_pte is a swap entry PTE // holding S1 again. folio_trylock(folio) move_swap_pte double_pt_lock is_pte_pages_stable // Check passed because src_pte == S1 folio_move_anon_rmap(...) // Moved invalid folio B here !!! The race window is very short and requires multiple collisions of multiple rare events, so it's very unlikely to happen, but with a deliberately constructed reproducer and increased time window, it can be reproduced easily. This can be fixed by checking if the folio returned by filemap is the valid swap cache folio after acquiring the folio lock. Another similar race is possible: filemap_get_folio may return NULL, but folio (A) could be swapped in and then swapped out again using the same swap entry after the lookup. In such a case, folio (A) may remain in the swap cache, so it must be moved too: CPU1 CPU2 userfaultfd_move move_pages_pte() entry = pte_to_swp_entry(orig_src_pte); // Here it got entry = S1, and S1 is not in swap cache folio = filemap_get ---truncated---
Impacted products
Vendor Product Version
Linux Linux Version: adef440691bab824e39c1b17382322d195e1fab0
Version: adef440691bab824e39c1b17382322d195e1fab0
Version: adef440691bab824e39c1b17382322d195e1fab0
Create a notification for this product.
Show details on NVD website


{
  "containers": {
    "cna": {
      "affected": [
        {
          "defaultStatus": "unaffected",
          "product": "Linux",
          "programFiles": [
            "mm/userfaultfd.c"
          ],
          "repo": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git",
          "vendor": "Linux",
          "versions": [
            {
              "lessThan": "4c443046d8c9ed8724a4f4c3c2457d3ac8814b2f",
              "status": "affected",
              "version": "adef440691bab824e39c1b17382322d195e1fab0",
              "versionType": "git"
            },
            {
              "lessThan": "db2ca8074955ca64187a4fb596dd290b9c446cd3",
              "status": "affected",
              "version": "adef440691bab824e39c1b17382322d195e1fab0",
              "versionType": "git"
            },
            {
              "lessThan": "0ea148a799198518d8ebab63ddd0bb6114a103bc",
              "status": "affected",
              "version": "adef440691bab824e39c1b17382322d195e1fab0",
              "versionType": "git"
            }
          ]
        },
        {
          "defaultStatus": "affected",
          "product": "Linux",
          "programFiles": [
            "mm/userfaultfd.c"
          ],
          "repo": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git",
          "vendor": "Linux",
          "versions": [
            {
              "status": "affected",
              "version": "6.8"
            },
            {
              "lessThan": "6.8",
              "status": "unaffected",
              "version": "0",
              "versionType": "semver"
            },
            {
              "lessThanOrEqual": "6.12.*",
              "status": "unaffected",
              "version": "6.12.37",
              "versionType": "semver"
            },
            {
              "lessThanOrEqual": "6.15.*",
              "status": "unaffected",
              "version": "6.15.5",
              "versionType": "semver"
            },
            {
              "lessThanOrEqual": "*",
              "status": "unaffected",
              "version": "6.16",
              "versionType": "original_commit_for_fix"
            }
          ]
        }
      ],
      "cpeApplicability": [
        {
          "nodes": [
            {
              "cpeMatch": [
                {
                  "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*",
                  "versionEndExcluding": "6.12.37",
                  "versionStartIncluding": "6.8",
                  "vulnerable": true
                },
                {
                  "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*",
                  "versionEndExcluding": "6.15.5",
                  "versionStartIncluding": "6.8",
                  "vulnerable": true
                },
                {
                  "criteria": "cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*",
                  "versionEndExcluding": "6.16",
                  "versionStartIncluding": "6.8",
                  "vulnerable": true
                }
              ],
              "negate": false,
              "operator": "OR"
            }
          ]
        }
      ],
      "descriptions": [
        {
          "lang": "en",
          "value": "In the Linux kernel, the following vulnerability has been resolved:\n\nmm: userfaultfd: fix race of userfaultfd_move and swap cache\n\nThis commit fixes two kinds of races, they may have different results:\n\nBarry reported a BUG_ON in commit c50f8e6053b0, we may see the same\nBUG_ON if the filemap lookup returned NULL and folio is added to swap\ncache after that.\n\nIf another kind of race is triggered (folio changed after lookup) we\nmay see RSS counter is corrupted:\n\n[  406.893936] BUG: Bad rss-counter state mm:ffff0000c5a9ddc0\ntype:MM_ANONPAGES val:-1\n[  406.894071] BUG: Bad rss-counter state mm:ffff0000c5a9ddc0\ntype:MM_SHMEMPAGES val:1\n\nBecause the folio is being accounted to the wrong VMA.\n\nI\u0027m not sure if there will be any data corruption though, seems no. \nThe issues above are critical already.\n\n\nOn seeing a swap entry PTE, userfaultfd_move does a lockless swap cache\nlookup, and tries to move the found folio to the faulting vma.  Currently,\nit relies on checking the PTE value to ensure that the moved folio still\nbelongs to the src swap entry and that no new folio has been added to the\nswap cache, which turns out to be unreliable.\n\nWhile working and reviewing the swap table series with Barry, following\nexisting races are observed and reproduced [1]:\n\nIn the example below, move_pages_pte is moving src_pte to dst_pte, where\nsrc_pte is a swap entry PTE holding swap entry S1, and S1 is not in the\nswap cache:\n\nCPU1                               CPU2\nuserfaultfd_move\n  move_pages_pte()\n    entry = pte_to_swp_entry(orig_src_pte);\n    // Here it got entry = S1\n    ... \u003c interrupted\u003e ...\n                                   \u003cswapin src_pte, alloc and use folio A\u003e\n                                   // folio A is a new allocated folio\n                                   // and get installed into src_pte\n                                   \u003cfrees swap entry S1\u003e\n                                   // src_pte now points to folio A, S1\n                                   // has swap count == 0, it can be freed\n                                   // by folio_swap_swap or swap\n                                   // allocator\u0027s reclaim.\n                                   \u003ctry to swap out another folio B\u003e\n                                   // folio B is a folio in another VMA.\n                                   \u003cput folio B to swap cache using S1 \u003e\n                                   // S1 is freed, folio B can use it\n                                   // for swap out with no problem.\n                                   ...\n    folio = filemap_get_folio(S1)\n    // Got folio B here !!!\n    ... \u003c interrupted again\u003e ...\n                                   \u003cswapin folio B and free S1\u003e\n                                   // Now S1 is free to be used again.\n                                   \u003cswapout src_pte \u0026 folio A using S1\u003e\n                                   // Now src_pte is a swap entry PTE\n                                   // holding S1 again.\n    folio_trylock(folio)\n    move_swap_pte\n      double_pt_lock\n      is_pte_pages_stable\n      // Check passed because src_pte == S1\n      folio_move_anon_rmap(...)\n      // Moved invalid folio B here !!!\n\nThe race window is very short and requires multiple collisions of multiple\nrare events, so it\u0027s very unlikely to happen, but with a deliberately\nconstructed reproducer and increased time window, it can be reproduced\neasily.\n\nThis can be fixed by checking if the folio returned by filemap is the\nvalid swap cache folio after acquiring the folio lock.\n\nAnother similar race is possible: filemap_get_folio may return NULL, but\nfolio (A) could be swapped in and then swapped out again using the same\nswap entry after the lookup.  In such a case, folio (A) may remain in the\nswap cache, so it must be moved too:\n\nCPU1                               CPU2\nuserfaultfd_move\n  move_pages_pte()\n    entry = pte_to_swp_entry(orig_src_pte);\n    // Here it got entry = S1, and S1 is not in swap cache\n    folio = filemap_get\n---truncated---"
        }
      ],
      "metrics": [
        {
          "cvssV3_1": {
            "baseScore": 7.8,
            "baseSeverity": "HIGH",
            "vectorString": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
            "version": "3.1"
          },
          "scenarios": [
            {
              "lang": "en",
              "value": "AV:L - The vulnerability is reached only through local syscalls \u2014 `userfaultfd(2)`/`/dev/userfaultfd` followed by `ioctl(UFFDIO_MOVE)` on the caller\u0027s own mm, combined with `madvise(MADV_PAGEOUT)` to drive swap. There is no remote or adjacent-network path into mm/userfaultfd.c.\nAC:L - The attacker controls every side of the race \u2014 the `UFFDIO_MOVE` thread, the faulting thread that swaps the entry back in, and `MADV_PAGEOUT` which forces the swap-out that recycles the entry \u2014 and can retry indefinitely at no cost, which the authors confirm by reproducing it with a constructed reproducer. Swap (including zram/zswap on Android and embedded targets) is the common default configuration, so no rare kernel option is required.\nPR:L - Any unprivileged local user can create a userfaultfd with `UFFD_USER_MODE_ONLY`, which `userfaultfd_syscall_allowed()` permits unconditionally regardless of `vm.unprivileged_userfaultfd` or `CAP_SYS_PTRACE`, and that flag does not restrict `UFFDIO_MOVE` at all. Registering two private anonymous VMAs and issuing the ioctl requires no capability whatsoever.\nUI:N - The attacker performs all operations within its own process and address space; no action by any other user or victim process is needed.\nS:U - The corruption occurs in kernel mm structures and is exploited to gain kernel-level control within the same security authority, following the standard kernel privilege-escalation convention. No hypervisor, IOMMU, or DMA boundary is crossed.\nC:H - `folio_move_anon_rmap()` re-parents a folio belonging to a *different* VMA \u2014 potentially another process\u0027s anon page or a shmem/tmpfs page-cache folio \u2014 into the attacker\u0027s `anon_vma` at an attacker-chosen index, so foreign page contents can become reachable from the attacker\u0027s address space. The resulting `address_space`/`anon_vma` type confusion and broken rmap state provide a general kernel-memory read primitive.\nI:H - The bug lets an unprivileged process overwrite `folio-\u003emapping` and `folio-\u003eindex` on a folio it does not own, producing a hard type confusion between `struct anon_vma` and `struct address_space` (including reachable `a_ops` function pointers) plus corrupted RSS accounting and rmap invariants. This is classic heap-groom-able memory corruption exploitable for arbitrary write and control-flow hijack.\nA:H - The commit documents a reproducible `kernel BUG at mm/rmap.c:1380` in `__page_check_anon_rmap()` reached from `do_swap_page()`, an unrecoverable oops, plus `BUG: Bad rss-counter state` on teardown. Any unprivileged user can trigger a full kernel panic."
            }
          ]
        }
      ],
      "providerMetadata": {
        "dateUpdated": "2026-08-05T12:00:38.652Z",
        "orgId": "416baaa9-dc9f-4396-8d5f-8c081fb06d67",
        "shortName": "Linux"
      },
      "references": [
        {
          "url": "https://git.kernel.org/stable/c/4c443046d8c9ed8724a4f4c3c2457d3ac8814b2f"
        },
        {
          "url": "https://git.kernel.org/stable/c/db2ca8074955ca64187a4fb596dd290b9c446cd3"
        },
        {
          "url": "https://git.kernel.org/stable/c/0ea148a799198518d8ebab63ddd0bb6114a103bc"
        }
      ],
      "title": "mm: userfaultfd: fix race of userfaultfd_move and swap cache",
      "x_generator": {
        "engine": "bippy-1.2.0"
      }
    }
  },
  "cveMetadata": {
    "assignerOrgId": "416baaa9-dc9f-4396-8d5f-8c081fb06d67",
    "assignerShortName": "Linux",
    "cveId": "CVE-2025-38242",
    "datePublished": "2025-07-09T10:42:25.396Z",
    "dateReserved": "2025-04-16T04:51:23.996Z",
    "dateUpdated": "2026-08-05T12:00:38.652Z",
    "state": "PUBLISHED"
  },
  "dataType": "CVE_RECORD",
  "dataVersion": "5.2"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
  • Confirmed: The vulnerability is confirmed from an analyst perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
  • Patched: This vulnerability was successfully patched by the user reporting the sighting.
  • Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
  • Not confirmed: The user expresses doubt about the veracity of the vulnerability.
  • Not patched: This vulnerability was not successfully patched by the user reporting the sighting.


Loading…

Loading…