From d2d85c7bbe15aa2a0f1dc711e72e804ad4dfaec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 19 Feb 2026 17:17:33 +0000 Subject: [PATCH] pkg/net: return errors for unexpected types in IP validators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit netGetIP and netGetIPCIDR silently accepted any value type, returning zero values instead of errors. This caused net.IPCIDR to incorrectly succeed on non-string types like integers, and other validators like net.IP, net.IPv4, net.IPv6 to give less informative "does not satisfy" errors without details. Change netGetIP to return (netip.Addr, error) and netGetIPCIDR to return an error for unexpected types. Update all callers: IPv4, IPv6, IP, LoopbackIP, MulticastIP, InterfaceLocalMulticastIP, LinkLocalMulticastIP, LinkLocalUnicastIP, GlobalUnicastIP, UnspecifiedIP now return (bool, error). ToIP4, ToIP16, IPString, AddIP, InCIDR, CompareIP, and JoinHostPort updated accordingly. As a side effect, all error messages now include the underlying cause from the standard library, e.g. "ParseAddr(...): IPv4 field has value >255" rather than just "does not satisfy net.IPv4". Fixes #3979. Signed-off-by: Daniel Martí Change-Id: Ica7468aaaf87d3e9c0ad94a616ebecdec6c49d9c Reviewed-on: https://cue.gerrithub.io/c/cue-lang/cue/+/1233776 Unity-Result: CUE porcuepine Reviewed-by: Roger Peppe TryBot-Result: CUEcueckoo --- pkg/net/host.go | 7 +- pkg/net/ip.go | 152 +++++++++++++++++++------------ pkg/net/pkg.go | 20 ++-- pkg/net/testdata/compareip.txtar | 12 +-- pkg/net/testdata/gen.txtar | 28 +++--- pkg/net/testdata/incidr.txtar | 8 +- 6 files changed, 134 insertions(+), 93 deletions(-) diff --git a/pkg/net/host.go b/pkg/net/host.go index aef0ad991..0d1634076 100644 --- a/pkg/net/host.go +++ b/pkg/net/host.go @@ -55,11 +55,12 @@ func JoinHostPort(host, port cue.Value) (string, error) { hostStr := "" switch host.Kind() { case cue.ListKind: - ipdata := netGetIP(host) - if !ipdata.IsValid() { + ipdata, ipErr := netGetIP(host) + if ipErr != nil { err = fmt.Errorf("invalid host %s", host) + } else { + hostStr = ipdata.String() } - hostStr = ipdata.String() case cue.BytesKind: var b []byte b, err = host.Bytes() diff --git a/pkg/net/ip.go b/pkg/net/ip.go index a4bf53f0f..20519eb81 100644 --- a/pkg/net/ip.go +++ b/pkg/net/ip.go @@ -30,55 +30,54 @@ const ( IPv6len = 16 ) -func netGetIP(ip cue.Value) (goip netip.Addr) { +func netGetIP(ip cue.Value) (netip.Addr, error) { switch ip.Kind() { case cue.StringKind: s, err := ip.String() if err != nil { - return netip.Addr{} + return netip.Addr{}, err } goip, err := netip.ParseAddr(s) if err != nil { - return netip.Addr{} + return netip.Addr{}, err } - return goip + return goip, nil case cue.BytesKind: b, err := ip.Bytes() if err != nil { - return netip.Addr{} + return netip.Addr{}, err } goip, err := netip.ParseAddr(string(b)) if err != nil { - return netip.Addr{} + return netip.Addr{}, err } - return goip + return goip, nil case cue.ListKind: iter, err := ip.List() if err != nil { - return netip.Addr{} + return netip.Addr{}, err } var bytes []byte for iter.Next() { v, err := iter.Value().Int64() if err != nil { - return netip.Addr{} + return netip.Addr{}, err } if v < 0 || 255 < v { - return netip.Addr{} + return netip.Addr{}, fmt.Errorf("invalid IP byte value %d", v) } bytes = append(bytes, byte(v)) } goip, ok := netip.AddrFromSlice(bytes) if !ok { - return netip.Addr{} + return netip.Addr{}, fmt.Errorf("invalid IP length %d", len(bytes)) } - return goip + return goip, nil default: - // TODO: return canonical invalid type. - return netip.Addr{} + return netip.Addr{}, fmt.Errorf("invalid type %v for IP address", ip.Kind()) } } @@ -107,8 +106,7 @@ func netGetIPCIDR(ip cue.Value) (gonet *netip.Prefix, err error) { return &cidr, nil default: - // TODO: return canonical invalid type. - return nil, nil + return nil, fmt.Errorf("invalid type %v for IP CIDR", ip.Kind()) } } @@ -136,24 +134,36 @@ func netToList(ip []byte) []uint { // IPv4 reports whether ip is a valid IPv4 address. // // The address may be a string or list of bytes. -func IPv4(ip cue.Value) bool { +func IPv4(ip cue.Value) (bool, error) { // TODO: convert to native CUE. - return netGetIP(ip).Is4() + goip, err := netGetIP(ip) + if err != nil { + return false, err + } + return goip.Is4(), nil } // IPv6 reports whether ip is a valid IPv6 address. // // The address may be a string or list of bytes. -func IPv6(ip cue.Value) bool { - return netGetIP(ip).Is6() +func IPv6(ip cue.Value) (bool, error) { + goip, err := netGetIP(ip) + if err != nil { + return false, err + } + return goip.Is6(), nil } // IP reports whether ip is a valid IPv4 or IPv6 address. // // The address may be a string or list of bytes. -func IP(ip cue.Value) bool { +func IP(ip cue.Value) (bool, error) { // TODO: convert to native CUE. - return netGetIP(ip).IsValid() + goip, err := netGetIP(ip) + if err != nil { + return false, err + } + return goip.IsValid(), nil } // IPCIDR reports whether ip is a valid IPv4 or IPv6 address with CIDR subnet notation. @@ -165,29 +175,49 @@ func IPCIDR(ip cue.Value) (bool, error) { } // LoopbackIP reports whether ip is a loopback address. -func LoopbackIP(ip cue.Value) bool { - return netGetIP(ip).IsLoopback() +func LoopbackIP(ip cue.Value) (bool, error) { + goip, err := netGetIP(ip) + if err != nil { + return false, err + } + return goip.IsLoopback(), nil } // MulticastIP reports whether ip is a multicast address. -func MulticastIP(ip cue.Value) bool { - return netGetIP(ip).IsMulticast() +func MulticastIP(ip cue.Value) (bool, error) { + goip, err := netGetIP(ip) + if err != nil { + return false, err + } + return goip.IsMulticast(), nil } // InterfaceLocalMulticastIP reports whether ip is an interface-local multicast // address. -func InterfaceLocalMulticastIP(ip cue.Value) bool { - return netGetIP(ip).IsInterfaceLocalMulticast() +func InterfaceLocalMulticastIP(ip cue.Value) (bool, error) { + goip, err := netGetIP(ip) + if err != nil { + return false, err + } + return goip.IsInterfaceLocalMulticast(), nil } // LinkLocalMulticastIP reports whether ip is a link-local multicast address. -func LinkLocalMulticastIP(ip cue.Value) bool { - return netGetIP(ip).IsLinkLocalMulticast() +func LinkLocalMulticastIP(ip cue.Value) (bool, error) { + goip, err := netGetIP(ip) + if err != nil { + return false, err + } + return goip.IsLinkLocalMulticast(), nil } // LinkLocalUnicastIP reports whether ip is a link-local unicast address. -func LinkLocalUnicastIP(ip cue.Value) bool { - return netGetIP(ip).IsLinkLocalUnicast() +func LinkLocalUnicastIP(ip cue.Value) (bool, error) { + goip, err := netGetIP(ip) + if err != nil { + return false, err + } + return goip.IsLinkLocalUnicast(), nil } // GlobalUnicastIP reports whether ip is a global unicast address. @@ -196,22 +226,30 @@ func LinkLocalUnicastIP(ip cue.Value) bool { // identification as defined in RFC 1122, RFC 4632 and RFC 4291 with the // exception of IPv4 directed broadcast addresses. It returns true even if ip is // in IPv4 private address space or local IPv6 unicast address space. -func GlobalUnicastIP(ip cue.Value) bool { - return netGetIP(ip).IsGlobalUnicast() +func GlobalUnicastIP(ip cue.Value) (bool, error) { + goip, err := netGetIP(ip) + if err != nil { + return false, err + } + return goip.IsGlobalUnicast(), nil } // UnspecifiedIP reports whether ip is an unspecified address, either the IPv4 // address "0.0.0.0" or the IPv6 address "::". -func UnspecifiedIP(ip cue.Value) bool { - return netGetIP(ip).IsUnspecified() +func UnspecifiedIP(ip cue.Value) (bool, error) { + goip, err := netGetIP(ip) + if err != nil { + return false, err + } + return goip.IsUnspecified(), nil } // ToIP4 converts a given IP address, which may be a string or a list, to its // 4-byte representation. func ToIP4(ip cue.Value) ([]uint, error) { - ipdata := netGetIP(ip) - if !ipdata.IsValid() { - return nil, fmt.Errorf("invalid IP %q", ip) + ipdata, err := netGetIP(ip) + if err != nil { + return nil, err } if !ipdata.Is4() { return nil, fmt.Errorf("cannot convert %q to IPv4", ipdata) @@ -223,9 +261,9 @@ func ToIP4(ip cue.Value) ([]uint, error) { // ToIP16 converts a given IP address, which may be a string or a list, to its // 16-byte representation. func ToIP16(ip cue.Value) ([]uint, error) { - ipdata := netGetIP(ip) - if !ipdata.IsValid() { - return nil, fmt.Errorf("invalid IP %q", ip) + ipdata, err := netGetIP(ip) + if err != nil { + return nil, err } as16 := ipdata.As16() return netToList(as16[:]), nil @@ -238,9 +276,9 @@ func ToIP16(ip cue.Value) ([]uint, error) { // - IPv6 ("2001:db8::1"), if ip is a valid IPv6 address // - the hexadecimal form of ip, without punctuation, if no other cases apply func IPString(ip cue.Value) (string, error) { - ipdata := netGetIP(ip) - if !ipdata.IsValid() { - return "", fmt.Errorf("invalid IP %q", ip) + ipdata, err := netGetIP(ip) + if err != nil { + return "", err } return ipdata.String(), nil } @@ -279,9 +317,9 @@ func AddIP(ip cue.Value, offset *big.Int) (string, error) { } return netip.PrefixFrom(addr, prefix.Bits()).String(), nil } - ipdata := netGetIP(ip) - if !ipdata.IsValid() { - return "", fmt.Errorf("invalid IP %q", ip) + ipdata, err := netGetIP(ip) + if err != nil { + return "", err } addr, err := netIPAdd(ipdata, offset) if err != nil { @@ -357,9 +395,9 @@ func ParseCIDR(s string) (*ParsedCIDR, error) { // InCIDR reports whether an IP address is contained a CIDR subnet string. func InCIDR(ip, cidr cue.Value) (bool, error) { - ipAddr := netGetIP(ip) - if !ipAddr.IsValid() { - return false, fmt.Errorf("invalid IP %q", ip) + ipAddr, err := netGetIP(ip) + if err != nil { + return false, err } prefix, err := netGetIPCIDR(cidr) @@ -376,13 +414,13 @@ func InCIDR(ip, cidr cue.Value) (bool, error) { // // The addresses may be strings or lists of bytes. func CompareIP(ip1, ip2 cue.Value) (int, error) { - addr1 := netGetIP(ip1) - if !addr1.IsValid() { - return 0, fmt.Errorf("invalid IP %q", ip1) + addr1, err := netGetIP(ip1) + if err != nil { + return 0, err } - addr2 := netGetIP(ip2) - if !addr2.IsValid() { - return 0, fmt.Errorf("invalid IP %q", ip2) + addr2, err := netGetIP(ip2) + if err != nil { + return 0, err } return addr1.Compare(addr2), nil } diff --git a/pkg/net/pkg.go b/pkg/net/pkg.go index 71138afc1..9b7bf0ca9 100644 --- a/pkg/net/pkg.go +++ b/pkg/net/pkg.go @@ -78,7 +78,7 @@ var p = &pkg.Package{ Func: func(c *pkg.CallCtxt) { ip := c.Value(0) if c.Do() { - c.Ret = IPv4(ip) + c.Ret, c.Err = IPv4(ip) } }, }, { @@ -90,7 +90,7 @@ var p = &pkg.Package{ Func: func(c *pkg.CallCtxt) { ip := c.Value(0) if c.Do() { - c.Ret = IPv6(ip) + c.Ret, c.Err = IPv6(ip) } }, }, { @@ -102,7 +102,7 @@ var p = &pkg.Package{ Func: func(c *pkg.CallCtxt) { ip := c.Value(0) if c.Do() { - c.Ret = IP(ip) + c.Ret, c.Err = IP(ip) } }, }, { @@ -126,7 +126,7 @@ var p = &pkg.Package{ Func: func(c *pkg.CallCtxt) { ip := c.Value(0) if c.Do() { - c.Ret = LoopbackIP(ip) + c.Ret, c.Err = LoopbackIP(ip) } }, }, { @@ -138,7 +138,7 @@ var p = &pkg.Package{ Func: func(c *pkg.CallCtxt) { ip := c.Value(0) if c.Do() { - c.Ret = MulticastIP(ip) + c.Ret, c.Err = MulticastIP(ip) } }, }, { @@ -150,7 +150,7 @@ var p = &pkg.Package{ Func: func(c *pkg.CallCtxt) { ip := c.Value(0) if c.Do() { - c.Ret = InterfaceLocalMulticastIP(ip) + c.Ret, c.Err = InterfaceLocalMulticastIP(ip) } }, }, { @@ -162,7 +162,7 @@ var p = &pkg.Package{ Func: func(c *pkg.CallCtxt) { ip := c.Value(0) if c.Do() { - c.Ret = LinkLocalMulticastIP(ip) + c.Ret, c.Err = LinkLocalMulticastIP(ip) } }, }, { @@ -174,7 +174,7 @@ var p = &pkg.Package{ Func: func(c *pkg.CallCtxt) { ip := c.Value(0) if c.Do() { - c.Ret = LinkLocalUnicastIP(ip) + c.Ret, c.Err = LinkLocalUnicastIP(ip) } }, }, { @@ -186,7 +186,7 @@ var p = &pkg.Package{ Func: func(c *pkg.CallCtxt) { ip := c.Value(0) if c.Do() { - c.Ret = GlobalUnicastIP(ip) + c.Ret, c.Err = GlobalUnicastIP(ip) } }, }, { @@ -198,7 +198,7 @@ var p = &pkg.Package{ Func: func(c *pkg.CallCtxt) { ip := c.Value(0) if c.Do() { - c.Ret = UnspecifiedIP(ip) + c.Ret, c.Err = UnspecifiedIP(ip) } }, }, { diff --git a/pkg/net/testdata/compareip.txtar b/pkg/net/testdata/compareip.txtar index 382ba8d49..8db741de4 100644 --- a/pkg/net/testdata/compareip.txtar +++ b/pkg/net/testdata/compareip.txtar @@ -36,11 +36,11 @@ t19: net.CompareIP("192.168.1.1", "invalid") t20: net.CompareIP("invalid1", "invalid2") -- out/net-v3 -- Errors: -t18: error in call to net.CompareIP: invalid IP "invalid": +t18: error in call to net.CompareIP: ParseAddr("invalid"): unable to parse IP: ./in.cue:33:6 -t19: error in call to net.CompareIP: invalid IP "invalid": +t19: error in call to net.CompareIP: ParseAddr("invalid"): unable to parse IP: ./in.cue:34:6 -t20: error in call to net.CompareIP: invalid IP "invalid1": +t20: error in call to net.CompareIP: ParseAddr("invalid1"): unable to parse IP: ./in.cue:35:6 Result: @@ -74,6 +74,6 @@ t16: 1 t17: 0 // Error cases -t18: _|_ // t18: error in call to net.CompareIP: invalid IP "invalid" -t19: _|_ // t19: error in call to net.CompareIP: invalid IP "invalid" -t20: _|_ // t20: error in call to net.CompareIP: invalid IP "invalid1" +t18: _|_ // t18: error in call to net.CompareIP: ParseAddr("invalid"): unable to parse IP +t19: _|_ // t19: error in call to net.CompareIP: ParseAddr("invalid"): unable to parse IP +t20: _|_ // t20: error in call to net.CompareIP: ParseAddr("invalid1"): unable to parse IP diff --git a/pkg/net/testdata/gen.txtar b/pkg/net/testdata/gen.txtar index 27db5b5a8..07855d87c 100644 --- a/pkg/net/testdata/gen.txtar +++ b/pkg/net/testdata/gen.txtar @@ -56,13 +56,15 @@ t46: net.AddIPCIDR("10.0.0.0/8", -11) t47: net.AddIPCIDR("255.0.0.0/8", 1) -- out/net-v3 -- Errors: -t20c: invalid value 123 (does not satisfy net.IP): +t20b: invalid value 123 (does not satisfy net.IPCIDR): error in call to net.IPCIDR: invalid type int for IP CIDR: + ./in.cue:23:7 +t20c: invalid value 123 (does not satisfy net.IP): error in call to net.IP: invalid type int for IP address: ./in.cue:24:7 -t20d: invalid value 123 (does not satisfy net.IPv4): +t20d: invalid value 123 (does not satisfy net.IPv4): error in call to net.IPv4: invalid type int for IP address: ./in.cue:25:7 -t20e: invalid value 123 (does not satisfy net.IPv6): +t20e: invalid value 123 (does not satisfy net.IPv6): error in call to net.IPv6: invalid type int for IP address: ./in.cue:26:7 -t25: invalid value "2001:db8::1234567" (does not satisfy net.IPv6): +t25: invalid value "2001:db8::1234567" (does not satisfy net.IPv6): error in call to net.IPv6: ParseAddr("2001:db8::1234567"): each group must have 4 or less digits (at "1234567"): ./in.cue:31:6 ./in.cue:31:17 t30: invalid value "%" (does not satisfy net.URL): error in call to net.URL: parse "%": invalid URL escape "%": @@ -74,7 +76,7 @@ t32: invalid value "/foo/bar" (does not satisfy net.AbsURL): error in call to ne t34: invalid value "%" (does not satisfy net.AbsURL): error in call to net.AbsURL: parse "%": invalid URL escape "%": ./in.cue:40:6 ./in.cue:40:19 -t9: invalid value "23.23.23.2333" (does not satisfy net.IPv4): +t9: invalid value "23.23.23.2333" (does not satisfy net.IPv4): error in call to net.IPv4: ParseAddr("23.23.23.2333"): IPv4 field has value >255: ./in.cue:11:6 ./in.cue:11:17 t7: error in call to net.JoinHostPort: invalid host [192, 30, 4]: @@ -87,7 +89,7 @@ t20: error in call to net.IPCIDR: netip.ParsePrefix("172.16.12.3"): no '/': t27: invalid value "23.23.23.23" (does not satisfy net.IPv6): ./in.cue:33:6 ./in.cue:33:19 -t39: error in call to net.AddIP: invalid IP "invalid ip": +t39: error in call to net.AddIP: ParseAddr("invalid ip"): unable to parse IP: ./in.cue:45:6 t40: error in call to net.AddIP: IP address arithmetic resulted in out-of-range address (underflow): ./in.cue:46:6 @@ -109,7 +111,7 @@ t5: "[2001:db8::1]:80" t6: "192.30.4.2:80" t7: _|_ // t7: error in call to net.JoinHostPort: invalid host [192, 30, 4] t8: true -t9: _|_ // t9: invalid value "23.23.23.2333" (does not satisfy net.IPv4) +t9: _|_ // t9: invalid value "23.23.23.2333" (does not satisfy net.IPv4): t9: error in call to net.IPv4: ParseAddr("23.23.23.2333"): IPv4 field has value >255 t10: true t11: true t12: false @@ -121,15 +123,15 @@ t17: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 127, 0, 0, 1] t18: true t19: true t20: _|_ // t20: error in call to net.IPCIDR: netip.ParsePrefix("172.16.12.3"): no '/' -t20b: 123 -t20c: _|_ // t20c: invalid value 123 (does not satisfy net.IP) -t20d: _|_ // t20d: invalid value 123 (does not satisfy net.IPv4) -t20e: _|_ // t20e: invalid value 123 (does not satisfy net.IPv6) +t20b: _|_ // t20b: invalid value 123 (does not satisfy net.IPCIDR): t20b: error in call to net.IPCIDR: invalid type int for IP CIDR +t20c: _|_ // t20c: invalid value 123 (does not satisfy net.IP): t20c: error in call to net.IP: invalid type int for IP address +t20d: _|_ // t20d: invalid value 123 (does not satisfy net.IPv4): t20d: error in call to net.IPv4: invalid type int for IP address +t20e: _|_ // t20e: invalid value 123 (does not satisfy net.IPv6): t20e: error in call to net.IPv6: invalid type int for IP address t21: "foo%2Fbar" t22: "foo/bar" t23: "f%25o" t24: "f%o" -t25: _|_ // t25: invalid value "2001:db8::1234567" (does not satisfy net.IPv6) +t25: _|_ // t25: invalid value "2001:db8::1234567" (does not satisfy net.IPv6): t25: error in call to net.IPv6: ParseAddr("2001:db8::1234567"): each group must have 4 or less digits (at "1234567") t26: true t27: _|_ // t27: invalid value "23.23.23.23" (does not satisfy net.IPv6) t28: true @@ -143,7 +145,7 @@ t35: "127.0.0.2" t36: "127.0.0.3/8" t37: "2001:db8::1" t38: "2001:db8::2/64" -t39: _|_ // t39: error in call to net.AddIP: invalid IP "invalid ip" +t39: _|_ // t39: error in call to net.AddIP: ParseAddr("invalid ip"): unable to parse IP t40: _|_ // t40: error in call to net.AddIP: IP address arithmetic resulted in out-of-range address (underflow) t41: _|_ // t41: error in call to net.AddIP: IP address arithmetic resulted in out-of-range address (overflow) t42: "::ffff:127.0.0.2" diff --git a/pkg/net/testdata/incidr.txtar b/pkg/net/testdata/incidr.txtar index 3e5367afe..c0baea554 100644 --- a/pkg/net/testdata/incidr.txtar +++ b/pkg/net/testdata/incidr.txtar @@ -38,13 +38,13 @@ v3: [192, 168, 1, 50] & net.InCIDR("192.168.1.0/24") v4: "2001:db8::1" & net.InCIDR("2001:db8::/32") -- out/net-v3 -- Errors: -t15: error in call to net.InCIDR: invalid IP "invalid": +t15: error in call to net.InCIDR: ParseAddr("invalid"): unable to parse IP: ./in.cue:28:6 t16: error in call to net.InCIDR: netip.ParsePrefix("invalid"): no '/': ./in.cue:29:6 t17: error in call to net.InCIDR: netip.ParsePrefix("192.168.1.0"): no '/': ./in.cue:30:6 -t18: error in call to net.InCIDR: invalid IP "192.168.1.0/24": +t18: error in call to net.InCIDR: ParseAddr("192.168.1.0/24"): unexpected character (at "/24"): ./in.cue:31:6 v2: invalid value "10.0.0.1" (does not satisfy net.InCIDR("192.168.1.0/24")): ./in.cue:35:18 @@ -77,10 +77,10 @@ t13: true t14: true // Error cases -t15: _|_ // t15: error in call to net.InCIDR: invalid IP "invalid" +t15: _|_ // t15: error in call to net.InCIDR: ParseAddr("invalid"): unable to parse IP t16: _|_ // t16: error in call to net.InCIDR: netip.ParsePrefix("invalid"): no '/' t17: _|_ // t17: error in call to net.InCIDR: netip.ParsePrefix("192.168.1.0"): no '/' -t18: _|_ // t18: error in call to net.InCIDR: invalid IP "192.168.1.0/24" +t18: _|_ // t18: error in call to net.InCIDR: ParseAddr("192.168.1.0/24"): unexpected character (at "/24") // Validator usage - IP is validated against a CIDR constraint v1: "192.168.1.50" -- 2.51.2