runtime: OpenSSL error with Ubuntu 22.04 on Arm32 architecture

When attempting to run a .NET app that makes an HTTPS connection with the upcoming release of Ubuntu 22.04 on the Arm32 architecture, it results in an OpenSSL error. This issue will block our upcoming support for Ubuntu 22.04 (see https://github.com/dotnet/core/issues/7038).

This only happens in Arm32. Using Arm64 works fine. The easiest way to reproduce this is with a Docker container. But because .NET doesn’t work with QEMU, emulation from an x64 machine won’t allow you to repro this. You’ll need an Arm machine.

I’ve repro’d this on my Raspberry Pi 4 machine. But it also repros on the .NET Docker team’s Jetson build machines which are Arm64.

Repro

  1. Get an Arm machine with Docker installed.

  2. Save the following contents to a file named Dockerfile in an empty directory:

    FROM mcr.microsoft.com/dotnet/sdk:6.0-focal AS build
    
    WORKDIR dotnetapp
    
    # Create a simple project that makes an HTTPS connection
    RUN dotnet new console --no-restore
    RUN echo 'var response = await new System.Net.Http.HttpClient().GetAsync("https://www.microsoft.com"); response.EnsureSuccessStatusCode(); System.Console.WriteLine("Hello World!");' > Program.cs
    
    RUN dotnet publish -c release -o /app -r linux-arm --self-contained /p:PublishSingleFile=true
    
    
    FROM arm32v7/ubuntu:jammy
    
    # Install .NET dependencies
    RUN apt-get update \
        && apt-get install -y --no-install-recommends \
            ca-certificates \
            \
            libc6 \
            libgcc1 \
            libgssapi-krb5-2 \
            libicu67 \
            libssl1.1 \
            libstdc++6 \
            zlib1g \
        && rm -rf /var/lib/apt/lists/*
    
    WORKDIR /app
    COPY --from=build /app .
    ENTRYPOINT ["./dotnetapp"]
    
  3. Set the current directory to the directory where the Dockerfile is located.

  4. $ docker build -t test

  5. $ docker run --rm test

Expected Results:

Hello World!

Actual Results:

Unhandled exception. System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
 ---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
 ---> Interop+OpenSsl+SslException: SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL.
 ---> Interop+Crypto+OpenSslCryptographicException: error:0A0000BF:SSL routines::no protocols available
   --- End of inner exception stack trace ---
   at Interop.OpenSsl.DoSslHandshake(SafeSslHandle context, ReadOnlySpan`1 input, Byte[]& sendBuf, Int32& sendCount)
   at System.Net.Security.SslStreamPal.HandshakeInternal(SafeFreeCredentials credential, SafeDeleteSslContext& context, ReadOnlySpan`1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions)
   --- End of inner exception stack trace ---
   at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(HttpRequestMessage request)
   at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at Program.<Main>$(String[] args) in /dotnetapp/Program.cs:line 1
   at Program.<Main>(String[] args)

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 26 (23 by maintainers)

Most upvoted comments

Dear @bartonjs and @rzikm, you both mentioned .NET Core 3.1 does not support OpenSSL 3.0. What does that mean in terms of the future support (or lack of) for .NET Core Runtime 3.1 on Ubuntu 22.04?

See https://github.com/dotnet/core/issues/7038#issuecomment-1110377345

3.1 is also not relevant, as we don’t support OpenSSL 3 with that release.

I think if there is not going to be another release of .NET 5 in those 17 days, we can skip the backport. Just wanted to point out the short window that we have there.

So the problem is that between OpenSSL 1.1 and OpenSSL 3 there has been change in the type of the argument to SSL(_CTX)?_set_options(SSL(_CTX)? *s, options) from uint32_t to uint64_t. Our source code still uses signatures with uint32_t. On 64-bit platforms, this makes no difference, as the arguments are passed the same way (register), but on arm32, the 64-bit int obviously does not fit into a register (where we put our 32-bit version of it) so OpenSSL looks for it elsewhere and reads garbage.

image

Hypotesis confirmed with following crude fix (which in turn, breaks arm32 with OpenSSL 1.1)

static void SSL_CTX_set_options_fixed(SSL_CTX* ctx, u_int32_t op)
{
    void (*func)(SSL_CTX* ctx, u_int64_t op) = (void(*)(SSL_CTX*, u_int64_t))SSL_CTX_set_options;
    func(ctx, op);
}