From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-3.1 required=5.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED,RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL,T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.4 Received: from second.openwall.net (second.openwall.net [193.110.157.125]) by inbox.vuxu.org (Postfix) with SMTP id 9C2BC2C7BD for ; Sun, 18 Aug 2024 04:03:51 +0200 (CEST) Received: (qmail 21961 invoked by uid 550); 18 Aug 2024 02:03:39 -0000 Mailing-List: contact musl-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Reply-To: musl@lists.openwall.com Received: (qmail 21891 invoked from network); 18 Aug 2024 02:03:36 -0000 Date: Sat, 17 Aug 2024 22:03:28 -0400 From: Rich Felker To: musl@lists.openwall.com Message-ID: <20240818020328.GU10433@brightrain.aerifal.cx> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="3607uds81ZQvwCD0" Content-Disposition: inline User-Agent: Mutt/1.5.21 (2010-09-15) Subject: [musl] Adding dns/resolver tests to libc-test --3607uds81ZQvwCD0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline I've been working on a framework to allow testing of libc resolver/dns functionality in libc-test, on Linux-based hosts, provided they have user-namespace functionality. The intent is that these tests would be made conditional on __linux__ or similar, with the freedom to add equivalent setup for other systems in the future if desired. I'm attaching a semi-polished (fallback and error handling should be mostly right) helper module that sets up the namespaced environment for the test in-process. The intended usage pattern would be putting a number of resolver tests in a single file, which would first call the enter_dns_test_ns() setup function, then sequentially run each test. For tests looking for crashes from malformed responses, it would make sense to fork a child for the test to run in, and wait in the parent. Each test would: 1. Truncate and rewrite /etc/resolv.conf and /etc/hosts (these are virtual replacements which only appear in the namespace) with desired contents. This would involve putting "nameserver 127.0.0.1" (or ::1 to test IPv6 nameserver support) in resolv.conf, and may also need options to disable functionality like edns0 that might not match the test payload. 2. Create a thread to listen on localhost:53 and respond with the packet contents to test the resolver against. Normally these would be hard-coded packets except for rewriting the query id to match (or mismatch, if testing rejection of mismatched id) the query, and selecting a reply based on the requested RR type. Should listen on both UDP and TCP unless testing the behavior when one or the other is not available. 3. From the main thread, call getaddrinfo, getnameinfo, get*by*[_r], res_query, res_send, etc. as desired to test against the payload. 4. Evaluate that the results match the expectation. A smart direction to go from here, I think, would be looking back over DNS-related bugs and limitations (like CNAME chain length) we've had in the past and collecting or constructing test payloads to trigger them. It should be possible to capture responses from the real world with a simple program using res_query, and incorporate them into tests. If doing so, my leaning would be to anonymize the actual contents (replace domains with example.com, IPs with private-use ranges), but maybe that doesn't matter? Using the arpa/nameser.h interfaces in libc it's probably easy enough to find all the data one may want to replace in a packet and replace it. Anyone up for fleshing some of this out? Rich --3607uds81ZQvwCD0 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="unshare-ns.c" #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include static int symlink_clone_dir(const char *pathname) { int ret = -1; char tmp[] = "/tmp/cloneXXXXXX"; char buf[sizeof tmp + 100]; if (!mkdtemp(tmp)) return -1; if (mount("none", tmp, "tmpfs", 0, "mode=0755") < 0) goto out1; int fd = open(tmp, O_RDONLY|O_DIRECTORY|O_CLOEXEC); if (fd < 0) goto out2; if (mkdirat(fd, ".orig", 0700) < 0) goto out3; snprintf(buf, sizeof buf, "%s/%s", tmp, ".orig"); if (mount(pathname, buf, 0, MS_BIND, 0) < 0) goto out3; DIR *dir = opendir(buf); if (!dir) goto out3; struct dirent *de; while ((de = readdir(dir))) { char lnk[sizeof ".orig/" + strlen(de->d_name)]; snprintf(lnk, sizeof lnk, ".orig/%s", de->d_name); symlinkat(lnk, fd, de->d_name); } closedir(dir); if (mount(tmp, pathname, 0, MS_MOVE, 0) < 0) goto out3; ret = 0; out3: close(fd); out2: if (ret) umount2(tmp, MNT_DETACH); out1: rmdir(tmp); return ret; } static int make_empty(const char *pathname) { int fd = open(pathname, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC, 0644); if (fd < 0) return -1; close(fd); return 0; } static int bindover(const char *pathname) { char tmp[] = "/tmp/bindoverXXXXXX"; int fd = mkostemp(tmp, O_CLOEXEC); if (fd < 0) return -1; int r = mount(tmp, pathname, 0, MS_BIND, 0); unlink(tmp); close(fd); return r; } static int writeproc(const char *pathname, const char *contents) { int fd = open(pathname, O_RDWR|O_CLOEXEC); if (fd < 0) return -1; if (write(fd, contents, strlen(contents)) != strlen(contents)) return -1; close(fd); return 0; } static int new_user_ns(int flags) { uid_t uid = getuid(); gid_t gid = getgid(); if (unshare(CLONE_NEWUSER|flags) < 0) return -1; char buf[3*sizeof(uintmax_t) + 10]; snprintf(buf, sizeof buf, "0 %ju 1", (uintmax_t)uid); if (writeproc("/proc/self/uid_map", buf) < 0) return -1; if (writeproc("/proc/self/setgroups", "deny") < 0) return -1; snprintf(buf, sizeof buf, "0 %ju 1", (uintmax_t)gid); if (writeproc("/proc/self/gid_map", buf) < 0) return -1; return 0; } int enter_dns_test_ns() { if (new_user_ns(CLONE_NEWNS|CLONE_NEWNET) < 0) return -1; int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); if (s < 0) return -1; struct ifreq ifr = { .ifr_name = "lo", }; if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) return -1; ifr.ifr_flags |= IFF_UP; if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) return -1; close(s); if (access("/etc/resolv.conf", F_OK) || access("/etc/hosts", F_OK)) { if (symlink_clone_dir("/etc") < 0) return -1; unlink("/etc/resolv.conf"); unlink("/etc/hosts"); if (make_empty("/etc/resolv.conf") || make_empty("/etc/hosts")) return -1; } else { if (bindover("/etc/resolv.conf")) return -1; if (bindover("/etc/hosts")) return -1; } return 0; } int main(int argc, char **argv) { if (enter_dns_test_ns()) { perror(0); return 1; } if (argc>1) execv(argv[1], argv+1); } --3607uds81ZQvwCD0--