Reject non-contiguous netmasks in is_netmask (#87235)

is_netmask only validated each octet against VALID_MASKS independently and
never checked that the octets form a contiguous run of network bits. As a
result masks like 255.255.0.255, 254.255.255.0 or 0.255.0.0 were reported as
valid, and downstream helpers such as to_masklen/to_subnet then produced
nonsensical results.

Verify that the assembled 32-bit mask is a contiguous block of 1s followed by
0s.

Signed-off-by: semx <7532921+semx@users.noreply.github.com>
Co-authored-by: Abhijeet Kasurde <akasurde@redhat.com>
This commit is contained in:
Sergey Sannikov
2026-07-15 07:56:03 -07:00
committed by GitHub
co-authored by Abhijeet Kasurde
parent db1e22f48e
commit 2da050b287
3 changed files with 25 additions and 3 deletions
@@ -0,0 +1,5 @@
bugfixes:
- module_utils - ``is_netmask`` now rejects non-contiguous netmasks such as
``255.255.0.255``, where each octet is individually valid but the mask is not
a contiguous run of network bits followed by host bits
(https://github.com/ansible/ansible/pull/87235).
+7 -3
View File
@@ -21,13 +21,17 @@ def is_netmask(val):
parts = str(val).split('.')
if not len(parts) == 4:
return False
mask = 0
for part in parts:
try:
if int(part) not in VALID_MASKS:
raise ValueError
octet = int(part)
except ValueError:
return False
return True
if octet not in VALID_MASKS:
return False
mask = (mask << 8) | octet
inverted = mask ^ 0xFFFFFFFF
return inverted & (inverted + 1) == 0
def is_masklen(val):
@@ -57,8 +57,21 @@ def test_is_masklen():
def test_is_netmask():
assert is_netmask('255.255.255.255')
assert is_netmask('0.0.0.0')
assert is_netmask('255.255.255.0')
assert is_netmask('255.255.254.0')
assert not is_netmask(24)
assert not is_netmask('foo')
# individually valid octets that are not a contiguous mask
assert not is_netmask('255.255.0.255')
assert not is_netmask('254.255.255.0')
assert not is_netmask('0.255.0.0')
assert not is_netmask('0.0.0.255')
# inet_aton-style legacy forms must stay rejected (they break to_masklen)
assert not is_netmask('0xffffff00')
assert not is_netmask('0377.0377.0377.0')
assert not is_netmask('4294967040')
assert not is_netmask('255.255.0')
def test_to_ipv6_network():