Skip to content

Commit

Permalink
inetutils: Fix deadlock in telnetd when cleanup
Browse files Browse the repository at this point in the history
the patch comes from:
https://bugs.launchpad.net/ubuntu/+source/netkit-telnet/+bug/507455
https://launchpadlibrarian.net/37882973/0001-telnetd-Fix-deadlock-on-cleanup.patch

The cleanup function in telnetd is called both directly and on SIGCHLD
signals. This, unfortunately, triggered a deadlock in eglibc 2.9 while
running on a 2.6.31.11 kernel.

What we were seeing is hangs like these:

  (gdb) bt
  #0  0xb7702424 in __kernel_vsyscall ()
  #1  0xb7658e61 in __lll_lock_wait_private () from ./lib/libc.so.6
  #2  0xb767e7b5 in _L_lock_15 () from ./lib/libc.so.6
  #3  0xb767e6e0 in utmpname () from ./lib/libc.so.6
  #4  0xb76bcde7 in logout () from ./lib/libutil.so.1
  #5  0x0804c827 in cleanup ()
  #6  <signal handler called>
  #7  0xb7702424 in __kernel_vsyscall ()
  #8  0xb7641003 in __fcntl_nocancel () from ./lib/libc.so.6
  #9  0xb767e0c3 in getutline_r_file () from ./lib/libc.so.6
  #10 0xb767d675 in getutline_r () from ./lib/libc.so.6
  #11 0xb76bce42 in logout () from ./lib/libutil.so.1
  #12 0x0804c827 in cleanup ()
  #13 0x0804a0b5 in telnet ()
  #14 0x0804a9c3 in main ()

and what has happened here is that the user closes the telnet session
via the escape character. This causes telnetd to call cleanup in frame
the SIGCHLD signal is delivered while telnetd is executing cleanup.

Telnetd then calls the signal handler for SIGCHLD, which is cleanup().
Ouch. The actual deadlock is in libc. getutline_r in frame #10 gets the
__libc_utmp_lock lock, and utmpname above does the same thing in frame

The fix registers the SIGCHLD handler as cleanup_sighandler, and makes
cleanup disable the SIGCHLD signal before calling cleanup_sighandler.

Signed-off-by: Simon Kagstrom <[email protected]>
Signed-off-by: Li Wang <[email protected]>
Signed-off-by: Martin Jansa <[email protected]>
  • Loading branch information
Li Wang authored and shr-project committed Aug 24, 2015
1 parent 62de6e6 commit 0f669a5
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
telnetd: Fix deadlock on cleanup

the patch comes from:
https://bugs.launchpad.net/ubuntu/+source/netkit-telnet/+bug/507455
https://launchpadlibrarian.net/37882973/0001-telnetd-Fix-deadlock-on-cleanup.patch

The cleanup function in telnetd is called both directly and on SIGCHLD
signals. This, unfortunately, triggered a deadlock in eglibc 2.9 while
running on a 2.6.31.11 kernel.

What we were seeing is hangs like these:

(gdb) bt
#0 0xb7702424 in __kernel_vsyscall ()
#1 0xb7658e61 in __lll_lock_wait_private () from ./lib/libc.so.6
#2 0xb767e7b5 in _L_lock_15 () from ./lib/libc.so.6
#3 0xb767e6e0 in utmpname () from ./lib/libc.so.6
#4 0xb76bcde7 in logout () from ./lib/libutil.so.1
#5 0x0804c827 in cleanup ()
#6 <signal handler called>
#7 0xb7702424 in __kernel_vsyscall ()
#8 0xb7641003 in __fcntl_nocancel () from ./lib/libc.so.6
#9 0xb767e0c3 in getutline_r_file () from ./lib/libc.so.6
#10 0xb767d675 in getutline_r () from ./lib/libc.so.6
#11 0xb76bce42 in logout () from ./lib/libutil.so.1
#12 0x0804c827 in cleanup ()
#13 0x0804a0b5 in telnet ()
#14 0x0804a9c3 in main ()

and what has happened here is that the user closes the telnet session
via the escape character. This causes telnetd to call cleanup in frame
the SIGCHLD signal is delivered while telnetd is executing cleanup.

Telnetd then calls the signal handler for SIGCHLD, which is cleanup().
Ouch. The actual deadlock is in libc. getutline_r in frame #10 gets the
__libc_utmp_lock lock, and utmpname above does the same thing in frame

The fix registers the SIGCHLD handler as cleanup_sighandler, and makes
cleanup disable the SIGCHLD signal before calling cleanup_sighandler.

Signed-off-by: Simon Kagstrom <[email protected]>
Signed-off-by: Li Wang <[email protected]>
---
telnetd/pty.c | 17 ++++++++++++++++-
telnetd/telnetd.c | 2 +-
telnetd/telnetd.h | 1 +
3 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/telnetd/pty.c b/telnetd/pty.c
index 21b0b69..22a17c5 100644
--- a/telnetd/pty.c
+++ b/telnetd/pty.c
@@ -145,7 +145,7 @@ start_login (char *host, int autologin, char *name)
* reported exit code.
*/
void
-cleanup (int sig)
+cleanup_sighandler (int sig)
{
int status = EXIT_FAILURE;
char *p;
@@ -168,3 +168,18 @@ cleanup (int sig)
shutdown (net, 2);
exit (status);
}
+
+void cleanup(int sig) {
+ sigset_t mask, oldmask;
+
+ /* Set up the mask of signals to temporarily block. */
+ sigemptyset (&mask);
+ sigaddset (&mask, SIGCHLD);
+
+ /* Block SIGCHLD while running cleanup */
+ sigprocmask (SIG_BLOCK, &mask, &oldmask);
+
+ cleanup_sighandler(sig);
+ /* Technically not needed since cleanup_sighandler exits */
+ sigprocmask (SIG_UNBLOCK, &mask, NULL);
+}
diff --git a/telnetd/telnetd.c b/telnetd/telnetd.c
index cf7ce0f..4fe95b5 100644
--- a/telnetd/telnetd.c
+++ b/telnetd/telnetd.c
@@ -527,7 +527,7 @@ telnetd_setup (int fd)
signal (SIGTTOU, SIG_IGN);
#endif

- signal (SIGCHLD, cleanup);
+ signal (SIGCHLD, cleanup_sighandler);
}

int
diff --git a/telnetd/telnetd.h b/telnetd/telnetd.h
index ce90fbc..8bac120 100644
--- a/telnetd/telnetd.h
+++ b/telnetd/telnetd.h
@@ -326,6 +326,7 @@ extern void add_slc (char func, char flag, cc_t val);
extern void check_slc (void);
extern void change_slc (char func, char flag, cc_t val);

+extern void cleanup_sighandler (int);
extern void cleanup (int);
extern void clientstat (int, int, int);
extern void copy_termbuf ();
--
1.7.9.5

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ SRC_URI = "${GNU_MIRROR}/inetutils/inetutils-${PV}.tar.gz \
file://telnet.xinetd.inetutils \
file://tftpd.xinetd.inetutils \
file://inetutils-1.9-PATH_PROCNET_DEV.patch \
file://telnetd-Fix-deadlock-on-cleanup.patch \
"

SRC_URI[md5sum] = "04852c26c47cc8c6b825f2b74f191f52"
Expand Down

0 comments on commit 0f669a5

Please sign in to comment.