⬢ DragonFlyBSD Kernel Audit
DF-3181 / umount_dataloss.sh
← back to finding ↓ download raw
#!/bin/sh
# DF-3181 — failed (non-forced) umount of tmpfs destroys regular-file data.
#
# tmpfs_unmount (sys/vfs/tmpfs/tmpfs_vfsops.c:284-334) truncates EVERY VREG
# node to length 0 BEFORE calling vflush().  If vflush() then fails (EBUSY —
# a vnode still referenced), unmount returns the error and the filesystem
# stays mounted with every file's contents already gone.
#
# Note: dounmount() only reaches VFS_UNMOUNT when the mount's namecache refs
# are drained (kern/vfs_syscalls.c dounmount retry loop), so the trigger
# must keep the vnode busy WITHOUT a namecache entry: an open fd on an
# already-UNLINKED file.
#
# Expect: umount exits EBUSY; reading the still-open fd then returns EOF
#         (size 0) although the mount survived and the fd was never
#         truncated by the application.
set -e
MNT=/mnt/df3181
FIFO=/tmp/df3181_report

mkdir -p $MNT
mount -t tmpfs dummy $MNT
dd if=/dev/urandom of=$MNT/file bs=1m count=4 2>/dev/null
echo "created: $(ls -l $MNT/file | awk '{print $5}') bytes"

rm -f $FIFO
mkfifo $FIFO
# holder: open the file, then unlink it, keep fd; report remaining bytes on
# request over the fifo
( exec 3<$MNT/file; rm -f $MNT/file; echo holder-ready > $FIFO; \
  while :; do read line < $FIFO; \
    n=$(dd bs=64k count=256 <&3 2>/dev/null | wc -c); \
    echo "still-readable-bytes=$n" > $FIFO; done ) &
HOLDER=$!
read x < $FIFO
echo "holder: $x (file unlinked, fd open)"

set +e
umount $MNT
RC=$?
set -e
echo "umount rc=$RC (nonzero == EBUSY family)"
echo "mount still present: $(mount | grep -c $MNT)"

echo poll > $FIFO
read y < $FIFO
echo "after failed umount: $y"

kill $HOLDER 2>/dev/null || true
wait $HOLDER 2>/dev/null || true
rm -f $FIFO
umount $MNT 2>/dev/null || true
rmdir $MNT 2>/dev/null || true