Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 26 Feb 1998 10:48:26 -0800 (PST)
From:      dima@best.net (Dima Ruban)
To:        tqbf@secnet.com
Cc:        freebsd-security@FreeBSD.ORG, commiters@FreeBSD.ORG
Subject:   Re: OpenBSD Security Advisory: mmap() Problem
Message-ID:  <199802261848.KAA06539@burka.rdy.com>
In-Reply-To: <19980226174359.3210.qmail@joshua.enteract.com> from "tqbf@secnet.com" at "Feb 26, 98 11:43:59 am"

next in thread | previous in thread | raw e-mail | index | archive | help
Is there anybody who's gonna review and commit this?

tqbf@secnet.com writes:
> 
> -------------------------------------------------------------------------
> 
> 			OpenBSD Security Advisory
> 
> 	  		    February 20, 1998
> 
> 		       4.4BSD mmap() Vulnerability
> 
> -------------------------------------------------------------------------
> 
> SYNOPSIS
> 
> Due to a 4.4BSD VM system problem, it is possible to memory-map a 
> read-only descriptor to a character device in read-write mode. This
> allows group "kmem" programs to become root, and root to lower the
> system securelevel, both by writing to the kernel memory device. 
> 
> -------------------------------------------------------------------------
> 
> AFFECTED SYSTEMS
> 
> This vulnerability has been confirmed against OpenBSD 2.2 (and below), 
> FreeBSD 2.2.5 (and below), and BSDI 3.0. NetBSD-current (without UVM) 
> and below is also affected.
> 
> -------------------------------------------------------------------------
> 
> DETAILS
> 
> The 4.4BSD VM system allows files to be "memory mapped", which causes
> the specified contents of a file to be made available to a process via
> its address space. Manipulations of that file can then be performed 
> simply by manipulating memory, rather than using filesystem I/O calls. 
> This technique is used to simplify code, speed up access to files, and
> provide interprocess communication.
> 
> Memory mappings can be "private" or "shared". In a private memory mapping,
> changes to the mapped memory are not committed back to the original file.
> Multiple processes with private mappings of the same file will not see
> each other's changes. In a shared mapping, changes to the mapped memory
> are reflected in the original file, and all processes mapping the same
> file see each others's changes.
> 
> In order to create a writeable mapping for a file descriptor, that file 
> descriptor must be open in read-write mode. This prevents users from using
> read-only access to system files to change the system configuration (by
> taking the read-only descriptors and mapping them read-write). The 4.4BSD
> VM system verifies that an open file descriptor is read-write before
> allowing a shared read-write mapping.
> 
> 4.4BSD does not perform this access check when the mapping is not shared;
> a process with a private mapping cannot modify the original file, so the
> potential for danger is minimized. Unfortunately, the 4.4BSD VM system
> automatically changes any private mapping of a character device to
> "shared", regardless of the flags passed to mmap(), after the access check
> is performed.
> 
> This allows a user with read-only access to a character device to create a
> read-write mapping to that device, and thus write to the device. This can
> be used against the raw memory device ("/dev/mem") to write arbitrary
> bytes directly to physical memory; if a process has read-only access to
> "/dev/mem" (processes in group "kmem" have this access), it can become
> "root" by altering kernel data structures. 
> 
> Furthermore, a process with a read-write mapping on "/dev/mem" can rewrite
> the system securelevel back to zero after it has been raised. This allows
> an attacker to bypass the "immutable" and "append-only" filesystem flags,
> along with any other securelevel protections.
> 
> -------------------------------------------------------------------------
> 
> TECHNICAL DETAILS
> 
> The code exhibiting this problem is located in "sys/vm/vm_mmap.c", in the
> functions "mmap()" (the mmap system call handler), and "vm_mmap()", the VM
> function that actually performs memory mapping. The problem is due to a
> faulty access check in mmap(), combined with a side-effect of character
> device mapping in vm_mmap().
> 
> The mmap() system call handler performs a read-write access check by
> examining the file descriptor passed in as an argument to the system call.
> Before allowing a shared read-write mapping, the system verifies that the
> file being mapped is open in write mode:
> 
> 	if (flags & MAP_SHARED) {
> 		if (fp->f_flag & FWRITE)
> 			maxprot |= VM_PROT_WRITE;
> 		else if (prot & PROT_WRITE)
> 			return (EACCES);
> 	}
> 
> If the requested mapping is not shared, the access check against the 
> file (the check for FWRITE in fp->f_flag, which is the file structure
> for the descriptor passed to mmap) is not performed. For regular files,
> this check is sufficient; a non-shared mapping will not allow a process
> to write to the actual file, only to a private copy in memory.
> 
> The vm_mmap() kernel VM function handles memory mapping for all of the
> kernel facilities that require this capability, including execve(), 
> System V shared memory, and the mmap() system call. vm_mmap() checks
> to see if a mapping is requested is associated with a character device,
> and, if so, automatically creates a shared mapping (comments from original
> source code):
> 
> 	if (vp->v_type == VCHR) {
> 		type = OBJT_DEVICE;
> 		handle = (caddr_t) vp->v_rdev;
> 	}
> 
> 	...
> 
> 	/*
>          * Force device mappings to be shared.
>          */             
>         if (type == OBJT_DEVICE) { 
>                 flags &= ~(MAP_PRIVATE|MAP_COPY);
>                 flags |= MAP_SHARED;
>         }
> 
> As a result of this code, it is possible to request a non-shared mapping
> of a character device (which will appear innocuous to the mmap() access
> checking code), and receive a shared, writeable mapping. This can be used
> to obtain write access to any readable character device.
> 
> This problem is particularly serious when a hostile process has read
> access to kernel memory devices. The system status utilities "ps",
> "netstat", "systat", and others operate setgid "kmem", allowing them to
> use the KVM library to directly access kernel memory. A bug in any of
> these programs can allow an attacker to trivially obtain root access, by
> mmap()'ing a read-only descriptor to "/dev/mem" and altering process 
> credential structures.
> 
> This issue also directly subverts the system securelevel. 4.4BSD has a
> facility called "securelevels" which adds restrictions to the kernel that
> take effect only when a flag in the kernel (the "securelevel") is set.
> These restrictions include "immutable" files, which cannot be altered
> (even by root), and "append-only" files, which can only have data appended
> to. The former is useful for system binaries (to prevent attackers from
> backdooring libraries and executables), and the latter is useful for logs
> (to prevent attackers from covering their tracks by deleting log data).
> 
> The 4.4BSD securelevel features are active when the securelevel is
> nonzero. The securelevel is set using the "sysctl" facility. The system
> does not allow the securelevel to be lowered once it is nonzero; if
> an attacker can lower the securelevel, she can evade securelevels
> protections by turning them off. 
> 
> The 4.4BSD kernel does not allow processes to write directly to kernel
> memory when the securelevel is nonzero; this prevents "root" from
> bypassing the securelevel simply by writing to "/dev/kmem". This is
> controlled by an access check in "sys/miscfs/specfs/spec_vnops.c", which
> provides vnode operations (open, read, write, etc) for special files (like
> character devices). 
> 
> The access check is performed in the "spec_open()" function, which handles
> the "open" system call for special files. When the securelevel is nonzero,
> the system explicitly checks for attempts to open devices in read-write
> mode, and prevents read-write opens for disk and kernel memory devices.
> 
> Unfortunately, the mmap() bug allows a process to write to a descriptor
> even if it is open read-only; the assumption made in spec_open() thus
> fails to catch attempts to reset the securelevel using mmap(). 
> 
> -------------------------------------------------------------------------
> 
> RESOLUTION
> 
> This is a kernel problem that can only be fixed by patching or upgrading
> the problematic system code. Patches for the OpenBSD operating system are
> provided in this advisory. The problem is fixed in OpenBSD-current and
> must be patched in versions 2.2 and below. 
> 
> The attached OpenBSD patch causes any attempt to create a private mapping
> of a character device to fail, and enhances access checking in mmap() to
> explicitly verify that the mapping requested is consistant with the open
> mode on the file descriptor being mapped.
> 
> Accelerated X from X Inside relies on this bug to operate correctly; this
> patch thus breaks the Accelerated X server. Contact your Accelerated X
> vendor for more information about this. XFree86 is not believed to be
> affected by the problem.
> 
> More information about the OpenBSD resolution to the problem is available
> at "http://www.openbsd.org/errata.html".
> 
> -------------------------------------------------------------------------
> 
> CREDITS
> 
> Documentation and testing of this problem was conducted by Theo de Raadt
> and Chuck Cranor. Theo de Raadt, Chuck Cranor, and Niklas Hallqvist of the
> OpenBSD project provided the OpenBSD patch for the problem.
> 
> The developers at OpenBSD would like to extend their gratitude to Perry
> "Scare Bear" Metzger for his continued support of their efforts. 
> 
> -------------------------------------------------------------------------
> 
> OPENBSD PATCH
> 
> Index: vm_mmap.c
> ===================================================================
> RCS file: /cvs/src/sys/vm/vm_mmap.c,v
> retrieving revision 1.10
> retrieving revision 1.13
> diff -u -9 -u -r1.10 -r1.13
> --- vm_mmap.c	1997/11/14 20:56:08	1.10
> +++ vm_mmap.c	1998/02/25 22:13:46	1.13
> @@ -1,10 +1,10 @@
> -/*	$OpenBSD: vm_mmap.c,v 1.10 1997/11/14 20:56:08 deraadt Exp $	*/
> +/*	$OpenBSD: vm_mmap.c,v 1.13 1998/02/25 22:13:46 deraadt Exp $	*/
>  /*	$NetBSD: vm_mmap.c,v 1.47 1996/03/16 23:15:23 christos Exp $	*/
>  
>  /*
>   * Copyright (c) 1988 University of Utah.
>   * Copyright (c) 1991, 1993
>   *	The Regents of the University of California.  All rights reserved.
>   *
>   * This code is derived from software contributed to Berkeley by
>   * the Systems Programming Group of the University of Utah Computer
> @@ -207,48 +207,60 @@
>  		 * Mapping file, get fp for validation.
>  		 * Obtain vnode and make sure it is of appropriate type.
>  		 */
>  		if (((unsigned)fd) >= fdp->fd_nfiles ||
>  		    (fp = fdp->fd_ofiles[fd]) == NULL)
>  			return (EBADF);
>  		if (fp->f_type != DTYPE_VNODE)
>  			return (EINVAL);
>  		vp = (struct vnode *)fp->f_data;
> -		if (vp->v_type != VREG && vp->v_type != VCHR)
> -			return (EINVAL);
> +
>  		/*
>  		 * XXX hack to handle use of /dev/zero to map anon
>  		 * memory (ala SunOS).
>  		 */
>  		if (vp->v_type == VCHR && iszerodev(vp->v_rdev)) {
>  			flags |= MAP_ANON;
>  			goto is_anon;
>  		}
> +
> +		/*
> +		 * Only files and cdevs are mappable, and cdevs does not
> +		 * provide private mappings of any kind.
> +		 */
> +		if (vp->v_type != VREG &&
> +		    (vp->v_type != VCHR || (flags & (MAP_PRIVATE|MAP_COPY))))
> +			return (EINVAL);
>  		/*
>  		 * Ensure that file and memory protections are
>  		 * compatible.  Note that we only worry about
>  		 * writability if mapping is shared; in this case,
>  		 * current and max prot are dictated by the open file.
>  		 * XXX use the vnode instead?  Problem is: what
>  		 * credentials do we use for determination?
>  		 * What if proc does a setuid?
>  		 */
>  		maxprot = VM_PROT_EXECUTE;	/* ??? */
>  		if (fp->f_flag & FREAD)
>  			maxprot |= VM_PROT_READ;
>  		else if (prot & PROT_READ)
> +			return (EACCES);
> +
> +		/*
> +		 * If we are sharing potential changes (either via MAP_SHARED
> +		 * or via the implicit sharing of character device mappings),
> +		 * and we are trying to get write permission although we
> +		 * opened it without asking for it, bail out.
> +		 */
> +		if (((flags & MAP_SHARED) != 0 || vp->v_type == VCHR) &&
> +		    (fp->f_flag & FWRITE) == 0 && (prot & PROT_WRITE) != 0)
>  			return (EACCES);
> -		if (flags & MAP_SHARED) {
> -			if (fp->f_flag & FWRITE)
> -				maxprot |= VM_PROT_WRITE;
> -			else if (prot & PROT_WRITE)
> -				return (EACCES);
> -		} else
> +		else
>  			maxprot |= VM_PROT_WRITE;
>  		handle = (caddr_t)vp;
>  	} else {
>  		/*
>  		 * (flags & MAP_ANON) == TRUE
>  		 * Mapping blank space is trivial.
>  		 */
>  		if (fd != -1)
>  			return (EINVAL);
> 
> 
> 
> To Unsubscribe: send mail to majordomo@FreeBSD.org
> with "unsubscribe security" in the body of the message
> 

-- dima

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe security" in the body of the message



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