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.