#982806 sshfs: SIGSEGV when using max_conns > 1

Package:
sshfs
Source:
sshfs-fuse
Description:
filesystem client based on SSH File Transfer Protocol
Submitter:
Peter Gerber
Date:
2021-02-16 04:03:03 UTC
Severity:
important
#982806#5
Date:
2021-02-14 17:58:00 UTC
From:
To:
Dear Maintainer,

the following steps crash sshfs with SIGSEGV when a file is open while
the folder containing it is renamed.

Steps to reproduce:

#!/usr/bin/python3
import os

os.mkdir('old_name')
f = open('old_name/f', 'w')
os.rename('old_name', 'new_name')
f.close()  # crashes here


Output from gdb:

user@media:~/sshfs/sshfs-fuse-3.7.1+repack/build$ gdb -ex r --args sshfs
mia.arbitrary.ch:/ /home/user/b -f -d -o max_conns=2
GNU gdb (Debian 10.1-1.7) 10.1.90.20210103-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
<http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from sshfs...
Reading symbols from
/usr/lib/debug/.build-id/0c/1ef7b947ed8cfbdddaa25f2bf189b9bf14347e.debug...
Starting program: /usr/bin/sshfs mia.arbitrary.ch:/ /home/user/b -f -d
-o max_conns=2
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
SSHFS version 3.7.1
[Detaching after fork from child process 15132]
[Detaching after fork from child process 15134]
executing <ssh> <-x> <-a> <-oClearAllForwardings=yes> <-2>
<mia.arbitrary.ch> <-s> <sftp>
Server version: 3
Extension: posix-rename@openssh.com <1>
Extension: statvfs@openssh.com <2>
Extension: fstatvfs@openssh.com <2>
Extension: hardlink@openssh.com <1>
Extension: fsync@openssh.com <1>
Extension: lsetstat@openssh.com <1>
[New Thread 0x7ffff7be1700 (LWP 15136)]
[New Thread 0x7ffff72de700 (LWP 15137)]
[New Thread 0x7ffff69db700 (LWP 15138)]
[00001] LSTAT
  [00001]          ATTRS       41bytes (3ms)
[00002] LSTAT
  [00002]          ATTRS       41bytes (2ms)
[00003] LSTAT
  [00003]          ATTRS       41bytes (1ms)
[00004] LSTAT
  [00004]          ATTRS       41bytes (1ms)
[00005] LSTAT
  [00005]         STATUS       33bytes (3ms)
[00006] LSTAT
  [00006]         STATUS       33bytes (3ms)
[00007] MKDIR
  [00007]         STATUS       28bytes (2ms)
[00008] LSTAT
  [00008]          ATTRS       41bytes (2ms)
[00009] LSTAT
  [00009]         STATUS       33bytes (1ms)
[00010] OPEN
[00011] LSTAT
  [00010]         HANDLE       17bytes (1ms)
  [00011]          ATTRS       41bytes (1ms)
[00012] FSTAT
  [00012]          ATTRS       41bytes (1ms)
[Detaching after fork from child process 15140]
executing <ssh> <-x> <-a> <-oClearAllForwardings=yes> <-2>
<mia.arbitrary.ch> <-s> <sftp>
Server version: 3
Extension: posix-rename@openssh.com <1>
Extension: statvfs@openssh.com <2>
Extension: fstatvfs@openssh.com <2>
Extension: hardlink@openssh.com <1>
Extension: fsync@openssh.com <1>
Extension: lsetstat@openssh.com <1>
[New Thread 0x7ffff61da700 (LWP 15142)]
[00013] LSTAT
  [00013]         STATUS       33bytes (1ms)
[00014] EXTENDED
  [00014]         STATUS       28bytes (1ms)
[New Thread 0x7ffff59d9700 (LWP 15143)]
[00015] CLOSE

Thread 2 "sshfs" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff7be1700 (LWP 15136)]
--Type <RET> for more, q to quit, c to continue without paging--c
0x0000555555560423 in sshfs_release (path=0x7ffff0000f60
"/media/_unsorted/_in/new_name/f", fi=0x7ffff7be0d30) at ../sshfs.c:2890
2890			ce->refcount--;
(gdb) t 2
[Switching to thread 2 (Thread 0x7ffff7be1700 (LWP 15136))]
#0  0x0000555555560423 in sshfs_release (path=0x7ffff0000f60
"/media/_unsorted/_in/new_name/f",
    fi=0x7ffff7be0d30) at ../sshfs.c:2890
2890			ce->refcount--;
(gdb) bt
#0  0x0000555555560423 in sshfs_release (path=0x7ffff0000f60
"/media/_unsorted/_in/new_name/f",
    fi=0x7ffff7be0d30) at ../sshfs.c:2890
#1  0x00007ffff7f82cba in fuse_do_release (f=0x555555571080, ino=6,
    path=0x7ffff0000f60 "/media/_unsorted/_in/new_name/f", fi=<optimized
out>) at ../lib/fuse.c:3142
#2  0x00007ffff7f85cb6 in fuse_lib_release (req=0x7ffff0001fb0, ino=6,
fi=0x7ffff7be0d30) at ../lib/fuse.c:4121
#3  0x00007ffff7f8c8c6 in do_release (req=<optimized out>,
nodeid=<optimized out>, inarg=<optimized out>)
    at ../lib/fuse_lowlevel.c:1455
#4  0x00007ffff7f8ea73 in fuse_session_process_buf_int
(se=0x555555571460, buf=buf@entry=0x555555591bb0,
    ch=<optimized out>) at ../lib/fuse_lowlevel.c:2666
#5  0x00007ffff7f8a383 in fuse_do_work (data=0x555555591b90) at
../lib/fuse_loop_mt.c:163
#6  0x00007ffff7f5cea7 in start_thread (arg=<optimized out>) at
pthread_create.c:477
#7  0x00007ffff7d5ddef in clone () at
../sysdeps/unix/sysv/linux/x86_64/clone.S:95
(gdb) print ce
$1 = <optimized out>
(gdb) print ce->refcount
value has been optimized out
(gdb) list
2885		chunk_put_locked(sf->readahead);
2886		if (sshfs.max_conns > 1) {
2887			pthread_mutex_lock(&sshfs.lock);
2888			sf->conn->file_count--;
2889			ce = g_hash_table_lookup(sshfs.conntab, path);
2890			ce->refcount--;
2891			if(ce->refcount == 0) {
2892				g_hash_table_remove(sshfs.conntab, path);
2893				g_free(ce);
2894			}


Output from dmesg:

[15894.745037] sshfs[11446]: segfault at 0 ip 00005ce63a6cd423 sp
00007579c9fc9c20 error 6 in sshfs[5ce63a6c5000+b000]


Looks to me like `ce` on line 2890 shown above is NULL.

#982806#10
Date:
2021-02-15 07:48:08 UTC
From:
To:
[Cc Timo Savola, the author of the "max_conns=" feature]
#982806#15
Date:
2021-02-15 07:48:08 UTC
From:
To:
[Cc Timo Savola, the author of the "max_conns=" feature]
#982806#20
Date:
2021-02-16 03:50:28 UTC
From:
To:
- The open file has an entry in sshfs's conntab, keyed by its path.
- sshfs_rename checks and updates only the given directory paths in conntab.
- During close, there is a dangling entry for "old_name/f" in conntab, and no entry for "new_name/f", breaking sshfs_release:

So sshfs_rename should rewrite all matching keys in conntab when a directory is renamed.  (Perhaps the data structure should be changed to a tree?)

I also noticed that now that the connections are refcounted, the refcount should be decremented when replacing an existing key (g_hash_table_replace).  Right, Nikolaus?

timo