-
Notifications
You must be signed in to change notification settings - Fork 25.8k
Description
Elasticsearch Version
Latest
Installed Plugins
No response
Java Version
bundled
OS Version
latest
Problem Description
The IP_PREFIX function has a bug in makePrefix() where the byte at index fullBytes is not cleared when remainingBits == 0 (i.e., when the prefix length is a multiple of 8). Due to scratch buffer reuse across rows, this causes data from previously processed rows to leak into subsequent results.
private static void makePrefix(BytesRef ip, BytesRef scratch, int fullBytes, int remainingBits) {
// Copy the first full bytes
System.arraycopy(ip.bytes, ip.offset, scratch.bytes, 0, fullBytes);
// Copy the last byte ignoring the trailing bits
if (remainingBits > 0) {
byte lastByteMask = (byte) (0xFF << (8 - remainingBits));
scratch.bytes[fullBytes] = (byte) (ip.bytes[ip.offset + fullBytes] & lastByteMask);
}
// Copy the last empty bytes - BUG: starts at fullBytes + 1, not fullBytes
if (fullBytes < 16) {
Arrays.fill(scratch.bytes, fullBytes + 1, 16, (byte) 0);
}
}
When remainingBits == 0:
- array copy copies bytes 0 to fullBytes - 1
- The if (remainingBits > 0) block is skipped, so byte fullBytes is NOT set
- Arrays.fill starts at fullBytes + 1, leaving byte fullBytes untouched
- Byte fullBytes retains its value from the previous row's computation
Some rows with v6_prefix=72 and ip=::1 will show incorrect prefix values like ::ff:0:0:0 instead of ::, because byte 9 retains 0xFF from a previously processed row with v6_prefix=80.
Expected Behavior
All rows with ip=::1 and v6_prefix=72 should return prefix :: (first 72 bits, which are all zeros).
Fix:
Change line 181 from:
Arrays.fill(scratch.bytes, fullBytes + 1, 16, (byte) 0);
to:
Arrays.fill(scratch.bytes, fullBytes, 16, (byte) 0);
Or add explicit zeroing when remainingBits == 0:
if (remainingBits > 0) {
byte lastByteMask = (byte) (0xFF << (8 - remainingBits));
scratch.bytes[fullBytes] = (byte) (ip.bytes[ip.offset + fullBytes] & lastByteMask);
} else if (fullBytes < 16) {
scratch.bytes[fullBytes] = 0;
}
Steps to Reproduce
PUT ip-prefix-test/_doc/1
{"ip": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "v6_prefix": 80}
PUT ip-prefix-test/_doc/2
{"ip": "::1", "v6_prefix": 72}
PUT ip-prefix-test/_doc/3
{"ip": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "v6_prefix": 80}
PUT ip-prefix-test/_doc/4
{"ip": "::1", "v6_prefix": 72}
FROM ip-prefix-test
| EVAL prefix = IP_PREFIX(ip::ip, 32, v6_prefix::int)
| KEEP ip, v6_prefix, prefix
Results:
Delete the doc 1 and doc 3 and run the query again:
Logs (if relevant)
No response