July 14, 2020·4 min read·engineeringgosecurityhttpx509

Go 1.14.5 fixes hidden writes and ignored certificate policy

A request-body read can write an HTTP response, and a platform verifier can omit requested policy. The July security release repairs both assumptions.

Reading an HTTP request body can cause the server to write to the connection. That happens when the client expects a 100 Continue response before sending the body. The apparent separation between a body reader and response writer therefore does not imply that their internal writes are independent. Go 1.14.5 repairs synchronization at that boundary.

The release history identifies security fixes in net/http and crypto/x509. The second repair concerns a different hidden assumption. On Windows, asking the platform verifier to validate a certificate did not reliably enforce the extended key usages supplied through Go's verification options. Both fixes make the implementation respect a contract that callers already had reason to expect.

I value explicit operational behavior in Go APIs. These defects show why implementation review must follow the effects of a call, including effects that its name does not reveal. A read can write protocol data, and a verification call can omit part of the requested policy.

The body reader has a protocol response to send

The HTTP repair changes the expectContinueReader used by the server. On the first request-body read, that wrapper can send the interim response. It shares the connection's buffered writer with the path serving the handler's response.

A handler can read the request body concurrently with writing a response. The reader's implicit interim response then competes with the handler's explicit writes. Without coordination, the two paths access shared output state concurrently. The vulnerability is CVE-2020-15586. Its affected handlers include httputil.ReverseProxy, according to the release announcement.

The patch adds a mutex and an atomic flag to the response state. An atomic flag records whether sending the interim response is still allowed. The mutex coordinates the actual write with the main response-writing path. These serve different purposes, because observing permission before acquiring a lock cannot establish that permission remains valid afterward.

The body reader first checks whether it may send 100 Continue. Once it holds the mutex, it checks again. If permission remains, it writes and flushes the interim response, then clears the flag. The main writer also takes the mutex before revoking permission. That prevents a body-reader write from remaining active while the main response proceeds.

The second check handles a specific race. The reader may observe the flag, wait for the lock, and discover that the main writer has already advanced the response. Sending an interim response at that point would be inappropriate. The lock serializes access, while the flag expresses the protocol state being protected.

The change also makes the reader's end-of-file state atomic. The server consults that state when deciding whether to close after the reply. Coordinating only the buffered write would leave another shared observation in the same interaction without the required synchronization.

The interim response therefore needs both exclusive access to the output and permission to appear in the current response state. This does not make arbitrary concurrent use of every HTTP object safe. It repairs the interaction between the body wrapper and response writer in this protocol path. Application code still needs to obey the contracts of the particular objects it shares.

Certificate purpose is distinct from certificate identity

The Windows verification repair concerns VerifyOptions.KeyUsages. These options identify acceptable extended key usages. A chain must allow at least one requested usage. An empty list means server authentication, while ExtKeyUsageAny requests acceptance without a particular usage restriction.

Those requirements are distinct from the DNS name being checked. A certificate can match an identity while having the wrong permitted purpose. The old Windows path derived usage behavior from whether the caller supplied DNSName. It requested server authentication when a name was present and no usage restriction otherwise. Explicit KeyUsages did not control the decision.

The defect applies when Roots is nil and Go uses the Windows platform verifier. A caller could state the intended policy in its Go options and still receive a result based on different parameters. The issue is CVE-2020-14039. It is a policy-enforcement failure, separate from whether the platform can construct a trusted chain.

This fragment illustrates a purpose-specific request. It assumes leaf is a parsed certificate and intermediates contains any supplied intermediate certificates.

chains, err := leaf.Verify(x509.VerifyOptions{
    Intermediates: intermediates,
    KeyUsages: []x509.ExtKeyUsage{
        x509.ExtKeyUsageClientAuth,
    },
})

The nil root pool selects system roots or the platform verifier. Client authentication is explicit even though no DNS name appears in the options. The correction makes that distinction meaningful on the affected Windows path.

Passing the requested policy into Windows

The new implementation maps known Go extended key usages to the object identifiers required by Windows. It defaults an empty usage list to server authentication. It builds the requested-usage list from the supplied options and asks Windows to accept a matching usage. Encountering ExtKeyUsageAny removes that particular restriction.

Server authentication retains compatibility with the Server Gated Crypto usages already accepted by the implementation. This is a detail of mapping Go's policy into the platform API, not a reason to infer the policy from a hostname. The caller's usage options now determine the request.

The patch also maps the Windows invalid-usage status to Go's CertificateInvalidError with the IncompatibleUsage reason. Rejecting the certificate is only part of an API correction. Reporting an appropriate error lets the caller understand why a previously accepted chain now fails.

The security announcement recommends updating to Go 1.14.5 or the corresponding Go 1.13.13 release. Its two disclosures describe different exposure conditions, so an application's relevant path matters when interpreting them. The HTTP defect depends on concurrent body reads and response writes. The certificate defect concerns requested usages on the Windows platform-verification path.

I see the common review question as whether each internal path preserves the policy visible at the public API. The HTTP implementation must coordinate the response hidden inside a read. The certificate implementation must pass the requested purpose into its platform call. Go 1.14.5 adds the synchronization and parameter handling that those respective paths require.