mailing list of musl libc
 help / color / mirror / code / Atom feed
* [musl] Adding dns/resolver tests to libc-test
@ 2024-08-18  2:03 Rich Felker
  2024-08-18  9:38 ` Szabolcs Nagy
  0 siblings, 1 reply; 7+ messages in thread
From: Rich Felker @ 2024-08-18  2:03 UTC (permalink / raw)
  To: musl

[-- Attachment #1: Type: text/plain, Size: 2547 bytes --]

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

[-- Attachment #2: unshare-ns.c --]
[-- Type: text/plain, Size: 3136 bytes --]

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
#include <sys/mount.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>

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);
}

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2024-09-11 21:34 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-08-18  2:03 [musl] Adding dns/resolver tests to libc-test Rich Felker
2024-08-18  9:38 ` Szabolcs Nagy
2024-08-27 15:30   ` Ryan Ward
2024-08-27 21:34     ` Rich Felker
2024-09-09 14:46       ` Ryan Ward
2024-09-11 20:38         ` Szabolcs Nagy
2024-09-11 21:33           ` Rich Felker

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/musl/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).