Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 20 Jun 2019 18:24:16 +0000 (UTC)
From:      Brooks Davis <brooks@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r349240 - in head: lib/libc/sys sys/sys sys/vm
Message-ID:  <201906201824.x5KIOGFE051785@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: brooks
Date: Thu Jun 20 18:24:16 2019
New Revision: 349240
URL: https://svnweb.freebsd.org/changeset/base/349240

Log:
  Extend mmap/mprotect API to specify the max page protections.
  
  A new macro PROT_MAX() alters a protection value so it can be OR'd with
  a regular protection value to specify the maximum permissions.  If
  present, these flags specify the maximum permissions.
  
  While these flags are non-portable, they can be used in portable code
  with simple ifdefs to expand PROT_MAX() to 0.
  
  This change allows (e.g.) a region that must be writable during run-time
  linking or JIT code generation to be made permanently read+execute after
  writes are complete.  This complements W^X protections allowing more
  precise control by the programmer.
  
  This change alters mprotect argument checking and returns an error when
  unhandled protection flags are set.  This differs from POSIX (in that
  POSIX only specifies an error), but is the documented behavior on Linux
  and more closely matches historical mmap behavior.
  
  In addition to explicit setting of the maximum permissions, an
  experimental sysctl vm.imply_prot_max causes mmap to assume that the
  initial permissions requested should be the maximum when the sysctl is
  set to 1.  PROT_NONE mappings are excluded from this for compatibility
  with rtld and other consumers that use such mappings to reserve
  address space before mapping contents into part of the reservation.  A
  final version this is expected to provide per-binary and per-process
  opt-in/out options and this sysctl will go away in its current form.
  As such it is undocumented.
  
  Reviewed by:	emaste, kib (prior version), markj
  Additional suggestions from:	alc
  Obtained from:	CheriBSD
  Sponsored by:	DARPA, AFRL
  Differential Revision:	https://reviews.freebsd.org/D18880

Modified:
  head/lib/libc/sys/mmap.2
  head/lib/libc/sys/mprotect.2
  head/sys/sys/mman.h
  head/sys/vm/vm_mmap.c

Modified: head/lib/libc/sys/mmap.2
==============================================================================
--- head/lib/libc/sys/mmap.2	Thu Jun 20 18:19:09 2019	(r349239)
+++ head/lib/libc/sys/mmap.2	Thu Jun 20 18:24:16 2019	(r349240)
@@ -28,7 +28,7 @@
 .\"	@(#)mmap.2	8.4 (Berkeley) 5/11/95
 .\" $FreeBSD$
 .\"
-.Dd June 22, 2017
+.Dd June 20, 2019
 .Dt MMAP 2
 .Os
 .Sh NAME
@@ -113,6 +113,22 @@ Pages may be written.
 Pages may be executed.
 .El
 .Pp
+In addition to these protection flags,
+.Fx
+provides the ability to set the maximum protection of a region allocated by
+.Nm
+and later altered by
+.Xr mprotect 2 .
+This is accomplished by
+.Em or Ns 'ing
+one or more
+.Dv PROT_
+values wrapped in the
+.Dv PROT_MAX()
+macro into the
+.Fa prot
+argument.
+.Pp
 The
 .Fa flags
 argument specifies the type of the mapped object, mapping options and
@@ -415,6 +431,11 @@ referenced a regular file or shared memory.
 An invalid value was passed in the
 .Fa prot
 argument.
+.It Bq Er EINVAL
+The
+.Fa prot
+argument contains permissions which are not a subset of the specified
+maximum permissions.
 .It Bq Er EINVAL
 An undefined option was set in the
 .Fa flags

Modified: head/lib/libc/sys/mprotect.2
==============================================================================
--- head/lib/libc/sys/mprotect.2	Thu Jun 20 18:19:09 2019	(r349239)
+++ head/lib/libc/sys/mprotect.2	Thu Jun 20 18:24:16 2019	(r349240)
@@ -28,7 +28,7 @@
 .\"	@(#)mprotect.2	8.1 (Berkeley) 6/9/93
 .\" $FreeBSD$
 .\"
-.Dd August 3, 2016
+.Dd June 20, 2019
 .Dt MPROTECT 2
 .Os
 .Sh NAME
@@ -65,6 +65,22 @@ The pages can be written.
 .It Dv PROT_EXEC
 The pages can be executed.
 .El
+.Pp
+In addition to these protection flags,
+.Fx
+provides the ability to set the maximum protection of a region
+(which prevents
+.Nm
+from upgrading the permissions).
+This is accomplished by
+.Em or Ns 'ing
+one or more
+.Dv PROT_
+values wrapped in the
+.Dv PROT_MAX()
+macro into the
+.Fa prot
+argument.
 .Sh RETURN VALUES
 .Rv -std mprotect
 .Sh ERRORS
@@ -78,6 +94,15 @@ The virtual address range specified by the
 and
 .Fa len
 arguments is not valid.
+.It Bq Er EINVAL
+The
+.Fa prot
+argument contains unhandled bits.
+.It Bq Er EINVAL
+The
+.Fa prot
+argument contains permissions which are not a subset of the specified
+maximum permissions.
 .It Bq Er EACCES
 The calling process was not allowed to change
 the protection to the value specified by

Modified: head/sys/sys/mman.h
==============================================================================
--- head/sys/sys/mman.h	Thu Jun 20 18:19:09 2019	(r349239)
+++ head/sys/sys/mman.h	Thu Jun 20 18:24:16 2019	(r349240)
@@ -55,6 +55,14 @@
 #define	PROT_READ	0x01	/* pages can be read */
 #define	PROT_WRITE	0x02	/* pages can be written */
 #define	PROT_EXEC	0x04	/* pages can be executed */
+#if __BSD_VISIBLE
+#define	_PROT_ALL	(PROT_READ | PROT_WRITE | PROT_EXEC)
+#define	PROT_EXTRACT(prot)	((prot) & _PROT_ALL)
+
+#define	_PROT_MAX_SHIFT	16
+#define	PROT_MAX(prot)		((prot) << _PROT_MAX_SHIFT)
+#define	PROT_MAX_EXTRACT(prot)	(((prot) >> _PROT_MAX_SHIFT) & _PROT_ALL)
+#endif
 
 /*
  * Flags contain sharing type and options.

Modified: head/sys/vm/vm_mmap.c
==============================================================================
--- head/sys/vm/vm_mmap.c	Thu Jun 20 18:19:09 2019	(r349239)
+++ head/sys/vm/vm_mmap.c	Thu Jun 20 18:24:16 2019	(r349240)
@@ -103,6 +103,9 @@ SYSCTL_INT(_vm, OID_AUTO, old_mlock, CTLFLAG_RWTUN, &o
 static int mincore_mapped = 1;
 SYSCTL_INT(_vm, OID_AUTO, mincore_mapped, CTLFLAG_RWTUN, &mincore_mapped, 0,
     "mincore reports mappings, not residency");
+static int imply_prot_max = 0;
+SYSCTL_INT(_vm, OID_AUTO, imply_prot_max, CTLFLAG_RWTUN, &imply_prot_max, 0,
+    "Imply maximum page permissions in mmap() when none are specified");
 
 #ifdef MAP_32BIT
 #define	MAP_32BIT_MAX_ADDR	((vm_offset_t)1 << 31)
@@ -187,9 +190,25 @@ kern_mmap(struct thread *td, uintptr_t addr0, size_t l
 	vm_offset_t addr;
 	vm_size_t pageoff, size;
 	vm_prot_t cap_maxprot;
-	int align, error;
+	int align, error, max_prot;
 	cap_rights_t rights;
 
+	if ((prot & ~(_PROT_ALL | PROT_MAX(_PROT_ALL))) != 0)
+		return (EINVAL);
+	max_prot = PROT_MAX_EXTRACT(prot);
+	prot = PROT_EXTRACT(prot);
+	if (max_prot != 0 && (max_prot & prot) != prot)
+		return (EINVAL);
+	/*
+	 * Always honor PROT_MAX if set.  If not, default to all
+	 * permissions unless we're implying maximum permissions.
+	 *
+	 * XXX: should be tunable per process and ABI.
+	 */
+	if (max_prot == 0)
+		max_prot = (imply_prot_max && prot != PROT_NONE) ?
+		    prot : _PROT_ALL;
+
 	vms = td->td_proc->p_vmspace;
 	fp = NULL;
 	AUDIT_ARG_FD(fd);
@@ -335,7 +354,7 @@ kern_mmap(struct thread *td, uintptr_t addr0, size_t l
 		 * This relies on VM_PROT_* matching PROT_*.
 		 */
 		error = vm_mmap_object(&vms->vm_map, &addr, size, prot,
-		    VM_PROT_ALL, flags, NULL, pos, FALSE, td);
+		    max_prot, flags, NULL, pos, FALSE, td);
 	} else {
 		/*
 		 * Mapping file, get fp for validation and don't let the
@@ -363,7 +382,7 @@ kern_mmap(struct thread *td, uintptr_t addr0, size_t l
 
 		/* This relies on VM_PROT_* matching PROT_*. */
 		error = fo_mmap(fp, &vms->vm_map, &addr, size, prot,
-		    cap_maxprot, flags, pos, td);
+		    max_prot & cap_maxprot, flags, pos, td);
 	}
 
 	if (error == 0)
@@ -594,9 +613,13 @@ kern_mprotect(struct thread *td, uintptr_t addr0, size
 {
 	vm_offset_t addr;
 	vm_size_t pageoff;
+	int vm_error, max_prot;
 
 	addr = addr0;
-	prot = (prot & VM_PROT_ALL);
+	if ((prot & ~(_PROT_ALL | PROT_MAX(_PROT_ALL))) != 0)
+		return (EINVAL);
+	max_prot = PROT_MAX_EXTRACT(prot);
+	prot = PROT_EXTRACT(prot);
 	pageoff = (addr & PAGE_MASK);
 	addr -= pageoff;
 	size += pageoff;
@@ -610,8 +633,18 @@ kern_mprotect(struct thread *td, uintptr_t addr0, size
 	if (addr + size < addr)
 		return (EINVAL);
 
-	switch (vm_map_protect(&td->td_proc->p_vmspace->vm_map, addr,
-	    addr + size, prot, FALSE)) {
+	vm_error = KERN_SUCCESS;
+	if (max_prot != 0) {
+		if ((max_prot & prot) != prot)
+			return (EINVAL);
+		vm_error = vm_map_protect(&td->td_proc->p_vmspace->vm_map,
+		    addr, addr + size, max_prot, TRUE);
+	}
+	if (vm_error == KERN_SUCCESS)
+		vm_error = vm_map_protect(&td->td_proc->p_vmspace->vm_map,
+		    addr, addr + size, prot, FALSE);
+
+	switch (vm_error) {
 	case KERN_SUCCESS:
 		return (0);
 	case KERN_PROTECTION_FAILURE:



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201906201824.x5KIOGFE051785>