Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/net/http2: Request is not treated as malformed when Content-Length is non-zero and no body is sent #72144

Open
slizco opened this issue Mar 6, 2025 · 2 comments

Comments

@slizco
Copy link

slizco commented Mar 6, 2025

Go version

go version go1.23.3 darwin/amd64

Output of go env in your module/workspace:

GO111MODULE='on'
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/Library/Caches/go-build'
GOENV='/home/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/home/workspace/go/pkg/mod'
GOOS='darwin'
GOPATH='/home/workspace/go'
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/.gobrew/current/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/.gobrew/current/go/pkg/tool/darwin_amd64'
GOVCS=''
GOVERSION='go1.23.3'
GODEBUG=''
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/Library/Application Support/go/telemetry'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='clang'
CXX='clang++'
CGO_ENABLED='1'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/wj/rjq3z3b5009frvvq79l2pdwm0000gp/T/go-build1764778311=/tmp/go-build -gno-record-gcc-switches -fno-common'

What did you do?

  1. Create locally signed cert for TLS: openssl req -x509 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -days 365 -nodes -subj '/CN=localhost'
  2. Set-up TLS server:
cat server.go
package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "server running!\n")
}

func main() {
	http.HandleFunc("/", handler)

	err := http.ListenAndServeTLS(":8443", "localhost.crt", "localhost.key", nil)
	if err != nil {
		fmt.Println("Error:", err)
	}
}
  1. Run TLS server: go run server.go

What did you see happen?

  1. Try HTTP/1.1 request with non-zero Content-Length and no body. This hangs indefinitely, with the server waiting for the 10-byte request body:
❯ curl -vk --http1.1 -H'Content-Length: 10' https://localhost:8443
* Host localhost:8443 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8443...
* Connected to localhost (::1) port 8443
* ALPN: curl offers http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256 / [blank] / UNDEF
* ALPN: server accepted http/1.1
* Server certificate:
*  subject: CN=localhost
*  start date: Mar  6 17:00:03 2025 GMT
*  expire date: Mar  6 17:00:03 2026 GMT
*  issuer: CN=localhost
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* using HTTP/1.x
> GET / HTTP/1.1
> Host: localhost:8443
> User-Agent: curl/8.7.1
> Accept: */*
> Content-Length: 10
>
* Request completely sent off
^C⏎
  1. Try HTTP/2 request with non-zero Content-Length and no body. This returns immediately with successful response:
❯ curl -vk --http2 -H'Content-Length: 10' https://localhost:8443
* Host localhost:8443 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8443...
* Connected to localhost (::1) port 8443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256 / [blank] / UNDEF
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=localhost
*  start date: Mar  6 17:00:03 2025 GMT
*  expire date: Mar  6 17:00:03 2026 GMT
*  issuer: CN=localhost
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://localhost:8443/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: localhost:8443]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]
* [HTTP/2] [1] [content-length: 10]
> GET / HTTP/2
> Host: localhost:8443
> User-Agent: curl/8.7.1
> Accept: */*
> Content-Length: 10
>
* Request completely sent off
< HTTP/2 200
< content-type: text/plain; charset=utf-8
< content-length: 16
< date: Thu, 06 Mar 2025 21:27:40 GMT
<
server running!
* Connection #0 to host localhost left intact

What did you expect to see?

It seems like this may be an HTTP/2 spec violation according to RFC 9113:

A request or response that includes message content can include a content-length header field. A request or response is also malformed if the value of a content-length header field does not equal the sum of the DATA frame payload lengths that form the content, unless the message is defined as having no content. For example, 204 or 304 responses contain no content, as does the response to a HEAD request. A response that is defined to have no content, as described in Section 6.4.1 of [HTTP], MAY have a non-zero content-length header field, even though no content is included in DATA frames.

I would expect that the HTTPS server continue waiting for the rest of the request or recognize after some timeout that the request is malformed due to failing this condition:

the value of a content-length header field does not equal the sum of the DATA frame payload lengths that form the content

I would not expect the server to return with a 200-level response.

@slizco slizco changed the title x/net/http2: Request is not treated as malformed when Content-Length is nonzero and no body is sent x/net/http2: Request is not treated as malformed when Content-Length is non-zero and no body is sent Mar 6, 2025
@ianlancetaylor
Copy link
Member

CC @neild @bradfitz

@neild
Copy link
Contributor

neild commented Mar 6, 2025

We skip parsing the Content-Length header when the HEADERS frame for a request includes the END_STREAM flag (indicating that there's no request data):
https://go.googlesource.com/net/+/refs/heads/master/http2/server.go#2284

Probably in this case we should verify that there's no Content-Length header or that it has a value of "0".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants