net/http: use ASCII space trimming throughout

Security hardening against HTTP request smuggling. Thank you to ZeddYu
for reporting this issue.

Change-Id: I98bd9f8ffe58360fc3bca9dc5d9a106773e55373
Reviewed-on: https://go-review.googlesource.com/c/go/+/231419
Reviewed-by: Katie Hockman <katie@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Filippo Valsorda 2020-05-01 01:14:04 -04:00
parent d5734d4f2d
commit 21898524f6
6 changed files with 19 additions and 15 deletions

View File

@ -21,6 +21,7 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"net/textproto"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -276,8 +277,8 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
continue continue
} }
header, val := parts[0], parts[1] header, val := parts[0], parts[1]
header = strings.TrimSpace(header) header = textproto.TrimString(header)
val = strings.TrimSpace(val) val = textproto.TrimString(val)
switch { switch {
case header == "Status": case header == "Status":
if len(val) < 3 { if len(val) < 3 {

View File

@ -7,6 +7,7 @@ package http
import ( import (
"log" "log"
"net" "net"
"net/textproto"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -60,11 +61,11 @@ func readSetCookies(h Header) []*Cookie {
} }
cookies := make([]*Cookie, 0, cookieCount) cookies := make([]*Cookie, 0, cookieCount)
for _, line := range h["Set-Cookie"] { for _, line := range h["Set-Cookie"] {
parts := strings.Split(strings.TrimSpace(line), ";") parts := strings.Split(textproto.TrimString(line), ";")
if len(parts) == 1 && parts[0] == "" { if len(parts) == 1 && parts[0] == "" {
continue continue
} }
parts[0] = strings.TrimSpace(parts[0]) parts[0] = textproto.TrimString(parts[0])
j := strings.Index(parts[0], "=") j := strings.Index(parts[0], "=")
if j < 0 { if j < 0 {
continue continue
@ -83,7 +84,7 @@ func readSetCookies(h Header) []*Cookie {
Raw: line, Raw: line,
} }
for i := 1; i < len(parts); i++ { for i := 1; i < len(parts); i++ {
parts[i] = strings.TrimSpace(parts[i]) parts[i] = textproto.TrimString(parts[i])
if len(parts[i]) == 0 { if len(parts[i]) == 0 {
continue continue
} }
@ -242,7 +243,7 @@ func readCookies(h Header, filter string) []*Cookie {
cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";")) cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";"))
for _, line := range lines { for _, line := range lines {
line = strings.TrimSpace(line) line = textproto.TrimString(line)
var part string var part string
for len(line) > 0 { // continue since we have rest for len(line) > 0 { // continue since we have rest
@ -251,7 +252,7 @@ func readCookies(h Header, filter string) []*Cookie {
} else { } else {
part, line = line, "" part, line = line, ""
} }
part = strings.TrimSpace(part) part = textproto.TrimString(part)
if len(part) == 0 { if len(part) == 0 {
continue continue
} }

View File

@ -756,7 +756,7 @@ func parseRange(s string, size int64) ([]httpRange, error) {
var ranges []httpRange var ranges []httpRange
noOverlap := false noOverlap := false
for _, ra := range strings.Split(s[len(b):], ",") { for _, ra := range strings.Split(s[len(b):], ",") {
ra = strings.TrimSpace(ra) ra = textproto.TrimString(ra)
if ra == "" { if ra == "" {
continue continue
} }
@ -764,7 +764,7 @@ func parseRange(s string, size int64) ([]httpRange, error) {
if i < 0 { if i < 0 {
return nil, errors.New("invalid range") return nil, errors.New("invalid range")
} }
start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:]) start, end := textproto.TrimString(ra[:i]), textproto.TrimString(ra[i+1:])
var r httpRange var r httpRange
if start == "" { if start == "" {
// If no start is specified, end specifies the // If no start is specified, end specifies the

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/textproto"
"strconv" "strconv"
"strings" "strings"
@ -221,7 +222,7 @@ func (rw *ResponseRecorder) Result() *http.Response {
// This a modified version of same function found in net/http/transfer.go. This // This a modified version of same function found in net/http/transfer.go. This
// one just ignores an invalid header. // one just ignores an invalid header.
func parseContentLength(cl string) int64 { func parseContentLength(cl string) int64 {
cl = strings.TrimSpace(cl) cl = textproto.TrimString(cl)
if cl == "" { if cl == "" {
return -1 return -1
} }

View File

@ -13,6 +13,7 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"net/textproto"
"net/url" "net/url"
"strings" "strings"
"sync" "sync"
@ -387,7 +388,7 @@ func shouldPanicOnCopyError(req *http.Request) bool {
func removeConnectionHeaders(h http.Header) { func removeConnectionHeaders(h http.Header) {
for _, f := range h["Connection"] { for _, f := range h["Connection"] {
for _, sf := range strings.Split(f, ",") { for _, sf := range strings.Split(f, ",") {
if sf = strings.TrimSpace(sf); sf != "" { if sf = textproto.TrimString(sf); sf != "" {
h.Del(sf) h.Del(sf)
} }
} }

View File

@ -660,9 +660,9 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header,
// Content-Length headers if they differ in value. // Content-Length headers if they differ in value.
// If there are dups of the value, remove the dups. // If there are dups of the value, remove the dups.
// See Issue 16490. // See Issue 16490.
first := strings.TrimSpace(contentLens[0]) first := textproto.TrimString(contentLens[0])
for _, ct := range contentLens[1:] { for _, ct := range contentLens[1:] {
if first != strings.TrimSpace(ct) { if first != textproto.TrimString(ct) {
return 0, fmt.Errorf("http: message cannot contain multiple Content-Length headers; got %q", contentLens) return 0, fmt.Errorf("http: message cannot contain multiple Content-Length headers; got %q", contentLens)
} }
} }
@ -701,7 +701,7 @@ func fixLength(isResponse bool, status int, requestMethod string, header Header,
// Logic based on Content-Length // Logic based on Content-Length
var cl string var cl string
if len(contentLens) == 1 { if len(contentLens) == 1 {
cl = strings.TrimSpace(contentLens[0]) cl = textproto.TrimString(contentLens[0])
} }
if cl != "" { if cl != "" {
n, err := parseContentLength(cl) n, err := parseContentLength(cl)
@ -1032,7 +1032,7 @@ func (bl bodyLocked) Read(p []byte) (n int, err error) {
// parseContentLength trims whitespace from s and returns -1 if no value // parseContentLength trims whitespace from s and returns -1 if no value
// is set, or the value if it's >= 0. // is set, or the value if it's >= 0.
func parseContentLength(cl string) (int64, error) { func parseContentLength(cl string) (int64, error) {
cl = strings.TrimSpace(cl) cl = textproto.TrimString(cl)
if cl == "" { if cl == "" {
return -1, nil return -1, nil
} }