]> Cypherpunks repositories - gostls13.git/commitdiff
net/http: make the zero value of CrossOriginProtection work
authorAustin Clements <austin@google.com>
Tue, 10 Jun 2025 16:26:03 +0000 (12:26 -0400)
committerGopher Robot <gobot@golang.org>
Tue, 10 Jun 2025 18:09:07 +0000 (11:09 -0700)
Currently, CrossOriginProtection must be constructed by
NewCrossOriginProtection. If you try to use the zero value, most
methods will panic with a nil dereference.

This CL makes CrossOriginProtection use on-demand initialization
instead, so the zero value has the same semantics as the value
currently returned by NewCrossOriginProtection. Now,
NewCrossOriginProtection just constructs the zero value.

We keep NewCrossOriginProtection by analogy to NewServeMux.

Updates #73626
Fixes #74089.

Change-Id: Ia80183eb6bfdafb0e002271c0b25c2d6230a159a
Reviewed-on: https://go-review.googlesource.com/c/go/+/680396
Auto-Submit: Austin Clements <austin@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
src/net/http/csrf.go

index a46071f806f5acbeafa219f55ce0e076ffb70a04..8812a508ae21b826e98a7b5411afd324201acd7f 100644 (file)
@@ -26,12 +26,15 @@ import (
 // Requests without Sec-Fetch-Site or Origin headers are currently assumed to be
 // either same-origin or non-browser requests, and are allowed.
 //
+// The zero value of CrossOriginProtection is valid and has no trusted origins
+// or bypass patterns.
+//
 // [Sec-Fetch-Site]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Site
 // [Origin]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin
 // [Cross-Site Request Forgery (CSRF)]: https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF
 // [safe methods]: https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP
 type CrossOriginProtection struct {
-       bypass    *ServeMux
+       bypass    atomic.Pointer[ServeMux]
        trustedMu sync.RWMutex
        trusted   map[string]bool
        deny      atomic.Pointer[Handler]
@@ -39,10 +42,7 @@ type CrossOriginProtection struct {
 
 // NewCrossOriginProtection returns a new [CrossOriginProtection] value.
 func NewCrossOriginProtection() *CrossOriginProtection {
-       return &CrossOriginProtection{
-               bypass:  NewServeMux(),
-               trusted: make(map[string]bool),
-       }
+       return &CrossOriginProtection{}
 }
 
 // AddTrustedOrigin allows all requests with an [Origin] header
@@ -70,6 +70,9 @@ func (c *CrossOriginProtection) AddTrustedOrigin(origin string) error {
        }
        c.trustedMu.Lock()
        defer c.trustedMu.Unlock()
+       if c.trusted == nil {
+               c.trusted = make(map[string]bool)
+       }
        c.trusted[origin] = true
        return nil
 }
@@ -82,7 +85,21 @@ var noopHandler = HandlerFunc(func(w ResponseWriter, r *Request) {})
 // AddInsecureBypassPattern can be called concurrently with other methods
 // or request handling, and applies to future requests.
 func (c *CrossOriginProtection) AddInsecureBypassPattern(pattern string) {
-       c.bypass.Handle(pattern, noopHandler)
+       var bypass *ServeMux
+
+       // Lazily initialize c.bypass
+       for {
+               bypass = c.bypass.Load()
+               if bypass != nil {
+                       break
+               }
+               bypass = NewServeMux()
+               if c.bypass.CompareAndSwap(nil, bypass) {
+                       break
+               }
+       }
+
+       bypass.Handle(pattern, noopHandler)
 }
 
 // SetDenyHandler sets a handler to invoke when a request is rejected.
@@ -149,9 +166,11 @@ func (c *CrossOriginProtection) Check(req *Request) error {
 // isRequestExempt checks the bypasses which require taking a lock, and should
 // be deferred until the last moment.
 func (c *CrossOriginProtection) isRequestExempt(req *Request) bool {
-       if _, pattern := c.bypass.Handler(req); pattern != "" {
-               // The request matches a bypass pattern.
-               return true
+       if bypass := c.bypass.Load(); bypass != nil {
+               if _, pattern := bypass.Handler(req); pattern != "" {
+                       // The request matches a bypass pattern.
+                       return true
+               }
        }
 
        c.trustedMu.RLock()