Hi,

I have modified ping(8) to grab a raw descriptor from a daemon over AF_UNIX
sockets.  This seems to work.  While what I call the sun daemon needs to be
tightened a lot more it should work to make people understand my concept.

benefits:
we lose inet pledge
we lose the setuid to root bit
root can bypass this entirely so it works in single user mode
it can be broadened to other programs such as traceroute

drawbacks:
not fully tested
sund needs more tightening or there is a security problem
if sund dies ping doesn't work for regular users

Here is a demonstration:
pjp@polarstern$ ls -l /tmp/ping
-rwxr-xr-x  2 root  wheel  1442864 Aug 24 11:38 /tmp/ping
pjp@polarstern$ /tmp/ping -D -c 1 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=255 time=0.073 ms

--- 127.0.0.1 ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 0.073/0.073/0.073/0.000 ms
pjp@polarstern$ /tmp/ping6 -D -c 1 centroid.eu
PING centroid.eu (2a03:6000:6f68:631::170): 56 data bytes
64 bytes from 2a03:6000:6f68:631::170: icmp_seq=0 hlim=54 time=31.059 ms

--- centroid.eu ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 31.059/31.059/31.059/0.000 ms

Here is the sund.c code (needs improving):


#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/syslimits.h>
#include <sys/uio.h>
#include <sys/un.h>

#include <netinet/in.h>
#include <netdb.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


#define EFFECTIVE_PINGUSER      1000
#define EFFECTIVE_PINGGROUP     1000

#define SUND_PATH       "/var/run/sund.sock"

void desc_write(int, int);

int
main(void)
{
        int so, new, sel;
        int raw, error;

        struct addrinfo hints, *res;
        struct sockaddr_un sun;
        fd_set rset;

        uid_t euid;
        gid_t egid;

        char buf[INET6_ADDRSTRLEN + 1];
        socklen_t sunsz = sizeof(struct sockaddr_un);
        size_t l;

        unveil(SUND_PATH, "rwc");
        unveil(NULL, NULL);

        unlink(SUND_PATH);


        so = socket(AF_UNIX, SOCK_STREAM, 0);
        if (so < 0) {
                perror("socket");
                exit(1);
        }

        memset(&sun, 0, sizeof(sun));
        sun.sun_family = AF_UNIX;       
        sun.sun_len = sizeof(struct sockaddr_un);
        
        strlcpy(sun.sun_path, "/var/run/sund.sock", sizeof(sun.sun_path));

        if (bind(so, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
                perror("bind");
                exit(1);
        }
        chmod(SUND_PATH, 0666);

        listen(so, 5);
        daemon(0, 0);

        if (pledge("stdio inet sendfd", NULL) == -1) {
                perror("pledge");
                exit(1);
        }

        for (;;) {
                FD_ZERO(&rset);
                FD_SET(so, &rset);
        
                sel = select(so + 1, &rset, NULL, NULL, NULL);
                switch (sel) {
                case -1:
                        perror("select");
                        continue;
                default:
                        break;
                }

                if (FD_ISSET(so, &rset)) {
                        new = accept(so, (struct sockaddr *)&sun, &sunsz);
                        if (new == -1) {
                                perror("accept");
                                continue;
                        }
        
                        if (getpeereid(new, &euid, &egid) == -1) {
                                perror("getpeereid");
                                close(new);
                                continue;
                        }


                        if ((euid != EFFECTIVE_PINGUSER) &&
                                (egid != EFFECTIVE_PINGGROUP)) {
                                close(new);
                                continue;
                        }

                        if ((l = recv(new, buf, sizeof(buf), 0)) == -1) {
                                close(new);
                                continue;
                        }
                        
                        buf[l] = '\0';

                        memset(&hints, 0, sizeof(hints));
                        if (strchr(buf, '.') != NULL) {
                                hints.ai_family = AF_INET;
                        } else {
                                hints.ai_family = AF_INET6;
                        }

                        hints.ai_flags = AI_NUMERICHOST;

                        if ((error = getaddrinfo(buf,"53",&hints,&res)) != 0) {
                                fprintf(stderr, "getaddrinfo: %s\n",
                                        gai_strerror(error));
                                close(new);
                                continue;
                        }
                                        
                        if ((raw = socket(res->ai_family, SOCK_RAW, 
                                res->ai_family == AF_INET ? IPPROTO_ICMP :
                                IPPROTO_ICMPV6)) == -1) {
                                perror("socket");
                                close(new);
                                continue;
                        }

                        freeaddrinfo(res);


                        /* send_descriptor */
                        desc_write(new, raw);
                        close(new);
                }
        }
}
                
                        
/*
 * based on msgbuf_write() libutil/imsg
 */
                        
void
desc_write(int new, int raw)
{
        struct iovec     iov[IOV_MAX];
        ssize_t          n;
        struct msghdr    msg;
        struct cmsghdr  *cmsg;
        union {
                struct cmsghdr  hdr;
                char            buf[CMSG_SPACE(sizeof(int))];
        } cmsgbuf;

        memset(&iov, 0, sizeof(iov));
        memset(&msg, 0, sizeof(msg));
        memset(&cmsgbuf, 0, sizeof(cmsgbuf));

        msg.msg_iov = iov;
        msg.msg_iovlen = 0;
        
        msg.msg_control = (caddr_t)&cmsgbuf.buf;
        msg.msg_controllen = sizeof(cmsgbuf.buf);

        cmsg = CMSG_FIRSTHDR(&msg);
        cmsg->cmsg_len = CMSG_LEN(sizeof(int));
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;

        *(int *)CMSG_DATA(cmsg) = raw;


        if ((n = sendmsg(new, &msg, 0)) == -1) {
                perror("sending descriptor");
                close(raw);
                return;
        }

        close(raw);     /* assumption */
}

And here is the ping(8) patch:


Index: ping.c
===================================================================
RCS file: /cvs/src/sbin/ping/ping.c,v
retrieving revision 1.248
diff -u -p -u -r1.248 ping.c
--- ping.c      1 Dec 2022 07:34:06 -0000       1.248
+++ ping.c      24 Aug 2023 09:39:37 -0000
@@ -81,6 +81,7 @@
 #include <sys/socket.h>
 #include <sys/time.h>
 #include <sys/uio.h>
+#include <sys/un.h>
 
 #include <netinet/in.h>
 #include <netinet/ip.h>
@@ -213,6 +214,7 @@ void                         summary(void);
 void                    onsignal(int);
 void                    retransmit(int);
 int                     pinger(int);
+int                     get_icmp_descriptor(char *);
 const char             *pr_addr(struct sockaddr *, socklen_t);
 void                    pr_pack(u_char *, int, struct msghdr *);
 __dead void             usage(void);
@@ -251,7 +253,7 @@ main(int argc, char *argv[])
        struct passwd *pw;
        socklen_t maxsizelen;
        int64_t preload;
-       int ch, i, optval = 1, packlen, maxsize, error, s, flooddone = 0;
+       int ch, i, optval = 1, packlen, maxsize, error, s = -1, flooddone = 0;
        int df = 0, tos = 0, bufspace = IP_MAXPACKET, hoplimit = -1, mflag = 0;
        u_char *datap, *packet;
        u_char ttl = MAXTTL;
@@ -267,21 +269,25 @@ main(int argc, char *argv[])
        /* Cannot pledge due to special setsockopt()s below */
        if (unveil("/", "r") == -1)
                err(1, "unveil /");
+       if (unveil("/var/run/sund.sock", "rw") == -1)
+               err(1, "unveil /var/run/sund.sock");
        if (unveil(NULL, NULL) == -1)
                err(1, "unveil");
 
+       ouid = getuid();
        if (strcmp("ping6", __progname) == 0) {
                v6flag = 1;
                maxpayload = MAXPAYLOAD6;
-               if ((s = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) == -1)
+               if (ouid == 0 && 
+                       (s = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) == -1)
                        err(1, "socket");
        } else {
-               if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
+               if (ouid == 0 &&
+                       (s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
                        err(1, "socket");
        }
 
        /* revoke privs */
-       ouid = getuid();
        if (ouid == 0 && (pw = getpwnam(PING_USER)) != NULL) {
                uid = pw->pw_uid;
                gid = pw->pw_gid;
@@ -289,10 +295,12 @@ main(int argc, char *argv[])
                uid = getuid();
                gid = getgid();
        }
-       if (ouid && (setgroups(1, &gid) ||
+#if NOTNEEDEDANYMORE
+       if (ouid == 0 && ((setgroups(1, &gid) ||
            setresgid(gid, gid, gid) ||
-           setresuid(uid, uid, uid)))
+           setresuid(uid, uid, uid))))
                err(1, "unable to revoke privs");
+#endif
 
        preload = 0;
        datap = &outpack[ECHOLEN + ECHOTMLEN];
@@ -419,9 +427,6 @@ main(int argc, char *argv[])
                        if (errstr)
                                errx(1, "rtable value is %s: %s", errstr,
                                    optarg);
-                       if (setsockopt(s, SOL_SOCKET, SO_RTABLE, &rtableid,
-                           sizeof(rtableid)) == -1)
-                               err(1, "setsockopt SO_RTABLE");
                        break;
                case 'v':
                        options |= F_VERBOSE;
@@ -437,9 +442,9 @@ main(int argc, char *argv[])
                }
        }
 
-       if (ouid == 0 && (setgroups(1, &gid) ||
+       if (ouid == 0 && ((setgroups(1, &gid) ||
            setresgid(gid, gid, gid) ||
-           setresuid(uid, uid, uid)))
+           setresuid(uid, uid, uid))))
                err(1, "unable to revoke privs");
 
        argc -= optind;
@@ -484,6 +489,25 @@ main(int argc, char *argv[])
                        err(1, "malloc");
        }
 
+       if (s == -1) {
+               char passbuf[INET6_ADDRSTRLEN];
+
+               inet_ntop(v6flag ? AF_INET6 : AF_INET, dst, 
+                               passbuf, sizeof(passbuf));
+               
+               s = get_icmp_descriptor(passbuf);
+               if (s == -1) {
+                       fprintf(stderr, "sun daemon (sund) is not running or 
defective, or you're not root\n");
+                       exit(1);
+               }
+       }
+
+       if (rtableid != 0) {
+               if (setsockopt(s, SOL_SOCKET, SO_RTABLE, &rtableid,
+                   sizeof(rtableid)) == -1)
+                       err(1, "setsockopt SO_RTABLE");
+       }
+
        if (res->ai_next) {
                if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf,
                    sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0)
@@ -493,6 +517,7 @@ main(int argc, char *argv[])
        }
        freeaddrinfo(res);
 
+
        if (source) {
                memset(&hints, 0, sizeof(hints));
                hints.ai_family = dst->sa_family;
@@ -768,10 +793,10 @@ main(int argc, char *argv[])
        }
 
        if (options & F_HOSTNAME) {
-               if (pledge("stdio inet dns", NULL) == -1)
+               if (pledge("stdio unix dns recvfd", NULL) == -1)
                        err(1, "pledge");
        } else {
-               if (pledge("stdio inet", NULL) == -1)
+               if (pledge("stdio unix recvfd", NULL) == -1)
                        err(1, "pledge");
        }
 
@@ -2273,4 +2298,98 @@ usage(void)
                    "\n\t[-t ttl] [-V rtable] [-w maxwait] host\n");
        }
        exit(1);
+}
+
+/*
+ * stolen from imsg.c mostly
+ */
+
+int
+get_icmp_descriptor(char *ipname)
+{
+       int so, raw, sel, j;
+       fd_set rset;
+
+       struct sockaddr_un sun;
+        struct msghdr            msg;
+        struct cmsghdr          *cmsg;
+        struct iovec             iov;
+
+        union {
+                struct cmsghdr hdr;
+                char    buf[CMSG_SPACE(sizeof(int) * 1)];
+        } cmsgbuf;
+
+        ssize_t                  n = -1;
+
+        memset(&msg, 0, sizeof(msg));
+        memset(&cmsgbuf, 0, sizeof(cmsgbuf));
+
+        iov.iov_base = NULL;
+        iov.iov_len = 0;
+        msg.msg_iov = &iov;
+        msg.msg_iovlen = 1;
+        msg.msg_control = &cmsgbuf.buf;
+        msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+       so = socket(AF_UNIX, SOCK_STREAM, 0);
+       if (so < 0) {
+               return -1;
+       }
+
+       memset(&sun, 0, sizeof(sun));
+       sun.sun_family = AF_UNIX;       
+       sun.sun_len = sizeof(struct sockaddr_un);
+       
+       strlcpy(sun.sun_path, "/var/run/sund.sock", sizeof(sun.sun_path));
+
+       if (connect(so, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+               goto bad;
+       }
+
+       if (send(so, ipname, strlen(ipname), 0) < 0) {
+               goto bad;
+       }
+
+       for (;;) {
+               FD_ZERO(&rset);
+               FD_SET(so, &rset);
+       
+               sel = select(so + 1, &rset, NULL, NULL, NULL);
+               switch (sel) {
+               case -1:
+                       continue;
+               default:
+                       break;
+               }
+
+               if (FD_ISSET(so, &rset)) {
+                       /* get descriptor */
+                       if ((n = recvmsg(so, &msg, 0)) == -1) {
+                               goto bad;
+                       }
+
+                       for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+                               cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+                               if (cmsg->cmsg_level == SOL_SOCKET &&
+                                       cmsg->cmsg_type == SCM_RIGHTS) {
+                                       j = ((char *)cmsg + cmsg->cmsg_len -
+                                               (char *)CMSG_DATA(cmsg)) /
+                                               sizeof(int);
+
+                                       if (j == 1) {
+                                               raw = ((int 
*)CMSG_DATA(cmsg))[0];
+                                       } else {
+                                               goto bad;
+                                       }
+                                       close(so);
+                                       return (raw);
+                               }
+                       }
+bad:
+                       close(so);
+                       return (-1);
+               }
+       }
+       /* NOTREACHED */
 }


-- 
Over thirty years experience on Unix-like Operating Systems starting with QNX.

Reply via email to