git: de770681234d - main - rfork(2): fix swap accounting in vmspace_unshare()

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Tue, 13 Jan 2026 14:03:23 UTC
The branch main has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=de770681234d001a1f4cdb8121179331dc3a2def

commit de770681234d001a1f4cdb8121179331dc3a2def
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2026-01-04 00:19:36 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-01-13 14:03:14 +0000

    rfork(2): fix swap accounting in vmspace_unshare()
    
    When an attempt to increase the swap charge for the ucred failed, we
    must forcibly increase the charge to allow the vmspace_destroy()
    operation to correctly un-charge the accumulated objects.
    
    Add a swap_reserve_force_by_cred() helper and use it in
    vmspace_unshare(), same as it is done in normal fork operations.
    
    Reviewed by:    markj
    Tested by:      pho
    Sponsored by:   The FreeBSD Foundation
    MFC after:      1 week
    Differential revision:  https://reviews.freebsd.org/D54572
---
 sys/vm/swap_pager.c | 10 ++++++++--
 sys/vm/vm.h         |  1 +
 sys/vm/vm_map.c     |  7 +++++++
 3 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/sys/vm/swap_pager.c b/sys/vm/swap_pager.c
index e0bdca07ff0f..3f077289ac30 100644
--- a/sys/vm/swap_pager.c
+++ b/sys/vm/swap_pager.c
@@ -330,7 +330,7 @@ out_error:
 }
 
 void
-swap_reserve_force(vm_ooffset_t incr)
+swap_reserve_force_by_cred(vm_ooffset_t incr, struct ucred *cred)
 {
 	u_long pincr;
 
@@ -346,7 +346,13 @@ swap_reserve_force(vm_ooffset_t incr)
 #endif
 	pincr = atop(incr);
 	atomic_add_long(&swap_reserved, pincr);
-	swap_reserve_force_rlimit(pincr, curthread->td_ucred);
+	swap_reserve_force_rlimit(pincr, cred);
+}
+
+void
+swap_reserve_force(vm_ooffset_t incr)
+{
+	swap_reserve_force_by_cred(incr, curthread->td_ucred);
 }
 
 void
diff --git a/sys/vm/vm.h b/sys/vm/vm.h
index d28c84dd1c95..0da1891dfcc7 100644
--- a/sys/vm/vm.h
+++ b/sys/vm/vm.h
@@ -168,6 +168,7 @@ void vm_ksubmap_init(struct kva_md_info *);
 bool swap_reserve(vm_ooffset_t incr);
 bool swap_reserve_by_cred(vm_ooffset_t incr, struct ucred *cred);
 void swap_reserve_force(vm_ooffset_t incr);
+void swap_reserve_force_by_cred(vm_ooffset_t incr, struct ucred *cred);
 void swap_release(vm_ooffset_t decr);
 void swap_release_by_cred(vm_ooffset_t decr, struct ucred *cred);
 
diff --git a/sys/vm/vm_map.c b/sys/vm/vm_map.c
index 68dcadd2b2f1..4c33b786eaa0 100644
--- a/sys/vm/vm_map.c
+++ b/sys/vm/vm_map.c
@@ -4957,6 +4957,13 @@ vmspace_unshare(struct proc *p)
 	if (newvmspace == NULL)
 		return (ENOMEM);
 	if (!swap_reserve_by_cred(fork_charge, p->p_ucred)) {
+		/*
+		 * The swap reservation failed. The accounting from
+		 * the entries of the copied newvmspace will be
+		 * subtracted in vmspace_free(), so force the
+		 * reservation there.
+		 */
+		swap_reserve_force_by_cred(fork_charge, p->p_ucred);
 		vmspace_free(newvmspace);
 		return (ENOMEM);
 	}