Date: Mon, 6 Aug 2007 14:38:21 +0300 From: Kostik Belousov <kostikbel@gmail.com> To: Tijl Coosemans <tijl@ulyssis.org> Cc: wine-freebsd@hub.org, freebsd-current@freebsd.org Subject: Re: mmap(2) MAP_FIXED isn't thread-safe (+testcase) Message-ID: <20070806113821.GB2738@deviant.kiev.zoral.com.ua> In-Reply-To: <200708051656.50168.tijl@ulyssis.org> References: <200708051656.50168.tijl@ulyssis.org>
next in thread | previous in thread | raw e-mail | index | archive | help
--2bJ57vwr75KGnr5s
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable
[I removed personal addresses from Cc:]
On Sun, Aug 05, 2007 at 04:56:46PM +0200, Tijl Coosemans wrote:
> Hi all,
>=20
> While investigating ports/115092 and other reports of seemingly random
> page faults when running Wine, I think I've found the cause to be mmap
> not being thread-safe when MAP_FIXED is used. It causes mmap(MAP_FIXED)
> to return -1(ENOMEM) sometimes when it shouldn't, but also to return an
> address with wrong protections, hence the protection faults occuring.
>=20
> Attached is a test program that shows this. It runs two threads. The
> first mmap()'s a region, starts a second thread and then goes in a loop
> calling mmap(PROT_WRITE,MAP_FIXED) on that region, essentially
> replacing that mapping. This is basically what rtld does to map an ELF
> object for instance when dlopen(3) is called. The second thread tries
> to steal the mapping from the first by calling mmap(PROT_NONE) in a
> loop. After a while the program segfaults when the first thread tries
> to write to the mapped region.
>=20
> Some lines are commented out. If you remove the commenting, I hit on
> the case where mmap(MAP_FIXED) returns -1.
>=20
> The problem is in sys/vm/vm_mmap.c:vm_mmap(). In case of MAP_FIXED
> first vm_map_remove() is called and then later vm_map_find(). This
> would need some locking, but I don't know which lock or how to approach
> this, so can somebody have a look at this?
> #include <sys/mman.h>
> #include <errno.h>
> #include <pthread.h>
> #include <stdio.h>
>=20
> #define MAPSIZE ( 16 * 4096 )
>=20
> void *second_thr( void *addr ) {
> int i;
> void *addr2;
> for( i =3D 0; ; i++ ) {
> addr2 =3D mmap( NULL, MAPSIZE, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0=
);
> /*
> if( addr2 !=3D ( addr + MAPSIZE ))
> break;
> */
> munmap( addr2, MAPSIZE );
> }
> printf( "thread2: addr(%p), addr2(%p), i(%d)\n", addr, addr2, i );
> return NULL;
> }
>=20
> int main( int argc, char **argv ) {
> int i;
> void *addr;
> volatile char *addr_fix;
> pthread_t thr;
>=20
> addr =3D mmap( NULL, MAPSIZE, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0 );
> pthread_create( &thr, NULL, &second_thr, addr );
>=20
> for( i =3D 0; ; i++ ) {
> addr_fix =3D mmap( addr, MAPSIZE, PROT_WRITE, MAP_ANON | MAP_PRIVATE | =
MAP_FIXED, -1, 0 );
> if( addr_fix =3D=3D addr )
> *addr_fix =3D i; /* should be writable */
> /*
> else
> break;
> */
> }
> printf( "thread1: addr(%p), addr_fix(%p), errno(%d), i(%d)\n", addr, add=
r_fix, errno, i );
>=20
> pthread_join( thr, NULL );
> return 0;
> }
Try the patch below. It changes behaviour for MAP_STACK|MAP_FIXED case, but
I am not sure whether it worth fixing.
diff --git a/sys/vm/vm_map.c b/sys/vm/vm_map.c
index 8799cc5..b9dabe8 100644
--- a/sys/vm/vm_map.c
+++ b/sys/vm/vm_map.c
@@ -155,6 +155,22 @@ static void vmspace_zdtor(void *mem, int size, void *a=
rg);
#define PROC_VMSPACE_LOCK(p) do { } while (0)
#define PROC_VMSPACE_UNLOCK(p) do { } while (0)
=20
+/*
+ * VM_MAP_RANGE_CHECK: [ internal use only ]
+ *
+ * Asserts that the starting and ending region
+ * addresses fall within the valid range of the map.
+ */
+#define VM_MAP_RANGE_CHECK(map, start, end) \
+ { \
+ if (start < vm_map_min(map)) \
+ start =3D vm_map_min(map); \
+ if (end > vm_map_max(map)) \
+ end =3D vm_map_max(map); \
+ if (start > end) \
+ start =3D end; \
+ }
+
void
vm_map_startup(void)
{
@@ -1160,7 +1176,7 @@ vm_map_find(vm_map_t map, vm_object_t object, vm_ooff=
set_t offset,
vm_size_t length, boolean_t find_space, vm_prot_t prot,
vm_prot_t max, int cow)
{
- vm_offset_t start;
+ vm_offset_t start, end;
int result;
=20
start =3D *addr;
@@ -1171,9 +1187,14 @@ vm_map_find(vm_map_t map, vm_object_t object, vm_oof=
fset_t offset,
return (KERN_NO_SPACE);
}
start =3D *addr;
+ end =3D start + length;
+ } else {
+ end =3D start + length;
+ VM_MAP_RANGE_CHECK(map, start, end);
+ (void) vm_map_delete(map, start, end);
}
result =3D vm_map_insert(map, object, offset,
- start, start + length, prot, max, cow);
+ start, end, prot, max, cow);
vm_map_unlock(map);
return (result);
}
@@ -1355,22 +1376,6 @@ _vm_map_clip_end(vm_map_t map, vm_map_entry_t entry,=
vm_offset_t end)
}
=20
/*
- * VM_MAP_RANGE_CHECK: [ internal use only ]
- *
- * Asserts that the starting and ending region
- * addresses fall within the valid range of the map.
- */
-#define VM_MAP_RANGE_CHECK(map, start, end) \
- { \
- if (start < vm_map_min(map)) \
- start =3D vm_map_min(map); \
- if (end > vm_map_max(map)) \
- end =3D vm_map_max(map); \
- if (start > end) \
- start =3D end; \
- }
-
-/*
* vm_map_submap: [ kernel use only ]
*
* Mark the given range as handled by a subordinate map.
diff --git a/sys/vm/vm_mmap.c b/sys/vm/vm_mmap.c
index 9522fec..a48fd77 100644
--- a/sys/vm/vm_mmap.c
+++ b/sys/vm/vm_mmap.c
@@ -1341,7 +1341,6 @@ vm_mmap(vm_map_t map, vm_offset_t *addr, vm_size_t si=
ze, vm_prot_t prot,
if (*addr !=3D trunc_page(*addr))
return (EINVAL);
fitit =3D FALSE;
- (void) vm_map_remove(map, *addr, *addr + size);
}
/*
* Lookup/allocate object.
--2bJ57vwr75KGnr5s
Content-Type: application/pgp-signature
Content-Disposition: inline
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (FreeBSD)
iD8DBQFGtwgtC3+MBN1Mb4gRAqanAKCgdwEDv2eKL8hZLyck6V7XwhG+dwCgrWi6
rtOjbPxmRr7XfVOlXDTd4eQ=
=9J1w
-----END PGP SIGNATURE-----
--2bJ57vwr75KGnr5s--
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20070806113821.GB2738>
