static errno_t ppfilter_data_out_raw (void *cookie, socket_t so, const struct sockaddr *to, __unused mbuf_t *data, __unused mbuf_t *control, __unused sflt_data_flag_t flags) { int32_t action = cookie ? ((struct pp_filter_cookie*)cookie)->action : COOKIE_NO_ACTION; if (action > COOKIE_NO_ACTION) return (action); // socket has already been filtered -- this will occur with UDP sockets that call connect if (!to) { struct sockaddr_in6 local; if (0 != sock_getpeername(so, (struct sockaddr*)&local, sizeof(local))) bzero(&local, sizeof(local)); to = (const struct sockaddr*)&local; } return ( ppfilter_connect((pp_filter_cookie_t)cookie, so, to, 0) ? EHOSTUNREACH : 0 ); }
int libcfs_sock_read (cfs_socket_t *sock, void *buffer, int nob, int timeout) { size_t rcvlen; int rc; cfs_duration_t to = cfs_time_seconds(timeout); cfs_time_t then; struct timeval tv; LASSERT(nob > 0); for (;;) { struct iovec iov = { .iov_base = buffer, .iov_len = nob }; struct msghdr msg = { .msg_name = NULL, .msg_namelen = 0, .msg_iov = &iov, .msg_iovlen = 1, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0, }; cfs_duration_usec(to, &tv); rc = -sock_setsockopt(C2B_SOCK(sock), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); if (rc != 0) { CERROR("Can't set socket recv timeout " "%ld.%06d: %d\n", (long)tv.tv_sec, (int)tv.tv_usec, rc); return rc; } then = cfs_time_current(); rc = -sock_receive(C2B_SOCK(sock), &msg, 0, &rcvlen); to -= cfs_time_current() - then; if (rc != 0 && rc != -EWOULDBLOCK) return rc; if (rcvlen == nob) return 0; if (to <= 0) return -EAGAIN; buffer = ((char *)buffer) + rcvlen; nob -= rcvlen; } return 0; } int libcfs_sock_write (cfs_socket_t *sock, void *buffer, int nob, int timeout) { size_t sndlen; int rc; cfs_duration_t to = cfs_time_seconds(timeout); cfs_time_t then; struct timeval tv; LASSERT(nob > 0); for (;;) { struct iovec iov = { .iov_base = buffer, .iov_len = nob }; struct msghdr msg = { .msg_name = NULL, .msg_namelen = 0, .msg_iov = &iov, .msg_iovlen = 1, .msg_control = NULL, .msg_controllen = 0, .msg_flags = (timeout == 0) ? MSG_DONTWAIT : 0, }; if (timeout != 0) { cfs_duration_usec(to, &tv); rc = -sock_setsockopt(C2B_SOCK(sock), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); if (rc != 0) { CERROR("Can't set socket send timeout " "%ld.%06d: %d\n", (long)tv.tv_sec, (int)tv.tv_usec, rc); return rc; } } then = cfs_time_current(); rc = -sock_send(C2B_SOCK(sock), &msg, ((timeout == 0) ? MSG_DONTWAIT : 0), &sndlen); to -= cfs_time_current() - then; if (rc != 0 && rc != -EWOULDBLOCK) return rc; if (sndlen == nob) return 0; if (to <= 0) return -EAGAIN; buffer = ((char *)buffer) + sndlen; nob -= sndlen; } return 0; } int libcfs_sock_getaddr (cfs_socket_t *sock, int remote, __u32 *ip, int *port) { struct sockaddr_in sin; int rc; if (remote != 0) /* Get remote address */ rc = -sock_getpeername(C2B_SOCK(sock), (struct sockaddr *)&sin, sizeof(sin)); else /* Get local address */ rc = -sock_getsockname(C2B_SOCK(sock), (struct sockaddr *)&sin, sizeof(sin)); if (rc != 0) { CERROR ("Error %d getting sock %s IP/port\n", rc, remote ? "peer" : "local"); return rc; } if (ip != NULL) *ip = ntohl (sin.sin_addr.s_addr); if (port != NULL) *port = ntohs (sin.sin_port); return 0; } int libcfs_sock_setbuf (cfs_socket_t *sock, int txbufsize, int rxbufsize) { int option; int rc; if (txbufsize != 0) { option = txbufsize; rc = -sock_setsockopt(C2B_SOCK(sock), SOL_SOCKET, SO_SNDBUF, (char *)&option, sizeof (option)); if (rc != 0) { CERROR ("Can't set send buffer %d: %d\n", option, rc); return (rc); } } if (rxbufsize != 0) { option = rxbufsize; rc = -sock_setsockopt (C2B_SOCK(sock), SOL_SOCKET, SO_RCVBUF, (char *)&option, sizeof (option)); if (rc != 0) { CERROR ("Can't set receive buffer %d: %d\n", option, rc); return (rc); } } return 0; }
//process // block/allow, or ask user and put thread to sleep kern_return_t process(void *cookie, socket_t so, const struct sockaddr *to) { //result kern_return_t result = kIOReturnError; //event firewallEvent event = {0}; //rule int action = RULE_STATE_NOT_FOUND; //awake reason int reason = 0; //socket type int socketType = 0; //length of socket type int socketTypeLength = 0; //process name char processName[PATH_MAX] = {0}; //what does rule say? // loop until we have an answer while(true) { //reset bzero(&event, sizeof(event)); //extract action action = ((struct cookieStruct*)cookie)->ruleAction; //get process name proc_selfname(processName, PATH_MAX); //block? if(RULE_STATE_BLOCK == action) { //dbg msg IOLog("LULU: rule says block for %s (pid: %d)\n", processName, proc_selfpid()); //gtfo! result = EPERM; //all done goto bail; } //allow? else if(RULE_STATE_ALLOW == action) { //dbg msg IOLog("LULU: rule says allow for %s (pid: %d)\n", processName, proc_selfpid()); //ok result = kIOReturnSuccess; //all done goto bail; } //not found // ->ask daemon and sleep for response else if(RULE_STATE_NOT_FOUND == action) { //dbg msg IOLog("LULU: no rule found for %s (pid: %d)\n", processName, proc_selfpid()); //zero out bzero(&event, sizeof(firewallEvent)); //set type event.networkOutEvent.type = EVENT_NETWORK_OUT; //add pid event.networkOutEvent.pid = proc_selfpid(); //init length socketTypeLength = sizeof(socketType); //get socket type sock_getsockopt(so, SOL_SOCKET, SO_TYPE, &socketType, &socketTypeLength); //save type event.networkOutEvent.socketType = socketType; //UDP sockets destination socket might be null // so grab via 'getpeername' and save as 'remote addr' if(NULL == to) { //copy into 'remote addr' for user mode if(0 != sock_getpeername(so, (struct sockaddr*)&(event.networkOutEvent.remoteAddress), sizeof(event.networkOutEvent.remoteAddress))) { //err msg IOLog("LULU ERROR: sock_getpeername() failed"); //bail goto bail; } } //copy remote socket for user mode else { //add remote (destination) socket addr memcpy(&(event.networkOutEvent.remoteAddress), to, sizeof(event.networkOutEvent.remoteAddress)); } //queue it up sharedDataQueue->enqueue_tail(&event, sizeof(firewallEvent)); //dbg msg IOLog("LULU: queued response to user mode, now going to sleep!\n"); //lock IOLockLock(ruleEventLock); //sleep reason = IOLockSleep(ruleEventLock, &ruleEventLock, THREAD_ABORTSAFE); //TODO: fix panic, think if kext is unloaded (sets ruleEventLock to NULL) this can still wake up? // "Preemption level underflow, possible cause unlocking an unlocked mutex or spinlock" // seems to happen when process is killed or kext unloaded while in the IOLockSleep!? //unlock IOLockUnlock(ruleEventLock); //thread wakeup cuz of signal, etc // ->just bail (process likely exited, etc) if(THREAD_AWAKENED != reason) { //dbg msg IOLog("LULU: thread awoke, but because of %d!\n", reason); //gtfo! result = EPERM; //all done goto bail; } //dbg msg IOLog("LULU: thread awoke, will check/process response\n"); //try get rule action again // ->not found, block, allow, etc ((struct cookieStruct*)(cookie))->ruleAction = queryRule(proc_selfpid()); //loop to (re)process } }//while bail: return result; }
//callback for incoming data // only interested in DNS responses for IP:URL mappings // code inspired by: https://github.com/williamluke/peerguardian-linux/blob/master/pgosx/kern/ppfilter.c static errno_t data_in(void *cookie, socket_t so, const struct sockaddr *from, mbuf_t *data, mbuf_t *control, sflt_data_flag_t flags) { //dbg msg IOLog("LULU: in %s\n", __FUNCTION__); //port in_port_t port = 0; //peer name struct sockaddr_in6 peerName = {0}; //mem buffer mbuf_t memBuffer = NULL; //response size size_t responseSize = 0; //dns header dnsHeader* dnsHeader = NULL; //firewall event firewallEvent event = {0}; //destination socket ('from') might be null? // if so, grab it via 'getpeername' from the socket if(NULL == from) { //lookup remote socket info if(0 != sock_getpeername(so, (struct sockaddr*)&peerName, sizeof(peerName))) { //err msg IOLog("LULU ERROR: sock_getpeername() failed\n"); //bail goto bail; } //now, assign from = (const struct sockaddr*)&peerName; } //get port switch(from->sa_family) { //IPv4 case AF_INET: port = ntohs(((const struct sockaddr_in*)from)->sin_port); break; //IPv6 case AF_INET6: port = ntohs(((const struct sockaddr_in6*)from)->sin6_port); break; default: break; } //ignore non-DNS if(53 != port) { //bail goto bail; } //init memory buffer memBuffer = *data; if(NULL == memBuffer) { //bail goto bail; } //get memory buffer while(MBUF_TYPE_DATA != mbuf_type(memBuffer)) { //get next memBuffer = mbuf_next(memBuffer); if(NULL == memBuffer) { //bail goto bail; } } //sanity check length if(mbuf_len(memBuffer) <= sizeof(struct dnsHeader)) { //bail goto bail; } //get data // should be a DNS header dnsHeader = (struct dnsHeader*)mbuf_data(memBuffer); //ignore everything that isn't a DNS response // top bit flag will be 0x1, for "a name service response" if(0 == ((ntohs(dnsHeader->flags)) & (1<<(15)))) { //bail goto bail; } //ignore any errors // bottom (4) bits will be 0x0 for "successful response" if(0 != ((ntohs(dnsHeader->flags)) & (1<<(0)))) { //bail goto bail; } //ignore any packets that don't have answers if(0 == ntohs(dnsHeader->ancount)) { //bail goto bail; } //zero out event struct bzero(&event, sizeof(firewallEvent)); //set type event.dnsResponseEvent.type = EVENT_DNS_RESPONSE; //set size // max, 512 responseSize = MIN(sizeof(event.dnsResponseEvent.response), mbuf_len(memBuffer)); //copy response memcpy(event.dnsResponseEvent.response, mbuf_data(memBuffer), responseSize); //queue it up sharedDataQueue->enqueue_tail(&event, sizeof(firewallEvent)); bail: return kIOReturnSuccess; }
// data in is currently used for PASV FTP support static errno_t ppfilter_data_in (__unused void *cookie, socket_t so, const struct sockaddr *from, mbuf_t *data, __unused mbuf_t *control, __unused sflt_data_flag_t flags) { in_addr_t ip4; in_port_t port; if (!from) { struct sockaddr_in6 local; if (0 != sock_getpeername(so, (struct sockaddr*)&local, sizeof(local))) bzero(&local, sizeof(local)); from = (const struct sockaddr*)&local; } if (AF_INET == from->sa_family) { port = ntohs(((const struct sockaddr_in*)from)->sin_port); } else if (AF_INET6 == from->sa_family) { const struct sockaddr_in6* addr6 = (const struct sockaddr_in6*)from; if (IN6_IS_ADDR_LOOPBACK(&addr6->sin6_addr) || !IN6_IS_ADDR_V4MAPPED(&addr6->sin6_addr)) return (0); // tables do not contain native ip6 addreses port = ntohs(addr6->sin6_port); } else return (0); // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // Short-circuit optimization for ftp filter -- if any other filters are ever added, // this will have to be removed. if (21 != port) return (0); // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX mbuf_t mdata = *data; while (mdata && MBUF_TYPE_DATA != mbuf_type(mdata)) { mdata = mbuf_next(mdata); } if (!mdata) return (0); char *pkt = mbuf_data(mdata); if (!pkt) return (0); size_t len = mbuf_len(mdata); ip4 = INADDR_NONE; int block, i; pp_data_filter_t filt = pp_data_filters[0]; for (i = 1; filt; ++i) { block = filt(pkt, len, &ip4, &port); if (INADDR_NONE != ip4) { // add to dynamic list pp_dynentries_lock(); pp_dyn_entry_t e = pp_dyn_entry_get(); if (e) { e->addr = ip4; e->port = port; e->block = block; } pp_dynentries_unlock(); break; } filt = pp_data_filters[i]; } return (0); }
/* * System call vectors. Since I (RIB) want to rewrite sockets as streams, * we have this level of indirection. Not a lot of overhead, since more of * the work is done via read/write/select directly. */ asmlinkage int sys_socketcall(int call, unsigned long *args) { int er; switch(call) { case SYS_SOCKET: er=verify_area(VERIFY_READ, args, 3 * sizeof(long)); if(er) return er; return(sock_socket(get_fs_long(args+0), get_fs_long(args+1), get_fs_long(args+2))); case SYS_BIND: er=verify_area(VERIFY_READ, args, 3 * sizeof(long)); if(er) return er; return(sock_bind(get_fs_long(args+0), (struct sockaddr *)get_fs_long(args+1), get_fs_long(args+2))); case SYS_CONNECT: er=verify_area(VERIFY_READ, args, 3 * sizeof(long)); if(er) return er; return(sock_connect(get_fs_long(args+0), (struct sockaddr *)get_fs_long(args+1), get_fs_long(args+2))); case SYS_LISTEN: er=verify_area(VERIFY_READ, args, 2 * sizeof(long)); if(er) return er; return(sock_listen(get_fs_long(args+0), get_fs_long(args+1))); case SYS_ACCEPT: er=verify_area(VERIFY_READ, args, 3 * sizeof(long)); if(er) return er; return(sock_accept(get_fs_long(args+0), (struct sockaddr *)get_fs_long(args+1), (int *)get_fs_long(args+2))); case SYS_GETSOCKNAME: er=verify_area(VERIFY_READ, args, 3 * sizeof(long)); if(er) return er; return(sock_getsockname(get_fs_long(args+0), (struct sockaddr *)get_fs_long(args+1), (int *)get_fs_long(args+2))); case SYS_GETPEERNAME: er=verify_area(VERIFY_READ, args, 3 * sizeof(long)); if(er) return er; return(sock_getpeername(get_fs_long(args+0), (struct sockaddr *)get_fs_long(args+1), (int *)get_fs_long(args+2))); case SYS_SOCKETPAIR: er=verify_area(VERIFY_READ, args, 4 * sizeof(long)); if(er) return er; return(sock_socketpair(get_fs_long(args+0), get_fs_long(args+1), get_fs_long(args+2), (unsigned long *)get_fs_long(args+3))); case SYS_SEND: er=verify_area(VERIFY_READ, args, 4 * sizeof(unsigned long)); if(er) return er; return(sock_send(get_fs_long(args+0), (void *)get_fs_long(args+1), get_fs_long(args+2), get_fs_long(args+3))); case SYS_SENDTO: er=verify_area(VERIFY_READ, args, 6 * sizeof(unsigned long)); if(er) return er; return(sock_sendto(get_fs_long(args+0), (void *)get_fs_long(args+1), get_fs_long(args+2), get_fs_long(args+3), (struct sockaddr *)get_fs_long(args+4), get_fs_long(args+5))); case SYS_RECV: er=verify_area(VERIFY_READ, args, 4 * sizeof(unsigned long)); if(er) return er; return(sock_recv(get_fs_long(args+0), (void *)get_fs_long(args+1), get_fs_long(args+2), get_fs_long(args+3))); case SYS_RECVFROM: er=verify_area(VERIFY_READ, args, 6 * sizeof(unsigned long)); if(er) return er; return(sock_recvfrom(get_fs_long(args+0), (void *)get_fs_long(args+1), get_fs_long(args+2), get_fs_long(args+3), (struct sockaddr *)get_fs_long(args+4), (int *)get_fs_long(args+5))); case SYS_SHUTDOWN: er=verify_area(VERIFY_READ, args, 2* sizeof(unsigned long)); if(er) return er; return(sock_shutdown(get_fs_long(args+0), get_fs_long(args+1))); case SYS_SETSOCKOPT: er=verify_area(VERIFY_READ, args, 5*sizeof(unsigned long)); if(er) return er; return(sock_setsockopt(get_fs_long(args+0), get_fs_long(args+1), get_fs_long(args+2), (char *)get_fs_long(args+3), get_fs_long(args+4))); case SYS_GETSOCKOPT: er=verify_area(VERIFY_READ, args, 5*sizeof(unsigned long)); if(er) return er; return(sock_getsockopt(get_fs_long(args+0), get_fs_long(args+1), get_fs_long(args+2), (char *)get_fs_long(args+3), (int *)get_fs_long(args+4))); default: return(-EINVAL); } }