mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 01:53:42 +08:00 
			
		
		
		
	full diff: https://github.com/golang/net/compare/v0.22.0...v0.23.0 Includes a fix for CVE-2023-45288, which is also addressed in go1.22.2 and go1.21.9; > http2: close connections when receiving too many headers > > Maintaining HPACK state requires that we parse and process > all HEADERS and CONTINUATION frames on a connection. > When a request's headers exceed MaxHeaderBytes, we don't > allocate memory to store the excess headers but we do > parse them. This permits an attacker to cause an HTTP/2 > endpoint to read arbitrary amounts of data, all associated > with a request which is going to be rejected. > > Set a limit on the amount of excess header frames we > will process before closing a connection. > > Thanks to Bartek Nowotarski for reporting this issue. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
		
			
				
	
	
		
			332 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			332 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2024 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 http2
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
// testSyncHooks coordinates goroutines in tests.
 | 
						|
//
 | 
						|
// For example, a call to ClientConn.RoundTrip involves several goroutines, including:
 | 
						|
//   - the goroutine running RoundTrip;
 | 
						|
//   - the clientStream.doRequest goroutine, which writes the request; and
 | 
						|
//   - the clientStream.readLoop goroutine, which reads the response.
 | 
						|
//
 | 
						|
// Using testSyncHooks, a test can start a RoundTrip and identify when all these goroutines
 | 
						|
// are blocked waiting for some condition such as reading the Request.Body or waiting for
 | 
						|
// flow control to become available.
 | 
						|
//
 | 
						|
// The testSyncHooks also manage timers and synthetic time in tests.
 | 
						|
// This permits us to, for example, start a request and cause it to time out waiting for
 | 
						|
// response headers without resorting to time.Sleep calls.
 | 
						|
type testSyncHooks struct {
 | 
						|
	// active/inactive act as a mutex and condition variable.
 | 
						|
	//
 | 
						|
	//  - neither chan contains a value: testSyncHooks is locked.
 | 
						|
	//  - active contains a value: unlocked, and at least one goroutine is not blocked
 | 
						|
	//  - inactive contains a value: unlocked, and all goroutines are blocked
 | 
						|
	active   chan struct{}
 | 
						|
	inactive chan struct{}
 | 
						|
 | 
						|
	// goroutine counts
 | 
						|
	total    int                     // total goroutines
 | 
						|
	condwait map[*sync.Cond]int      // blocked in sync.Cond.Wait
 | 
						|
	blocked  []*testBlockedGoroutine // otherwise blocked
 | 
						|
 | 
						|
	// fake time
 | 
						|
	now    time.Time
 | 
						|
	timers []*fakeTimer
 | 
						|
 | 
						|
	// Transport testing: Report various events.
 | 
						|
	newclientconn func(*ClientConn)
 | 
						|
	newstream     func(*clientStream)
 | 
						|
}
 | 
						|
 | 
						|
// testBlockedGoroutine is a blocked goroutine.
 | 
						|
type testBlockedGoroutine struct {
 | 
						|
	f  func() bool   // blocked until f returns true
 | 
						|
	ch chan struct{} // closed when unblocked
 | 
						|
}
 | 
						|
 | 
						|
func newTestSyncHooks() *testSyncHooks {
 | 
						|
	h := &testSyncHooks{
 | 
						|
		active:   make(chan struct{}, 1),
 | 
						|
		inactive: make(chan struct{}, 1),
 | 
						|
		condwait: map[*sync.Cond]int{},
 | 
						|
	}
 | 
						|
	h.inactive <- struct{}{}
 | 
						|
	h.now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
 | 
						|
	return h
 | 
						|
}
 | 
						|
 | 
						|
// lock acquires the testSyncHooks mutex.
 | 
						|
func (h *testSyncHooks) lock() {
 | 
						|
	select {
 | 
						|
	case <-h.active:
 | 
						|
	case <-h.inactive:
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// waitInactive waits for all goroutines to become inactive.
 | 
						|
func (h *testSyncHooks) waitInactive() {
 | 
						|
	for {
 | 
						|
		<-h.inactive
 | 
						|
		if !h.unlock() {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// unlock releases the testSyncHooks mutex.
 | 
						|
// It reports whether any goroutines are active.
 | 
						|
func (h *testSyncHooks) unlock() (active bool) {
 | 
						|
	// Look for a blocked goroutine which can be unblocked.
 | 
						|
	blocked := h.blocked[:0]
 | 
						|
	unblocked := false
 | 
						|
	for _, b := range h.blocked {
 | 
						|
		if !unblocked && b.f() {
 | 
						|
			unblocked = true
 | 
						|
			close(b.ch)
 | 
						|
		} else {
 | 
						|
			blocked = append(blocked, b)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	h.blocked = blocked
 | 
						|
 | 
						|
	// Count goroutines blocked on condition variables.
 | 
						|
	condwait := 0
 | 
						|
	for _, count := range h.condwait {
 | 
						|
		condwait += count
 | 
						|
	}
 | 
						|
 | 
						|
	if h.total > condwait+len(blocked) {
 | 
						|
		h.active <- struct{}{}
 | 
						|
		return true
 | 
						|
	} else {
 | 
						|
		h.inactive <- struct{}{}
 | 
						|
		return false
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// goRun starts a new goroutine.
 | 
						|
func (h *testSyncHooks) goRun(f func()) {
 | 
						|
	h.lock()
 | 
						|
	h.total++
 | 
						|
	h.unlock()
 | 
						|
	go func() {
 | 
						|
		defer func() {
 | 
						|
			h.lock()
 | 
						|
			h.total--
 | 
						|
			h.unlock()
 | 
						|
		}()
 | 
						|
		f()
 | 
						|
	}()
 | 
						|
}
 | 
						|
 | 
						|
// blockUntil indicates that a goroutine is blocked waiting for some condition to become true.
 | 
						|
// It waits until f returns true before proceeding.
 | 
						|
//
 | 
						|
// Example usage:
 | 
						|
//
 | 
						|
//	h.blockUntil(func() bool {
 | 
						|
//		// Is the context done yet?
 | 
						|
//		select {
 | 
						|
//		case <-ctx.Done():
 | 
						|
//		default:
 | 
						|
//			return false
 | 
						|
//		}
 | 
						|
//		return true
 | 
						|
//	})
 | 
						|
//	// Wait for the context to become done.
 | 
						|
//	<-ctx.Done()
 | 
						|
//
 | 
						|
// The function f passed to blockUntil must be non-blocking and idempotent.
 | 
						|
func (h *testSyncHooks) blockUntil(f func() bool) {
 | 
						|
	if f() {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	ch := make(chan struct{})
 | 
						|
	h.lock()
 | 
						|
	h.blocked = append(h.blocked, &testBlockedGoroutine{
 | 
						|
		f:  f,
 | 
						|
		ch: ch,
 | 
						|
	})
 | 
						|
	h.unlock()
 | 
						|
	<-ch
 | 
						|
}
 | 
						|
 | 
						|
// broadcast is sync.Cond.Broadcast.
 | 
						|
func (h *testSyncHooks) condBroadcast(cond *sync.Cond) {
 | 
						|
	h.lock()
 | 
						|
	delete(h.condwait, cond)
 | 
						|
	h.unlock()
 | 
						|
	cond.Broadcast()
 | 
						|
}
 | 
						|
 | 
						|
// broadcast is sync.Cond.Wait.
 | 
						|
func (h *testSyncHooks) condWait(cond *sync.Cond) {
 | 
						|
	h.lock()
 | 
						|
	h.condwait[cond]++
 | 
						|
	h.unlock()
 | 
						|
}
 | 
						|
 | 
						|
// newTimer creates a new fake timer.
 | 
						|
func (h *testSyncHooks) newTimer(d time.Duration) timer {
 | 
						|
	h.lock()
 | 
						|
	defer h.unlock()
 | 
						|
	t := &fakeTimer{
 | 
						|
		hooks: h,
 | 
						|
		when:  h.now.Add(d),
 | 
						|
		c:     make(chan time.Time),
 | 
						|
	}
 | 
						|
	h.timers = append(h.timers, t)
 | 
						|
	return t
 | 
						|
}
 | 
						|
 | 
						|
// afterFunc creates a new fake AfterFunc timer.
 | 
						|
func (h *testSyncHooks) afterFunc(d time.Duration, f func()) timer {
 | 
						|
	h.lock()
 | 
						|
	defer h.unlock()
 | 
						|
	t := &fakeTimer{
 | 
						|
		hooks: h,
 | 
						|
		when:  h.now.Add(d),
 | 
						|
		f:     f,
 | 
						|
	}
 | 
						|
	h.timers = append(h.timers, t)
 | 
						|
	return t
 | 
						|
}
 | 
						|
 | 
						|
func (h *testSyncHooks) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
 | 
						|
	ctx, cancel := context.WithCancel(ctx)
 | 
						|
	t := h.afterFunc(d, cancel)
 | 
						|
	return ctx, func() {
 | 
						|
		t.Stop()
 | 
						|
		cancel()
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (h *testSyncHooks) timeUntilEvent() time.Duration {
 | 
						|
	h.lock()
 | 
						|
	defer h.unlock()
 | 
						|
	var next time.Time
 | 
						|
	for _, t := range h.timers {
 | 
						|
		if next.IsZero() || t.when.Before(next) {
 | 
						|
			next = t.when
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if d := next.Sub(h.now); d > 0 {
 | 
						|
		return d
 | 
						|
	}
 | 
						|
	return 0
 | 
						|
}
 | 
						|
 | 
						|
// advance advances time and causes synthetic timers to fire.
 | 
						|
func (h *testSyncHooks) advance(d time.Duration) {
 | 
						|
	h.lock()
 | 
						|
	defer h.unlock()
 | 
						|
	h.now = h.now.Add(d)
 | 
						|
	timers := h.timers[:0]
 | 
						|
	for _, t := range h.timers {
 | 
						|
		t := t // remove after go.mod depends on go1.22
 | 
						|
		t.mu.Lock()
 | 
						|
		switch {
 | 
						|
		case t.when.After(h.now):
 | 
						|
			timers = append(timers, t)
 | 
						|
		case t.when.IsZero():
 | 
						|
			// stopped timer
 | 
						|
		default:
 | 
						|
			t.when = time.Time{}
 | 
						|
			if t.c != nil {
 | 
						|
				close(t.c)
 | 
						|
			}
 | 
						|
			if t.f != nil {
 | 
						|
				h.total++
 | 
						|
				go func() {
 | 
						|
					defer func() {
 | 
						|
						h.lock()
 | 
						|
						h.total--
 | 
						|
						h.unlock()
 | 
						|
					}()
 | 
						|
					t.f()
 | 
						|
				}()
 | 
						|
			}
 | 
						|
		}
 | 
						|
		t.mu.Unlock()
 | 
						|
	}
 | 
						|
	h.timers = timers
 | 
						|
}
 | 
						|
 | 
						|
// A timer wraps a time.Timer, or a synthetic equivalent in tests.
 | 
						|
// Unlike time.Timer, timer is single-use: The timer channel is closed when the timer expires.
 | 
						|
type timer interface {
 | 
						|
	C() <-chan time.Time
 | 
						|
	Stop() bool
 | 
						|
	Reset(d time.Duration) bool
 | 
						|
}
 | 
						|
 | 
						|
// timeTimer implements timer using real time.
 | 
						|
type timeTimer struct {
 | 
						|
	t *time.Timer
 | 
						|
	c chan time.Time
 | 
						|
}
 | 
						|
 | 
						|
// newTimeTimer creates a new timer using real time.
 | 
						|
func newTimeTimer(d time.Duration) timer {
 | 
						|
	ch := make(chan time.Time)
 | 
						|
	t := time.AfterFunc(d, func() {
 | 
						|
		close(ch)
 | 
						|
	})
 | 
						|
	return &timeTimer{t, ch}
 | 
						|
}
 | 
						|
 | 
						|
// newTimeAfterFunc creates an AfterFunc timer using real time.
 | 
						|
func newTimeAfterFunc(d time.Duration, f func()) timer {
 | 
						|
	return &timeTimer{
 | 
						|
		t: time.AfterFunc(d, f),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (t timeTimer) C() <-chan time.Time        { return t.c }
 | 
						|
func (t timeTimer) Stop() bool                 { return t.t.Stop() }
 | 
						|
func (t timeTimer) Reset(d time.Duration) bool { return t.t.Reset(d) }
 | 
						|
 | 
						|
// fakeTimer implements timer using fake time.
 | 
						|
type fakeTimer struct {
 | 
						|
	hooks *testSyncHooks
 | 
						|
 | 
						|
	mu   sync.Mutex
 | 
						|
	when time.Time      // when the timer will fire
 | 
						|
	c    chan time.Time // closed when the timer fires; mutually exclusive with f
 | 
						|
	f    func()         // called when the timer fires; mutually exclusive with c
 | 
						|
}
 | 
						|
 | 
						|
func (t *fakeTimer) C() <-chan time.Time { return t.c }
 | 
						|
 | 
						|
func (t *fakeTimer) Stop() bool {
 | 
						|
	t.mu.Lock()
 | 
						|
	defer t.mu.Unlock()
 | 
						|
	stopped := t.when.IsZero()
 | 
						|
	t.when = time.Time{}
 | 
						|
	return stopped
 | 
						|
}
 | 
						|
 | 
						|
func (t *fakeTimer) Reset(d time.Duration) bool {
 | 
						|
	if t.c != nil || t.f == nil {
 | 
						|
		panic("fakeTimer only supports Reset on AfterFunc timers")
 | 
						|
	}
 | 
						|
	t.mu.Lock()
 | 
						|
	defer t.mu.Unlock()
 | 
						|
	t.hooks.lock()
 | 
						|
	defer t.hooks.unlock()
 | 
						|
	active := !t.when.IsZero()
 | 
						|
	t.when = t.hooks.now.Add(d)
 | 
						|
	if !active {
 | 
						|
		t.hooks.timers = append(t.hooks.timers, t)
 | 
						|
	}
 | 
						|
	return active
 | 
						|
}
 |