golang.org/x/net contains a fix for CVE-2022-41717, which was addressed in stdlib in go1.19.4 and go1.18.9; > net/http: limit canonical header cache by bytes, not entries > > An attacker can cause excessive memory growth in a Go server accepting > HTTP/2 requests. > > HTTP/2 server connections contain a cache of HTTP header keys sent by > the client. While the total number of entries in this cache is capped, > an attacker sending very large keys can cause the server to allocate > approximately 64 MiB per open connection. > > This issue is also fixed in golang.org/x/net/http2 v0.4.0, > for users manually configuring HTTP/2. full diff: https://github.com/golang/net/compare/c63010009c80...v0.4.0 other dependency updates (due to (circular) dependencies between them): - golang.org/x/sys v0.3.0: https://github.com/golang/sys/compare/v0.2.0...v0.3.0 - golang.org/x/term v0.3.0: https://github.com/golang/term/compare/v0.1.0...v0.3.0 - golang.org/x/text v0.5.0: https://github.com/golang/text/compare/v0.4.0...v0.5.0 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
		
			
				
	
	
		
			246 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			246 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2014 The Go Authors. All rights reserved.
 | 
						|
// Use of this source code is governed by a BSD-style
 | 
						|
// license that can be found in the LICENSE file.
 | 
						|
 | 
						|
package hpack
 | 
						|
 | 
						|
import (
 | 
						|
	"io"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	uint32Max              = ^uint32(0)
 | 
						|
	initialHeaderTableSize = 4096
 | 
						|
)
 | 
						|
 | 
						|
type Encoder struct {
 | 
						|
	dynTab dynamicTable
 | 
						|
	// minSize is the minimum table size set by
 | 
						|
	// SetMaxDynamicTableSize after the previous Header Table Size
 | 
						|
	// Update.
 | 
						|
	minSize uint32
 | 
						|
	// maxSizeLimit is the maximum table size this encoder
 | 
						|
	// supports. This will protect the encoder from too large
 | 
						|
	// size.
 | 
						|
	maxSizeLimit uint32
 | 
						|
	// tableSizeUpdate indicates whether "Header Table Size
 | 
						|
	// Update" is required.
 | 
						|
	tableSizeUpdate bool
 | 
						|
	w               io.Writer
 | 
						|
	buf             []byte
 | 
						|
}
 | 
						|
 | 
						|
// NewEncoder returns a new Encoder which performs HPACK encoding. An
 | 
						|
// encoded data is written to w.
 | 
						|
func NewEncoder(w io.Writer) *Encoder {
 | 
						|
	e := &Encoder{
 | 
						|
		minSize:         uint32Max,
 | 
						|
		maxSizeLimit:    initialHeaderTableSize,
 | 
						|
		tableSizeUpdate: false,
 | 
						|
		w:               w,
 | 
						|
	}
 | 
						|
	e.dynTab.table.init()
 | 
						|
	e.dynTab.setMaxSize(initialHeaderTableSize)
 | 
						|
	return e
 | 
						|
}
 | 
						|
 | 
						|
// WriteField encodes f into a single Write to e's underlying Writer.
 | 
						|
// This function may also produce bytes for "Header Table Size Update"
 | 
						|
// if necessary. If produced, it is done before encoding f.
 | 
						|
func (e *Encoder) WriteField(f HeaderField) error {
 | 
						|
	e.buf = e.buf[:0]
 | 
						|
 | 
						|
	if e.tableSizeUpdate {
 | 
						|
		e.tableSizeUpdate = false
 | 
						|
		if e.minSize < e.dynTab.maxSize {
 | 
						|
			e.buf = appendTableSize(e.buf, e.minSize)
 | 
						|
		}
 | 
						|
		e.minSize = uint32Max
 | 
						|
		e.buf = appendTableSize(e.buf, e.dynTab.maxSize)
 | 
						|
	}
 | 
						|
 | 
						|
	idx, nameValueMatch := e.searchTable(f)
 | 
						|
	if nameValueMatch {
 | 
						|
		e.buf = appendIndexed(e.buf, idx)
 | 
						|
	} else {
 | 
						|
		indexing := e.shouldIndex(f)
 | 
						|
		if indexing {
 | 
						|
			e.dynTab.add(f)
 | 
						|
		}
 | 
						|
 | 
						|
		if idx == 0 {
 | 
						|
			e.buf = appendNewName(e.buf, f, indexing)
 | 
						|
		} else {
 | 
						|
			e.buf = appendIndexedName(e.buf, f, idx, indexing)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	n, err := e.w.Write(e.buf)
 | 
						|
	if err == nil && n != len(e.buf) {
 | 
						|
		err = io.ErrShortWrite
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// searchTable searches f in both stable and dynamic header tables.
 | 
						|
// The static header table is searched first. Only when there is no
 | 
						|
// exact match for both name and value, the dynamic header table is
 | 
						|
// then searched. If there is no match, i is 0. If both name and value
 | 
						|
// match, i is the matched index and nameValueMatch becomes true. If
 | 
						|
// only name matches, i points to that index and nameValueMatch
 | 
						|
// becomes false.
 | 
						|
func (e *Encoder) searchTable(f HeaderField) (i uint64, nameValueMatch bool) {
 | 
						|
	i, nameValueMatch = staticTable.search(f)
 | 
						|
	if nameValueMatch {
 | 
						|
		return i, true
 | 
						|
	}
 | 
						|
 | 
						|
	j, nameValueMatch := e.dynTab.table.search(f)
 | 
						|
	if nameValueMatch || (i == 0 && j != 0) {
 | 
						|
		return j + uint64(staticTable.len()), nameValueMatch
 | 
						|
	}
 | 
						|
 | 
						|
	return i, false
 | 
						|
}
 | 
						|
 | 
						|
// SetMaxDynamicTableSize changes the dynamic header table size to v.
 | 
						|
// The actual size is bounded by the value passed to
 | 
						|
// SetMaxDynamicTableSizeLimit.
 | 
						|
func (e *Encoder) SetMaxDynamicTableSize(v uint32) {
 | 
						|
	if v > e.maxSizeLimit {
 | 
						|
		v = e.maxSizeLimit
 | 
						|
	}
 | 
						|
	if v < e.minSize {
 | 
						|
		e.minSize = v
 | 
						|
	}
 | 
						|
	e.tableSizeUpdate = true
 | 
						|
	e.dynTab.setMaxSize(v)
 | 
						|
}
 | 
						|
 | 
						|
// MaxDynamicTableSize returns the current dynamic header table size.
 | 
						|
func (e *Encoder) MaxDynamicTableSize() (v uint32) {
 | 
						|
	return e.dynTab.maxSize
 | 
						|
}
 | 
						|
 | 
						|
// SetMaxDynamicTableSizeLimit changes the maximum value that can be
 | 
						|
// specified in SetMaxDynamicTableSize to v. By default, it is set to
 | 
						|
// 4096, which is the same size of the default dynamic header table
 | 
						|
// size described in HPACK specification. If the current maximum
 | 
						|
// dynamic header table size is strictly greater than v, "Header Table
 | 
						|
// Size Update" will be done in the next WriteField call and the
 | 
						|
// maximum dynamic header table size is truncated to v.
 | 
						|
func (e *Encoder) SetMaxDynamicTableSizeLimit(v uint32) {
 | 
						|
	e.maxSizeLimit = v
 | 
						|
	if e.dynTab.maxSize > v {
 | 
						|
		e.tableSizeUpdate = true
 | 
						|
		e.dynTab.setMaxSize(v)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// shouldIndex reports whether f should be indexed.
 | 
						|
func (e *Encoder) shouldIndex(f HeaderField) bool {
 | 
						|
	return !f.Sensitive && f.Size() <= e.dynTab.maxSize
 | 
						|
}
 | 
						|
 | 
						|
// appendIndexed appends index i, as encoded in "Indexed Header Field"
 | 
						|
// representation, to dst and returns the extended buffer.
 | 
						|
func appendIndexed(dst []byte, i uint64) []byte {
 | 
						|
	first := len(dst)
 | 
						|
	dst = appendVarInt(dst, 7, i)
 | 
						|
	dst[first] |= 0x80
 | 
						|
	return dst
 | 
						|
}
 | 
						|
 | 
						|
// appendNewName appends f, as encoded in one of "Literal Header field
 | 
						|
// - New Name" representation variants, to dst and returns the
 | 
						|
// extended buffer.
 | 
						|
//
 | 
						|
// If f.Sensitive is true, "Never Indexed" representation is used. If
 | 
						|
// f.Sensitive is false and indexing is true, "Incremental Indexing"
 | 
						|
// representation is used.
 | 
						|
func appendNewName(dst []byte, f HeaderField, indexing bool) []byte {
 | 
						|
	dst = append(dst, encodeTypeByte(indexing, f.Sensitive))
 | 
						|
	dst = appendHpackString(dst, f.Name)
 | 
						|
	return appendHpackString(dst, f.Value)
 | 
						|
}
 | 
						|
 | 
						|
// appendIndexedName appends f and index i referring indexed name
 | 
						|
// entry, as encoded in one of "Literal Header field - Indexed Name"
 | 
						|
// representation variants, to dst and returns the extended buffer.
 | 
						|
//
 | 
						|
// If f.Sensitive is true, "Never Indexed" representation is used. If
 | 
						|
// f.Sensitive is false and indexing is true, "Incremental Indexing"
 | 
						|
// representation is used.
 | 
						|
func appendIndexedName(dst []byte, f HeaderField, i uint64, indexing bool) []byte {
 | 
						|
	first := len(dst)
 | 
						|
	var n byte
 | 
						|
	if indexing {
 | 
						|
		n = 6
 | 
						|
	} else {
 | 
						|
		n = 4
 | 
						|
	}
 | 
						|
	dst = appendVarInt(dst, n, i)
 | 
						|
	dst[first] |= encodeTypeByte(indexing, f.Sensitive)
 | 
						|
	return appendHpackString(dst, f.Value)
 | 
						|
}
 | 
						|
 | 
						|
// appendTableSize appends v, as encoded in "Header Table Size Update"
 | 
						|
// representation, to dst and returns the extended buffer.
 | 
						|
func appendTableSize(dst []byte, v uint32) []byte {
 | 
						|
	first := len(dst)
 | 
						|
	dst = appendVarInt(dst, 5, uint64(v))
 | 
						|
	dst[first] |= 0x20
 | 
						|
	return dst
 | 
						|
}
 | 
						|
 | 
						|
// appendVarInt appends i, as encoded in variable integer form using n
 | 
						|
// bit prefix, to dst and returns the extended buffer.
 | 
						|
//
 | 
						|
// See
 | 
						|
// https://httpwg.org/specs/rfc7541.html#integer.representation
 | 
						|
func appendVarInt(dst []byte, n byte, i uint64) []byte {
 | 
						|
	k := uint64((1 << n) - 1)
 | 
						|
	if i < k {
 | 
						|
		return append(dst, byte(i))
 | 
						|
	}
 | 
						|
	dst = append(dst, byte(k))
 | 
						|
	i -= k
 | 
						|
	for ; i >= 128; i >>= 7 {
 | 
						|
		dst = append(dst, byte(0x80|(i&0x7f)))
 | 
						|
	}
 | 
						|
	return append(dst, byte(i))
 | 
						|
}
 | 
						|
 | 
						|
// appendHpackString appends s, as encoded in "String Literal"
 | 
						|
// representation, to dst and returns the extended buffer.
 | 
						|
//
 | 
						|
// s will be encoded in Huffman codes only when it produces strictly
 | 
						|
// shorter byte string.
 | 
						|
func appendHpackString(dst []byte, s string) []byte {
 | 
						|
	huffmanLength := HuffmanEncodeLength(s)
 | 
						|
	if huffmanLength < uint64(len(s)) {
 | 
						|
		first := len(dst)
 | 
						|
		dst = appendVarInt(dst, 7, huffmanLength)
 | 
						|
		dst = AppendHuffmanString(dst, s)
 | 
						|
		dst[first] |= 0x80
 | 
						|
	} else {
 | 
						|
		dst = appendVarInt(dst, 7, uint64(len(s)))
 | 
						|
		dst = append(dst, s...)
 | 
						|
	}
 | 
						|
	return dst
 | 
						|
}
 | 
						|
 | 
						|
// encodeTypeByte returns type byte. If sensitive is true, type byte
 | 
						|
// for "Never Indexed" representation is returned. If sensitive is
 | 
						|
// false and indexing is true, type byte for "Incremental Indexing"
 | 
						|
// representation is returned. Otherwise, type byte for "Without
 | 
						|
// Indexing" is returned.
 | 
						|
func encodeTypeByte(indexing, sensitive bool) byte {
 | 
						|
	if sensitive {
 | 
						|
		return 0x10
 | 
						|
	}
 | 
						|
	if indexing {
 | 
						|
		return 0x40
 | 
						|
	}
 | 
						|
	return 0
 | 
						|
}
 |