diff --git a/ndhc/arp.c b/ndhc/arp.c index 4bd1cca..39bbf98 100644 --- a/ndhc/arp.c +++ b/ndhc/arp.c @@ -29,15 +29,10 @@ #include #include #include -#include -#include #include -#include #include -#include #include #include -#include #include #include "nk/log.h" #include "nk/io.h" @@ -48,6 +43,7 @@ #include "ifchange.h" #include "options.h" #include "leasefile.h" +#include "sockd.h" #define ARP_MSG_SIZE 0x2a #define ARP_RETRANS_DELAY 5000 // ms @@ -206,44 +202,23 @@ static void arp_set_bpf_defense(struct client_state_t *cs, int fd) sizeof sfp_arp) != -1; } +static int get_arp_socket(void) +{ + return request_sockd_fd("a", 1, NULL); +} + static int arp_open_fd(struct client_state_t *cs) { if (cs->arpFd != -1) return 0; - - int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP)); - if (fd == -1) { + cs->arpFd = get_arp_socket(); + if (cs->arpFd == -1) { log_error("arp: Failed to create socket: %s", strerror(errno)); - goto out; + return -1; } - - int opt = 1; - if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof opt) == -1) { - log_error("arp: Failed to set broadcast: %s", strerror(errno)); - goto out_fd; - } - if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) { - log_error("arp: Failed to set non-blocking: %s", strerror(errno)); - goto out_fd; - } - struct sockaddr_ll saddr = { - .sll_family = AF_PACKET, - .sll_protocol = htons(ETH_P_ARP), - .sll_ifindex = client_config.ifindex, - }; - if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_ll)) < 0) { - log_error("arp: bind failed: %s", strerror(errno)); - goto out_fd; - } - - cs->arpFd = fd; - epoll_add(cs->epollFd, fd); + epoll_add(cs->epollFd, cs->arpFd); arpreply_clear(); return 0; -out_fd: - close(fd); -out: - return -1; } static void arp_switch_state(struct client_state_t *cs, arp_state_t state) diff --git a/ndhc/dhcp.c b/ndhc/dhcp.c index a07181a..97c0169 100644 --- a/ndhc/dhcp.c +++ b/ndhc/dhcp.c @@ -31,18 +31,10 @@ #include #include #include -#include #include -#include #include -#include -#include -#include -#include #include #include -#include -#include #include #include "nk/log.h" #include "nk/io.h" @@ -54,6 +46,7 @@ #include "ifchange.h" #include "sys.h" #include "options.h" +#include "sockd.h" typedef enum { LM_NONE = 0, @@ -61,73 +54,33 @@ typedef enum { LM_RAW } listen_mode_t; -// Returns fd of new udp socket bound on success, or -1 on failure. -static int create_udp_socket(uint32_t ip, uint16_t port, char *iface) +static int get_udp_unicast_socket(void) { - int fd; - if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { - log_error("%s: (%s) socket failed: %s", - client_config.interface, __func__, strerror(errno)); - goto out; - } - int opt = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) { - log_error("%s: (%s) Set reuse addr failed: %s", - client_config.interface, __func__, strerror(errno)); - goto out_fd; - } - if (setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof opt) == -1) { - log_error("%s: (%s) Set don't route failed: %s", - client_config.interface, __func__, strerror(errno)); - goto out_fd; - } - struct ifreq ifr; - memset(&ifr, 0, sizeof ifr); - ssize_t sl = snprintf(ifr.ifr_name, sizeof ifr.ifr_name, "%s", iface); - if (sl < 0 || (size_t)sl >= sizeof ifr.ifr_name) { - log_error("%s: (%s) Set interface name failed.", - client_config.interface, __func__); - goto out_fd; - } - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof ifr) < 0) { - log_error("%s: (%s) Set bind to device failed: %s", - client_config.interface, __func__, strerror(errno)); - goto out_fd; - } - if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) { - log_error("%s: (%s) Set non-blocking failed: %s", - client_config.interface, __func__, strerror(errno)); - goto out_fd; - } - - struct sockaddr_in sa = { - .sin_family = AF_INET, - .sin_port = htons(port), - .sin_addr.s_addr = ip, - }; - if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == -1) - goto out_fd; - - return fd; - out_fd: - close(fd); - out: - return -1; + return request_sockd_fd("u", 1, NULL); } -// Returns fd of new listen socket bound to 0.0.0.0:@68 on interface @inf -// on success, or -1 on failure. -static int create_udp_listen_socket(char *inf) +static int get_raw_broadcast_socket(void) { - int fd = create_udp_socket(INADDR_ANY, DHCP_CLIENT_PORT, inf); - if (fd == -1) - return -1; - int opt = 1; - if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof opt) == -1) { - log_error("%s: (%s) Set broadcast failed: %s", - client_config.interface, __func__, strerror(errno)); - close(fd); - return -1; + return request_sockd_fd("s", 1, NULL); +} + +static int get_udp_listen_socket(struct client_state_t *cs) +{ + char buf[32]; + buf[0] = 'u'; + memcpy(buf + 1, &cs->clientAddr, sizeof cs->clientAddr); + return request_sockd_fd(buf, 1 + sizeof cs->clientAddr, NULL); +} + +static int get_raw_listen_socket(struct client_state_t *cs) +{ + char resp; + int fd = request_sockd_fd("L", 1, &resp); + switch (resp) { + case 'L': cs->using_dhcp_bpf = 1; break; + case 'l': cs->using_dhcp_bpf = 0; break; + default: suicide("%s: (%s) expected l or L sockd reply but got %c", + client_config.interface, __func__, resp); } return fd; } @@ -136,8 +89,7 @@ static int create_udp_listen_socket(char *inf) static int send_dhcp_cooked(struct client_state_t *cs, struct dhcpmsg *payload) { int ret = -1; - int fd = create_udp_socket(cs->clientAddr, DHCP_CLIENT_PORT, - client_config.interface); + int fd = get_udp_unicast_socket(); if (fd == -1) goto out; @@ -324,110 +276,11 @@ static int get_raw_packet(struct client_state_t *cs, struct dhcpmsg *payload) return l; } -static int create_raw_socket(struct client_state_t *cs, struct sockaddr_ll *sa, - const struct sock_fprog *filter_prog) -{ - int fd; - if ((fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { - log_error("create_raw_socket: socket failed: %s", strerror(errno)); - goto out; - } - - if (cs) { - if (filter_prog && (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, - filter_prog, sizeof *filter_prog) != -1)) - cs->using_dhcp_bpf = 1; - else - cs->using_dhcp_bpf = 0; - } - - int opt = 1; - if (setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof opt) == -1) { - log_error("create_raw_socket: Failed to set don't route: %s", - strerror(errno)); - goto out_fd; - } - if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) { - log_error("create_raw_socket: Set non-blocking failed: %s", - strerror(errno)); - goto out_fd; - } - if (bind(fd, (struct sockaddr *)sa, sizeof *sa) < 0) { - log_error("create_raw_socket: bind failed: %s", strerror(errno)); - goto out_fd; - } - return fd; -out_fd: - close(fd); -out: - return -1; -} - -static int create_raw_listen_socket(struct client_state_t *cs, int ifindex) -{ - static const struct sock_filter sf_dhcp[] = { - // Verify that the packet has a valid IPv4 version nibble and - // that no IP options are defined. - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 0), - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x45, 1, 0), - BPF_STMT(BPF_RET + BPF_K, 0), - // Verify that the IP header has a protocol number indicating UDP. - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 9), - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), - BPF_STMT(BPF_RET + BPF_K, 0), - // Make certain that the packet is not a fragment. All bits in - // the flag and fragment offset field must be set to zero except - // for the Evil and DF bits (0,1). - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 6), - BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x3fff, 0, 1), - BPF_STMT(BPF_RET + BPF_K, 0), - // Packet is UDP. Advance X past the IP header. - BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), - // Verify that the UDP client and server ports match that of the - // IANA-assigned DHCP ports. - BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0), - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, - (DHCP_SERVER_PORT << 16) + DHCP_CLIENT_PORT, 1, 0), - BPF_STMT(BPF_RET + BPF_K, 0), - // Get the UDP length field and store it in X. - BPF_STMT(BPF_LD + BPF_H + BPF_IND, 4), - BPF_STMT(BPF_MISC + BPF_TAX, 0), - // Get the IPv4 length field and store it in A and M[0]. - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 2), - BPF_STMT(BPF_ST, 0), - // Verify that UDP length = IP length - IP header size - BPF_STMT(BPF_ALU + BPF_SUB + BPF_K, 20), - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), - BPF_STMT(BPF_RET + BPF_K, 0), - // Pass the number of octets that are specified in the IPv4 header. - BPF_STMT(BPF_LD + BPF_MEM, 0), - BPF_STMT(BPF_RET + BPF_A, 0), - }; - static const struct sock_fprog sfp_dhcp = { - .len = sizeof sf_dhcp / sizeof sf_dhcp[0], - .filter = (struct sock_filter *)sf_dhcp, - }; - struct sockaddr_ll sa = { - .sll_family = AF_PACKET, - .sll_protocol = htons(ETH_P_IP), - .sll_ifindex = ifindex, - }; - return create_raw_socket(cs, &sa, &sfp_dhcp); -} - // Broadcast a DHCP message using a raw socket. static int send_dhcp_raw(struct dhcpmsg *payload) { int ret = -1; - struct sockaddr_ll da = { - .sll_family = AF_PACKET, - .sll_protocol = htons(ETH_P_IP), - .sll_pkttype = PACKET_BROADCAST, - .sll_ifindex = client_config.ifindex, - .sll_halen = 6, - }; - memcpy(da.sll_addr, "\xff\xff\xff\xff\xff\xff", 6); - int fd = create_raw_socket(NULL, &da, NULL); + int fd = get_raw_broadcast_socket(); if (fd == -1) return ret; @@ -459,19 +312,25 @@ static int send_dhcp_raw(struct dhcpmsg *payload) .version = IPVERSION, .ttl = IPDEFTTL, }, - .udp = { - .source = htons(DHCP_CLIENT_PORT), - .dest = htons(DHCP_SERVER_PORT), - .len = htons(ud_len), - .check = 0, - }, .data = *payload, }; + iudmsg.udp.source = htons(DHCP_CLIENT_PORT); + iudmsg.udp.dest = htons(DHCP_SERVER_PORT); + iudmsg.udp.len = htons(ud_len); + iudmsg.udp.check = 0; uint16_t udpcs = net_checksum(&iudmsg.udp, ud_len); uint16_t phcs = net_checksum(&ph, sizeof ph); iudmsg.udp.check = net_checksum_add(udpcs, phcs); iudmsg.ip.check = net_checksum(&iudmsg.ip, sizeof iudmsg.ip); + struct sockaddr_ll da = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_pkttype = PACKET_BROADCAST, + .sll_ifindex = client_config.ifindex, + .sll_halen = 6, + }; + memcpy(da.sll_addr, "\xff\xff\xff\xff\xff\xff", 6); ret = safe_sendto(fd, (const char *)&iudmsg, iud_len, 0, (struct sockaddr *)&da, sizeof da); if (ret == -1) @@ -490,11 +349,11 @@ static void change_listen_mode(struct client_state_t *cs, int new_mode) close(cs->listenFd); cs->listenFd = -1; } - if (new_mode == LM_NONE) - return; - cs->listenFd = new_mode == LM_RAW ? - create_raw_listen_socket(cs, client_config.ifindex) : - create_udp_listen_socket(client_config.interface); + switch (cs->listenMode) { + default: return; + case LM_RAW: cs->listenFd = get_raw_listen_socket(cs); break; + case LM_COOKED: cs->listenFd = get_udp_listen_socket(cs); break; + } if (cs->listenFd < 0) suicide("%s: FATAL: Couldn't listen on socket: %s", client_config.interface, strerror(errno)); diff --git a/ndhc/ndhc.c b/ndhc/ndhc.c index 8eaf74d..061ae7f 100644 --- a/ndhc/ndhc.c +++ b/ndhc/ndhc.c @@ -70,6 +70,7 @@ #include "ifset.h" #include "ifchd.h" #include "duiaid.h" +#include "sockd.h" struct client_state_t cs = { .ifchWorking = 0, @@ -341,22 +342,86 @@ int pToNdhcW; int pToIfchR; int pToIfchW; -static void create_ipc_pipes(void) { +int psToNdhcR; +int psToNdhcW; +int pToSockdR; +int pToSockdW; + +static void create_ifch_ipc_pipes(void) { int niPipe[2]; int inPipe[2]; - if (pipe2(niPipe, O_NONBLOCK)) + if (pipe2(niPipe, 0)) suicide("FATAL - can't create ndhc -> ndhc-ifch pipe: %s", strerror(errno)); + if (fcntl(niPipe[0], F_SETFL, fcntl(niPipe[0], F_GETFL) | O_NONBLOCK) < 0) + suicide("FATAL - failed to set ndhc -> ndhc-ifch read-side nonblocking: %s", + strerror(errno)); pToNdhcR = niPipe[0]; pToNdhcW = niPipe[1]; - if (pipe2(inPipe, O_NONBLOCK)) + if (pipe2(inPipe, 0)) suicide("FATAL - can't create ndhc-ifch -> ndhc pipe: %s", strerror(errno)); + if (fcntl(inPipe[0], F_SETFL, fcntl(inPipe[0], F_GETFL) | O_NONBLOCK) < 0) + suicide("FATAL - failed to set ndhc-ifch -> ndhc read-side nonblocking: %s", + strerror(errno)); pToIfchR = inPipe[0]; pToIfchW = inPipe[1]; } +static void create_sockd_ipc_pipes(void) { + int nsPipe[2]; + int snPipe[2]; + + if (pipe2(nsPipe, 0)) + suicide("FATAL - can't create ndhc -> ndhc-sockd pipe: %s", + strerror(errno)); + psToNdhcR = nsPipe[0]; + psToNdhcW = nsPipe[1]; + if (pipe2(snPipe, 0)) + suicide("FATAL - can't create ndhc-sockd -> ndhc pipe: %s", + strerror(errno)); + if (fcntl(snPipe[0], F_SETFL, fcntl(snPipe[0], F_GETFL) | O_NONBLOCK) < 0) + suicide("FATAL - failed to set ndhc-sockd -> ndhc read-side nonblocking: %s", + strerror(errno)); + pToSockdR = snPipe[0]; + pToSockdW = snPipe[1]; +} + +static void spawn_ifch(void) +{ + create_ifch_ipc_pipes(); + pid_t ifch_pid = fork(); + if (ifch_pid == 0) { + close(pToNdhcR); + close(pToIfchW); + // Don't share the RNG state with the master process. + nk_random_u32_init(&cs.rnd32_state); + ifch_main(); + } else if (ifch_pid > 0) { + close(pToIfchR); + close(pToNdhcW); + } else + suicide("failed to fork ndhc-ifch: %s", strerror(errno)); +} + +static void spawn_sockd(void) +{ + create_sockd_ipc_pipes(); + pid_t sockd_pid = fork(); + if (sockd_pid == 0) { + close(psToNdhcR); + close(pToSockdW); + // Don't share the RNG state with the master process. + nk_random_u32_init(&cs.rnd32_state); + sockd_main(); + } else if (sockd_pid > 0) { + close(pToSockdR); + close(psToNdhcW); + } else + suicide("failed to fork ndhc-sockd: %s", strerror(errno)); +} + static void ndhc_main(void) { prctl(PR_SET_NAME, "ndhc: master"); log_line("ndhc client " NDHC_VERSION " started on interface [%s].", @@ -376,8 +441,6 @@ static void ndhc_main(void) { nk_set_chroot(chroot_dir); memset(chroot_dir, '\0', sizeof chroot_dir); - - nk_set_capability("cap_net_bind_service,cap_net_broadcast,cap_net_raw=ep"); nk_set_uidgid(ndhc_uid, ndhc_gid); if (cs.ifsPrevState != IFS_UP) @@ -402,7 +465,7 @@ void background(void) write_pid(pidfile); } -int main(int argc, char **argv) +static void parse_program_options(int argc, char *argv[]) { static const struct option arg_options[] = { {"clientid", required_argument, 0, 'c'}, @@ -417,6 +480,7 @@ int main(int argc, char **argv) {"vendorid", required_argument, 0, 'V'}, {"user", required_argument, 0, 'u'}, {"ifch-user", required_argument, 0, 'U'}, + {"sockd-user", required_argument, 0, 'D'}, {"chroot", required_argument, 0, 'C'}, {"state-dir", required_argument, 0, 's'}, {"seccomp-enforce", no_argument, 0, 'S'}, @@ -435,7 +499,7 @@ int main(int argc, char **argv) while (1) { int c; - c = getopt_long(argc, argv, "c:bp:P:h:i:nqr:V:u:U:C:s:Sdw:W:m:M:t:R:Hv?", + c = getopt_long(argc, argv, "c:bp:P:h:i:nqr:V:u:U:D:C:s:Sdw:W:m:M:t:R:Hv?", arg_options, NULL); if (c == -1) break; @@ -479,6 +543,10 @@ int main(int argc, char **argv) if (nk_uidgidbyname(optarg, &ifch_uid, &ifch_gid)) suicide("invalid ifch user '%s' specified", optarg); break; + case 'D': + if (nk_uidgidbyname(optarg, &sockd_uid, &sockd_gid)) + suicide("invalid sockd user '%s' specified", optarg); + break; case 'C': copy_cmdarg(chroot_dir, optarg, sizeof chroot_dir, "chroot"); break; @@ -567,6 +635,11 @@ int main(int argc, char **argv) show_usage(); } } +} + +int main(int argc, char *argv[]) +{ + parse_program_options(argc, argv); nk_random_u32_init(&cs.rnd32_state); @@ -582,28 +655,14 @@ int main(int argc, char **argv) get_clientid(&cs, &client_config); switch (perform_ifup()) { - case 1: - cs.ifsPrevState = IFS_UP; - case 0: - break; - default: - suicide("failed to set the interface to up state"); + case 1: cs.ifsPrevState = IFS_UP; + case 0: break; + default: suicide("failed to set the interface to up state"); } - create_ipc_pipes(); - pid_t ifch_pid = fork(); - if (ifch_pid == 0) { - close(pToNdhcR); - close(pToIfchW); - // Don't share the RNG state with the master process. - nk_random_u32_init(&cs.rnd32_state); - ifch_main(); - } else if (ifch_pid > 0) { - close(pToIfchR); - close(pToNdhcW); - ndhc_main(); - } else - suicide("failed to fork ndhc-ifch: %s", strerror(errno)); + spawn_ifch(); + spawn_sockd(); + ndhc_main(); exit(EXIT_SUCCESS); } - + diff --git a/ndhc/ndhc.h b/ndhc/ndhc.h index ccefc80..c86d0a5 100644 --- a/ndhc/ndhc.h +++ b/ndhc/ndhc.h @@ -71,6 +71,10 @@ extern int pToIfchR; extern int pToIfchW; extern int pToNdhcR; extern int pToNdhcW; +extern int psToNdhcR; +extern int psToNdhcW; +extern int pToSockdR; +extern int pToSockdW; extern char state_dir[PATH_MAX]; extern char chroot_dir[PATH_MAX]; extern char resolv_conf_d[PATH_MAX]; diff --git a/ndhc/seccomp.c b/ndhc/seccomp.c index d116c09..c512853 100644 --- a/ndhc/seccomp.c +++ b/ndhc/seccomp.c @@ -173,3 +173,63 @@ int enforce_seccomp_ifch(void) return 0; } +int enforce_seccomp_sockd(void) +{ +#ifdef ENABLE_SECCOMP_FILTER + if (!seccomp_enforce) + return 0; + struct sock_filter filter[] = { + VALIDATE_ARCHITECTURE, + EXAMINE_SYSCALL, + ALLOW_SYSCALL(epoll_wait), + ALLOW_SYSCALL(epoll_ctl), + ALLOW_SYSCALL(read), + ALLOW_SYSCALL(write), + ALLOW_SYSCALL(close), + +#if defined(__x86_64__) || (defined(__arm__) && defined(__ARM_EABI__)) + ALLOW_SYSCALL(sendto), // used for glibc syslog routines + ALLOW_SYSCALL(recvmsg), + ALLOW_SYSCALL(socket), + ALLOW_SYSCALL(setsockopt), + ALLOW_SYSCALL(getsockname), + ALLOW_SYSCALL(connect), + ALLOW_SYSCALL(bind), + ALLOW_SYSCALL(socketpair), +#elif defined(__i386__) + ALLOW_SYSCALL(socketcall), + ALLOW_SYSCALL(fcntl64), +#else +#error Target platform does not support seccomp-filter. +#endif + + ALLOW_SYSCALL(fcntl), + ALLOW_SYSCALL(open), + + // Allowed by vDSO + ALLOW_SYSCALL(getcpu), + ALLOW_SYSCALL(time), + ALLOW_SYSCALL(gettimeofday), + ALLOW_SYSCALL(clock_gettime), + + ALLOW_SYSCALL(rt_sigreturn), +#ifdef __NR_sigreturn + ALLOW_SYSCALL(sigreturn), +#endif + ALLOW_SYSCALL(exit_group), + ALLOW_SYSCALL(exit), + KILL_PROCESS, + }; + struct sock_fprog prog = { + .len = (unsigned short)(sizeof filter / sizeof filter[0]), + .filter = filter, + }; + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) + return -1; + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) + return -1; + log_line("ndhc-sockd seccomp filter installed. Please disable seccomp if you encounter problems."); +#endif + return 0; +} + diff --git a/ndhc/seccomp.h b/ndhc/seccomp.h index 860d492..6c8e7c2 100644 --- a/ndhc/seccomp.h +++ b/ndhc/seccomp.h @@ -34,5 +34,6 @@ extern bool seccomp_enforce; int enforce_seccomp_ndhc(void); int enforce_seccomp_ifch(void); +int enforce_seccomp_sockd(void); #endif /* NJK_NDHC_SECCOMP_H_ */ diff --git a/ndhc/sockd.c b/ndhc/sockd.c new file mode 100644 index 0000000..dd1e5f5 --- /dev/null +++ b/ndhc/sockd.c @@ -0,0 +1,511 @@ +/* sockd.c - privsep socket creation daemon + * + * Copyright (c) 2014 Nicholas J. Kain + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "nk/log.h" +#include "nk/io.h" +#include "nk/privilege.h" + +#include "sockd.h" +#include "ndhc-defines.h" +#include "ndhc.h" +#include "dhcp.h" +#include "sys.h" +#include "seccomp.h" + +static int epollfd, signalFd; +/* Slots are for signalFd and the ndhc -> ifchd pipe. */ +static struct epoll_event events[2]; + +uid_t sockd_uid = 0; +gid_t sockd_gid = 0; + +// Interface to make requests of sockd. Called from ndhc process. +int request_sockd_fd(char *buf, size_t buflen, char *response) +{ + if (!buflen) + return -1; + ssize_t r = safe_write(pToSockdW, buf, buflen); + if (r < 0 || (size_t)r != buflen) + suicide("%s: (%s) write failed: %d", client_config.interface, + __func__, r); + + char data[MAX_BUF], control[MAX_BUF]; + struct iovec iov = { + .iov_base = data, + .iov_len = sizeof data - 1, + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = control, + .msg_controllen = sizeof control + }; + retry: + r = recvmsg(psToNdhcR, &msg, 0); + if (r == 0) { + suicide("%s: (%s) recvmsg received EOF", client_config.interface, + __func__); + } else if (r < 0) { + if (errno == EINTR) + goto retry; + suicide("%s: (%s) recvmsg failed: %s", client_config.interface, + __func__, strerror(errno)); + } + data[iov.iov_len] = '\0'; + char repc = data[0]; + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + if (response) + *response = repc; + else if (repc != buf[0]) + suicide("%s: (%s) expected %c sockd reply but got %c", + client_config.interface, __func__, buf[0], repc); + int *fd = (int *)CMSG_DATA(cmsg); + return *fd; + } + } + suicide("%s: (%s) sockd reply did not include a fd", + client_config.interface, __func__); +} + +static int create_arp_socket(void) +{ + int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP)); + if (fd == -1) { + log_error("%s: (%s) socket failed: %s", client_config.interface, + __func__, strerror(errno)); + goto out; + } + + int opt = 1; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof opt) == -1) { + log_error("%s: (%s) setsockopt failed: %s", client_config.interface, + __func__, strerror(errno)); + goto out_fd; + } + if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) { + log_error("%s: (%s) fcntl failed: %s", client_config.interface, + __func__, strerror(errno)); + goto out_fd; + } + struct sockaddr_ll saddr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_ARP), + .sll_ifindex = client_config.ifindex, + }; + if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_ll)) < 0) { + log_error("%s: (%s) bind failed: %s", client_config.interface, + __func__, strerror(errno)); + goto out_fd; + } + return fd; + out_fd: + close(fd); + out: + return -1; +} + +// Returns fd of new udp socket bound on success, or -1 on failure. +static int create_udp_socket(uint32_t ip, uint16_t port, char *iface) +{ + int fd; + if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { + log_error("%s: (%s) socket failed: %s", + client_config.interface, __func__, strerror(errno)); + goto out; + } + int opt = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) { + log_error("%s: (%s) Set reuse addr failed: %s", + client_config.interface, __func__, strerror(errno)); + goto out_fd; + } + if (setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof opt) == -1) { + log_error("%s: (%s) Set don't route failed: %s", + client_config.interface, __func__, strerror(errno)); + goto out_fd; + } + struct ifreq ifr; + memset(&ifr, 0, sizeof ifr); + ssize_t sl = snprintf(ifr.ifr_name, sizeof ifr.ifr_name, "%s", iface); + if (sl < 0 || (size_t)sl >= sizeof ifr.ifr_name) { + log_error("%s: (%s) Set interface name failed.", + client_config.interface, __func__); + goto out_fd; + } + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof ifr) < 0) { + log_error("%s: (%s) Set bind to device failed: %s", + client_config.interface, __func__, strerror(errno)); + goto out_fd; + } + if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) { + log_error("%s: (%s) Set non-blocking failed: %s", + client_config.interface, __func__, strerror(errno)); + goto out_fd; + } + + struct sockaddr_in sa = { + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr.s_addr = ip, + }; + if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == -1) + goto out_fd; + + return fd; + out_fd: + close(fd); + out: + return -1; +} + +static int create_raw_socket(struct sockaddr_ll *sa, bool *using_bpf, + const struct sock_fprog *filter_prog) +{ + int fd; + if ((fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { + log_error("create_raw_socket: socket failed: %s", strerror(errno)); + goto out; + } + + if (filter_prog) { + int r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, filter_prog, + sizeof *filter_prog); + if (using_bpf) + *using_bpf = !r; + } + + int opt = 1; + if (setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &opt, sizeof opt) == -1) { + log_error("create_raw_socket: Failed to set don't route: %s", + strerror(errno)); + goto out_fd; + } + if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK) == -1) { + log_error("create_raw_socket: Set non-blocking failed: %s", + strerror(errno)); + goto out_fd; + } + if (bind(fd, (struct sockaddr *)sa, sizeof *sa) < 0) { + log_error("create_raw_socket: bind failed: %s", strerror(errno)); + goto out_fd; + } + return fd; +out_fd: + close(fd); +out: + return -1; +} + +// Returns fd of new listen socket bound to 0.0.0.0:@68 on interface @inf +// on success, or -1 on failure. +static int create_udp_listen_socket(void) +{ + int fd = create_udp_socket(INADDR_ANY, DHCP_CLIENT_PORT, + client_config.interface); + if (fd == -1) + return -1; + int opt = 1; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof opt) == -1) { + log_error("%s: (%s) Set broadcast failed: %s", + client_config.interface, __func__, strerror(errno)); + close(fd); + return -1; + } + return fd; +} + +static int create_udp_send_socket(uint32_t client_addr) +{ + return create_udp_socket(client_addr, DHCP_CLIENT_PORT, + client_config.interface); +} + +static int create_raw_listen_socket(bool *using_bpf) +{ + static const struct sock_filter sf_dhcp[] = { + // Verify that the packet has a valid IPv4 version nibble and + // that no IP options are defined. + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 0), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x45, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), + // Verify that the IP header has a protocol number indicating UDP. + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 9), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), + // Make certain that the packet is not a fragment. All bits in + // the flag and fragment offset field must be set to zero except + // for the Evil and DF bits (0,1). + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 6), + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x3fff, 0, 1), + BPF_STMT(BPF_RET + BPF_K, 0), + // Packet is UDP. Advance X past the IP header. + BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0), + // Verify that the UDP client and server ports match that of the + // IANA-assigned DHCP ports. + BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, + (DHCP_SERVER_PORT << 16) + DHCP_CLIENT_PORT, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), + // Get the UDP length field and store it in X. + BPF_STMT(BPF_LD + BPF_H + BPF_IND, 4), + BPF_STMT(BPF_MISC + BPF_TAX, 0), + // Get the IPv4 length field and store it in A and M[0]. + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 2), + BPF_STMT(BPF_ST, 0), + // Verify that UDP length = IP length - IP header size + BPF_STMT(BPF_ALU + BPF_SUB + BPF_K, 20), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), + // Pass the number of octets that are specified in the IPv4 header. + BPF_STMT(BPF_LD + BPF_MEM, 0), + BPF_STMT(BPF_RET + BPF_A, 0), + }; + static const struct sock_fprog sfp_dhcp = { + .len = sizeof sf_dhcp / sizeof sf_dhcp[0], + .filter = (struct sock_filter *)sf_dhcp, + }; + struct sockaddr_ll sa = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_ifindex = client_config.ifindex, + }; + return create_raw_socket(&sa, using_bpf, &sfp_dhcp); +} + +static int create_raw_broadcast_socket(void) +{ + struct sockaddr_ll da = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_pkttype = PACKET_BROADCAST, + .sll_ifindex = client_config.ifindex, + .sll_halen = 6, + }; + memcpy(da.sll_addr, "\xff\xff\xff\xff\xff\xff", 6); + return create_raw_socket(&da, NULL, NULL); +} + +// XXX: Can share with ifch +static void setup_signals_sockd(void) +{ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGPIPE); + sigaddset(&mask, SIGUSR1); + sigaddset(&mask, SIGUSR2); + sigaddset(&mask, SIGTSTP); + sigaddset(&mask, SIGTTIN); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGHUP); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) + suicide("sigprocmask failed"); + signalFd = signalfd(-1, &mask, SFD_NONBLOCK); + if (signalFd < 0) + suicide("signalfd failed"); +} + +// XXX: Can share with ifch +static void signal_dispatch(void) +{ + int t; + size_t off = 0; + struct signalfd_siginfo si = {0}; + again: + t = read(signalFd, (char *)&si + off, sizeof si - off); + if (t < 0) { + if (t == EAGAIN || t == EWOULDBLOCK || t == EINTR) + goto again; + else + suicide("signalfd read error"); + } + if (off + (unsigned)t < sizeof si) + off += t; + switch (si.ssi_signo) { + case SIGINT: + case SIGTERM: + exit(EXIT_SUCCESS); + break; + case SIGPIPE: + log_line("ndhc-sockd: IPC pipe closed. Exiting."); + exit(EXIT_SUCCESS); + break; + default: + break; + } +} + +static void xfer_fd(int fd, char cmd) +{ + char control[sizeof(struct cmsghdr) + 10]; + struct iovec iov = { + .iov_base = &cmd, + .iov_len = 1, + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = control, + .msg_controllen = sizeof control, + }; + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof fd); + int *cmsg_fd = (int *)CMSG_DATA(cmsg); + *cmsg_fd = fd; + msg.msg_controllen = cmsg->cmsg_len; + retry: + if (sendmsg(psToNdhcW, &msg, 0) < 0) { + if (errno == EINTR) + goto retry; + suicide("%s: (%s) sendmsg failed: %s", client_config.interface, + __func__, strerror(errno)); + } +} + +static size_t execute_sockd(char *buf, size_t buflen) +{ + if (!buflen) + return 0; + + char c = buf[0]; + switch (c) { + case 'L': { + bool using_bpf; + int fd = create_raw_listen_socket(&using_bpf); + xfer_fd(fd, using_bpf ? 'L' : 'l'); + return 1; + } + case 'U': xfer_fd(create_udp_listen_socket(), 'U'); return 1; + case 'a': xfer_fd(create_arp_socket(), 'a'); return 1; + case 's': xfer_fd(create_raw_broadcast_socket(), 's'); return 1; + case 'u': { + uint32_t client_addr; + if (buflen < 1 + sizeof client_addr) + return 0; + memcpy(&client_addr, buf + 1, sizeof client_addr); + xfer_fd(create_udp_send_socket(client_addr), 'u'); + return 5; + } + default: suicide("%s: (%s) received invalid commands: '%c'", + client_config.interface, __func__, c); + } +} + +static void process_client_pipe(void) +{ + static char buf[MAX_BUF]; + static size_t buflen; + + if (buflen == MAX_BUF) + suicide("%s: (%s) receive buffer exhausted", client_config.interface, + __func__); + + int r = safe_read(pToSockdR, buf + buflen, sizeof buf - buflen); + if (r == 0) { + // Remote end hung up. + exit(EXIT_SUCCESS); + } else if (r < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return; + suicide("%s: (%s) error reading from ndhc -> sockd pipe: %s", + client_config.interface, __func__, strerror(errno)); + } + buflen += (size_t)r; + buflen -= execute_sockd(buf, buflen); +} + +static void do_sockd_work(void) +{ + epollfd = epoll_create1(0); + if (epollfd == -1) + suicide("epoll_create1 failed"); + + if (enforce_seccomp_sockd()) + log_line("sockd seccomp filter cannot be installed"); + + epoll_add(epollfd, pToSockdR); + epoll_add(epollfd, signalFd); + + for (;;) { + int r = epoll_wait(epollfd, events, 2, -1); + if (r == -1) { + if (errno == EINTR) + continue; + else + suicide("epoll_wait failed"); + } + for (int i = 0; i < r; ++i) { + int fd = events[i].data.fd; + if (fd == pToSockdR) + process_client_pipe(); + else if (fd == signalFd) + signal_dispatch(); + else + suicide("sockd: unexpected fd while performing epoll"); + } + } +} + +void sockd_main(void) +{ + prctl(PR_SET_NAME, "ndhc: sockd"); + umask(077); + setup_signals_sockd(); + nk_set_chroot(chroot_dir); + memset(chroot_dir, 0, sizeof chroot_dir); + nk_set_capability("cap_net_bind_service,cap_net_broadcast,cap_net_raw=ep"); + nk_set_uidgid(sockd_uid, sockd_gid); + do_sockd_work(); +} + diff --git a/ndhc/sockd.h b/ndhc/sockd.h new file mode 100644 index 0000000..0289a91 --- /dev/null +++ b/ndhc/sockd.h @@ -0,0 +1,9 @@ +#ifndef NDHC_SOCKD_H_ +#define NDHC_SOCKD_H_ + +extern uid_t sockd_uid; +extern gid_t sockd_gid; +int request_sockd_fd(char *buf, size_t buflen, char *response); +void sockd_main(void); + +#endif /* NDHC_SOCKD_H_ */