From 838ffeed251553622a356783950c473341529264 Mon Sep 17 00:00:00 2001
From: Taylor R Campbell <riastradh@NetBSD.org>
Date: Mon, 18 Apr 2022 18:51:32 +0000
Subject: [PATCH] mmap(2): If we fail with a hint, try again without it.

`Hint' here means nonzero addr, but no MAP_FIXED or MAP_TRYFIXED.

This is suboptimal -- we could teach uvm_mmap to do a fancier search
using the address as a hint.  But this should do for now.

Candidate fix for PR kern/55533.
---
 sys/uvm/uvm_mmap.c | 37 +++++++++++++++++++++++++++++++++----
 1 file changed, 33 insertions(+), 4 deletions(-)

diff --git a/sys/uvm/uvm_mmap.c b/sys/uvm/uvm_mmap.c
index 3e4a66ce9f41..a0f202668fef 100644
--- a/sys/uvm/uvm_mmap.c
+++ b/sys/uvm/uvm_mmap.c
@@ -277,7 +277,8 @@ sys_mmap(struct lwp *l, const struct sys_mmap_args *uap, register_t *retval)
 	vsize_t size, pageoff, newsize;
 	vm_prot_t prot, maxprot, extraprot;
 	int flags, fd, advice;
-	vaddr_t defaddr;
+	vaddr_t defaddr = 0;	/* XXXGCC */
+	bool addrhint = false;
 	struct file *fp = NULL;
 	struct uvm_object *uobj;
 	int error;
@@ -349,6 +350,12 @@ sys_mmap(struct lwp *l, const struct sys_mmap_args *uap, register_t *retval)
 			addr = MAX(addr, defaddr);
 		else
 			addr = MIN(addr, defaddr);
+
+		/*
+		 * If addr is nonzero and not the default, then the
+		 * address is a hint.
+		 */
+		addrhint = (addr != 0 && addr != defaddr);
 	}
 
 	/*
@@ -399,11 +406,30 @@ sys_mmap(struct lwp *l, const struct sys_mmap_args *uap, register_t *retval)
 	pax_aslr_mmap(l, &addr, orig_addr, flags);
 
 	/*
-	 * now let kernel internal function uvm_mmap do the work.
+	 * Now let kernel internal function uvm_mmap do the work.
+	 *
+	 * If the user provided a hint, take a reference to uobj in
+	 * case the first attempt to satisfy the hint fails, so we can
+	 * try again with the default address.
 	 */
-
+	if (addrhint) {
+		if (uobj)
+			(*uobj->pgops->pgo_reference)(uobj);
+	}
 	error = uvm_mmap(&p->p_vmspace->vm_map, &addr, size, prot, maxprot,
 	    flags, advice, uobj, pos, p->p_rlimit[RLIMIT_MEMLOCK].rlim_cur);
+	if (addrhint) {
+		if (error) {
+			addr = defaddr;
+			pax_aslr_mmap(l, &addr, orig_addr, flags);
+			error = uvm_mmap(&p->p_vmspace->vm_map, &addr, size,
+			    prot, maxprot, flags, advice, uobj, pos,
+			    p->p_rlimit[RLIMIT_MEMLOCK].rlim_cur);
+		} else if (uobj) {
+			/* Release the exta reference we took.  */
+			(*uobj->pgops->pgo_detach)(uobj);
+		}
+	}
 
 	/* remember to add offset */
 	*retval = (register_t)(addr + pageoff);
@@ -818,9 +844,12 @@ sys_munlockall(struct lwp *l, const void *v, register_t *retval)
  * - used by sys_mmap and various framebuffers
  * - uobj is a struct uvm_object pointer or NULL for MAP_ANON
  * - caller must page-align the file offset
+ *
+ * XXX This appears to leak the uobj in various error branches?  Need
+ * to clean up the contract around uobj reference.
  */
 
-int
+static int
 uvm_mmap(struct vm_map *map, vaddr_t *addr, vsize_t size, vm_prot_t prot,
     vm_prot_t maxprot, int flags, int advice, struct uvm_object *uobj,
     voff_t foff, vsize_t locklimit)