24
24
25
25
MAXFDS_TO_SEND = 256
26
26
SIGNED_STRUCT = struct .Struct ('q' ) # large enough for pid_t
27
+ _authkey_len = 32 # <= PIPEBUF so it fits a single write to an empty pipe.
27
28
28
29
#
29
30
# Forkserver class
32
33
class ForkServer (object ):
33
34
34
35
def __init__ (self ):
36
+ self ._forkserver_authkey = None
35
37
self ._forkserver_address = None
36
38
self ._forkserver_alive_fd = None
37
39
self ._forkserver_pid = None
@@ -58,6 +60,7 @@ def _stop_unlocked(self):
58
60
if not util .is_abstract_socket_namespace (self ._forkserver_address ):
59
61
os .unlink (self ._forkserver_address )
60
62
self ._forkserver_address = None
63
+ self ._forkserver_authkey = None
61
64
62
65
def set_forkserver_preload (self , modules_names ):
63
66
'''Set list of module names to try to load in forkserver process.'''
@@ -92,6 +95,16 @@ def connect_to_new_process(self, fds):
92
95
resource_tracker .getfd ()]
93
96
allfds += fds
94
97
try :
98
+ if self ._forkserver_authkey :
99
+ client .setblocking (True )
100
+ wrapped_client = connection .Connection (client .fileno ())
101
+ try :
102
+ connection .answer_challenge (
103
+ wrapped_client , self ._forkserver_authkey )
104
+ connection .deliver_challenge (
105
+ wrapped_client , self ._forkserver_authkey )
106
+ finally :
107
+ wrapped_client ._detach ()
95
108
reduction .sendfds (client , allfds )
96
109
return parent_r , parent_w
97
110
except :
@@ -119,6 +132,7 @@ def ensure_running(self):
119
132
return
120
133
# dead, launch it again
121
134
os .close (self ._forkserver_alive_fd )
135
+ self ._forkserver_authkey = None
122
136
self ._forkserver_address = None
123
137
self ._forkserver_alive_fd = None
124
138
self ._forkserver_pid = None
@@ -129,9 +143,9 @@ def ensure_running(self):
129
143
if self ._preload_modules :
130
144
desired_keys = {'main_path' , 'sys_path' }
131
145
data = spawn .get_preparation_data ('ignore' )
132
- data = {x : y for x , y in data .items () if x in desired_keys }
146
+ main_kws = {x : y for x , y in data .items () if x in desired_keys }
133
147
else :
134
- data = {}
148
+ main_kws = {}
135
149
136
150
with socket .socket (socket .AF_UNIX ) as listener :
137
151
address = connection .arbitrary_address ('AF_UNIX' )
@@ -143,19 +157,31 @@ def ensure_running(self):
143
157
# all client processes own the write end of the "alive" pipe;
144
158
# when they all terminate the read end becomes ready.
145
159
alive_r , alive_w = os .pipe ()
160
+ # A short lived pipe to initialize the forkserver authkey.
161
+ authkey_r , authkey_w = os .pipe ()
146
162
try :
147
- fds_to_pass = [listener .fileno (), alive_r ]
163
+ fds_to_pass = [listener .fileno (), alive_r , authkey_r ]
164
+ main_kws ['authkey_r' ] = authkey_r
148
165
cmd %= (listener .fileno (), alive_r , self ._preload_modules ,
149
- data )
166
+ main_kws )
150
167
exe = spawn .get_executable ()
151
168
args = [exe ] + util ._args_from_interpreter_flags ()
152
169
args += ['-c' , cmd ]
153
170
pid = util .spawnv_passfds (exe , args , fds_to_pass )
154
171
except :
155
172
os .close (alive_w )
173
+ os .close (authkey_w )
156
174
raise
157
175
finally :
158
176
os .close (alive_r )
177
+ os .close (authkey_r )
178
+ # Prevent access from processes not in our process tree that
179
+ # have the same shared key for this forkserver.
180
+ try :
181
+ self ._forkserver_authkey = os .urandom (_authkey_len )
182
+ os .write (authkey_w , self ._forkserver_authkey )
183
+ finally :
184
+ os .close (authkey_w )
159
185
self ._forkserver_address = address
160
186
self ._forkserver_alive_fd = alive_w
161
187
self ._forkserver_pid = pid
@@ -164,8 +190,18 @@ def ensure_running(self):
164
190
#
165
191
#
166
192
167
- def main (listener_fd , alive_r , preload , main_path = None , sys_path = None ):
168
- '''Run forkserver.'''
193
+ def main (listener_fd , alive_r , preload , main_path = None , sys_path = None ,
194
+ * , authkey_r = None ):
195
+ """Run forkserver."""
196
+ if authkey_r is not None :
197
+ # If there is no authkey, the parent closes the pipe without writing
198
+ # anything resulting in an empty authkey of b'' here.
199
+ authkey = os .read (authkey_r , _authkey_len )
200
+ assert len (authkey ) == _authkey_len or not authkey
201
+ os .close (authkey_r )
202
+ else :
203
+ authkey = b''
204
+
169
205
if preload :
170
206
if '__main__' in preload and main_path is not None :
171
207
process .current_process ()._inheriting = True
@@ -254,6 +290,13 @@ def sigchld_handler(*_unused):
254
290
if listener in rfds :
255
291
# Incoming fork request
256
292
with listener .accept ()[0 ] as s :
293
+ if authkey :
294
+ wrapped_s = connection .Connection (s .fileno ())
295
+ try :
296
+ connection .deliver_challenge (wrapped_s , authkey )
297
+ connection .answer_challenge (wrapped_s , authkey )
298
+ finally :
299
+ wrapped_s ._detach ()
257
300
# Receive fds from client
258
301
fds = reduction .recvfds (s , MAXFDS_TO_SEND + 1 )
259
302
if len (fds ) > MAXFDS_TO_SEND :
0 commit comments