Libvirt fuzzing technique

Материал из ALT Linux Wiki
(перенаправлено с «Libvirt fuzzing techntique»)

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