Skip to content

Commit 5725a54

Browse files
MegaManSecsquid-anubis
authored andcommitted
ICMP: Harden echo paths, fix overflows, UB, and leaks (#2199)
ICMPv4 send now validates getAddrInfo results before touching ai_addr, avoiding null-deref, and frees the address on error. Logging no longer passes nullptr strings, preventing UB. ICMPv4 recv gained strict bounds checks: reject packets shorter than IP/ICMP headers, reject bogus iphdrlen, and require enough bytes for echo meta. Payload length is computed from real data, clamped to MAX_PAYLOAD, and malformed negatives are dropped. The result size calculation now uses PINGER_PAYLOAD_SZ instead of the wrong MAX_PKT4_SZ, fixing excess memory disclosure. ICMPv6 send added the same getAddrInfo validation and safe log string handling. The recv path now checks for minimal header lengths, validates echo meta, fixes ident comparison with ntohs, and clamps payload length safely before copying into preply. The pinger handshake no longer uses strlen() on raw buffers; it now echoes exactly the bytes received, avoiding OOB reads. Squid Recv was changed to treat pingerReplyData as variable- length. It validates base header size, available payload, and non-negative psize, and rejects truncated or oversized datagrams, fixing past OOB read risks. Finally, netdbBinaryExchange now flushes its 4K buffer before writing the next record, fixing a possible overflow at the end of the buffer.
1 parent 85b9d6d commit 5725a54

File tree

6 files changed

+133
-30
lines changed

6 files changed

+133
-30
lines changed

src/icmp/Icmp4.cc

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#if USE_ICMP
1616

17+
#include "base/Assure.h"
1718
#include "compat/socket.h"
1819
#include "debug/Stream.h"
1920
#include "Icmp4.h"
@@ -118,7 +119,7 @@ Icmp4::SendEcho(Ip::Address &to, int opcode, const char *payload, int len)
118119
echo->opcode = (unsigned char) opcode;
119120
memcpy(&echo->tv, &current_time, sizeof(struct timeval));
120121

121-
icmp_pktsize += sizeof(struct timeval) + sizeof(char);
122+
icmp_pktsize += sizeof(icmpEchoData) - MAX_PAYLOAD;
122123

123124
if (payload) {
124125
if (len > MAX_PAYLOAD)
@@ -131,7 +132,9 @@ Icmp4::SendEcho(Ip::Address &to, int opcode, const char *payload, int len)
131132

132133
icmp->icmp_cksum = CheckSum((unsigned short *) icmp, icmp_pktsize);
133134

134-
to.getAddrInfo(S);
135+
to.getAddrInfo(S, AF_INET);
136+
Assure(S);
137+
135138
((sockaddr_in*)S->ai_addr)->sin_port = 0;
136139
assert(icmp_pktsize <= MAX_PKT4_SZ);
137140

@@ -146,10 +149,10 @@ Icmp4::SendEcho(Ip::Address &to, int opcode, const char *payload, int len)
146149

147150
if (x < 0) {
148151
int xerrno = errno;
149-
debugs(42, DBG_IMPORTANT, MYNAME << "ERROR: sending to ICMP packet to " << to << ": " << xstrerr(xerrno));
152+
debugs(42, DBG_IMPORTANT, "ERROR: sending ICMP packet to " << to << ": " << xstrerr(xerrno));
150153
}
151154

152-
Log(to, ' ', nullptr, 0, 0);
155+
Log(to, ' ', "", 0, 0);
153156
Ip::Address::FreeAddr(S);
154157
}
155158

@@ -203,6 +206,11 @@ Icmp4::Recv(void)
203206
debugs(42, 8, n << " bytes from " << preply.from);
204207

205208
ip = (struct iphdr *) (void *) pkt;
209+
if (n < static_cast<int>(sizeof(*ip))) {
210+
debugs(42, 2, "short packet: only " << n << " bytes; expecting at least " << sizeof(*ip) << "-byte IP header");
211+
Ip::Address::FreeAddr(from);
212+
return;
213+
}
206214

207215
#if HAVE_STRUCT_IPHDR_IP_HL
208216

@@ -220,7 +228,18 @@ Icmp4::Recv(void)
220228
#endif
221229
#endif /* HAVE_STRUCT_IPHDR_IP_HL */
222230

231+
if (iphdrlen < 20 || n < iphdrlen) {
232+
debugs(42, 2, "bogus IP header length " << iphdrlen << " in " << n << "-byte packet");
233+
Ip::Address::FreeAddr(from);
234+
return;
235+
}
223236
icmp = (struct icmphdr *) (void *) (pkt + iphdrlen);
237+
const int icmpAvail = n - iphdrlen;
238+
if (icmpAvail < static_cast<int>(sizeof(*icmp))) {
239+
debugs(42, 2, "short ICMP header: only " << icmpAvail << " bytes available; expecting at least " << sizeof(*icmp));
240+
Ip::Address::FreeAddr(from);
241+
return;
242+
}
224243

225244
if (icmp->icmp_type != ICMP_ECHOREPLY) {
226245
Ip::Address::FreeAddr(from);
@@ -234,6 +253,14 @@ Icmp4::Recv(void)
234253

235254
echo = (icmpEchoData *) (void *) (icmp + 1);
236255

256+
const auto echoHdr = static_cast<int>(sizeof(icmpEchoData) - MAX_PAYLOAD);
257+
const auto icmpDataLen = icmpAvail - sizeof(*icmp);
258+
if (icmpDataLen < echoHdr) { // do not read past end of the packet
259+
debugs(42, 2, "short ICMP echo data: " << icmpDataLen << " bytes; expecting " << echoHdr);
260+
Ip::Address::FreeAddr(from);
261+
return;
262+
}
263+
237264
preply.opcode = echo->opcode;
238265

239266
preply.hops = ipHops(ip->ip_ttl);
@@ -242,15 +269,18 @@ Icmp4::Recv(void)
242269
memcpy(&tv, &echo->tv, sizeof(struct timeval));
243270
preply.rtt = tvSubMsec(tv, now);
244271

245-
preply.psize = n - iphdrlen - (sizeof(icmpEchoData) - MAX_PKT4_SZ);
272+
// Payload length = (ICMP total data) - (opcode + timeval)
273+
preply.psize = icmpDataLen - echoHdr;
274+
if (preply.psize > MAX_PAYLOAD)
275+
preply.psize = MAX_PAYLOAD;
246276

247277
if (preply.psize < 0) {
248278
debugs(42, DBG_CRITICAL, "ERROR: Malformed ICMP packet.");
249279
Ip::Address::FreeAddr(from);
250280
return;
251281
}
252282

253-
control.SendResult(preply, (sizeof(pingerReplyData) - MAX_PKT4_SZ + preply.psize) );
283+
control.SendResult(preply, (sizeof(pingerReplyData) - PINGER_PAYLOAD_SZ + preply.psize));
254284

255285
Log(preply.from, icmp->icmp_type, IcmpPacketType(icmp->icmp_type), preply.rtt, preply.hops);
256286
Ip::Address::FreeAddr(from);

src/icmp/Icmp6.cc

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#if USE_ICMP
1616

17+
#include "base/Assure.h"
1718
#include "compat/socket.h"
1819
#include "debug/Stream.h"
1920
#include "Icmp6.h"
@@ -166,7 +167,9 @@ Icmp6::SendEcho(Ip::Address &to, int opcode, const char *payload, int len)
166167

167168
icmp->icmp6_cksum = CheckSum((unsigned short *) icmp, icmp6_pktsize);
168169

169-
to.getAddrInfo(S);
170+
to.getAddrInfo(S, AF_INET6);
171+
Assure(S);
172+
170173
((sockaddr_in6*)S->ai_addr)->sin6_port = 0;
171174

172175
assert(icmp6_pktsize <= MAX_PKT6_SZ);
@@ -182,11 +185,11 @@ Icmp6::SendEcho(Ip::Address &to, int opcode, const char *payload, int len)
182185

183186
if (x < 0) {
184187
int xerrno = errno;
185-
debugs(42, DBG_IMPORTANT, MYNAME << "ERROR: sending to ICMPv6 packet to " << to << ": " << xstrerr(xerrno));
188+
debugs(42, DBG_IMPORTANT, "ERROR: sending ICMPv6 packet to " << to << ": " << xstrerr(xerrno));
186189
}
187190
debugs(42,9, "x=" << x);
188191

189-
Log(to, 0, nullptr, 0, 0);
192+
Log(to, 0, "", 0, 0);
190193
Ip::Address::FreeAddr(S);
191194
}
192195

@@ -228,6 +231,10 @@ Icmp6::Recv(void)
228231
Ip::Address::FreeAddr(from);
229232
return;
230233
}
234+
if (n < static_cast<int>(sizeof(struct icmp6_hdr))) {
235+
Ip::Address::FreeAddr(from);
236+
return;
237+
}
231238

232239
preply.from = *from;
233240

@@ -289,13 +296,18 @@ Icmp6::Recv(void)
289296
return;
290297
}
291298

292-
if (icmp6header->icmp6_id != icmp_ident) {
299+
if (ntohs(icmp6header->icmp6_id) != static_cast<uint16_t>(icmp_ident)) {
293300
debugs(42, 8, "dropping Icmp6 read. IDENT check failed. ident=='" << icmp_ident << "'=='" << icmp6header->icmp6_id << "'");
294301
Ip::Address::FreeAddr(from);
295302
return;
296303
}
297304

298-
echo = (icmpEchoData *) (pkt + sizeof(icmp6_hdr));
305+
const auto meta = static_cast<int>(sizeof(struct icmp6_hdr) + sizeof(struct timeval) + sizeof(unsigned char));
306+
if (n < meta) {
307+
Ip::Address::FreeAddr(from);
308+
return;
309+
}
310+
echo = (icmpEchoData *)(pkt + sizeof(struct icmp6_hdr));
299311

300312
preply.opcode = echo->opcode;
301313

@@ -311,13 +323,14 @@ Icmp6::Recv(void)
311323
*/
312324
preply.hops = 1;
313325

314-
preply.psize = n - /* sizeof(ip6_hdr) - */ sizeof(icmp6_hdr) - (sizeof(icmpEchoData) - MAX_PKT6_SZ);
326+
auto payload_len = n - meta;
327+
assert(payload_len >= 0);
328+
if (payload_len > MAX_PAYLOAD)
329+
payload_len = MAX_PAYLOAD;
315330

316-
/* Ensure the response packet has safe payload size */
317-
if ( preply.psize > (unsigned short) MAX_PKT6_SZ) {
318-
preply.psize = MAX_PKT6_SZ;
319-
} else if ( preply.psize < (unsigned short)0) {
320-
preply.psize = 0;
331+
preply.psize = payload_len;
332+
if (preply.psize > 0) {
333+
memcpy(preply.payload, echo->payload, preply.psize);
321334
}
322335

323336
Log(preply.from,

src/icmp/IcmpPinger.cc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ IcmpPinger::Open(void)
122122
return -1;
123123
}
124124

125-
x = xsend(icmp_sock, buf, strlen(buf), 0);
125+
x = xsend(icmp_sock, buf, x, 0);
126126
xerrno = errno;
127127

128128
if (x < 3 || strncmp("OK\n", buf, 3)) {
@@ -152,10 +152,11 @@ void
152152
IcmpPinger::Close(void)
153153
{
154154
#if _SQUID_WINDOWS_
155-
156-
shutdown(icmp_sock, SD_BOTH);
157-
xclose(icmp_sock);
158-
icmp_sock = -1;
155+
if (icmp_sock >= 0) {
156+
shutdown(icmp_sock, SD_BOTH);
157+
xclose(icmp_sock);
158+
icmp_sock = -1;
159+
}
159160
#endif
160161

161162
/* also shutdown the helper engines */

src/icmp/IcmpSquid.cc

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
/* DEBUG: section 37 ICMP Routines */
1010

1111
#include "squid.h"
12+
#include "base/Assure.h"
1213
#include "comm.h"
1314
#include "comm/Loops.h"
1415
#include "compat/socket.h"
@@ -71,9 +72,10 @@ IcmpSquid::SendEcho(Ip::Address &to, int opcode, const char *payload, int len)
7172
else if (payload && len == 0)
7273
len = strlen(payload);
7374

74-
// XXX: If length specified or auto-detected is greater than the possible payload squid will die with an assert.
75-
// TODO: This should perhapse be reduced to a truncated payload? or no payload. A WARNING is due anyway.
76-
assert(len <= PINGER_PAYLOAD_SZ);
75+
// All our callers supply a DNS name. PINGER_PAYLOAD_SZ must accommodate the
76+
// longest DNS name Squid supports. TODO: Simplify and improve the rest of
77+
// this code accordingly.
78+
Assure(len <= PINGER_PAYLOAD_SZ);
7779

7880
pecho.to = to;
7981

@@ -151,6 +153,32 @@ IcmpSquid::Recv()
151153
return;
152154
}
153155

156+
const auto base = static_cast<int>(sizeof(preply) - sizeof(preply.payload));
157+
if (n < base) {
158+
debugs(37, 2, "short reply header (" << n << " < " << base << "); dropping");
159+
return;
160+
}
161+
const auto avail = n - base;
162+
if (avail > static_cast<int>(sizeof(preply.payload))) {
163+
debugs(37, 2, "oversized reply payload (" << avail << "); dropping");
164+
return;
165+
}
166+
if (preply.psize < 0) {
167+
debugs(37, 2, "negative psize (" << preply.psize << "); dropping");
168+
return;
169+
}
170+
if (preply.psize > avail) {
171+
debugs(37, 2, "truncated reply (psize=" << preply.psize << ", avail=" << avail << "); dropping");
172+
return;
173+
}
174+
// Accept variable-length replies: base header + psize bytes.
175+
// We already validated 'n >= base' and 'preply.psize <= avail'.
176+
// If the datagram was truncated in transit, drop it.
177+
if (n < (base + preply.psize)) {
178+
debugs(37, 2, "truncated reply datagram; dropping");
179+
return;
180+
}
181+
154182
F = preply.from;
155183

156184
F.port(0);

src/icmp/net_db.cc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,11 @@ netdbBinaryExchange(StoreEntry * s)
11271127
if ( !addr.isIPv4() )
11281128
continue;
11291129

1130+
if (i + rec_sz > 4096) {
1131+
s->append(buf, i);
1132+
i = 0;
1133+
}
1134+
11301135
buf[i] = (char) NETDB_EX_NETWORK;
11311136
++i;
11321137

@@ -1152,11 +1157,6 @@ netdbBinaryExchange(StoreEntry * s)
11521157
memcpy(&buf[i], &j, sizeof(int));
11531158

11541159
i += sizeof(int);
1155-
1156-
if (i + rec_sz > 4096) {
1157-
s->append(buf, i);
1158-
i = 0;
1159-
}
11601160
}
11611161

11621162
if (i > 0) {

src/icmp/pinger.cc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#if USE_ICMP
4646

4747
#include "base/Stopwatch.h"
48+
#include "base/TextException.h"
4849
#include "compat/select.h"
4950
#include "compat/socket.h"
5051
#include "Icmp4.h"
@@ -95,13 +96,43 @@ Icmp6 icmp6;
9596

9697
int icmp_pkts_sent = 0;
9798

99+
/// reports std::terminate() cause (e.g., an uncaught or prohibited exception)
100+
static std::ostream &
101+
TerminationReason(std::ostream &os)
102+
{
103+
if (std::current_exception())
104+
os << CurrentException;
105+
else
106+
os << "An undetermined failure";
107+
return os;
108+
}
109+
110+
static void
111+
OnTerminate()
112+
{
113+
// ignore recursive calls to avoid termination loops
114+
static bool terminating = false;
115+
if (terminating)
116+
return;
117+
terminating = true;
118+
119+
debugs(1, DBG_CRITICAL, "FATAL: " << TerminationReason);
120+
121+
control.Close(); // TODO: Here and elsewhere, rely on IcmpPinger class destructor instead.
122+
Debug::PrepareToDie();
123+
abort();
124+
}
125+
98126
/**
99127
\ingroup pinger
100128
\par This is the pinger external process.
101129
*/
102130
int
103131
main(int, char **)
104132
{
133+
// TODO: Apply this try/catch-less approach to address SquidMainSafe() XXX.
134+
(void)std::set_terminate(&OnTerminate);
135+
105136
fd_set R;
106137
int max_fd = 0;
107138

0 commit comments

Comments
 (0)