7

If you've ever tried writing tools that depend upon ICMP echo requests, you've inevitably run into the same problem as everyone else: you can't do it unless your tool is running as root.

When you try to find out why this is the case, the answer is always the same: "sending icmp traffic requires the privileges to open a raw socket, so you must have root privileges to send ICMP traffic."

While this is technically true, and at least advises the engineer writing the tool of their options, it fails to answer the underlying question of why ICMP traffic is somehow so special that there is no facility whatsoever for sending and receiving it as an unprivileged user.

UDP and TCP are both considerably more complex than an ICMP echo request, but UDP and TCP have both been exposed to the user without giving them the full power of reading and writing to the network with reckless abandon. What is it that actually prevents a library from providing unprivileged ability to send and receive pings with no more control over the contents than icmp sequence, packet contents (or even just the size) id, ttl, and MAYBE tos?

I'm not looking for an engineering workaround here, because one does not exist for the general purpose. I've seen advice to just make a system call to /usr/bin/ping but that's not really suitable either.

The question comes down to this: is there some potential situation where having a general-purpose ICMP echo library would lead to users either conflicting with each other or somehow provide a level of power to the user that is dangerous? The latter scenario seems ludicrous to me just based on the fact that an echo request is actually less configurable than a UDP datagram. I'd use UDP if it would serve the functions I need, but it absolutely does not.

12
  • 1
    Look up "ping of death". Search for "exfiltration of data via ping". Internet Control Message Protocol (ICMP) packets travel below IP/TCP and IP/UDP, and are used for setting up networks, doing "router advertisements", etc. It would be unsafe to make this functionality available to the general user. Commented Mar 2 at 22:25
  • 2
    "sending icmp traffic requires the privileges to open a raw socket, so you must have root privileges to send ICMP traffic" - this hasn't been true for a number of years Commented Mar 2 at 23:21
  • @ChrisDavies: despite what the links in that article say, trying to open an ICMP socket with SOCK_DGRAM still generates a permissions error on modern linux distros. For example, I get blocked on Ubuntu 24.04.2 w/ kernel 6.8.0-52-generic. I also need my tools to be runnable on Windows and OS/X. There may be ways to configure around it in linux, but requiring that would make a tool unportable and only accessible to a fraction of users - those with linux access and with the ability to make administrative changes to that machine. If they had that, they could just setuid the tool and be done. Commented Mar 2 at 23:36
  • 3
    @waltinator, I don't think ICMP is the issue in the "ping of death" -case. I mean, at least the one case named like that was about IP fragments crafted to create an over-long IP packet, and that could be done with any IP packet. An OS interface for sending ICMP pings would presumably create correctly-built packets, same as the TCP and UDP functions do. Commented Mar 3 at 6:54
  • 1
    As for exfiltration, if you mean sending data out covertly (and not pulling data out from a remote system via some vulnerability), well, pretty much anything could be used for that. E.g. DNS queries. Or ICMP error packets, if you can build them, which you probably couldn't with a purpose-built OS interface for sending pings. And (a few implementations of) the command line ping tool already supports including arbitrary data with the -p option. Commented Mar 3 at 6:54

2 Answers 2

8

Looking at possible ICMP control messages, there is no legitimate reason to send any them (except ping) from an unprivileged user process at all. ICMP packets generally are used by the network layer for low level control of the network for both internal and external purposes, and allowing a user process to interfere with this would be a severe security risk to other processes on the same machine using the network and possibly for the network itself.

Additionally, as pointed out in comments, even the most harmless icmp packet, the ping ICMP packet, can cause havoc if it is maliciously or recklessly constructed and transmitted. (Although modern IP stacks that are more security conscious should drop such packets.)

Presuming that we did want to make it possible for an unprivileged process to send a ping, a kernel API would have to be created that would properly filter and restrict what options would go in that packet that would be unlikely to cause issues, and even then it would have to be rate limited to prevent harm. This would be a kernel API with very narrow use with a very complicated implementation, and with lots of potential bugs and unforeseen abuses.

We already have something that implements this -- the ping program. Since this works as is, there's not a lot of reason to reimplement it or import the ping code into the kernel.

Additionally, user processes actually can receive information from ICMP packets -- just not directly. For instance, when a TCP socket connection is attempted and it fails, the errno return from connect(2) may include information derived from an ICMP packet.

There are already a lot of security issues with ICMP, and many kernel algorithms to try to mitigate these. Allowing user processes to also emit ICMP packets would make this worse. Even ping is a problem, and many routers and firewalls (and even some host operating systems) are configured to block or ignore ping, as even as limited in functionality and restricted by the ping command as it is, it can still easily cause denial of service attacks.

Addendum:

The above explains why it is dangerous. However...

It appears that in linux kernel version 2.6.39, sysctl parameter net.ipv4.ping_group_range was added which opens up ping to a range of group ids, which initially defaulted to "1-0" meaning no group. Many bugs in this were discovered and fixed and then somewhere around kernel version 3.13, this range was opened up, and current kernels seem to default to ICMP echo (ping) being unrestricted. However, there are extensive parameters to rate limit ICMP packets in general, both incoming and outgoing. Also according to the ICMP(7) man page, it is possible for a user process to listen to all incoming ICMP packets.

Note that the ping executable in Ubuntu 22 dropped the cap_net_admin privilege and RedHat 8 also dropped cap_net_raw so ping has no special capabilities. (And even in Ubuntu 20 which hasn't dropped them, ping works without capabilities.) So perhaps your initial assumptions here are invalid and you should be looking for an "engineering solution" rather than an "engineering workaround". I would suggest by starting with the source code to ping, traceroute, tcptraceroute, and possibly others.

Such a solution should work on all modern linux kernels and you are unlikely to get more "general purpose" than that for now. (If someone knows the status of this in other operating systems, they can answer that.)

Note that while researching this, I found that net.ipv4.ping_group_range was added to support ping in rootless containers, but I was unable to find discussion of security implications. However, in discussions of christmas tree packets and ping of death packets, it was noted that vulnerabilities to these is considered a security risk (i.e., a bug), and modern operating systems should be hardened against them. User processes emitting icmp packets is still a security risk for DoS attacks and data exfiltration, but not significantly more so than UDP packets. Blocking ping is a common practice, but arguably not a best practice.

9
  • > there's not a lot of reason to reimplement it or import the ping code into the kernel. - For questions of "why does this feature/API not exist", always consider the -100 point hole that potential features start in. Every kernel API is a development and maintenance burden that's nigh-impossible to later remove, so very often the answer to "why not" is just "it isn't worth it", especially if a (mostly) suitable alternative exists. It's telling that once the alternative proved unsuitable for containers, new functionality was added. Commented Mar 3 at 7:15
  • True enough! Answers correct for years suddenly aren't. Commented Mar 3 at 12:08
  • 2
    "... possible for an unprivileged process to send a ping, a kernel API [...] would have to be rate limited to prevent harm." - Why? Neither TCP connections nor UDP packets are rate-limited for unprivileged processes. Commented Mar 3 at 12:15
  • The ping command does rate limit ping. Kernel 6.8 has 142 tcpv4 tuning parameters, many of which are related to rate limiting or completely dropping packets in some condition, and 9 are related to icmp. Commented Mar 3 at 12:18
  • 1
    Normal processes have no business sending most ICMP messages, but how is that a point against a dedicated API for sending pings? With the implementation using raw sockets, the tool itself has the capability to send arbitrary messages, while a dedicated API would likely be implemented on the OS level so that it would only allow sending echo requests, not anything else. Commented Mar 3 at 17:46
1

In the ol’ youthful days of Unix graybeards, ARP was used by DEC/Intel/Xerox/IBM for Ethernet, 802.3, Frame Relay, Token Ring, FDDI, X.25, NetROM. Life was good.

Then came along DARPA IP protocol version 1, 2, and 3. Nah. Version 4 works better.

While ARP works great at troubleshooting ISO Data Layer 2, something was needed for ISO Network Layer 3: ICMP was born in IPv3 (then later came IETF RFC777).

Despite IPv1 emerging first, ARP became more popular. ARP had an Ethertype of 0x0806 for CDMA/CD, Token-Ring, NetBIOS, DECNET, NetROM despite IPv4 having started firstly with 0x0800.

Then came IETF RFC826 ARP.

It is interesting to note that arp.c resides haphazardly within Linux kernel net/ipv4 repo directory despite serving so many other non-IP protocols.

The fact that Ethernet type were different, is the primary reason why abstraction of TCP/UDP and ICMP required different UNIX network function calls.

While ICMP and ARP are equally filterable by nftables, that is the Internet scope of the Linux kernel.

Whereas in the pathways of ARP and ICMP are different within the Unix/Linux kernel, ARP packet process entirely within the kernel, so does some of ICMP packets; receiving ICMP-REQUEST and spitting out ICMP-RESPONSE all done within kernel context.

Receiving an ICMP-REQUEST leverages ip_rt_put() and SKB buffers for their autonomous reply, which is in-kernel. Whereas ARP drops its packet directly (without ip_rt_put()) into the netdev xmit queue (netfilter still can arbitrate ARP below that).

When users become involved, both protocol’s REQUEST/RESPONSE reversed; arp-scan (ARP) and ping (ICMP) can be used also with a different set of kernel APIs when ARP/ICMP traversing from userland to kernel network thru these tools, of course, all required root privilege.

That said, UDP/TCP/DDCP also all have their set of own APIs, one for userland applications to use and one for system daemons to use.

ICMP didn’t get migrated with UDP/TCP/DDCP/SCTP protocol over Unix evolution because there was “no” data payload in which to send over ICMP with. This is by original and current design. Sure, we can abuse it to delivery “data”, also within root mode.

So why can’t user do the same thing?

ICMP-REQUEST also requires broadcast mode over multiplex-type physical interfaces (Ethernet, 802.3 DIX, Token Ring, X.25, Frame Relay). This is the secret sauce in getting all remote hosts to look, decide if they’re “it”, and selectively-respond accordingly thus making them look "alive".

However, broadcast mode is a common but easily abused into flooding the neighbor physical links as the early ARPA design demonstrated in 1978. Christmas Tree and Ping-of-Death are also results of broadcast-type abuse but they were not the first to abuse, just happens to be the largest and biggest denial of service kind. While PPTP is a rare exception by the virtue of its leveraging a simplex model over a multiplex-type link, a bastardization, if you will, but not enough to grant your precious user-privilege toward those same API that are providing dangerous multiplex-link-type transmit): PPtP also must run in root.

Is abuse of broadcast-mode the only reason to shield ICMP from direct non-privileged user access to thereof? No. End-users are typically not allowed to learn of its neighbors (hence, frequent disabling (chmod go-rwx /usr/bin/ping, blocking certain ARP-related /proc, and not installing arp-scan tools).

So, even if ICMP is apart from UDP, TCP, DDCP, SCTP, it must be further restricted to stay apart to ensure that:

  • bridges are protected from flooding packet storms,
  • route tables are shielded from corruptions, and
  • nosy (but literal residential) neighbors are kept apart.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.