Libvirt fuzzing technique

Материал из ALT Linux Wiki

Fuzzing is done by socket calls to the monolithic libvirtd daemon using the libvirt RPC protocol and preloading the desock library to create dummy sockets, as afl++ natively does not support network interface fuzzing.

Prepare environment

Prepare workdir

Optionally, run podman container or continue in alt p10/sisyphus:

$ podman run -ti alt:p10

Create /fuzz directory:

$ mkdir /fuzz
$ cd /fuzz

Prepare patches and config

Put following files to /fuzz directory:

deferred_init.patch

diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c
index d880711c91..d77ffbbbe4 100644
--- a/src/remote/remote_daemon.c
+++ b/src/remote/remote_daemon.c
@@ -1023,7 +1023,9 @@ int main(int argc, char **argv) {
         ret = VIR_DAEMON_ERR_DRIVER;
         goto cleanup;
     }
-
+#ifdef __AFL_HAVE_MANUAL_CONTROL
+  __AFL_INIT();
+#endif
     if (!(srv = virNetServerNew(DAEMON_NAME, 1,
                                 config->min_workers,
                                 config->max_workers,

virnetsocket.patch

diff --git a/src/rpc/virnetsocket.c b/src/rpc/virnetsocket.c
index 910fb8dd67..2b88f58e7f 100644
--- a/src/rpc/virnetsocket.c
+++ b/src/rpc/virnetsocket.c
@@ -266,18 +266,6 @@ virNetSocketNew(virSocketAddr *localAddr,
     sock->isClient = isClient;
     sock->unlinkUNIX = unlinkUNIX;
 
-    /* Disable nagle for TCP sockets */
-    if (sock->localAddr.data.sa.sa_family == AF_INET ||
-        sock->localAddr.data.sa.sa_family == AF_INET6) {
-        if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
-                       &no_slow_start,
-                       sizeof(no_slow_start)) < 0) {
-            virReportSystemError(errno, "%s",
-                                 _("Unable to disable nagle algorithm"));
-            goto error;
-        }
-    }
-
 
     if (localAddr &&
         !(sock->localAddrStrSASL = virSocketAddrFormatFull(localAddr, true, ";")))

fix-sigsev-when-desock-and-capng.patch

From 1ca99bdecd4ba7d28f56d8f38f01c59eba725ed9 Mon Sep 17 00:00:00 2001
From: Alexandr Shashkin <dutyrok@altlinux.org>
Date: Fri, 25 Aug 2023 15:17:37 +0300
Subject: [PATCH 1/1] desock.c: move initialization of pointer variables of
 original functions to their overrides

If we use __attribute__((constructor)) void preeny_desock_orig() to save
original function pointer it will lead to segmentation fault at init_lib()
(src/cap-ng.c:230 at 0.8.2-alt1), when "/proc/sys/kernel/cap_last_cap" fd is closed.
That remains to be examined why constructor from cap-ng called earlier than one
from preloaded library.
Moving initialization to workaround this issue.
---
 src/desock.c | 35 ++++++++++++++++++++++++-----------
 1 file changed, 24 insertions(+), 11 deletions(-)

diff --git a/src/desock.c b/src/desock.c
index 19be82d..affe238 100644
--- a/src/desock.c
+++ b/src/desock.c
@@ -159,20 +159,12 @@ int (*original_connect)(int sockfd, const struct sockaddr *addr, socklen_t addrl
 int (*original_close)(int fd);
 int (*original_shutdown)(int sockfd, int how);
 int (*original_getsockname)(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-__attribute__((constructor)) void preeny_desock_orig()
-{
-	original_socket = dlsym(RTLD_NEXT, "socket");
-	original_listen = dlsym(RTLD_NEXT, "listen");
-	original_accept = dlsym(RTLD_NEXT, "accept");
-	original_bind = dlsym(RTLD_NEXT, "bind");
-	original_connect = dlsym(RTLD_NEXT, "connect");
-	original_close = dlsym(RTLD_NEXT, "close");
-	original_shutdown = dlsym(RTLD_NEXT, "shutdown");
-	original_getsockname = dlsym(RTLD_NEXT, "getsockname");
-}
 
 int socket(int domain, int type, int protocol)
 {
+	if (original_socket == NULL)
+		original_socket = dlsym(RTLD_NEXT, "socket");
+
 	int fds[2];
 	int front_socket;
 	int back_socket;
@@ -222,6 +214,9 @@ int socket(int domain, int type, int protocol)
 
 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
 {
+	if (original_accept == NULL)
+		original_accept = dlsym(RTLD_NEXT, "accept");
+
 	if (preeny_desock_accepted_sock >= 0)
 	{
                 errno = ECONNRESET;
@@ -257,6 +252,9 @@ int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags)
 
 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
 {
+	if (original_bind == NULL)
+		original_bind = dlsym(RTLD_NEXT, "bind");
+
 	if (preeny_socket_threads_to_front[sockfd])
 	{
 		preeny_info("Emulating bind on port %d\n", ntohs(((struct sockaddr_in*)addr)->sin_port));
@@ -270,17 +268,26 @@ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
 
 int listen(int sockfd, int backlog)
 {
+	if (original_listen == NULL)
+		original_listen = dlsym(RTLD_NEXT, "listen");
+
 	if (preeny_socket_threads_to_front[sockfd]) return 0;
 	else return original_listen(sockfd, backlog);
 }
 
 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
 {
+	if (original_connect == NULL)
+		original_connect = dlsym(RTLD_NEXT, "connect");
+
 	if (preeny_socket_threads_to_front[sockfd]) return 0;
 	else return original_connect(sockfd, addr, addrlen);
 }
 
 int close(int fd) {
+	if (original_close == NULL)
+		original_close = dlsym(RTLD_NEXT, "close");
+
 	if (preeny_desock_accepted_sock != -1 && preeny_desock_accepted_sock == fd)
 		exit(0);
 
@@ -288,6 +295,9 @@ int close(int fd) {
 }
 
 int shutdown(int sockfd, int how) {
+	if (original_shutdown == NULL)
+		original_shutdown = dlsym(RTLD_NEXT, "shutdown");
+
 	if (preeny_desock_accepted_sock != -1 && preeny_desock_accepted_sock == sockfd)
 		exit(0);
 
@@ -295,6 +305,9 @@ int shutdown(int sockfd, int how) {
 }
 
 int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
+	if (original_getsockname == NULL)
+		original_getsockname = dlsym(RTLD_NEXT, "getsockname");
+
 	struct sockaddr_in target;
 	socklen_t copylen = sizeof(target);
 
-- 
2.33.8

libvirtd.conf

listen_tls = 0
listen_tcp = 1
tcp_port = "16509"
listen_addr = "127.0.0.1"
unix_sock_group = "vmusers"
auth_tcp = "none"
min_workers = 1
max_workers = 1
prio_workers = 1
admin_min_workers = 1
admin_max_workers = 1

Install dependencies, build libvirt & preeny

Update system and install deps:

$ apt-get update
$ apt-get dist-upgrade -y
$ apt-get install -y git make lcov

# install libvirt deps
$ apt-get install -y xsltproc xml-utils libgnutls-devel libnl-devel \
    libdevmapper-devel libpciaccess-devel udev libudev-devel \
    libpciaccess-devel libyajl-devel libpcap-devel libselinux-devel dnsmasq \
    iptables radvd ebtables libsasl2-devel util-linux lvm2 libparted-devel \
    parted dmsetup libdevmapper-devel open-iscsi libiscsi-devel libnuma-devel \
    libcap-ng-devel libcurl-gnutls-compat libcurl-devel libaudit-devel \
    pm-utils bridge-utils libblkid-devel libgcrypt-devel libp11-kit-devel \
    libreadline-devel libtasn1-devel attr libxml2-devel xsltproc python3 \
    python3-dev iproute2 dmidecode libtirpc-devel kmod autoconf-archive \
    python3-module-flake8 gnutls-utils python3-module-docutils meson rpcgen \
    libgio-devel

# install preeny deps
$ apt-get install -y libini_config-devel libseccomp-devel

Install AFLplusplus and it's deps:

$ apt-get install -y AFLplusplus llvm15.0

Clone repos:

$ cd fuzz
$ git clone git://git.altlinux.org/gears/l/libvirt.git
$ git clone https://github.com/zardus/preeny.git

Build libvirt with afl++ instrumentation:

$ cd libvirt
$ git checkout 9.3.0-alt1

$ git apply /fuzz/virnetsocket.patch
$ git apply /fuzz/deferred_init.patch

$ export CC=afl-clang-lto \
       CXX=afl-clang-lto++ \
       CFLAGS="-O0 -Wframe-larger-than=30000" \
       CXXFLAGS="-O0 -Wframe-larger-than=30000" \
       AR=llvm-ar \
       RANLIB=llvm-ranlib \
       AS=llvm-as \
       GIT_SSL_NO_VERIFY=1

$ meson build -Dsystem=true -Dgit_werror=disabled
$ cd build
$ ninja

Build preeny:

$ cd /fuzz/preeny
$ git apply /fuzz/fix-sigsev-when-desock-and-capng.patch

$ export CC="gcc" \
       CFLAGS="-Werror=frame-larger-than=70000"
       
$ make -C src desock.so

Run fuzzing

Run AFLplusplus:

$ mkdir input
$ echo "testinput" > input/1

$ export INPUT=/fuzz/input \
       OUTPUT=/fuzz/out \
       LD_LIBRARY_PATH=/fuzz/libvirt/build/src/ \
       TARGET="/fuzz/libvirt/build/src/libvirtd -l -f /fuzz/libvirtd.conf" \
       AFL_PRELOAD="$(ls /fuzz/libvirt/build/src/*.so | tr -s '[:space:]' ' ') /fuzz/preeny/src/desock.so"

$ afl-fuzz -i $INPUT -o $OUTPUT -- $TARGET

Gather coverage

Rebuild libvirt:

$ git clone git://git.altlinux.org/gears/l/libvirt.git /fuzz/libvirt-cov
$ cd /fuzz/libvirt-cov
$ git fetch origin
$ git checkout 9.3.0-alt1

$ git apply /fuzz/virnetsocket.patch
$ export CC=gcc \
       CXX=g++ \
       CFLAGS="-Wno-error -O0 -Werror=frame-larger-than=30000" \
       CFLAGS="$CFLAGS -fprofile-arcs -ftest-coverage" \
       CXXFLAGS="-Wno-eqrror -O0 -Werror=frame-larger-than=30000" \
       CXXFLAGS="$CXXFLAGS -fprofile-arcs -ftest-coverage" \
       GIT_SSL_NO_VERIFY=1


$ meson build -Dsystem=true -Dgit_werror=disabled
$ cd build
$ ninja

Install afl-cov:

$ git clone https://github.com/vanhauser-thc/afl-cov.git /fuzz/afl-cov
$ cd /fuzz/afl-cov
$ make install

Gather coverage:

$ export OUT=/fuzz/out/ \
       TARGET="/fuzz/libvirt-cov/build/src/libvirtd -l -f /fuzz/libvirtd.conf" \
       SOURCE_CODE=/fuzz/libvirt-cov/build/src \
       LD_LIBRARY_PATH=/fuzz/libvirt-cov/build/src/ \
       LD_PRELOAD="$(ls /fuzz/libvirt-cov/build/src/*.so | tr -s '[:space:]' ' ') /fuzz/preeny/src/desock.so"

$ afl-cov --overwrite -v -d $OUT --coverage-cmd "$TARGET" \
    --code-dir $SOURCE_CODE