shithub: mc

Download patch

ref: 4696e9dc4fda1400c881f2177183da3b024633f1
parent: 01cf9608db97e2b4fe6bec921ab1c28201605f82
author: iriri <[email protected]>
date: Tue Mar 12 21:58:49 EDT 2019

Fix use-after-free in futex-based semaphore implementations.

The current implementation of `sem_post` first increments the value of
the semaphore and then checks the waiter count to see if it must call
`ftxwake`.  However, it is possible for `sem_wait` to return at any time
after the value has been incremented, meaning that the memory this check
looks at might already be deallocated.

Happily, it turns out the OS X futex operations actually take a 32 bit
value which greatly simplifies things.

--- a/lib/sys/sys+osx-x64.myr
+++ b/lib/sys/sys+osx-x64.myr
@@ -835,8 +835,8 @@
 		-> int)
 
 	/* ulock */
-	const ulock_wait	: (op : ulockop, uaddr : uint64#, val : uint64, timeout : uint32 -> int)
-	const ulock_wake	: (op : ulockop, uaddr : uint64#, wakeval : uint64 -> int)
+	const ulock_wait	: (op : ulockop, uaddr : void#, val : uint64, timeout : uint32 -> int)
+	const ulock_wake	: (op : ulockop, uaddr : void#, wakeval : uint64 -> int)
 
 	extern const cstring : (str : byte[:] -> byte#)
 	/* filled by start code */
@@ -1110,7 +1110,7 @@
 }
 
 const ulock_wake = {op, uaddr, wakeval
-	-> (syscall(Sysulock_wake, a(op), uaddr, a(wakeval)) : int)
+	-> (syscall(Sysulock_wake, a(op), uaddr, wakeval) : int)
 }
 
 const waitstatus = {st
--- a/lib/thread/futex+osx.myr
+++ b/lib/thread/futex+osx.myr
@@ -4,8 +4,13 @@
 use "atomic"
 use "common"
 
+/*
+The `ulock_` functions are undocumented but the relevant source can be found at
+https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/bsd/kern/sys_ulock.c
+*/
 pkg thread =
-	type ftxtag = uint64
+	/* `UL_COMPARE_AND_WAIT` only uses 32 bits of the value (line 407) */
+	type ftxtag = uint32
 	impl atomic ftxtag
 
 	const ftxwait : (uaddr : ftxtag#, val : ftxtag, tmout : std.time -> sys.errno)
@@ -13,16 +18,12 @@
 	const ftxwakeall : (uaddr : ftxtag# -> void)
 ;;
 
-/*
- * The ulock_ functions are undocumented but the relevant source can be found at
- * https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/bsd/kern/sys_ulock.c
- */
 const ftxwait = {uaddr, val, tmout
 	var rc
 
 	if tmout < 0
 		while (rc = (sys.ulock_wait(sys.Ulockcompareandwait,
-				(uaddr : uint64#),
+				(uaddr : void#),
 				(val : uint64),
 				0) : sys.errno)) == sys.Eintr
 		;;
@@ -34,7 +35,7 @@
 		t = (tmout : uint32)
 
 		while (rc = (sys.ulock_wait(sys.Ulockcompareandwait,
-				(uaddr : uint64#),
+				(uaddr : void#),
 				(val : uint64),
 				t) : sys.errno)) == sys.Eintr
 			var now
@@ -69,17 +70,17 @@
 }
 
 const ftxwake = {uaddr
-	sys.ulock_wake(sys.Ulockcompareandwait, (uaddr : uint64#), 0)
+	sys.ulock_wake(sys.Ulockcompareandwait, (uaddr : void#), 0)
 }
 
 const ftxwakeall = {uaddr
-	sys.ulock_wake(sys.Ulockcompareandwait | sys.Ulockulfwakeall, (uaddr : uint64#), 0)
+	sys.ulock_wake(sys.Ulockcompareandwait | sys.Ulockulfwakeall, (uaddr : void#), 0)
 }
 
 impl atomic ftxtag =
-	xget = {p; -> (xget64((p : uint64#)) : ftxtag)}
-	xset = {p, v; xset64((p : uint64#), (v : uint64))}
-	xadd = {p, v; -> (xadd64((p : uint64#), (v : uint64)) : ftxtag)}
-	xcas = {p, old, new; -> (xcas64((p : uint64#), (old : uint64), (new : uint64)) : ftxtag)}
-	xchg = {p, v; -> (xchg64((p : uint64#), (v : uint64)) : ftxtag)}
+	xget = {p; -> (xget32((p : uint32#)) : ftxtag)}
+	xset = {p, v; xset32((p : uint32#), (v : uint32))}
+	xadd = {p, v; -> (xadd32((p : uint32#), (v : uint32)) : ftxtag)}
+	xcas = {p, old, new; -> (xcas32((p : uint32#), (old : uint32), (new : uint32)) : ftxtag)}
+	xchg = {p, v; -> (xchg32((p : uint32#), (v : uint32)) : ftxtag)}
 ;;
--- a/lib/thread/sem+futex.myr
+++ b/lib/thread/sem+futex.myr
@@ -4,6 +4,10 @@
 use "futex"
 
 pkg thread =
+	/*
+	We want to be able to read both members in the same atomic operation
+	and this implementation assumes `ftxtag` is 32 bits.
+	*/
 	type sem = struct
 		_val      : ftxtag
 		_nwaiters : uint32
@@ -48,9 +52,20 @@
 }
 
 const sempost = {s
-	std.assert((xadd(&s._val, 1) : uint32) != ~0x0, "error: semaphore overflowed\n")
+	/*
+	It's possible for the semaphore to be deallocated at any time after
+	`_val` is incremented so we must not dereference `s` a second time. We
+	work around this by also reading the value of `_nwaiters` during the
+	atomic fetch and add.
+	*/
+	var state = xadd((s : uint64#), 1)
+	std.assert((state : uint32) != ~0x0, "error: semaphore overflowed\n")
 
-	if xget(&s._nwaiters) > 0
+	if (state >> 32) > 0
+		/*
+		However, it is both legal and expected to pass a potentially
+		invalid address to `ftxwake`.
+		*/
 		ftxwake(&s._val)
 	;;
 }