OS X and the Unremovable File
I’ve been working on a Fuse filesystem project for quite some time. Now that OS X v10.9 is out, one of my major roadblocks has been removed. But I’ve still been experiencing some difficulty getting OS X to treat my filesystem like a first-class citizen. So to debug it I’ve been banging on it with Tuxera’s port of Pawel Jakub Dawidek’s POSIX file system test suite.
During this process, I made some baselines of my non-FUSE HFS+ boot volume and I came across a “fun” HFS+ surprise:
An Unremovable File
On an HFS+ filesystem you can make a symlink that can’t (easily) be removed…
# be root, for example with: sudo -i
str1=$(python -c "print '1' * 255")
str2=$(python -c "print '2' * 255")
str3=$(python -c "print '3' * 255")
str4=$(python -c "print '4' * 253")
mkdir -p $str1/$str2/$str3/$str4
ln -s ftw $str1/$str2/$str3/$str4/L
Now we’ve created a tree that can’t be removed. None of these commands will work on OS X v10.9:
# still as root...
unlink 1*/2*/3*/4*/L
unlink $str1/$str2/$str3/$str4/L
rm -rf 1*
rm -rf $str1
rm -rf $str1/$str2/$str3/$str4
rm -rf $str1/$str2/$str3/$str4/L
(cd $str1/$str2/$str3/$str4; unlink L)
(cd $str1/$str2/$str3/$str4; rm -rf L)
They all boil down to the following error. (Note that I’m abbreviating the path components with “[ … ]” here for blog readability)
root# pwd
/private/tmp/111[ ... ]111/222[ ... ]222/333[ ... ]333/444[ ... ]444
root# ls
L
root# rm -f L
rm: L: No space left on device
root# df -H
Filesystem Size Used Avail Capacity iused ifree %iused Mounted on
/dev/disk1 250G 108G 142G 44% 26385563 34601956 43% /
[...]
To make sure this is really happening, I’ve made a quick program to make the direct syscall. I’ll place it at /tmp/fixit.c
:
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char* argv[]) {
printf("Unlink returned %i\n", unlink("L"));
perror("Error was");
return 0;
}
Now to run it:
root# pwd
/private/tmp/111[ ... ]111/222[ ... ]222/333[ ... ]333/444[ ... ]444
root# gcc -o /tmp/fixit /tmp/fixit.c
root# /tmp/fixit
Unlink returned -1
Error was: No space left on device
ENOSPC? Guess which error you can’t find mentioned in the OS X unlink(2) man page?
It’s complicated:
- If a regular user creates this tree, they can easily remove it with
rm -rf
- If a regular user creates the tree, root cannot remove it. Weird!
- If root creates the tree, root cannot remove it
- If root creates the tree, typical permissions prevent regular users from removing it
- If root creates the tree, chown/chmod can change the protection of the containing directories so that a regular user can remove the entire tree
- If root creates the tree,
chmod -h
andchown -h
on the link return ENOSPC - If root creates the tree, this works:
mkdir -p some/containing/paths; mv 1111* some/containing/paths/
BUT afterward this doesn’t work:rm -rf some
(it returns “directory not empty” and ENOSPC errors)
Workaround
Somehow the path is short enough to create a symlink but too long to remove it. The workaround is just to shorten the path.
root# pwd
/private/tmp/111[ ... ]111/222[ ... ]222/333[ ... ]333/444[ ... ]444
root# ls
L
root# mv /private/tmp/1* /private/tmp/one
root# pwd
/private/tmp/one/222[ ... ]222/333[ ... ]333/444[ ... ]444
root# rm L
root# ls
root# rm -rf /tmp/one
root#
Is The Workaround Enough?
So yeah, there’s a manual workaround, but here are some questions:
- Is your antivirus program smart enough employ it if malware is stored in this manner? AV programs already have to deal with chflags(2) issues. Imagine a file stored in such a path combined with hostile file names and various applications of chflags.
- Do you have a replacement tool for
rm -rf
if a malicious program or individual starts to fill your filesystem with these things?