Because of varying definitions of what an IP range is, it is possible to create user accounts with names that are considered to be valid, yet are recognized by some of the blocking code as IP ranges. Then, either the "ranges" are rejected for being too large (/0 block), or the "ends" of the ranges are simply ignored, making it impossible to block the accounts via the web interface. (Blocking via the API seems to be possible if the deprecated userid parameter is used, though admins should not be expected to know how to do this.) An example of such a username is "1.2.3.4-5.6.7.8".
Steps to reproduce:
- While logged out, go to Special:CreateAccount.
- Enter the username "1.2.3.4-5.6.7.8" (no quotes) and a valid password.
- Click "Create your account".
- Log in to an administrator account.
- Go to Special:Log/newusers.
- Click the block link for the user "1.2.3.4-5.6.7.8".
- From the Expiration drop-down list, select "indefinite".
- Click "Block this user".
- Log in to the "1.2.3.4-5.6.7.8" user account.
- Make an edit to any unprotected page.
- Log in to an administrator account.
- Go to Special:Block.
- For "Username, IP address, or IP range:", enter "1.2.3.4-5.6.7.8".
- From the Expiration drop-down list, select "indefinite".
- Click "Block this user".
Explanation:
UserNameUtils::isValid attempts to check whether the username is actually an IP address or an IP range:
- It checks for usernames that are valid IPv4 or IPv6 addresses, using the function IPUtils::isValid. The documentation for that function states, "Ranges are NOT considered valid."
- It checks for usernames that look like IPv4 addresses, using the regex ^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$.
- It checks for usernames that contain "/", which may be IP ranges in CIDR notation.
An "explicit" IPv4 range in the form "<start IP>-<end IP>" does not fall within any of these cases. In contrast, BlockUtils::parseBlockTarget, after checking whether the target is a valid IPv4 or IPv6 address using IPUtils::isValid, then checks whether the target is a valid IPv4 or IPv6 range using IPUtils::isValidRange. The documentation for that function states than an IP range is valid if "given with valid CIDR prefix or in explicit notation".
IPUtils::sanitizeRange is then called. That function first calls parseCIDR, which will return [ false, false ] in this case. Then, it calls parseRange, which will return the start and end of the range in hex format. Because parseCIDR returned false as the number of bits, sanitizeRange returns only the start of the range, because the range "wasn't actually a range".
BlockUtils::validateTarget, after calling BlockUtils::parseBlockTarget, checks which type of target was returned. For a range, it splits on the "/" in a range in CIDR notation. It tries to set $range to the part after the slash, generating a PHP notice or warning. It then casts the resulting null to the integer 0 before calling validateIPv4Range. That function, of course, rejects a /0 block as too large.