Bug #16691
openTitle: sanitize_ipaddr() incorrectly expands bare IPv4 addresses ending in .0 to /24 subnets
0%
Description
When pfBlockerNG processes an upstream IP feed containing a bare IPv4 address whose fourth octet is `0` (e.g., `N.N.N.0` with no CIDR notation), the `sanitize_ipaddr()` function automatically appends a `/24` mask. This converts a single host entry into a 256-address subnet block.
Upstream feed publishers such as CINS Army (`ci-badguys.txt`) publish lists of individual host IPv4 addresses. These lists do not document any convention where a trailing `.0` should be interpreted as a `/24` range, and an address ending in `.0` is a valid host address — particularly within larger allocations (e.g., a cloud provider's `/20` range, where `N.N.N.0` is simply a host like any other).
The result is that pfBlockerNG silently blocks an entire /24 subnet when the upstream feed intended to flag only a single host. This has caused a legitimate cloud-hosted service to become unreachable from a pfSense-protected network because a single flagged IP on the same cloud provider subnet triggered a /24 block covering the service's IP as well.
- Steps to Reproduce
1. Subscribe to an IP feed that contains a bare IPv4 address ending in `.0` with no CIDR mask (e.g., CINS Army `ci-badguys.txt`).
2. Allow pfBlockerNG to process and apply the feed.
3. Observe that the address is expanded to a `/24` block in `/var/db/pfblockerng/deny/<feed>_v4.txt`.
4. Any legitimate host within that /24 range is now blocked, despite only one address appearing in the upstream list.
- Root Cause
In `pfblockerng.inc`, function `sanitize_ipaddr()` (lines ~3605–3662):
- Line ~3621: Checks whether the fourth octet is `0` AND no CIDR mask is present.
- Line ~3622: Automatically sets `$mask = 24`.
- Line ~3659: Returns the address as `N.N.N.0/24`.
```php
if ($key 3) {
// If mask is not defined and 4th octet is '0', set mask to '24'
if ($octet 0 && empty($mask)) {
$mask = 24;
}
}
```
Reference: [pfblockerng.inc line 3622 on GitHub](https://github.com/pfsense/FreeBSD-ports/blob/f4e18ffa0e4d72d9346cbb86f85c2c8cb631c444/net/pfSense-pkg-pfBlockerNG-devel/files/usr/local/pkg/pfblockerng/pfblockerng.inc#L3622)
- Expected Behavior
A bare IPv4 address with no CIDR notation should be treated as a single host (`/32`), regardless of its fourth octet value. pfBlockerNG should not reinterpret upstream feed entries beyond the specification provided by the feed publisher. If no mask is present, the default should be `/32`.
- Illustrative Fix
In `sanitize_ipaddr()`, when no CIDR mask is provided, default to `/32` for all bare IP addresses rather than inferring `/24` from a fourth octet of `0`:
```php
if ($key == 3) {
if (empty($mask)) {
$mask = 32;
}
}
```
- Additional Context
- The CINS Army feed is defined in `pfblockerng_feeds.json` (lines 71–75, header `CINS_army`), materialized as the `CINS_army_v4` file prefix. ([GitHub link](https://github.com/pfsense/FreeBSD-ports/blob/f4e18ffa0e4d72d9346cbb86f85c2c8cb631c444/net/pfSense-pkg-pfBlockerNG-devel/files/usr/local/www/pfblockerng/pfblockerng_feeds.json#L71))
- This behavior applies to any feed (processed by the referenced pfBlockerNG code path) containing bare `.0` addresses, not just CINS Army.
- Cloud providers commonly allocate IPs from large CIDR blocks (e.g., /20 or larger), making `.0` addresses routine host assignments rather than subnet boundaries.
- Environment
- pfSense CE (current release)
- pfBlockerNG-devel (current version)
No data to display