OS-8152: lx should return EACCES when SOCK_DGRAM is used with IPPROTO_ICMP

Details

Issue Type:Bug
Priority:4 - Normal
Status:Resolved
Created at:2020-04-01T17:53:48.143Z
Updated at:2020-04-07T18:06:05.241Z

People

Created by:Michael Zeller
Reported by:Michael Zeller
Assigned to:Michael Zeller

Resolution

Fixed: A fix for this issue is checked into the tree and tested.
(Resolution Date: 2020-04-07T18:06:05.233Z)

Fix Versions

2020-04-09 Prognosis Negative (Release Date: 2020-04-09)

Related Links

Description

On distributions using a newer ipnetutils-ping package, the ping command fails:

root@ubuntu:~# ping 1.1.1.1
ping: 1.1.1.1: Address family for hostname not supported
root@ubuntu:~# strace -e socket ping 1.1.1.1
socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP) = -1 EPROTONOSUPPORT (Protocol not supported)
socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6) = -1 EPROTONOSUPPORT (Protocol not supported)
socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6) = 3
ping: 1.1.1.1: Address family for hostname not supported
+++ exited with 2 +++
 

Linux supports using SOCK_DGRAM + IPPROTO_ICMP for a setuid-less ping capability. See this linux commit: https://github.com/torvalds/linux/commit/c319b4d76b9e583a5d88d6bf190e079c4e43213d

The ipnetutils-ping command contains a nice block comment that explains what errno values it's looking for:

	/* Attempt to create a ping socket if requested. Attempt to create a raw
	 * socket otherwise or as a fallback. Well known errno values follow.
	 *
	 * 1) EACCES
	 *
	 * Kernel returns EACCES for all ping socket creation attempts when the
	 * user isn't allowed to use ping socket. A range of group ids is
	 * configured using the `net.ipv4.ping_group_range` sysctl. Fallback
	 * to raw socket is necessary.
	 *
	 * Kernel returns EACCES for all raw socket creation attempts when the
	 * process doesn't have the `CAP_NET_RAW` capability.
	 *
	 * 2) EAFNOSUPPORT
	 *
	 * Kernel returns EAFNOSUPPORT for IPv6 ping or raw socket creation
	 * attempts when run with IPv6 support disabled (e.g. via `ipv6.disable=1`
	 * kernel command-line option.
	 *
	 * https://github.com/iputils/iputils/issues/32
	 *
	 * OpenVZ 2.6.32-042stab113.11 and possibly other older kernels return
	 * EAFNOSUPPORT for all IPv4 ping socket creation attempts due to lack
	 * of support in the kernel. Fallback to raw socket is necessary.
	 *
	 * https://github.com/iputils/iputils/issues/54
	 *
	 * 3) EPROTONOSUPPORT
	 *
	 * OpenVZ 2.6.32-042stab113.11 and possibly other older kernels return
	 * EPROTONOSUPPORT for all IPv6 ping socket creation attempts due to lack
	 * of support in the kernel [1]. Debian 9.5 based container with kernel 4.10
	 * returns EPROTONOSUPPORT also for IPv4 [2]. Fallback to raw socket is
	 * necessary.
	 *
	 * [1] https://github.com/iputils/iputils/issues/54
	 * [2] https://github.com/iputils/iputils/issues/129
	 */

It seems even newer versions of ipnetutils-net (I tested on ubuntu 18.04) may handle EPROTONOSUPPORT more gracefully.

I think it's worth while we return EACCES when we see these socket options combined which would signify "the user isn't allowed to use ping socket.". This seems like a safe enough lie until we ever implement a setuid-less ping capability.

Comments

Comment by Michael Zeller
Created at 2020-04-01T17:56:58.399Z
Updated at 2020-04-01T17:57:23.278Z

With the patch in place I seeĀ  see the following:

root@ubuntu:~# strace -e socket ping -c 1 1.1.1.1
socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP) = -1 EACCES (Permission denied)
socket(AF_INET, SOCK_RAW, IPPROTO_ICMP) = 3
socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6) = -1 EACCES (Permission denied)
socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6) = 4
socket(AF_INET, SOCK_DGRAM, IPPROTO_IP) = 5
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=58 time=16.6 ms

--- 1.1.1.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 16.671/16.671/16.671/0.000 ms
+++ exited with 0 +++

Comment by Michael Zeller
Created at 2020-04-06T20:39:27.321Z

@accountid:70121:6490ccfd-5932-4e7a-936d-554bdd3dc0d3 provided the following test case:

#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <err.h>

static void
usage(char *progname, const char *toktype, char *tok)
{
        (void) fprintf(stderr, "Bad %s: %s\n", toktype, tok);
        (void) fprintf(stderr, "Usage:\n\t%s <family> <type> <protocol>\n",
            progname);
        exit(2);
}

struct table {
        char *name;     /* case-insensitive */
        int value;
};

struct table famtab[] =
{
        { "inet", AF_INET },
        { "ipv4", AF_INET },
        { "v4", AF_INET },
        { "inet6", AF_INET6 },
        { "ipv6", AF_INET6 },
        { "v6", AF_INET6 },
        { NULL, -1}
};

struct table typetab[] =
{
        { "dgram", SOCK_DGRAM },
        { "datagram", SOCK_DGRAM },
        { "stream", SOCK_STREAM },
        { "raw", SOCK_RAW },
        { NULL, -1}
};

/*
 * Should probably use getprotobyname() but most /etc/protocols don't have
 * aliases.  Also probably not interested in ALL of the protocols...
 */
struct table prototab[] =
{
        { "icmp", IPPROTO_ICMP },
        { "icmp4", IPPROTO_ICMP },
        { "icmpv4", IPPROTO_ICMP },
        { "icmp6", IPPROTO_ICMPV6 },
        { "icmpv6", IPPROTO_ICMPV6 },
        { "tcp", IPPROTO_TCP },
        { "udp", IPPROTO_UDP },
        { NULL, -1 }
};

/*
 * Using int 0 and int 1 for boolean to be more portable because boolean_t
 * doesn't exist everywhere.
 */
int
table_lookup(struct table *table, char *attempt, int *answer)
{
        while (table->value != -1) {
                if (strcasecmp(table->name, attempt) == 0) {
                        *answer = table->value;
                        return (1);
                }
                table++;
        }

        return (0);
}

char *
table_name(struct table *table, int value)
{
        while (table->value != -1) {
                if (value == table->value)
                        return (table->name);
                table++;
        }

        return ("UNKNOWN");
}

#define getfam(a, b) table_lookup(famtab, (a), (b))
#define gettype(a, b) table_lookup(typetab, (a), (b))
#define getproto(a, b) table_lookup(prototab, (a), (b))

#define namefam(a) table_name(famtab, (a))
#define nametype(a) table_name(typetab, (a))
#define nameproto(a) table_name(prototab, (a))

int
main(int argc, char *argv[])
{
        int s;
        int fam = AF_INET6, type = SOCK_DGRAM, proto = IPPROTO_ICMP;

        if (argc > 1 && !getfam(argv[1], &fam))
                usage(argv[0], "family", argv[1]);
        if (argc > 2 && !gettype(argv[2], &type))
                usage(argv[0], "type", argv[2]);
        if (argc > 3 && !getproto(argv[3], &proto))
                usage(argv[0], "protocol", argv[3]);

        (void) printf("Using %s, %s, %s\n", namefam(fam), nametype(type),
            nameproto(proto));

        s = socket(fam, type, proto);
        if (s == -1)
                err(-1, "socket");
        else
                (void) fprintf(stderr, "WTF, it opened?\n");

        return (1);
}

Running the following to compare output

#!/usr/bin/env bash

./a.out inet dgram icmp
./a.out inet dgram icmp6
./a.out inet6 dgram icmp6
./a.out inet6 raw icmp6
./a.out inet raw icmp

lx prior to the patch:

link@plex:~$ ./run.sh
Using inet, dgram, icmp
a.out: socket: Protocol not supported
Using inet, dgram, icmp6
a.out: socket: Protocol not supported
Using inet6, dgram, icmp6
a.out: socket: Protocol not supported
Using inet6, raw, icmp6
a.out: socket: Permission denied
Using inet, raw, icmp
a.out: socket: Permission denied
link@plex:~$ sudo ./run.sh
Using inet, dgram, icmp
a.out: socket: Protocol not supported
Using inet, dgram, icmp6
a.out: socket: Protocol not supported
Using inet6, dgram, icmp6
a.out: socket: Protocol not supported
Using inet6, raw, icmp6
WTF, it opened?
Using inet, raw, icmp
WTF, it opened?

Bhyve with ubuntu 18.04

link@wireguard:~$ ./run.sh
Using inet, dgram, icmp
a.out: socket: Permission denied
Using inet, dgram, icmp6
a.out: socket: Protocol not supported
Using inet6, dgram, icmp6
a.out: socket: Permission denied
Using inet6, raw, icmp6
a.out: socket: Operation not permitted
Using inet, raw, icmp
a.out: socket: Operation not permitted
link@wireguard:~$ sudo ./run.sh
Using inet, dgram, icmp
a.out: socket: Permission denied
Using inet, dgram, icmp6
a.out: socket: Protocol not supported
Using inet6, dgram, icmp6
a.out: socket: Permission denied
Using inet6, raw, icmp6
WTF, it opened?
Using inet, raw, icmp
WTF, it opened?

lx with the applied patch

link@ubuntu:~$ ./run.sh
Using inet, dgram, icmp
a.out: socket: Permission denied
Using inet, dgram, icmp6
a.out: socket: Protocol not supported
Using inet6, dgram, icmp6
a.out: socket: Permission denied
Using inet6, raw, icmp6
a.out: socket: Permission denied
Using inet, raw, icmp
a.out: socket: Permission denied
link@ubuntu:~$ exit
$ ^D
root@ubuntu:~# ./run.sh
Using inet, dgram, icmp
a.out: socket: Permission denied
Using inet, dgram, icmp6
a.out: socket: Protocol not supported
Using inet6, dgram, icmp6
a.out: socket: Permission denied
Using inet6, raw, icmp6
WTF, it opened?
Using inet, raw, icmp
WTF, it opened?

Comment by Michael Zeller
Created at 2020-04-06T20:51:10.169Z

It looks like in the non root case we still differ in opening SOCK_RAW.  lx returns EACCES and linux returns EPERM.  However, that's outside the scope of this ticket.


Comment by Jira Bot
Created at 2020-04-07T18:02:28.187Z

illumos-joyent commit cb1f21263e5283c3502e718042f50eeef6ced5bb (branch master, by Michael Zeller)

OS-8152 lx should return EACCES when SOCK_DGRAM is used with IPPROTO_ICMP

Reviewed by: Dan McDonald <danmcd@kebe.com>
Reviewed by: Mike Gerdts <mike.gerdts@joyent.com>
Approved by: Dan McDonald <danmcd@kebe.com>