Go version
Tested with current master and reported against Go 1.26.3 / Go 1.27 development builds.
What did you do?
net.parsePort accumulates decimal digits in a uint32. For oversized decimal port strings, multiplication can overflow before the current cutoff check clamps the value.
A small reproducer:
package main
import (
"fmt"
"net"
)
func main() {
addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:4294985376")
fmt.Printf("addr=%v err=%v\n", addr, err)
}
4294985376 is 2^32 + 18080.
What did you expect to see?
The oversized port should be rejected as invalid, the same way other ports outside the valid TCP/UDP range are rejected.
What did you see instead?
The port wraps during parsing and is accepted as port 18080.
The issue appears to come from src/net/port.go. The parser currently clamps when n >= 1<<30, but uint32 multiplication by 10 overflows when n > math.MaxUint32/10. For example:
n = 429498537
n *= 10 // wraps from 4294985370 to 18074 as uint32
nn = 18074 + 6 // 18080, no addition overflow
The final LookupPort range check then accepts the wrapped value because 18080 is in range.
Why this matters
This creates a parser differential between URL-level validation and dial-level behavior. For example, net/url leaves an oversized numeric port string visible via URL.Port(), while a later net.Dial / http.Client path can resolve and connect to the wrapped low port. Applications that denylist sensitive ports based on URL.Port() but then fetch the original URL can miss the actual port that will be dialed.
This is not a vulnerability in every Go program, and applications that strictly reject ports outside [1, 65535] or reconstruct URLs from validated components are not affected. It is still a real parsing bug and can bypass denylist-based SSRF protections in affected applications.
Possible fix
Clamp before multiplication can overflow, for example by checking n > max/10 before n *= 10, or by otherwise parsing in a way that rejects/clamps oversized decimal ports before wraparound occurs.
A related hardening option is for net/url.validOptionalPort to reject numeric port strings outside [0, 65535], but the concrete wraparound bug is in net.parsePort.
Go version
Tested with current
masterand reported against Go 1.26.3 / Go 1.27 development builds.What did you do?
net.parsePortaccumulates decimal digits in auint32. For oversized decimal port strings, multiplication can overflow before the current cutoff check clamps the value.A small reproducer:
4294985376is2^32 + 18080.What did you expect to see?
The oversized port should be rejected as invalid, the same way other ports outside the valid TCP/UDP range are rejected.
What did you see instead?
The port wraps during parsing and is accepted as port
18080.The issue appears to come from
src/net/port.go. The parser currently clamps whenn >= 1<<30, butuint32multiplication by 10 overflows whenn > math.MaxUint32/10. For example:The final
LookupPortrange check then accepts the wrapped value because18080is in range.Why this matters
This creates a parser differential between URL-level validation and dial-level behavior. For example,
net/urlleaves an oversized numeric port string visible viaURL.Port(), while a laternet.Dial/http.Clientpath can resolve and connect to the wrapped low port. Applications that denylist sensitive ports based onURL.Port()but then fetch the original URL can miss the actual port that will be dialed.This is not a vulnerability in every Go program, and applications that strictly reject ports outside
[1, 65535]or reconstruct URLs from validated components are not affected. It is still a real parsing bug and can bypass denylist-based SSRF protections in affected applications.Possible fix
Clamp before multiplication can overflow, for example by checking
n > max/10beforen *= 10, or by otherwise parsing in a way that rejects/clamps oversized decimal ports before wraparound occurs.A related hardening option is for
net/url.validOptionalPortto reject numeric port strings outside[0, 65535], but the concrete wraparound bug is innet.parsePort.