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


  1. Setup your own package mirror for future builds; external mirrors can drop unsupported FreeBSD releases at any time. ↩︎ ↩︎