Patching FreeBSD on TrueNAS Core 13.3
iXsystems has abandoned TrueNAS Core, but it works just fine, so I’m keeping it running. The problem is the lack of security updates. Let’s backport a few fixes.
This follows the tmprotect post and covers rebuilding and deploying a kernel on TrueNAS Core 13.3-U1.2, using the RPCSEC_GSS and WireGuard patches as examples.
Preparing the build environment
Core 13.3-U1.2 uses FreeBSD 13.3-RELEASE-p4, specifically truenas/os@41f6a830f8ee1254c5a3efcb204d9ed6733b2f76.
Create a jail and save the host’s kernel configuration:
sudo sh
iocage create -n build-jail -r 13.3-RELEASE nat=1
sysctl -n kern.conftxt > /mnt/tank/iocage/jails/build-jail/root/tmp/SAVED_CONFIG
iocage console -f build-jail
Inside the jail, fetch the source and move the saved configuration into it:
exec sh
fetch -o /tmp/os.tar.gz https://codeload.github.com/truenas/os/tar.gz/41f6a830f8ee1254c5a3efcb204d9ed6733b2f76
mkdir -p /usr/src
cd /usr/src
tar -xf /tmp/os.tar.gz --strip-components=1
mv /tmp/SAVED_CONFIG sys/amd64/conf/SAVED_CONFIG
Applying patches
Read each advisory and review the patches linked under Solution. RPCSEC_GSS’s official patch applies unchanged to Core’s source; WireGuard’s 14.x patch needs the cryptography API change shown below.
For those too impatient, here is the combined patch you can review and apply. But ideally, you shouldn’t be applying random patches from random websites—including this one.
RPCSEC_GSS remote code execution (CVE-2026-4747)
FreeBSD-SA-26:08.rpcsec_gss fixes a buffer overflow in the RPCSEC_GSS validation used by Kerberos NFS. svc_rpc_gss_validate() copies the client’s credentials into a fixed stack buffer without checking whether they fit.
The official patch checks the credential length before copying into the buffer. Download and apply it inside the jail:
cd /usr/src
fetch -o /tmp/rpcsec_gss.patch https://security.FreeBSD.org/patches/SA-26:08/rpcsec_gss.patch
patch -p0 < /tmp/rpcsec_gss.patch
WireGuard missing authentication checks (CVE-2026-58085)
FreeBSD-SA-26:52.if_wg fixes a bug that lets WireGuard accept packets that fail authentication. A zero return from crypto_dispatch() means the request was submitted. The actual result is in crp.crp_etype.
The fix checks that result in the encryption and decryption functions and requires requests to finish before crypto_dispatch() returns. The 14.x patch uses CRYPTO_SESS_SYNC(), which is absent in 13.3. Use crypto_ses2caps() instead:
cd /usr/src
patch -p1 <<'PATCH'
--- a/sys/dev/wg/wg_crypto.c
+++ b/sys/dev/wg/wg_crypto.c
@@ -230,6 +230,8 @@
crp.crp_cipher_key = key;
crp.crp_callback = crypto_callback;
ret = crypto_dispatch(&crp);
+ if (ret == 0)
+ ret = crp.crp_etype;
crypto_destroyreq(&crp);
return (ret);
}
@@ -253,6 +255,8 @@
crp.crp_cipher_key = key;
crp.crp_callback = crypto_callback;
ret = crypto_dispatch(&crp);
+ if (ret == 0)
+ ret = crp.crp_etype;
crypto_destroyreq(&crp);
if (ret)
return (ret);
@@ -273,6 +277,10 @@
int ret = crypto_newsession(&chacha20_poly1305_sid, &csp, CRYPTOCAP_F_SOFTWARE);
if (ret != 0)
return (ret);
+ if (!(crypto_ses2caps(chacha20_poly1305_sid) & CRYPTOCAP_F_SYNC)) {
+ crypto_freesession(chacha20_poly1305_sid);
+ return (ENXIO);
+ }
return (0);
}
PATCH
Building and deploying
The jail already includes FreeBSD’s compiler and build tools, sufficient to rebuild the binaries affected by these two patches. Some other components need additional tools installed from packages; for example, the OpenZFS library discussed in Further Reading requires GNU make and autotools.1
Building
Build the kernel, WireGuard module, and RPCSEC_GSS library:
cd /usr/src
make buildkernel KERNCONF=SAVED_CONFIG MODULES_OVERRIDE=if_wg
make -C /usr/src/lib/librpcsec_gss
exit
Back on the host, stop the jail:
iocage stop build-jail
RPCSEC_GSS is compiled into the kernel; WireGuard is in if_wg.ko. The userspace library is librpcsec_gss.so.1.
Deploying
Clone the active environment as freebsd-patched in System → Boot and enable Keep for both environments. Mount the clone and make it available inside the build jail:
mkdir /mnt/kernel-patched
beadm mount freebsd-patched /mnt/kernel-patched
mount -t nullfs /mnt/kernel-patched /mnt/tank/iocage/jails/build-jail/root/mnt
iocage console -f build-jail
Inside the jail, install the rebuilt files. With the shown install options, the clone’s other modules remain in place. KERN_DEBUGDIR is set to an empty string to preserve TrueNAS’s layout, with debug files alongside their binaries:
cd /usr/src
make reinstallkernel KERNCONF=SAVED_CONFIG MODULES_OVERRIDE=if_wg KERN_DEBUGDIR= DESTDIR=/mnt
make -C /usr/src/lib/librpcsec_gss install LIBRARIES_ONLY=yes DESTDIR=/mnt
exit
Back on the host, unmount the clone from the jail and remove its copied reboot marker to avoid a false unscheduled-reboot alert:
umount /mnt/tank/iocage/jails/build-jail/root/mnt
rm /mnt/kernel-patched/data/sentinels/unscheduled-reboot
Unmount and activate the new environment:
beadm unmount freebsd-patched
midclt call bootenv.activate freebsd-patched
Reboot through the TrueNAS web interface.
Verifying
Check the kernel version and build date:
uname -a
Rolling back
In System → Boot, activate the previous environment and reboot. If the system fails to reach the web interface, restart it, interrupt startup at the console, and select the previous boot environment from the menu.
Further reading
Reviewing the FreeBSD security advisories, I found a number of interesting candidates worth backporting:
| Advisory | Released | Fix | Official patch |
|---|---|---|---|
| SA-24:14.umtx | 2024-09-04 | Access to freed shared locks | umtx.patch |
| SA-26:05.route | 2026-02-24 | Routing message length checks | route.patch |
| SA-26:10.tty | 2026-04-21 | Access to freed terminal memory | tty-13.patch |
| SA-26:13.exec | 2026-04-29 | Argument buffer size checks when starting a program | exec.patch |
| SA-26:19.file | 2026-05-20 | Process descriptor and netmap cleanup | file-14.patch |
| SA-26:21.ptrace | 2026-05-20 | Checks on system calls made through ptrace |
ptrace-14.3.patch |
| SA-26:25.thr | 2026-06-09 | Permissions for sending signals to threads | thr.patch |
| SA-26:40.zfs | 2026-06-30 | ZFS metadata permissions and buffer size checks | zfs-14.patch |
| SA-26:54.sysvsem | 2026-07-29 | Buffer size checks in semaphore operations | sysvsem.patch |
| SA-26:55.elf | 2026-07-29 | Buffer size checks when writing ELF core dumps | elf-14.patch |
| SA-26:56.hwpmc | 2026-08-25 | Performance counter permissions when starting a program | hwpmc-14.patch |
| SA-26:62.tty | 2026-08-25 | Access to a terminal after it closes | tty.patch |
| SA-26:64.sysvsem | 2026-09-29 | Overlapping semaphore operations and buffer size checks | sysvsem.patch |
I’ll leave the details as an exercise for the reader.
For the impatient
Again, impatient readers can use my combined patch, which contains all the patches above ported to 13.3, as not all of them applied cleanly, but they probably should not, at least not before thoroughly reviewing it.
The patch expects OS source at /usr/src and truenas/zfs@4f2aa13822f0db5e9b94a76f3a6b5165a282b30b at /usr/src/zfs. These binaries will be updated:
/boot/kernel/kernel/boot/kernel/kernel.debug/boot/kernel/if_wg.ko/boot/kernel/if_wg.ko.debug/boot/kernel/hwpmc.ko/boot/kernel/hwpmc.ko.debug/boot/kernel/netmap.ko/boot/kernel/netmap.ko.debug/boot/kernel/linker.hints/boot/modules/openzfs.ko/boot/modules/openzfs.ko.debug/boot/modules/linker.hints/usr/local/lib/libnvpair.so.3.0.0/usr/local/lib/libnvpair.so.3/usr/local/lib/libnvpair.so/usr/local/lib/libnvpair.a/usr/local/lib/libnvpair.la
Enter the build jail:
iocage console -f build-jail
Inside the jail, configure your package mirror1 and install the build tools:
exec sh
mkdir -p /usr/local/etc/pkg/repos
cat > /usr/local/etc/pkg/repos/FreeBSD.conf <<'EOF'
FreeBSD: {
url: "http://<mirror-ip>/13.3",
mirror_type: "none",
signature_type: "fingerprints",
fingerprints: "/usr/share/keys/pkg"
}
EOF
pkg install autoconf automake libtool pkgconf gettext-tools gmake
Download and unpack the ZFS source:
fetch -o /tmp/zfs.tar.gz https://codeload.github.com/truenas/zfs/tar.gz/4f2aa13822f0db5e9b94a76f3a6b5165a282b30b
mkdir -p /usr/src/zfs
cd /usr/src/zfs
tar -xf /tmp/zfs.tar.gz --strip-components=1
Download and apply the patch:
cd /usr/src
fetch -o /tmp/truenas-core-13.3-further-fixes.patch \
https://blog.arrogantrabbit.com/assets/patches/truenas-core-13.3-further-fixes.patch
patch -p1 < /tmp/truenas-core-13.3-further-fixes.patch
Rebuild the kernel with if_wg, hwpmc, and netmap:
cd /usr/src
make buildkernel KERNCONF=SAVED_CONFIG MODULES_OVERRIDE="if_wg hwpmc netmap"
Set the version headers and build the ZFS module with FreeBSD’s build tools:
cd /usr/src/zfs
cat > zfs_config.h <<'EOF'
#define ZFS_META_VERSION "2.2.5"
#define ZFS_META_RELEASE "4f2aa1382-patched"
EOF
sh scripts/make_gitrev.sh include/zfs_gitrev.h
cd /usr/src/zfs/module
make -f Makefile.bsd SYSDIR=/usr/src/sys
libnvpair contains the same code for reading lists of names and values. Rebuild it too; malformed data can otherwise make applications read past a buffer and crash.
cd /usr/src/zfs
./autogen.sh
env MAKE=gmake ./configure --with-config=user
gmake gitrev libnvpair.la
Mount the clone as shown under Deploying, then install the kernel, modules, and library inside the jail:
cd /usr/src
make reinstallkernel KERNCONF=SAVED_CONFIG MODULES_OVERRIDE="if_wg hwpmc netmap" KERN_DEBUGDIR= DESTDIR=/mnt
cd /usr/src/zfs/module
make -f Makefile.bsd install SYSDIR=/usr/src/sys KMODDIR=/boot/modules KERN_DEBUGDIR= DESTDIR=/mnt
cd /usr/src/zfs
gmake install-libLTLIBRARIES lib_LTLIBRARIES=libnvpair.la DESTDIR=/mnt
Exit the jail, then unmount and activate the clone as shown under Deploying. Reboot to use the rebuilt kernel, modules, and library.
Footnotes
-
Setup your own package mirror for future builds; external mirrors can drop unsupported FreeBSD releases at any time. ↩︎ ↩︎