2424
2525MAXFDS_TO_SEND  =  256 
2626SIGNED_STRUCT  =  struct .Struct ('q' )     # large enough for pid_t 
27+ _authkey_len  =  32   # <= PIPEBUF so it fits a single write to an empty pipe. 
2728
2829# 
2930# Forkserver class 
3233class  ForkServer (object ):
3334
3435    def  __init__ (self ):
36+         self ._forkserver_authkey  =  None 
3537        self ._forkserver_address  =  None 
3638        self ._forkserver_alive_fd  =  None 
3739        self ._forkserver_pid  =  None 
@@ -58,6 +60,7 @@ def _stop_unlocked(self):
5860        if  not  util .is_abstract_socket_namespace (self ._forkserver_address ):
5961            os .unlink (self ._forkserver_address )
6062        self ._forkserver_address  =  None 
63+         self ._forkserver_authkey  =  None 
6164
6265    def  set_forkserver_preload (self , modules_names ):
6366        '''Set list of module names to try to load in forkserver process.''' 
@@ -92,6 +95,16 @@ def connect_to_new_process(self, fds):
9295                      resource_tracker .getfd ()]
9396            allfds  +=  fds 
9497            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 ()
95108                reduction .sendfds (client , allfds )
96109                return  parent_r , parent_w 
97110            except :
@@ -119,6 +132,7 @@ def ensure_running(self):
119132                    return 
120133                # dead, launch it again 
121134                os .close (self ._forkserver_alive_fd )
135+                 self ._forkserver_authkey  =  None 
122136                self ._forkserver_address  =  None 
123137                self ._forkserver_alive_fd  =  None 
124138                self ._forkserver_pid  =  None 
@@ -129,9 +143,9 @@ def ensure_running(self):
129143            if  self ._preload_modules :
130144                desired_keys  =  {'main_path' , 'sys_path' }
131145                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 }
133147            else :
134-                 data  =  {}
148+                 main_kws  =  {}
135149
136150            with  socket .socket (socket .AF_UNIX ) as  listener :
137151                address  =  connection .arbitrary_address ('AF_UNIX' )
@@ -143,19 +157,31 @@ def ensure_running(self):
143157                # all client processes own the write end of the "alive" pipe; 
144158                # when they all terminate the read end becomes ready. 
145159                alive_r , alive_w  =  os .pipe ()
160+                 # A short lived pipe to initialize the forkserver authkey. 
161+                 authkey_r , authkey_w  =  os .pipe ()
146162                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 
148165                    cmd  %=  (listener .fileno (), alive_r , self ._preload_modules ,
149-                             data )
166+                             main_kws )
150167                    exe  =  spawn .get_executable ()
151168                    args  =  [exe ] +  util ._args_from_interpreter_flags ()
152169                    args  +=  ['-c' , cmd ]
153170                    pid  =  util .spawnv_passfds (exe , args , fds_to_pass )
154171                except :
155172                    os .close (alive_w )
173+                     os .close (authkey_w )
156174                    raise 
157175                finally :
158176                    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 )
159185                self ._forkserver_address  =  address 
160186                self ._forkserver_alive_fd  =  alive_w 
161187                self ._forkserver_pid  =  pid 
@@ -164,8 +190,18 @@ def ensure_running(self):
164190# 
165191# 
166192
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+ 
169205    if  preload :
170206        if  '__main__'  in  preload  and  main_path  is  not   None :
171207            process .current_process ()._inheriting  =  True 
@@ -254,6 +290,13 @@ def sigchld_handler(*_unused):
254290                if  listener  in  rfds :
255291                    # Incoming fork request 
256292                    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 ()
257300                        # Receive fds from client 
258301                        fds  =  reduction .recvfds (s , MAXFDS_TO_SEND  +  1 )
259302                        if  len (fds ) >  MAXFDS_TO_SEND :
0 commit comments