SSHFS always follows symbolic links (even for eg. lchown)
https://pastebin.com/fH356wkk
when symlink is copied over SSHFS it results in error "Permission denied" (if file exists on host and sshfs user doesn't have permission to it) or "No such file or directory" (if it doesn't) while performing the same operation on local filesystem results with success. It breaks scripts basing on return codes.
Simple example with /etc/shadow:
lapsio@linux-qzuq ~> ln -s /etc/shadow ./shadlink
lapsio@linux-qzuq ~> cp --preserve=all -Rnv shadlink ./shadlink2
‘shadlink’ -> ‘./shadlink2’
lapsio@linux-qzuq ~> file shadlink2
shadlink2: symbolic link to `/etc/shadow'
lapsio@linux-qzuq ~> cp --preserve=all -Rnv shadlink ~/SSHFS/lap/home/lapsio/shadlink3 ‘shadlink’ -> ‘/home/lapsio/SSHFS/lap/home/lapsio/shadlink3’
cp: failed to preserve ownership for /home/lapsio/SSHFS/lap/home/lapsio/shadlink3: Permission denied
lapsio@linux-qzuq ~> file ~/SSHFS/lap/home/lapsio/shadlink3
/home/lapsio/SSHFS/lap/home/lapsio/shadlink3: symbolic link to `/etc/shadow'
Most probably the SFTP server calls chown(2) to set the ownership, which attempts to set ownership of the target of the symlink (contratry to lchown(2)).
The SFTP command SETSTAT (https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6) does not provide a way to explicitely request a "lchown".
I agree with g-raud. I'm not sure if there's a way to improve the behavior. At first I thought that maybe we should just not translate lchown to chown - but this translation actually happens in the kernel. sshfs just gets an inode, and can't tell if the original call was chown or lchown.
See also issue #250 for the related problem when setting other inode attributes.
Copying my comment from issue #250
There is an OpenSSH extension (and almost everybody uses OpenSSH so that is enough) to support this.
[email protected]. It was added in OpenSSH 8.0, in 2019.
3.7. sftp: Extension request "[email protected]"
This request is like the "setstat" command, but sets file attributes on
symlinks. It is implemented as a SSH_FXP_EXTENDED request with the
following format:
uint32 id
string "[email protected]"
string path
ATTRS attrs
See the "setstat" command for more details.
This extension is advertised in the SSH_FXP_VERSION hello with version
"1".
I was just about to file a new issue titled
Security:
fchownat(..., AT_SYMLINK_NOFOLLOW)follows symlink anyway
when I discovered this existing issue.
Posting what I wanted to post there here for googleability:
I found that sshfs silently ignores AT_SYMLINK_NOFOLLOW. This can be a security issue because an admin/script that uses e.g. chown -R --no-dereference to avoid following symlinks is led to believe that their invocation works, when it fact it does the opposite of wha they expect.
Repro:
# Create sshfs mount with a symlink pointing to a file
mkdir -p sshfs-dir
touch sshfs-dir/myfile
ln -s myfile sshfs-dir/link-to-myfile
mkdir -p sshfs-mount
sshfs localhost:sshfs-dir sshfs-mount
chown --no-dereference $(whoami):wheel sshfs-mount/link-to-myfile
ls -l sshfs-dir
# Now `myfile` is owned by group `wheel` -- it followed the symlink despite `--no-dereference`!
Another repro that shows that AT_SYMLINK_NOFOLLOW is ignored even in the case that the symlink target does NOT exist:
mkdir -p sshfs-dir
mkdir -p sshfs-mount
sshfs localhost:sshfs-dir sshfs-mount
ln -s nowhere sshfs-mount/symlink-pointing-nowhere
chown --no-dereference $USER sshfs-dir/symlink-pointing-nowhere # works as expected
chown --no-dereference $USER sshfs-mount/symlink-pointing-nowhere
# The last command fails incorrectly with:
# chown: changing ownership of 'sshfs-mount/symlink-pointing-nowhere': No such file or directory
# `strace -fye newfstatat,fchownat` on the last command shows that it's not `chown`'s fault,
# it simply reports what the kernel and `sshfs` return:
#
# newfstatat(AT_FDCWD</home/niklas>, "sshfs-mount/symlink-pointing-nowhere", {st_mode=S_IFLNK|0777, st_size=7, ...}, AT_SYMLINK_NOFOLLOW) = 0
# fchownat(AT_FDCWD</home/niklas>, "sshfs-mount/symlink-pointing-nowhere", 1000, -1, AT_SYMLINK_NOFOLLOW) = -1 ENOENT (No such file or directory)
#
# The `newfstatat()` proves that the symlink exists, so returning `ENOENT` is wrong.
# That `fchownat` result should be the same as when run against `sshfs-dir`:
#
# fchownat(AT_FDCWD</home/niklas>, "sshfs-dir/symlink-pointing-nowhere", 1000, -1, AT_SYMLINK_NOFOLLOW) = 0
This can be a security issue because an admin/script [..] is led to believe that their invocation works, when it fact it does the opposite
Wouldn't it be safer from a security perspective that sshfs should fail loudly when AT_SYMLINK_NOFOLLOW is used, when sshfs cannot implement it?
This can be a security issue because an admin/script [..] is led to believe that their invocation works, when it fact it does the opposite
Wouldn't it be safer from a security perspective that sshfs should fail loudly when
AT_SYMLINK_NOFOLLOWis used, when sshfs cannot implement it?
Absolutely, yes.
I don't see how it could be implemented though. As far as I know, things like AT_SYMLINK_NOFOLLOW are handled by the kernel (same as chown vs lchown etc). So SSHFS has no idea what the original request was.
As far as I know, things like AT_SYMLINK_NOFOLLOW are handled by the kernel (same as chown vs lchown etc). So SSHFS has no idea what the original request was.
Maybe it would make sense to create an upstream kernel bug / feature request, so this can be tracked? https://www.kernel.org/doc/html/v4.19/admin-guide/reporting-bugs.html