← Back to Blogs
HN Story

Deep Dive into FatGid: Local Privilege Escalation in FreeBSD 14.x

May 21, 2026

Deep Dive into FatGid: Local Privilege Escalation in FreeBSD 14.x

A critical vulnerability, dubbed FatGid, has been uncovered in the FreeBSD 14.x kernel. This flaw allows an unprivileged local user to achieve full Local Privilege Escalation (LPE), granting them root access to the system. The vulnerability is particularly dangerous because it occurs before any privilege checks are performed, meaning any user on the system can trigger the exploit.

This post provides a technical breakdown of the root cause, the exploitation chain—including bypasses for modern mitigations like SMAP and SMEP—and the current status of the fix.

The Root Cause: A sizeof Typo

The vulnerability resides in sys/kern/kern_prot.c within the function kern_setcred_copyin_supp_groups(). The issue stems from a simple but catastrophic type error involving the sizeof operator.

The Type Mismatch

The function signature defines the groups argument as a double pointer:

static int
kern_setcred_copyin_supp_groups(struct setcred *const wcred,
    const u_int flags, gid_t *const smallgroups, gid_t **const groups)

Because groups is a gid_t **, the expression sizeof(*groups) evaluates to the size of a pointer (sizeof(gid_t *)), which is 8 bytes on an LP64 system. However, the intended logic required the size of a group ID (sizeof(gid_t)), which is only 4 bytes.

The Overflow Mechanism

This error manifests in two critical areas:

  1. Allocation: On the heap path, the allocation is 2× oversized, which is generally safe.
  2. Copyin: When the number of suppressed groups (sc_supp_groups_nb) is less than CRED_SMALLGROUPS_NB (16), the kernel uses a stack-allocated array smallgroups in the caller function user_setcred().

In this scenario, the destination for copyin is &smallgroups[1], leaving 60 bytes of usable space. However, the kernel calculates the amount of data to copy as sc_supp_groups_nb * sizeof(*groups). With a maximum value of 15 for sc_supp_groups_nb, the kernel attempts to write 120 bytes (15 * 8) into a 60-byte buffer.

This results in a 60-byte stack buffer overflow using attacker-controlled data from user space.

Exploitation Paths

Because the overflow occurs before the priv_check_cred(PRIV_CRED_SETCRED) call, any local user can trigger it by calling setcred(2) with a specifically crafted wcred structure.

Legacy Kernels (No SMAP/SMEP)

On older kernels without Supervisor Mode Access Prevention (SMAP) or Supervisor Mode Execution Prevention (SMEP), the exploit is straightforward. The overflow corrupts callee-saved registers on the stack, specifically r12.

When user_setcred() returns, the corrupted r12 propagates up to amd64_syscall(). At offset +0x155, the kernel uses r12 as a td_proc pointer to perform a two-level indirect call: *(r12+0x3f8) provides a pointer, and the offset +0xc8 from that pointer is the final call target. Without SMAP/SMEP, the attacker can place fake structures and shellcode in user-mode memory, which the kernel will happily execute to zero out the process's UIDs and GIDs.

Modern Kernels (SMAP/SMEP Enabled)

Bypassing SMAP and SMEP requires a more sophisticated chain that avoids executing user-space code and avoids direct user-space memory access.

  1. The Gadget: The exploit leverages a gadget in zfs.ko (ZSTD_initCStream_advanced). This gadget can be manipulated to write a value (K1 + 1) into td->td_ucred.
  2. Fake Credentials: The attacker uses setproctitle(2) to plant a fake ucred structure in the kernel's PARGS UMA zone. By carefully crafting the cr_uid and cr_ruid fields to 0, the process effectively becomes root.
  3. The K1 Pointer: To point the kernel toward the fake credentials, the attacker forks a child process and uses setproctitle(2) again to place the value P_base - 1 (where P_base is the address of the fake credentials) into the child's pargs slab. This provides the K1 value needed for the gadget to set the thread's credentials to the fake root structure.

Impact and Affected Versions

Version Status Note
FreeBSD 13.x and earlier Not Affected setcred(2) was not present.
FreeBSD 14.4-RELEASE Vulnerable Fully exploitable for LPE.
FreeBSD stable/14 Vulnerable Fully exploitable for LPE.
FreeBSD 15.0 Vulnerable Typo exists, but results in kernel panic rather than LPE.
FreeBSD main Fixed Fixed in commit 000d5b5 (2025-11-27).

As noted by community members, this vulnerability has significant implications for operators of TrueNAS and various network appliances that rely on the FreeBSD kernel.

Remediation

The vulnerability is tracked as CVE-2026-45250 (FreeBSD-SA-26:18.setcred).

Immediate Action: Users running FreeBSD 14.4-RELEASE or stable/14 should apply the security updates provided by the FreeBSD Security Team. If an official patch is not yet available for your specific build, the recommended mitigation is to cherry-pick commit 000d5b5 into the local kernel tree and rebuild the kernel.

There is no effective userland mitigation (such as restricting setuid bits), as the exploit runs entirely within the kernel and targets a system call available to all users.

References

HN Stories