Updates the Linux kernel to the latest 6.12 LTS release (v6.12.91). This update mitigates several kernel vulnerabilities, including the Dirty-Frag local privilege escalation (CVE-2026-43284 and CVE-2026-43500), the CIFSwitch local privilege escalation in the CIFS client (CVE-2026-46243), a ptrace privilege issue (CVE-2026-46333), and the related Fragnesia privilege escalation in the ESP-in-TCP path (CVE-2026-46300). It also enables additional CPU side-channel mitigations.
Updates Samba to version 4.22.10 to address multiple security vulnerabilities. This Samba maintenance release resolves several CVEs, including a missing access check that let read-only users set or delete reparse point attributes (CVE-2026-1933) and a flaw in the WORM (Write Once, Read Many) module that allowed protected files to be overwritten by renaming a new file over them (CVE-2026-2340). See the Samba 2026 security release impact statement for the TrueNAS-specific impact assessment, or the Samba 4.22.10 release notes for the complete upstream list.
Fixes a potential double free when freeing blocks cloned after deduplication table pruning. Blocks created through block cloning could be freed more than once if their deduplication table (DDT) entries had already been pruned, because the free path did not check the block reference table (BRT) first.
Fixes virtual machines stored on NFSv4.1 datasets failing to power on. A change introduced in 25.10.2.1 could cause the NFSv4 change cookie to move backward after a file left the cache due to memory pressure, a remount, or a reboot. NFSv4.1 clients that depend on a monotonic change cookie, notably VMware ESXi, rejected the affected files. A virtual machine stored on an NFSv4.1-exported ZFS dataset then failed to power on with the error “The file specified is not a virtual disk.” This release reverts that change while a complete fix is finalized upstream.
Fixes a kernel crash in the iSCSI target layer during SCSI bus or LUN resets. A use-after-free in the clustered locking path of the iSCSI target could crash the system during a SCSI reset, most often on Enterprise High Availability (HA) systems while a peer controller was leaving the cluster. The target layer now waits for lock teardown to complete before releasing the associated memory.
Improves iSCSI LUN replacement during High Availability failover. On Enterprise HA systems, cleanup of a replaced LUN could stall during failover while a peer controller was being evicted from the cluster, which blocked later LUN replacements. Cleanup is now held until the cluster coordination it depends on has finished.
Fixes a validation error that blocked static network configuration on some High Availability systems. On HA-capable systems, saving a static network configuration could incorrectly fail with “Enabling DHCPv4/v6 on HA systems is unsupported” even when DHCP was not being enabled. This affected fresh installs before any interface had a saved configuration. The check now triggers only when DHCP or IPv6 autoconfiguration is explicitly enabled.
Improves Active Directory rejoin, reset, and recovery handling. This release hardens the Active Directory rejoin and directory services reset operations, improves domain controller selection on systems with more than one available controller, and produces clearer diagnostics when a join or authentication problem occurs.
Fixes ZFS automatic snapshots not being created after a Time Machine backup until the Mac reconnects. When a Time Machine SMB share has automatic snapshots enabled, recent macOS versions (Tahoe and later) sometimes keep the SMB session open after a backup completes instead of disconnecting, which prevented the post-backup ZFS snapshot from being taken until the client reconnected or restarted. The snapshot logic is updated to handle these persistent Time Machine sessions.
Reduces excessive winbind log messages for failed user and group lookups. When Active Directory is enabled, looking up a user or group that does not exist (for example through getpwnam or getgrnam) generated a warning for every failed lookup, which could rapidly fill the winbind log. These messages are now logged at informational level instead of as warnings.
The server runs TrueNAS SCALE (release 26.0.0-BETA.1) on an AMD Ryzen 7 PRO 8845HS, with 32 GiB of memory and no swap configured. Alongside ZFS it hosts a substantial collection of services including Portainer-managed containers, immich, Forgejo, FreshRSS, Jellyfin an automation platform, several PostgreSQL databases, an identity provider, and a number of smaller tools. At the moment of measurement, the operating system reported the following:
total used free buff/cache available
Mem: 30Gi 27Gi 1.2Gi 3.8Gi 3.3Gi
Uptime stats:
The following figures come from /proc/spl/kstat/zfs/arcstats, accumulated over an uptime of 53 days:
Metric
Value
ARC size
5.04 GiB
ARC target (c)
5.09 GiB
Maximum (c_max)
29.68 GiB
Minimum (c_min)
0.96 GiB
L2ARC
none configured
The hit and miss rates, derived from the raw counters, break down as follows:
Access class
Hit rate
Miss rate
Overall
96.4%
3.6%
Demand data
98.05%
1.95%
Demand metadata
97.91%
2.09%
Prefetch data
6.0%
94.0%
Prefetch metadata
66.9%
33.1%
Live view of hit/miss performance:
Because the counters above are cumulative since boot, they represent an average spanning nearly two months and cannot, on their own, describe the system’s present behaviour. To capture that, I sampled the cache once per second under active load using arcstat:
With reads peaking at roughly 1500 per second, the demand-data hit rate held steady at approximately 98% matching that of the uptime, thus confirming that the long-term average is not concealing a recent decline in performance. The cache is presently serving requests just as effectively as it has, on average, throughout its uptime.
ddh% : Demand-data hit
dmh% : Demand-metadata hit
Stats were obtained from /proc/spl/kstat/zfs/arcstats, with live sampling via arcstat.
The equivalent figures are available on FreeBSD under kstat.zfs.misc.arcstats.
RAM prices are getting high, so I set up a self-hosted price tracking service called PriceBuddy to monitor specific products on Amazon.de, Amazon.com, and Geizhals.
I also built a SeleniumBase scraper to parse data from the Geizhals website.
In April I expanded my main pool from four-wide RAIDZ2 to five-wide by adding a single 10 TB Seagate IronWolf to four existing 4 TB WD Red Plus drives. OpenZFS 2.3+ supports RAIDZ expansion: the new column gets added, the existing data keeps its old parity layout until rewritten, and the pool stays online throughout. The expansion completed normally.
About five weeks later, a scrub showed the following CKSUM error:
$ sudo zpool status zfs_tank
pool: zfs_tank
state: ONLINE
status: One or more devices has experienced an unrecoverable error. An
attempt was made to correct the error. Applications are unaffected.
action: Determine if the device needs to be replaced, and clear the errors
using 'zpool clear' or replace the device with 'zpool replace'.
config:
NAME STATE READ WRITE CKSUM
zfs_tank ONLINE 0 0 0
raidz2-0 ONLINE 0 0 0
6a169351-6031-41d5-ad2a-9681142190c5 ONLINE 0 0 0
a006e053-c865-4330-8861-e21e4a3e37a6 ONLINE 0 0 0
b7d78b79-cb70-4afe-b9d0-8e4b2282fb18 ONLINE 0 0 0
707c32af-4e4e-4fc7-b000-dd5b52f75158 ONLINE 0 0 1
ff3c3a00-9f71-4b6b-87e6-c56deb4c6854 ONLINE 0 0 1
errors: No known data errors
One checksum error on each of two disks. The pool itself reports zero errors: errors: No known data errors. So no data were degraded.
RAIDZ2 carries two parity columns, the scrub detected bad blocks, and ZFS reconstructed them from parity so the pool status remains healthy. But zpool status only tells you that an error happened and not if it got corrected.
So where is the healing actually recorded?
What zpool status shows, and what it doesn’t
The four columns in zpool status map directly to four counters in the kernel’s vdev_stat_t structure (include/sys/vdev_impl.h):
vs_read_errors
vs_write_errors
vs_checksum_errors
the implicit STATE
zpool status parses each leaf vdev’s stats and prints those four numbers. It does not print any of the other ~30 fields in the structure — including this one:
uint64_t vs_self_healed; /* total bytes self-healed */
vs_self_healed is incremented in vdev_stat_update() whenever ZFS issues a write with the ZIO_FLAG_SELF_HEAL flag set, which happens after a successful parity reconstruction. The kernel knows exactly how many bytes were healed on each disk. It just doesn’t tell you via the standard zpool status output.
Three places the heal counter does surface
1. Raw kstats (Linux only)
The OpenZFS Linux module exposes every leaf vdev’s full vdev_stat_t under /proc/spl/kstat/zfs/<pool>/. The filenames use the leaf vdev GUID. Pull those GUIDs out of the pool query:
$ sudo ls /proc/spl/kstat/zfs/zfs_tank/ | head
io
state
txgs
vdev_395717205876781294
vdev_4003307236673040230
vdev_7306733904703790705
vdev_803393823450321549
vdev_9021081546382363770
The 9021... and 4003... files are the two disks with errors. Inside:
self_healed 4096 — four kilobytes. The pool’s ashift is 12, so one block. Exactly one block was reconstructed and rewritten on this disk. Same value on the other affected disk.
2. The TrueNAS middleware API
If you’re on TrueNAS, the same field comes back as JSON from the pool.query endpoint:
{
"name": "707c32af-4e4e-4fc7-b000-dd5b52f75158",
"stats": {
"checksum_errors": 1,
"self_healed": 4096,
"read_errors": 0,
"write_errors": 0
}
}
This is how I first saw the number. The middleware just unpacks vdev_stat_t into JSON.
3. zpool events — the actual heal log
Counters tell you how much. To see when and where, look at the ZFS event ring buffer:
Each event names the affected disk, the byte offset on that disk, the size (here 0x1000 = 4 KiB), the dataset and object, and both checksums. With this you can compute exactly which file (if any) the block belonged to. The buffer holds ~1000 events by default (zfs_zevent_len_max), so old events roll out unless ZED has persisted them to /var/log/zfs/zed.log.
This is the closest thing ZFS has to a “self-heal log.”
What I actually had
Two disks, one block each, both healed. Different manufacturers (WD Red Plus 4 TB / Seagate IronWolf 10 TB), different uptime (6124 h / 3724 h), so a shared hardware fault was unlikely. SMART on both was clean:
Same story on the Seagate. No reallocated sectors, no pending sectors, no UDMA CRC errors.
UDMA_CRC_Error_Count is the SATA-link error counter. If a cable, backplane, or HBA channel is marginal, this is where it shows up. Both at zero rules out the data path between disk and controller.
What it doesn’t rule out is RAM. This system runs 32 GB of non-ECC DDR5. A single bit-flip in a write buffer leaves a permanently-bad block on disk that scrubs will keep detecting and healing on every pass. The block stays wrong because the heal write reads the (correct) reconstructed buffer from the same RAM that may flip again. Without ECC, you can’t fully exclude this; with non-ECC, you also can’t measure it.
zpool clear vs zpool scrub
The counters in zpool status and vs_self_healed are cumulative since the last zpool clear (or since pool creation). A scrub does not reset them.
So when I ran a scrub after the original event, the 1s in the CKSUM column did not go away — they were the same 1s from before.
$ sudo zpool clear zfs_tank
$ sudo zpool status zfs_tank
pool: zfs_tank
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
zfs_tank ONLINE 0 0 0
...
errors: No known data errors
The status line and the per-disk counters reset together. vs_self_healed resets too. After that, the next scrub starts from zero — if the same blocks show up healed again, you know the corruption is persistent on-disk (and the suspicion shifts toward RAM); if they don’t, the original event was probably a one-shot.
After the zpool clear
V Series includeing V160 and TrueNAS 25.10.3 hotfix.
This is a guide of the steps I followed to downgrade from TrueNAS 26 MASTER to TrueNAS 26 BETA1. (specifically from 26.0.0-MASTER+20260405-020459 to 26.0.0-BETA.1)
TrueNAS considers MASTER a higher train than BETA. Attempting a manual update from MASTER to BETA.1 gives:
Unable to downgrade from 26.0.0-MASTER+20260405-020459 to 26.0.0-BETA.1