On 25/03/2025 14:19, Ethan Heilman wrote:
Howdy coreutils,
I would like to add a O_NOFOLLOW flag to cat so that users can specify
if cat should follow a symlink at the final file in a path. This is
motivated by the need to atomically read a file while ensuring it is
not a symlink. This functionality is absent from coreutils, as far as
I am aware.
I’d be grateful for any feedback, criticism, ideas on this proposed
change. Is this something coreutils would be interested in merging if
I wrote code + tests.
Rationale
====
Without this functionality in cat, users are likely to do the next
easiest option which is to use two separate commands to first check if
the file is a symlink and then read the file. This can be a source of
bugs and harm security because using two commands introduces a TOCTOU
(Time-of-Check to Time-of-Use) issue. This is because it is always
possible after the symlink check has occurred, but before cat is run
the file is replaced with symlink. Such race condition bugs are
especially dangerous because the harmful behavior is triggered only in
rare circumstances, allowing them to enter production and be hard to
debug.
When might one encounter this:
1. Having privileged services or sudoer commands where a symlink be
might be maliciously or accidentally substituted as the final file in
the path (often called the confused deputy problem[0]).
`serviceuser ALL=(ALL) NOPASSWD: cat /home/[0-9a-z_-]/file123`
2. Different scripts or users reading and writing to a shared
directory. This need not be a security issue. If files are being
created and destroyed frequently, a user may just wish to ensure they
don’t accidentally read from a symlink when they don’t expect the file
to be a symlink. For instance ensuring that a read does not leave the
current directory.
This is not a problem for directories because one can check if a
directory is a symlink safely with any later action because you can
compare the expected file path after traversing into that directory
but before taking the action. This is safe because someone can’t
change the directory you are already in into a symlink after you have
transverse it. Thus, this change would strictly maintain the behavior
of the O_NOFOLLOW flag in following directory symlinks prior to the
last part of a path.
While this can be solved by creating a binary or using python, I
believe enabling reads to be atomic with symlink checks is an
important building block that should be available at the level of
coreutils. Allowing cat to support O_NOFOLLOW via a flag would be a
simple way to add this functionality.
Thanks for taking the time to read this,
Ethan
[0]: https://en.wikipedia.org/wiki/Confused_deputy_problem
Would dd suffice for such fine grained control?
$ dd iflag=nofollow if=symlink
dd: failed to open 'symlink': Too many levels of symbolic links
cheers,
Pádraig