File: 130-segfault-on-null-cstack.lua

package info (click to toggle)
lua-cqueues 20200726-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye
  • size: 1,848 kB
  • sloc: ansic: 23,623; sh: 2,984; makefile: 24
file content (82 lines) | stat: -rwxr-xr-x 2,257 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!/bin/sh
_=[[
	. "${0%%/*}/regress.sh"
	runlua "$0" "$@"
	RC="$?"
	if [ ${RC} -eq 0 ]; then
		case ${REGRESS_VERBOSE} in
		0)
			;;
		1)
			printf "\n%s: OK\n" "${REGRESS_PROGNAME}"
			;;
		*)
			printf "%s: OK\n" "${REGRESS_PROGNAME}"
			;;
		esac
	fi
	exit ${RC}
]]
require"regress".export".*"

--
-- Issue #130 -- Segfault due to passing NULL cstack to cstack_isrunning from
-- condition variable __gc
--
-- cqueue_destroy only cleaned up threads on the polling queue, not the
-- pending queue. If a thread was waiting on a condition variable, but was
-- made pending for another reason (e.g. timeout or other event), then the
-- condition variable <-> controller association would still be installed,
-- holding a reference to the controller. If the controller was garbage
-- collected with the thread still on the pending queue and before the
-- condition variable was collected (as it could be if both were destroyed
-- in the same cycle, like when both survive until Lua VM destruction time),
-- then the condition variable destructor cond__gc would attempt to access a
-- pointer to a defunct controller object via the wakecb structure. In this
-- particular case, one of the results was a NULL pointer dereference.
--
-- The fix was to walk both the thread.polling _and_ thread.pending queues,
-- calling thread_del. thread_del breaks any wakecb associations.
--

-- stop GC to ensure everything destroyed on exit in same cycle
info"stopping GC"
collectgarbage"stop"


-- instantiate first so cond__gc invoked after cqueue__gc (only required for
-- 5.2 and 5.3).
local cv1 = check(condition.new())
local cv2 = check(condition.new())
local cq = cqueues.new()

local ready = false

cq:wrap(function()
	info"starting thread 1"

	ready = true

	-- one condvar to wake us; another to ensure we still
	-- have a wakecb installed
	info"thread 1 polling"
	cqueues.poll(cv1, cv2)
end)

check(cq:step(), "thread 1 failed to start")

cq:wrap(function()
	info"starting thread 2"

	-- put 1st thread in pending state
	info"putting thread 1 into pending state"
	check(ready, "thread 1 not ready")
	cv1:signal()

	-- short-circuit loop so 2nd thread is never run
	info"short-circuiting loop"
	error"oops"
end)


check(not cq:loop(), "loop was expected to fail")