Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ Thank you!
Karl Benoit <karl.isatwork@gmail.com>
Khalid Abdullah <khalidcomilla58@gmail.com>
Kieran Whitbread <k.j.whitbread@qmul.ac.uk>
Kirill Furman <kir.furman@gmail.com>
Klaubert Herr <klaubert@gmail.com>
Klaus Singvogel <kssingvo@suse.de>
Kolics Bertold <bertold@tohotom.vein.hu>
Expand Down
42 changes: 24 additions & 18 deletions src/acl/Ip.cc
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,18 @@ acl_ip_data::containsVetted(const Ip::Address &needle) const
}

/* Handle either type of address, IPv6 will be discarded with a warning if disabled */
#define SCAN_ACL1_6 "%[0123456789ABCDEFabcdef:]-%[0123456789ABCDEFabcdef:]/%[0123456789]"
#define SCAN_ACL2_6 "%[0123456789ABCDEFabcdef:]-%[0123456789ABCDEFabcdef:]%c"
#define SCAN_ACL3_6 "%[0123456789ABCDEFabcdef:]/%[0123456789]"
#define SCAN_ACL4_6 "%[0123456789ABCDEFabcdef:]/%c"
#define SCAN_ACL1_6 "%39[0123456789ABCDEFabcdef:]-%39[0123456789ABCDEFabcdef:]/%3[0123456789]%c"
#define SCAN_ACL2_6 "%39[0123456789ABCDEFabcdef:]-%39[0123456789ABCDEFabcdef:]%c"
#define SCAN_ACL3_6 "%39[0123456789ABCDEFabcdef:]/%3[0123456789]%c"
#define SCAN_ACL4_6 "%39[0123456789ABCDEFabcdef:]/%c"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SCAN_ACL4_6 pattern and, hence, the code that uses SCAN_ACL4_6 seem to miss an overflow check. However, the root of the problem lies much deeper and creates more/other bugs. It took me a while to sort through this mess. @k-furman, this is not your fault -- your PR simply exposed more old bugs than we thought it did. I detailed my findings further below.

At this point, we need to make a decision:

  1. Do our best to ignore the newly discovered bugs in the official parsing code. Reduce this PR scope to preventing known buffer overflows. Remove "reject trailing garbage" bonus changes because they cannot be done right until other parsing bugs are fixed. AFAICT, this implies undoing %c additions and related sscanf() calls/conditions changes in this PR.

  2. Fix all known parsing bugs, including, but not limited to, buffer overflows and failures to reject trailing garbage.

@k-furman, which option do you prefer? I will update this PR review and/or code based on your preference.


After examining the history of this code, I now know that trailing %c in the official IPv4 patterns were added to correctly handle cases like 123.example.com and 123-456.example.com (see year-2000 commits 20a4484 and 588b63e). Those cases are quite similar to the "trailing garbage" handling that this PR is trying to fix, but that trailing input is a part of a valid domain name in those cases... AFAICT, when adding IPv6 support, 2007 commit cc192b5 then misinterpreted and broke that tricky code by replacing the corresponding correct (but strange-looking) IPv4 conditions/pattern and propagating those buggy replacements to IPv6 code:

-#define SCAN_ACL4   "%[0123456789.]%c"
+#define SCAN_ACL4_4 "%[0123456789.]/%c" // wrong pattern change
...
-    } else if (sscanf(t, SCAN_ACL2, addr1, addr2, &c) == 2) {
+    } else if (sscanf(t, SCAN_ACL2_4, addr1, addr2, &c) >= 2) { // wrong condition change
...
-    } else if (sscanf(t, SCAN_ACL4, addr1, &c) == 1) {
+    } else if (sscanf(t, SCAN_ACL4_4, addr1,&c) == 2) { // wrong condition change
+#define SCAN_ACL4_6       "%[0123456789ABCDEFabcdef:]/%c" // broken IPv4 pattern copied to IPv6 code
...
+    } else if (sscanf(t, SCAN_ACL2_6, addr1, addr2, &c) >= 2) { // broken IPv4 condition copied to IPv6 code
...
+    } else if (sscanf(t, SCAN_ACL4_6, addr1, mask) == 2) { // broken IPv4 condition copied to IPv6 code and the wrong parameter used for storing %c

Since that 2007 commit cc192b5, Squid misinterprets various ACL configurations, rejecting valid input like dead-beef.example.test. We are also silently accepting buggy input like 192.0.2.1/XXX4, which is arguably even worse:

2026/08/24 14:03:21| ERROR: aclIpParseIpData: unknown first address in 'dead-beef.example.test'
2026/08/24 11:10:53.840| 28,9| Ip.cc(272) FactoryParse: aclIpParseIpData: '192.0.2.1/XXX4' matched: SCAN4-v4: %[0123456789.]/%c
2026/08/24 11:10:53.840| 28,9| Ip.cc(416) FactoryParse: Parsed: 192.0.2.1-[::]/[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff](/128)

/* We DO need to know which is which though, for proper CIDR masking. */
#define SCAN_ACL1_4 "%[0123456789.]-%[0123456789.]/%[0123456789.]"
#define SCAN_ACL2_4 "%[0123456789.]-%[0123456789.]%c"
#define SCAN_ACL3_4 "%[0123456789.]/%[0123456789.]"
#define SCAN_ACL4_4 "%[0123456789.]/%c"
#define SCAN_ACL1_4 "%15[0123456789.]-%15[0123456789.]/%15[0123456789.]%c"
#define SCAN_ACL2_4 "%15[0123456789.]-%15[0123456789.]%c"
#define SCAN_ACL3_4 "%15[0123456789.]/%15[0123456789.]%c"
#define SCAN_ACL4_4 "%15[0123456789.]/%c"
/* Handle non-ip/incorrect patterns */
#define SCAN_ACLX_1 "%255[^/]/%255s%c"
#define SCAN_ACLX_2 "%255s%c"

acl_ip_data *
acl_ip_data::FactoryParse(const char *t)
Expand All @@ -257,32 +260,32 @@ acl_ip_data::FactoryParse(const char *t)
debugs(28, 5, "aclIpParseIpData: " << t);

// IPv4
if (sscanf(t, SCAN_ACL1_4, addr1, addr2, mask) == 3) {
if (sscanf(t, SCAN_ACL1_4, addr1, addr2, mask, &c) == 3) {
debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN1-v4: " << SCAN_ACL1_4);
iptype=AF_INET;
} else if (sscanf(t, SCAN_ACL2_4, addr1, addr2, &c) >= 2) {
} else if (sscanf(t, SCAN_ACL2_4, addr1, addr2, &c) == 2) {
debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN2-v4: " << SCAN_ACL2_4);
mask[0] = '\0';
iptype=AF_INET;
} else if (sscanf(t, SCAN_ACL3_4, addr1, mask) == 2) {
} else if (sscanf(t, SCAN_ACL3_4, addr1, mask, &c) == 2) {
debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN3-v4: " << SCAN_ACL3_4);
addr2[0] = '\0';
iptype=AF_INET;
} else if (sscanf(t, SCAN_ACL4_4, addr1,&c) == 2) {
} else if (sscanf(t, SCAN_ACL4_4, addr1, &c) == 1) {
debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN4-v4: " << SCAN_ACL4_4);
addr2[0] = '\0';
mask[0] = '\0';
iptype=AF_INET;

// IPv6
} else if (sscanf(t, SCAN_ACL1_6, addr1, addr2, mask) == 3) {
} else if (sscanf(t, SCAN_ACL1_6, addr1, addr2, mask, &c) == 3) {
debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN1-v6: " << SCAN_ACL1_6);
iptype=AF_INET6;
} else if (sscanf(t, SCAN_ACL2_6, addr1, addr2, &c) >= 2) {
} else if (sscanf(t, SCAN_ACL2_6, addr1, addr2, &c) == 2) {
debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN2-v6: " << SCAN_ACL2_6);
mask[0] = '\0';
iptype=AF_INET6;
} else if (sscanf(t, SCAN_ACL3_6, addr1, mask) == 2) {
} else if (sscanf(t, SCAN_ACL3_6, addr1, mask, &c) == 2) {
debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: SCAN3-v6: " << SCAN_ACL3_6);
addr2[0] = '\0';
iptype=AF_INET6;
Expand All @@ -292,10 +295,10 @@ acl_ip_data::FactoryParse(const char *t)
iptype=AF_INET6;

// Neither
} else if (sscanf(t, "%[^/]/%s", addr1, mask) == 2) {
debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: non-IP pattern: %[^/]/%s");
} else if (sscanf(t, SCAN_ACLX_1, addr1, mask, &c) == 2) {
debugs(28, 9, "aclIpParseIpData: '" << t << "' matched: non-IP pattern: " << SCAN_ACLX_1);
addr2[0] = '\0';
} else if (sscanf(t, "%s", addr1) == 1) {
} else if (sscanf(t, SCAN_ACLX_2, addr1, &c) == 1) {
/*
* Note, must use plain getaddrinfo() here because at startup
* ipcache hasn't been initialized
Expand Down Expand Up @@ -357,6 +360,9 @@ acl_ip_data::FactoryParse(const char *t)
}

return q;
} else {
delete q;
throw TextException(ToSBuf("Excessively long ACL parameter value: ", t), Here());
}

/* ignore IPv6 addresses when built with IPv4-only */
Expand Down