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() 可以检测到的错误代码并包装到引发的异常中(EIdOSSLUnderlyingCryptoErrorEIdOSSLAcceptErrorEIdSocketError,等等取决于错误代码的性质)在等待握手完成的客户端线程的上下文中。

但是,当 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