From dc0e01f9c91a831ea01803d457e6d5153c991dfa Mon Sep 17 00:00:00 2001 From: Jacob Saunders Date: Fri, 16 Jan 2026 12:18:33 -0500 Subject: [PATCH] Support for building VMF on FreeBSD and execution of basic fuzzing. This uses upstream AFL (most recently tested commit e33e7cd4f4b1da9cc76c19b7766deea790a2c5ee) and the support is added to the Linux codepath in VMF. Use the standard Linux fuzzing modules. --- README.md | 23 ++++++++++++++++++- vmf/src/framework/CMakeLists.txt | 5 +++- vmf/src/framework/util/OSAPI.hpp | 2 +- vmf/src/framework/util/linux/OSAPIImp.cpp | 19 ++++++++++++++- vmf/src/framework/util/linux/OSAPIImp.hpp | 4 ++-- .../linux/executor/AFLForkserverExecutor.cpp | 19 ++++++++++++++- 6 files changed, 65 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 222b451..f42249e 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,10 @@ See [migration.md](docs/migration.md) for a list of the API changes in the lates ## VMF Compatibility -As of now, VMF can be run in Docker and on the following distributions of Linux: +As of now, VMF can be run in Docker and on the following distributions of Linux and BSD: - CentOS 8 and 9 +- FreeBSD 15.0 and 15.1 - Kali - Oracle Linux 8 and 9 - RedHat 8 and 9 @@ -116,6 +117,26 @@ You may alternatively open the VMF.sln file that has been generated in the build More information on the build system is available in our [Build System Documentation](docs/build_system.md). +### Building VMF (FreeBSD) + +Install dependencies for the build with `pkg install curl cmake gmake`. + +When building and installing AFL++, support for FreeBSD has been merged into the official repository. The most recent tested commit is e33e7cd4f4b1da9cc76c19b7766deea790a2c5ee. + +Execute the following commands to build and install VMF. Note the use of the `gmake` command instead of `make`, the BSD make is incompatible and the GNU make must be used. + +*Note: The -DCMAKE_INSTALL_PREFIX may be used to optionally specify an install location other than the default (build/vmf_install).* + +```bash +# from /path/to/vmf/ directory: +mkdir build +cd build +cmake .. +#Or optionally use this version instead to specify an install path +#cmake -DCMAKE_INSTALL_PREFIX= .. +gmake install -j8 +``` + ### Running VMF VMF can be run in a standalone mode, with a single fuzzing instance, as well as in a distributed mode where multiple VMF instances work together to fuzz something. diff --git a/vmf/src/framework/CMakeLists.txt b/vmf/src/framework/CMakeLists.txt index 9cab590..17c8e41 100644 --- a/vmf/src/framework/CMakeLists.txt +++ b/vmf/src/framework/CMakeLists.txt @@ -59,7 +59,10 @@ set(Framework_LINKLIST restclient-cpp ) -if(NOT WIN32) +if (BSD) + #explicitly linking stdc++fs not needed on FreeBSD + list(APPEND Framework_LINKLIST) +elseif(NOT WIN32) list(APPEND Framework_LINKLIST stdc++fs) else() list(APPEND Framework_LINKLIST wldap32 ws2_32 Crypt32.lib Wldap32 Normaliz) diff --git a/vmf/src/framework/util/OSAPI.hpp b/vmf/src/framework/util/OSAPI.hpp index 8ccad33..f7787b0 100644 --- a/vmf/src/framework/util/OSAPI.hpp +++ b/vmf/src/framework/util/OSAPI.hpp @@ -130,7 +130,7 @@ class OSAPI * @param handler this is the pointer to a function that will be run when an interrupt signal is received by * VMF. */ - virtual void setSignalHandlers(sighandler_t handler) = 0; + virtual void setSignalHandlers(__sighandler_t handler) = 0; #endif virtual ~OSAPI() {}; diff --git a/vmf/src/framework/util/linux/OSAPIImp.cpp b/vmf/src/framework/util/linux/OSAPIImp.cpp index 5b1b489..49d296e 100644 --- a/vmf/src/framework/util/linux/OSAPIImp.cpp +++ b/vmf/src/framework/util/linux/OSAPIImp.cpp @@ -25,7 +25,12 @@ #include #include // dirname #include // readlink +#ifdef __FreeBSD__ +#include +#include +#else #include // PATH_MAX +#endif using namespace vmf; @@ -100,7 +105,19 @@ std::string OSAPIImp::getExecutablePath() char result[PATH_MAX]; const char *path = nullptr; + #ifdef __FreeBSD__ + //no /proc/self/exe on FreeBSD + size_t count = sizeof(result); + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; + sysctl(mib, 4, result, &count, NULL, 0); + #else ssize_t count = readlink("/proc/self/exe", result, PATH_MAX); + + #endif if (count > 0) { result[count] = 0; path = dirname(result); @@ -164,7 +181,7 @@ bool OSAPIImp::commandLineZip(std::string zipFilePath, std::string inputDir) return success; } -void OSAPIImp::setSignalHandlers(sighandler_t handler) +void OSAPIImp::setSignalHandlers(__sighandler_t handler) { signal(SIGINT, handler); signal(SIGTERM, handler); diff --git a/vmf/src/framework/util/linux/OSAPIImp.hpp b/vmf/src/framework/util/linux/OSAPIImp.hpp index d565d9b..ea75806 100644 --- a/vmf/src/framework/util/linux/OSAPIImp.hpp +++ b/vmf/src/framework/util/linux/OSAPIImp.hpp @@ -56,8 +56,8 @@ class OSAPIImp: public OSAPI * @param handler this is the pointer to a function that will be run when an interrupt signal is received by * VMF. */ - virtual void setSignalHandlers(sighandler_t handler); + virtual void setSignalHandlers(__sighandler_t handler); virtual ~OSAPIImp(); }; -} \ No newline at end of file +} diff --git a/vmf/src/modules/linux/executor/AFLForkserverExecutor.cpp b/vmf/src/modules/linux/executor/AFLForkserverExecutor.cpp index cfd62ff..c55b8bc 100644 --- a/vmf/src/modules/linux/executor/AFLForkserverExecutor.cpp +++ b/vmf/src/modules/linux/executor/AFLForkserverExecutor.cpp @@ -102,7 +102,12 @@ void AFLForkserverExecutor::init(ConfigInterface& config) { parseSUTDebugInfo(); validateVersionCompatibility(); + #ifndef __FreeBSD__ bool useCoreDumpCheck = config.getBoolParam(getModuleName(),"enableCoreDumpCheck", true); + #else //FreeBSD + /* /proc/sys/kernel/core_pattern does not exist on FreeBSD, cannot verify core pattern. */ + bool useCoreDumpCheck = config.getBoolParam(getModuleName(),"enableCoreDumpCheck", false); + #endif if(useCoreDumpCheck) { verifyCorePattern(); @@ -138,6 +143,10 @@ bool AFLForkserverExecutor::verifyCorePattern(void) { /* Check that the core dump pattern does not begin with a pipe. This causes crashes to be sent to an external utility, which is very slow and causes VMF to misinterpret them as timeouts. As such, this is a fatal error. */ + ///proc/sys/kernel/core_pattern does not exist on FreeBSD. + #ifdef __FreeBSD__ + throw RuntimeException("/proc/sys/kernel/core_pattern does not exist on FreeBSD, cannot verify core pattern.", RuntimeException::CONFIGURATION_ERROR); + #endif int corepattern_fd = open("/proc/sys/kernel/core_pattern", O_RDONLY); if (corepattern_fd < 0) @@ -150,7 +159,7 @@ bool AFLForkserverExecutor::verifyCorePattern(void) { "to be misinterpreted as timeouts.\nTo fix, please log in as root " "and run the following command: \n" " echo core >/proc/sys/kernel/core_pattern", - RuntimeException::UNEXPECTED_ERROR); + RuntimeException::CONFIGURATION_ERROR); return true; } @@ -1294,10 +1303,18 @@ void AFLForkserverExecutor::setResourceLimits(void) { * values as high as our default constants for pipes */ long unsigned max_fd = std::max({CTRL_PIPE_RD, CTRL_PIPE_WR, STAT_PIPE_RD, STAT_PIPE_WR}); getrlimit(RLIMIT_NOFILE, &r); + #ifdef __FreeBSD__ + /* freebsd r.rlim_cur is signed long, and clang cares about unsigned vs signed comparison */ + if (static_cast(r.rlim_cur) < max_fd + 1) { + r.rlim_cur = max_fd + 2; + setrlimit(RLIMIT_NOFILE, &r); + } + #else if (r.rlim_cur < max_fd + 1) { r.rlim_cur = max_fd + 2; setrlimit(RLIMIT_NOFILE, &r); } + #endif /* Memory Limit */ if (sut_mem_limit > 0) {