forked from ethanchewy/PythonBuddy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_stacklet_shadowstack.py
More file actions
184 lines (160 loc) · 6.61 KB
/
_stacklet_shadowstack.py
File metadata and controls
184 lines (160 loc) · 6.61 KB
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
from rpython.rlib import _rffi_stacklet as _c
from rpython.rlib.debug import ll_assert
from rpython.rlib import rgc
from rpython.rtyper.annlowlevel import llhelper, MixLevelHelperAnnotator
from rpython.rtyper.lltypesystem import lltype, llmemory, rffi
from rpython.rtyper.lltypesystem.lloperation import llop
from rpython.annotator import model as annmodel
from rpython.rtyper.llannotation import lltype_to_annotation
#
# A GC wrapper around the C stacklet handles, with additionally a
# copy of the shadowstack (for all stacklets different than the main)
#
STACKLET = lltype.GcStruct('Stacklet',
('s_handle', _c.handle),
('s_sscopy', llmemory.Address),
rtti=True)
STACKLET_PTR = lltype.Ptr(STACKLET)
NULL_STACKLET = lltype.nullptr(STACKLET)
def complete_destrptr(gctransformer):
translator = gctransformer.translator
mixlevelannotator = MixLevelHelperAnnotator(translator.rtyper)
args_s = [lltype_to_annotation(STACKLET_PTR)]
s_result = annmodel.s_None
destrptr = mixlevelannotator.delayedfunction(stacklet_destructor,
args_s, s_result)
mixlevelannotator.finish()
lltype.attachRuntimeTypeInfo(STACKLET, destrptr=destrptr)
# Note: it's important that this is a light finalizer, otherwise
# the GC will call it but still expect the object to stay around for
# a while---and it can't stay around, because s_sscopy points to
# freed nonsense and customtrace() will crash
@rgc.must_be_light_finalizer
def stacklet_destructor(stacklet):
sscopy = stacklet.s_sscopy
if sscopy:
llmemory.raw_free(sscopy)
h = stacklet.s_handle
if h:
_c.destroy(h)
SIZEADDR = llmemory.sizeof(llmemory.Address)
def customtrace(gc, obj, callback, arg):
stacklet = llmemory.cast_adr_to_ptr(obj, STACKLET_PTR)
sscopy = stacklet.s_sscopy
if sscopy:
length_bytes = sscopy.signed[0]
while length_bytes > 0:
addr = sscopy + length_bytes
gc._trace_callback(callback, arg, addr)
length_bytes -= SIZEADDR
lambda_customtrace = lambda: customtrace
def sscopy_detach_shadow_stack():
base = llop.gc_adr_of_root_stack_base(llmemory.Address).address[0]
top = llop.gc_adr_of_root_stack_top(llmemory.Address).address[0]
length_bytes = top - base
result = llmemory.raw_malloc(SIZEADDR + length_bytes)
if result:
result.signed[0] = length_bytes
llmemory.raw_memcopy(base, result + SIZEADDR, length_bytes)
llop.gc_adr_of_root_stack_top(llmemory.Address).address[0] = base
return result
def sscopy_attach_shadow_stack(sscopy):
base = llop.gc_adr_of_root_stack_base(llmemory.Address).address[0]
ll_assert(llop.gc_adr_of_root_stack_top(llmemory.Address).address[0]==base,
"attach_shadow_stack: ss is not empty?")
length_bytes = sscopy.signed[0]
llmemory.raw_memcopy(sscopy + SIZEADDR, base, length_bytes)
llop.gc_adr_of_root_stack_top(llmemory.Address).address[0] = (
base + length_bytes)
llmemory.raw_free(sscopy)
def alloc_stacklet():
new_stacklet = lltype.malloc(STACKLET)
new_stacklet.s_handle = _c.null_handle
new_stacklet.s_sscopy = llmemory.NULL
return new_stacklet
def attach_handle_on_stacklet(stacklet, h):
ll_assert(stacklet.s_handle == _c.null_handle, "attach stacklet 1: garbage")
ll_assert(stacklet.s_sscopy == llmemory.NULL, "attach stacklet 2: garbage")
if not h:
raise MemoryError
elif _c.is_empty_handle(h):
ll_assert(gcrootfinder.sscopy == llmemory.NULL,
"empty_handle but sscopy != NULL")
return NULL_STACKLET
else:
# This is a return that gave us a real handle. Store it.
stacklet.s_handle = h
stacklet.s_sscopy = gcrootfinder.sscopy
ll_assert(gcrootfinder.sscopy != llmemory.NULL,
"!empty_handle but sscopy == NULL")
gcrootfinder.sscopy = llmemory.NULL
llop.gc_writebarrier(lltype.Void, llmemory.cast_ptr_to_adr(stacklet))
return stacklet
def consume_stacklet(stacklet):
h = stacklet.s_handle
ll_assert(bool(h), "consume_stacklet: null handle")
stacklet.s_handle = _c.null_handle
stacklet.s_sscopy = llmemory.NULL
return h
def _new_callback(h, arg):
# There is a fresh stacklet object waiting on the gcrootfinder,
# so populate it with data that represents the parent suspended
# stacklet and detach the stacklet object from gcrootfinder.
stacklet = gcrootfinder.fresh_stacklet
gcrootfinder.fresh_stacklet = NULL_STACKLET
ll_assert(stacklet != NULL_STACKLET, "_new_callback: NULL #1")
stacklet = attach_handle_on_stacklet(stacklet, h)
ll_assert(stacklet != NULL_STACKLET, "_new_callback: NULL #2")
#
# Call the main function provided by the (RPython) user.
stacklet = gcrootfinder.runfn(stacklet, arg)
#
# Here, 'stacklet' points to the target stacklet to which we want
# to jump to next. Read the 'handle' and forget about the
# stacklet object.
gcrootfinder.sscopy = llmemory.NULL
return consume_stacklet(stacklet)
def _new(thread_handle, arg):
# No shadowstack manipulation here (no usage of gc references)
sscopy = sscopy_detach_shadow_stack()
gcrootfinder.sscopy = sscopy
if not sscopy:
return _c.null_handle
h = _c.new(thread_handle, llhelper(_c.run_fn, _new_callback), arg)
sscopy_attach_shadow_stack(sscopy)
return h
_new._dont_inline_ = True
def _switch(h):
# No shadowstack manipulation here (no usage of gc references)
sscopy = sscopy_detach_shadow_stack()
gcrootfinder.sscopy = sscopy
if not sscopy:
return _c.null_handle
h = _c.switch(h)
sscopy_attach_shadow_stack(sscopy)
return h
_switch._dont_inline_ = True
class StackletGcRootFinder(object):
fresh_stacklet = NULL_STACKLET
@staticmethod
def new(thrd, callback, arg):
rgc.register_custom_trace_hook(STACKLET, lambda_customtrace)
result_stacklet = alloc_stacklet()
gcrootfinder.fresh_stacklet = alloc_stacklet()
gcrootfinder.runfn = callback
thread_handle = thrd._thrd
h = _new(thread_handle, arg)
return attach_handle_on_stacklet(result_stacklet, h)
@staticmethod
def switch(stacklet):
# 'stacklet' has a handle to target, i.e. where to switch to
h = consume_stacklet(stacklet)
h = _switch(h)
return attach_handle_on_stacklet(stacklet, h)
@staticmethod
def is_empty_handle(stacklet):
return not stacklet
@staticmethod
def get_null_handle():
return NULL_STACKLET
gcrootfinder = StackletGcRootFinder()