#1140227 apt: HTTPS acquire fails under OpenSSL FIPS-only mode (missing ERR_clear_error before SSL_read/write)

Package:
apt
Source:
apt
Description:
commandline package manager
Submitter:
Michael Hamill
Date:
2026-06-29 17:37:05 UTC
Severity:
normal
#1140227#5
Date:
2026-06-17 13:00:09 UTC
From:
To:
Dear Maintainer,

With OpenSSL configured in FIPS-only mode (only the "base" and "fips"
providers
active; the "default" provider disabled), "apt-get update"/"install"
against an
HTTPS repository intermittently fails with:

  Err:N https://...repo...
    OpenSSL error: error:0308010C:digital envelope routines::unsupported
    Error reading from server - read (5: Input/output error)

Root cause
----------
During the TLS handshake, libssl performs an implicit EVP_MD_fetch() for the
legacy MD5 / MD5-SHA1 digests (used for pre-TLS-1.2 handshake signing and
the
TLS 1.0/1.1 PRF). Under a FIPS-only provider configuration those digests are
unavailable (they exist only in the default provider), so the fetch fails
and
leaves "error:0308010C ... unsupported" on the thread's OpenSSL error queue.

This is benign: the handshake completes fine. "openssl s_client" to the same
host under the identical FIPS config connects successfully with the very
same
failed MD5/MD5-SHA1 fetches (verifiable with an LD_PRELOAD trace of
EVP_MD_fetch).

The actual failure is that apt does not clear the OpenSSL error queue
before its
TLS I/O. In methods/connect.cc, TlsFd::Read() and TlsFd::Write() call
SSL_read()/SSL_write() and then HandleError() -> SSL_get_error() WITHOUT a
preceding ERR_clear_error(). When SSL_read() later returns <= 0 for a benign
reason, SSL_get_error() consults the non-empty error queue, returns
SSL_ERROR_SSL, and apt reports the stale MD5 error as a fatal read failure
(errno = EIO -> "Error reading from server").

This violates the documented precondition in SSL_get_error(3): "The current
thread's error queue must be empty before the TLS/SSL I/O operation is
attempted, [...] as the SSL_get_error() function uses the error queue
[...]."

PostgreSQL fixed the identical class of bug (stale FIPS-mode error-queue
entry
misreported later) by calling ERR_clear_error() "on the way in"; libpq
already
does this around its OpenSSL I/O.

This did not occur before Debian 13 / apt 3.0: apt 2.6 (bookworm) used
GnuTLS
for its TLS transport, which does not touch OpenSSL's providers or error
queue.

Reproduction (Debian 13)
------------------------------
Minimal, self-contained Dockerfile. The build itself fails at the final RUN
(installing Docker from an HTTPS repo) -- "docker build ." is the whole
repro:

    FROM debian:13-slim
    SHELL ["/bin/bash", "-o", "pipefail", "-c"]

    # FIPS-only OpenSSL: install the FIPS provider, then activate base +
fips and
    # disable the default provider.
    RUN apt-get update --yes \
     && apt-get install --yes --no-install-recommends \
            ca-certificates openssl openssl-provider-fips \
     && MODULES_DIR="$(openssl version -m | cut -d'"' -f2)" \
     && openssl fipsinstall -out /etc/ssl/fipsmodule.cnf -module
"${MODULES_DIR}/fips.so"
    RUN sed -i 's|^#\s*\.include\s\+fipsmodule.cnf|.include
/etc/ssl/fipsmodule.cnf|' /etc/ssl/openssl.cnf \
     && sed -i 's/^default\s*=\s*default_sect/# default = default_sect/'
/etc/ssl/openssl.cnf \
     && sed -i 's/^#\s*fips\s*=\s*fips_sect/fips = fips_sect\nbase =
base_sect\n\n[base_sect]\nactivate = 1/' /etc/ssl/openssl.cnf
    # ("openssl list -providers" now shows only base + fips.)

    # Install Docker from its official HTTPS apt repo (any HTTPS repo
triggers it;
    # this is just a convenient public one). This RUN fails:
    #   OpenSSL error: error:0308010C ... Error reading from server
    #   E: Package 'docker-ce' has no installation candidate
    RUN apt-get install --no-install-recommends -y ca-certificates curl
gnupg \
     && install -m 0755 -d /etc/apt/keyrings \
     && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg
--dearmor -o /etc/apt/keyrings/docker.gpg \
     && echo "deb [signed-by=/etc/apt/keyrings/docker.gpg]
https://download.docker.com/linux/debian trixie stable" \
          > /etc/apt/sources.list.d/docker.list \
     && apt-get update \
     && apt-get install --no-install-recommends -y docker-ce docker-ce-cli
containerd.io

Build it:

    docker build .

The build fails at the final RUN with the error:0308010C / "Error reading
from
server" message above. (The underlying trigger is a read returning <= 0
while
the stale error is queued, so in principle a fluke pass is possible; in
practice
fetching the Docker repo over HTTPS this way fails on essentially every
build,
matching what we see in CI. If a build does pass, rebuild with --no-cache.)

For contrast, the connection itself is fine and the failed MD5 fetch is
benign --
both of these succeed under the identical FIPS config:

    # same handshake, succeeds, proving MD5 is not actually needed:
    openssl s_client -connect download.docker.com:443 -servername
download.docker.com </dev/null
    # and apt works if the default provider is made available:
    OPENSSL_CONF=/dev/null apt-get update     # (with the docker.list
source above)

System information
------------------
Debian release: 13 (trixie), amd64

Versions of relevant packages:
  apt          3.0.3
  libssl3t64   3.5.6-1~deb13u2   (OpenSSL; apt's TLS backend in 3.0)
  openssl      3.5.6-1~deb13u2
  libc6        2.41-12+deb13u3

Reproduced in a stock debian:13-slim container (see Dockerfile above).

Suggested fix
-------------
Clear the OpenSSL error queue immediately before each
SSL_read()/SSL_write() in
methods/connect.cc, mirroring libpq:

    ssize_t Read(void *buf, size_t count) override {
       assert(ssl);
    +  ERR_clear_error();
       return HandleError(SSL_read(ssl, buf, count));
    }
    ssize_t Write(void *buf, size_t count) override {
       assert(ssl);
    +  ERR_clear_error();
       return HandleError(SSL_write(ssl, buf, count));
    }

This makes apt robust to any benign leftover OpenSSL error, not just the
FIPS/MD5 case.

Michael Hamill
Senior Software Engineer II
michael.hamill@wellhive.com
www.wellhive.com


WELLHIVE CONFIDENTIALITY NOTICE: The contents of this email message and any attachments are intended solely for the addressee(s). Unless otherwise indicated, it contains information that is confidential, privileged and/or exempt from disclosure under applicable law. If you are not the named addressee, you are not authorized to read, print, retain, copy or disseminate this message or any part of it. If you have received this message in error, please notify the sender immediately by e-mail and delete all copies of the message.

#1140227#10
Date:
2026-06-19 07:37:09 UTC
From:
To:
Control: reassign -1 openssl

Thanks for your bug report.

This seems to be a bug in OpenSSL, and the proposed workaround is
wholly inappropriate. This also points out there is _another_ bug
somewhere, as we _should_ have raised this error _before_ we enter
the Read/Write functions.

You'll find you often need to patch OpenSSL to use it in a FIPS
setting, and relying on the packaged version is insufficient.

Thanks!

#1140227#19
Date:
2026-06-20 08:49:56 UTC
From:
To:
Is this a technical profound statement? If so based on what? As someone
who maintains the package in question I am curious what I or upstream
could do.

Sebastian

#1140227#24
Date:
2026-06-20 09:16:44 UTC
From:
To:
control: forwarded -1 https://github.com/openssl/openssl/issues/31624
#1140227#31
Date:
2026-06-25 18:54:01 UTC
From:
To:
Control: reassign -1 apt 3.0.3
https://github.com/openssl/openssl/issues/31624

There is:
| The correct fix is for the application (apt in this case), to do what
| postgresql did, and call ERR_clear_error(), prior to a subsequent SSL
| call.

and

| I want to add one nuance after checking the source code of 3.5: I'm not
| convinced that upstream libssl 3.5.6 is the source of the stale
| MD5/MD5-SHA1 error described here. In the code, the optional digest
| fetch helper ssl_evp_md_fetch uses ERR_set_mark / ERR_pop_to_mark, and
| the sigalg probing code is also protected.
|
| Apt source code also uses OpenSSL EVP digest code outside the TLS path,
| including MD5 hashing, so there may be more than one possible source of
| a stale FIPS/provider error before the later TLS I/O.


Sebastian