SSL_accept 中的 Indy / Libssl32 访问冲突在 TIdTCPServer 停止
Indy / Libssl32 access violation in SSL_accept at TIdTCPServer stop
我使用 Delphi 10.1 更新 2 和 Indy 10.6.2.5341。
我们在 SSL_accept 中遇到访问冲突。如果使用 SSL 设置 TIdTCPServer 并且在 TIdTCPServer 停止时存在尚未协商 TLS 的打开连接,则会发生这种情况。
这看起来像是 Libssl32 或 Indy 中的问题。这可以使用以下代码和使用 RAW 连接的 Putty 简单地重现。有谁知道防止这些崩溃的解决方案(或解决方法)?
procedure TSslCrash.HandlerOnExecute(AContext: TIdContext);
begin
//
end;
procedure TSslCrash.HandlerOnConnect(AContext: TIdContext);
begin
TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough := False;
end;
procedure TSslCrash.ButtonStartClick(Sender: TObject);
begin
LServer := TIdTCPServer.Create;
LIOHandler := TIdServerIOHandlerSSLOpenSSL.Create;
LIOHandler.SSLOptions.Mode := sslmServer;
LIOHandler.SSLOptions.Method := sslvTLSv1_2;
LIOHandler.SSLOptions.VerifyMode := [];
LIOHandler.SSLOptions.VerifyDepth := 0;
LIOHandler.SSLOptions.CertFile := 'localhost.crt';
LIOHandler.SSLOptions.RootCertFile := 'localhost.crt';
LIOHandler.SSLOptions.KeyFile := 'localhost.key';
LServer.Bindings.Add.Port := 10000;
LServer.IOHandler := LIOHandler;
LServer.OnExecute := HandlerOnExecute;
LServer.OnConnect := HandlerOnConnect;
LServer.Active := True;
//Now open a RAW connection with Putty on port 10000 and keep it open
end;
procedure TSslCrash.ButtonStopClick(Sender: TObject);
begin
if Assigned(LServer) then begin
LServer.Active := False; //This causes an AV in TIdSSLSocket.Accept
FreeAndNil(LIOHandler);
FreeAndNil(LServer);
end;
end;
当 Putty 以 Raw 模式连接时,没有执行 SSL/TLS 握手,因此 SSL_accept()
卡在等待从未到达的握手请求中。
当 TIdTCPServer
被停用时,它会断开活动的套接字连接,使其他线程中正在进行的任何阻塞套接字操作失败。在 SSL_accept()
的情况下,应该取消阻止它,以便它可以退出并返回 TIdSSLSocket.Accept()
可以检测到的错误代码并包装到引发的异常中(EIdOSSLUnderlyingCryptoError
,EIdOSSLAcceptError
, EIdSocketError
,等等取决于错误代码的性质)在等待握手完成的客户端线程的上下文中。
但是,当 TIdTCPServer
在停用期间断开套接字连接时,会调用 TIdTCPConnection.Disconnect()
,后者会调用 TIdIOHandler.Close()
,TIdSSLIOhandlerSocketOpenSSL
已覆盖以释放其内部 TIdSSLSocket
对象 - 调用 SSL_accept()
的同一个对象。因此,很可能底层 OpenSSL SSL
对象在 TIdSSLSocket.Destroy()
(调用 SSL_shutdown()
和 SSL_free()
)中在停用线程的上下文中被释放,同时仍然处于活动状态在客户端线程的上下文中用于 TIdSSLObject.Accept()
(调用 SSL_accept()
),从而导致访问冲突。
在不更改 Indy 源代码的情况下,对此无能为力。例如,可能将 TIdCustomTCPServer.DoTerminateContext()
更改为调用 AContext.Binding.CloseSocket()
而不是 AContext.Connection.Disconnect(False)
,这样 IOHandler
本身不会关闭,只是底层套接字(类似于 TIdCustomTCPServer.StopListening()
所做的终止其侦听 accept()
线程时)。
我已经在 Indy 的问题跟踪器中为您开了一张票:
#218: Access Violation in SSL_accept() when deactivating TIdTCPServer
我使用 Delphi 10.1 更新 2 和 Indy 10.6.2.5341。
我们在 SSL_accept 中遇到访问冲突。如果使用 SSL 设置 TIdTCPServer 并且在 TIdTCPServer 停止时存在尚未协商 TLS 的打开连接,则会发生这种情况。
这看起来像是 Libssl32 或 Indy 中的问题。这可以使用以下代码和使用 RAW 连接的 Putty 简单地重现。有谁知道防止这些崩溃的解决方案(或解决方法)?
procedure TSslCrash.HandlerOnExecute(AContext: TIdContext);
begin
//
end;
procedure TSslCrash.HandlerOnConnect(AContext: TIdContext);
begin
TIdSSLIOHandlerSocketBase(AContext.Connection.IOHandler).PassThrough := False;
end;
procedure TSslCrash.ButtonStartClick(Sender: TObject);
begin
LServer := TIdTCPServer.Create;
LIOHandler := TIdServerIOHandlerSSLOpenSSL.Create;
LIOHandler.SSLOptions.Mode := sslmServer;
LIOHandler.SSLOptions.Method := sslvTLSv1_2;
LIOHandler.SSLOptions.VerifyMode := [];
LIOHandler.SSLOptions.VerifyDepth := 0;
LIOHandler.SSLOptions.CertFile := 'localhost.crt';
LIOHandler.SSLOptions.RootCertFile := 'localhost.crt';
LIOHandler.SSLOptions.KeyFile := 'localhost.key';
LServer.Bindings.Add.Port := 10000;
LServer.IOHandler := LIOHandler;
LServer.OnExecute := HandlerOnExecute;
LServer.OnConnect := HandlerOnConnect;
LServer.Active := True;
//Now open a RAW connection with Putty on port 10000 and keep it open
end;
procedure TSslCrash.ButtonStopClick(Sender: TObject);
begin
if Assigned(LServer) then begin
LServer.Active := False; //This causes an AV in TIdSSLSocket.Accept
FreeAndNil(LIOHandler);
FreeAndNil(LServer);
end;
end;
当 Putty 以 Raw 模式连接时,没有执行 SSL/TLS 握手,因此 SSL_accept()
卡在等待从未到达的握手请求中。
当 TIdTCPServer
被停用时,它会断开活动的套接字连接,使其他线程中正在进行的任何阻塞套接字操作失败。在 SSL_accept()
的情况下,应该取消阻止它,以便它可以退出并返回 TIdSSLSocket.Accept()
可以检测到的错误代码并包装到引发的异常中(EIdOSSLUnderlyingCryptoError
,EIdOSSLAcceptError
, EIdSocketError
,等等取决于错误代码的性质)在等待握手完成的客户端线程的上下文中。
但是,当 TIdTCPServer
在停用期间断开套接字连接时,会调用 TIdTCPConnection.Disconnect()
,后者会调用 TIdIOHandler.Close()
,TIdSSLIOhandlerSocketOpenSSL
已覆盖以释放其内部 TIdSSLSocket
对象 - 调用 SSL_accept()
的同一个对象。因此,很可能底层 OpenSSL SSL
对象在 TIdSSLSocket.Destroy()
(调用 SSL_shutdown()
和 SSL_free()
)中在停用线程的上下文中被释放,同时仍然处于活动状态在客户端线程的上下文中用于 TIdSSLObject.Accept()
(调用 SSL_accept()
),从而导致访问冲突。
在不更改 Indy 源代码的情况下,对此无能为力。例如,可能将 TIdCustomTCPServer.DoTerminateContext()
更改为调用 AContext.Binding.CloseSocket()
而不是 AContext.Connection.Disconnect(False)
,这样 IOHandler
本身不会关闭,只是底层套接字(类似于 TIdCustomTCPServer.StopListening()
所做的终止其侦听 accept()
线程时)。
我已经在 Indy 的问题跟踪器中为您开了一张票:
#218: Access Violation in SSL_accept() when deactivating TIdTCPServer