[BUG] fs/ntfs3: hard-linking an ADS leaves stale directory entries

From: shaos

Date: Mon Oct 19 2026 - 20:39:57 EST


Hello,

ntfs_link() accepts a stream inode and stores the new $FILE_NAME in its base MFT record, while the VFS dentry is attached to the ADS inode. A subsequent unlink follows the ADS removal path, deletes the stream, and leaves the directory entry behind. After the base name is removed, revalidating and unlinking the stale name calls drop_nlink() on a zero link count and wraps i_nlink to 0xffffffff.

HEAD commit: fe2ec83746e501645709761605c2464a44fd2929
git tree: upstream Linux checkout, local branch master
source version: Linux 7.3.0-rc4-70-gfe2ec83746e50
compiler: gcc 11.4.0, GNU ld 2.38 (from the kernel build banner)

console output:
https://gist.githubusercontent.com/shaos26/894a30b9b56d66f7eb8d82c79f2d0072/raw/1eb1367ed38b55daf97baa8ce89ec16458e0c2f8/console-output.txt
kernel config:
https://gist.githubusercontent.com/shaos26/894a30b9b56d66f7eb8d82c79f2d0072/raw/48442967d9156a166f389d2713eebeabcffacf32/kernel-config.txt
reproducer:
https://gist.githubusercontent.com/shaos26/894a30b9b56d66f7eb8d82c79f2d0072/raw/e74225d4e25db01c554ff2de341139708b8273ff/poc.c

Run in a disposable VM with a read-write NTFS3 mount. The PoC creates the base file and ADS, hard-links the stream, removes names, drops the dcache as root, and checks the resulting link count:

dd if=/dev/zero of=/tmp/probe.img bs=1M count=64 status=none
mkntfs -F -q -L TEST -c 4096 -s 512 /tmp/probe.img
mkdir -p /mnt
mount -t ntfs3 -o loop,ads,rw /tmp/probe.img /mnt
gcc -static -O2 -Wall -Wextra -o ads_link_nlink poc.c
./ads_link_nlink
dmesg | tail -50
umount /mnt

Two fresh-image runs produced the same result: unlinking the hard link removed the named $DATA stream but left its $FILE_NAME on disk; a later unlink warned in drop_nlink() and changed i_nlink from 0 to 0xffffffff. A plain hard-link control reached nlink 0 and ENOENT with no warning.

Impact and prerequisites:
A local user able to write to a read-write NTFS3 directory can destroy an ADS and leave stale on-disk metadata. With forced cache revalidation the kernel also reports a VFS link-count invariant warning. No memory corruption or privilege escalation is demonstrated.

Validation: Two fresh positive boots reproduced the stale directory entry and link-count underflow. A plain hard-link control reached nlink 0 and ENOENT without a warning.

Thanks,

shaos