System.Directory.removeDirectoryRecursive and symlinks

Hi all, A crime scene: Prelude System.Directory> :!mkdir a-directory Prelude System.Directory> :!touch a-directory/a-file.txt Prelude System.Directory> :!ln -s "a-directory" "a-symlink-to-a-directory" Prelude System.Directory> :!ls a-directory a-file.txt Prelude System.Directory> :!ls a-symlink-to-a-directory a-file.txt Prelude System.Directory> removeDirectoryRecursive "a-symlink-to-a-directory" *** Exception: a-symlink-to-a-directory: removeDirectory: inappropriate type (Not a directory) Prelude System.Directory> :!ls a-symlink-to-a-directory Prelude System.Directory> :!ls a-directory Prelude System.Directory> :!ls -a a-directory . .. Prelude System.Directory> :!ls -a a-symlink-to-a-directory . .. Prelude System.Directory> removeDirectoryRecursive is removing all contents *of the directory linked* but is unable to remove the symlink itself. This behavior is surprizing if not dangerous. I understand that this mimics behavior of unlink/rmdir and DeleteFile/RemoveDirectory. but let me quote relevant manuals: man rm: The rm utility removes symbolic links, not the files referenced by the links. DeleteFile docs: If the path points to a symbolic link, the symbolic link is deleted, not the target. To delete a target, you must call CreateFile and specify FILE_FLAG_DELETE_ON_CLOSE. RemoveDirectory removes a directory junction, even if the contents of the target are not empty; the function removes directory junctions regardless of the state of the target object. Note: doesDirectoryExist and doesFileExist follow symlinks so they add more surprize to the scenario. What can we do about this? -- Gracjan

The documentation also says "Be careful, if the directory contains symlinks, the function will follow them.", which is dangerous and inappropriate but thankfully not actually what removeDirectoryRecursive does (based on testing and also on reading the code). That statement should be removed from the documentation. I'm indifferent on whether the argument path itself should be able to be a symlink to a directory, and if so, whether the target directory and/or the symlink should be removed, and whether this should differ based on whether the path ends in a "/" or not. (Many Unix operations on symlinks, like `ls`, do differ based on a trailing slash. `rm -rf symlink` removes just the symlink; `rm -rf symlink/` appears to remove the contents of the target directory but neither the symlink nor the target directory itself...) -Isaac On 06/10/2014 09:42 AM, Gracjan Polak wrote:
Hi all,
A crime scene:
Prelude System.Directory> :!mkdir a-directory Prelude System.Directory> :!touch a-directory/a-file.txt Prelude System.Directory> :!ln -s "a-directory" "a-symlink-to-a-directory" Prelude System.Directory> :!ls a-directory a-file.txt Prelude System.Directory> :!ls a-symlink-to-a-directory a-file.txt Prelude System.Directory> removeDirectoryRecursive "a-symlink-to-a-directory" *** Exception: a-symlink-to-a-directory: removeDirectory: inappropriate type (Not a directory) Prelude System.Directory> :!ls a-symlink-to-a-directory Prelude System.Directory> :!ls a-directory Prelude System.Directory> :!ls -a a-directory . .. Prelude System.Directory> :!ls -a a-symlink-to-a-directory . .. Prelude System.Directory>
removeDirectoryRecursive is removing all contents *of the directory linked* but is unable to remove the symlink itself.
This behavior is surprizing if not dangerous. I understand that this mimics behavior of unlink/rmdir and DeleteFile/RemoveDirectory. but let me quote relevant manuals:
man rm: The rm utility removes symbolic links, not the files referenced by the links.
DeleteFile docs: If the path points to a symbolic link, the symbolic link is deleted, not the target. To delete a target, you must call CreateFile and specify FILE_FLAG_DELETE_ON_CLOSE.
RemoveDirectory removes a directory junction, even if the contents of the target are not empty; the function removes directory junctions regardless of the state of the target object.
Note: doesDirectoryExist and doesFileExist follow symlinks so they add more surprize to the scenario.
What can we do about this?

Isaac Dupree
That statement should be removed from the documentation.
https://github.com/haskell/directory/pull/1
Many Unix operations on symlinks, like `ls`, do differ based on a trailing slash. `rm -rf symlink` removes just the symlink; `rm -rf symlink/` appears to remove the contents of the target directory but neither the symlink nor the target directory itself...)
Sounds like a very reasonable idea and has prior art. -- Gracjan

On 06/10/2014 11:37 AM, Gracjan Polak wrote:
Isaac Dupree
writes: Many Unix operations on symlinks, like `ls`, do differ based on a trailing slash. `rm -rf symlink` removes just the symlink; `rm -rf symlink/` appears to remove the contents of the target directory but neither the symlink nor the target directory itself...)
Sounds like a very reasonable idea and has prior art.
Should removeDirectoryRecursive also remove regular files when called on a file? That's what `rm -r` does, but it might not be what we want `removeDirectoryRecursive` to do. -Isaac

Isaac Dupree
Should removeDirectoryRecursive also remove regular files when called on a file? That's what `rm -r` does, but it might not be what we want `removeDirectoryRecursive` to do.
Easiest would be to removeLink, if that fails due to directory then empty directory and then removeDirectory, but unlink man page says: [EPERM] The named file is a directory and the effective user ID of the process is not the super-user. So unlink seems to sometimes be able to unlink directories. What happens to such directories is not know to me. I also wasn't able to sucessfully unlink directory, so hard to say what is going on. (Note that this bug is also present in current implementation that recurses, tries removeFile, then getDirectoryContents, then removeDirectory). Who would have thought that removing directory would be that hard? -- Gracjan

Gracjan Polak
Who would have thought that removing directory would be that hard?
http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/remove.c -- Gracjan

On Tue, Jun 10, 2014 at 3:19 PM, Gracjan Polak
Easiest would be to removeLink, if that fails due to directory then empty directory and then removeDirectory, but unlink man page says:
[EPERM] The named file is a directory and the effective user ID of the process is not the super-user.
So unlink seems to sometimes be able to unlink directories. What happens to
unlink(2) can't unlink directories except as root, plus you have to follow certain specific rules. rmdir(2) can. This is a historical thing. (You must manually unlink(2) the '.' and '..' entries along with everything else in the directory before you can unlink(2) the directory itself. except on systems where this functionality was completely blocked in order to make you use rmdir(2). This behavior is how directories worked in 7th research edition and earlier, and AT&T System III; the syscalls were added primarily for the benefit of NFS so that directory creation/removal would be atomic and not involve root permissions vs. root squashing.) -- brandon s allbery kf8nh sine nomine associates allbery.b@gmail.com ballbery@sinenomine.net unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net

Following symlinks can be potentially dangerous/security exploit: symlink to '/' can be created by non-priviliged user and then removed using removeDirectoryRecursive called by priviliged user (example: root deleting user account & directory). Catastrophic system damage can follow. Also might be used for selective attacks, e.g. deleting files that contain firewall settings or something alike. All best, Krzysztof Skrzętnicki On Tue, Jun 10, 2014 at 5:23 PM, Isaac Dupree < ml@isaac.cedarswampstudios.org> wrote:
The documentation also says "Be careful, if the directory contains symlinks, the function will follow them.", which is dangerous and inappropriate but thankfully not actually what removeDirectoryRecursive does (based on testing and also on reading the code). That statement should be removed from the documentation.
I'm indifferent on whether the argument path itself should be able to be a symlink to a directory, and if so, whether the target directory and/or the symlink should be removed, and whether this should differ based on whether the path ends in a "/" or not. (Many Unix operations on symlinks, like `ls`, do differ based on a trailing slash. `rm -rf symlink` removes just the symlink; `rm -rf symlink/` appears to remove the contents of the target directory but neither the symlink nor the target directory itself...)
-Isaac
On 06/10/2014 09:42 AM, Gracjan Polak wrote:
Hi all,
A crime scene:
Prelude System.Directory> :!mkdir a-directory Prelude System.Directory> :!touch a-directory/a-file.txt Prelude System.Directory> :!ln -s "a-directory" "a-symlink-to-a-directory" Prelude System.Directory> :!ls a-directory a-file.txt Prelude System.Directory> :!ls a-symlink-to-a-directory a-file.txt Prelude System.Directory> removeDirectoryRecursive "a-symlink-to-a-directory" *** Exception: a-symlink-to-a-directory: removeDirectory: inappropriate type (Not a directory) Prelude System.Directory> :!ls a-symlink-to-a-directory Prelude System.Directory> :!ls a-directory Prelude System.Directory> :!ls -a a-directory . .. Prelude System.Directory> :!ls -a a-symlink-to-a-directory . .. Prelude System.Directory>
removeDirectoryRecursive is removing all contents *of the directory linked* but is unable to remove the symlink itself.
This behavior is surprizing if not dangerous. I understand that this mimics behavior of unlink/rmdir and DeleteFile/RemoveDirectory. but let me quote relevant manuals:
man rm: The rm utility removes symbolic links, not the files referenced by the links.
DeleteFile docs: If the path points to a symbolic link, the symbolic link is deleted, not the target. To delete a target, you must call CreateFile and specify FILE_FLAG_DELETE_ON_CLOSE.
RemoveDirectory removes a directory junction, even if the contents of the target are not empty; the function removes directory junctions regardless of the state of the target object.
Note: doesDirectoryExist and doesFileExist follow symlinks so they add more surprize to the scenario.
What can we do about this?
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries
participants (4)
-
Brandon Allbery
-
Gracjan Polak
-
Isaac Dupree
-
Krzysztof Skrzętnicki