|
| 1 | +telnetd: Fix deadlock on cleanup |
| 2 | + |
| 3 | +the patch comes from: |
| 4 | +https://bugs.launchpad.net/ubuntu/+source/netkit-telnet/+bug/507455 |
| 5 | +https://launchpadlibrarian.net/37882973/0001-telnetd-Fix-deadlock-on-cleanup.patch |
| 6 | + |
| 7 | +The cleanup function in telnetd is called both directly and on SIGCHLD |
| 8 | +signals. This, unfortunately, triggered a deadlock in eglibc 2.9 while |
| 9 | +running on a 2.6.31.11 kernel. |
| 10 | + |
| 11 | +What we were seeing is hangs like these: |
| 12 | + |
| 13 | + (gdb) bt |
| 14 | + #0 0xb7702424 in __kernel_vsyscall () |
| 15 | + #1 0xb7658e61 in __lll_lock_wait_private () from ./lib/libc.so.6 |
| 16 | + #2 0xb767e7b5 in _L_lock_15 () from ./lib/libc.so.6 |
| 17 | + #3 0xb767e6e0 in utmpname () from ./lib/libc.so.6 |
| 18 | + #4 0xb76bcde7 in logout () from ./lib/libutil.so.1 |
| 19 | + #5 0x0804c827 in cleanup () |
| 20 | + #6 <signal handler called> |
| 21 | + #7 0xb7702424 in __kernel_vsyscall () |
| 22 | + #8 0xb7641003 in __fcntl_nocancel () from ./lib/libc.so.6 |
| 23 | + #9 0xb767e0c3 in getutline_r_file () from ./lib/libc.so.6 |
| 24 | + #10 0xb767d675 in getutline_r () from ./lib/libc.so.6 |
| 25 | + #11 0xb76bce42 in logout () from ./lib/libutil.so.1 |
| 26 | + #12 0x0804c827 in cleanup () |
| 27 | + #13 0x0804a0b5 in telnet () |
| 28 | + #14 0x0804a9c3 in main () |
| 29 | + |
| 30 | +and what has happened here is that the user closes the telnet session |
| 31 | +via the escape character. This causes telnetd to call cleanup in frame |
| 32 | +the SIGCHLD signal is delivered while telnetd is executing cleanup. |
| 33 | + |
| 34 | +Telnetd then calls the signal handler for SIGCHLD, which is cleanup(). |
| 35 | +Ouch. The actual deadlock is in libc. getutline_r in frame #10 gets the |
| 36 | +__libc_utmp_lock lock, and utmpname above does the same thing in frame |
| 37 | + |
| 38 | +The fix registers the SIGCHLD handler as cleanup_sighandler, and makes |
| 39 | +cleanup disable the SIGCHLD signal before calling cleanup_sighandler. |
| 40 | + |
| 41 | +Signed-off-by: Simon Kagstrom < [email protected]> |
| 42 | +Signed-off-by: Li Wang < [email protected]> |
| 43 | +--- |
| 44 | + telnetd/pty.c | 17 ++++++++++++++++- |
| 45 | + telnetd/telnetd.c | 2 +- |
| 46 | + telnetd/telnetd.h | 1 + |
| 47 | + 3 files changed, 18 insertions(+), 2 deletions(-) |
| 48 | + |
| 49 | +diff --git a/telnetd/pty.c b/telnetd/pty.c |
| 50 | +index 21b0b69..22a17c5 100644 |
| 51 | +--- a/telnetd/pty.c |
| 52 | ++++ b/telnetd/pty.c |
| 53 | +@@ -145,7 +145,7 @@ start_login (char *host, int autologin, char *name) |
| 54 | + * reported exit code. |
| 55 | + */ |
| 56 | + void |
| 57 | +-cleanup (int sig) |
| 58 | ++cleanup_sighandler (int sig) |
| 59 | + { |
| 60 | + int status = EXIT_FAILURE; |
| 61 | + char *p; |
| 62 | +@@ -168,3 +168,18 @@ cleanup (int sig) |
| 63 | + shutdown (net, 2); |
| 64 | + exit (status); |
| 65 | + } |
| 66 | ++ |
| 67 | ++void cleanup(int sig) { |
| 68 | ++ sigset_t mask, oldmask; |
| 69 | ++ |
| 70 | ++ /* Set up the mask of signals to temporarily block. */ |
| 71 | ++ sigemptyset (&mask); |
| 72 | ++ sigaddset (&mask, SIGCHLD); |
| 73 | ++ |
| 74 | ++ /* Block SIGCHLD while running cleanup */ |
| 75 | ++ sigprocmask (SIG_BLOCK, &mask, &oldmask); |
| 76 | ++ |
| 77 | ++ cleanup_sighandler(sig); |
| 78 | ++ /* Technically not needed since cleanup_sighandler exits */ |
| 79 | ++ sigprocmask (SIG_UNBLOCK, &mask, NULL); |
| 80 | ++} |
| 81 | +diff --git a/telnetd/telnetd.c b/telnetd/telnetd.c |
| 82 | +index cf7ce0f..4fe95b5 100644 |
| 83 | +--- a/telnetd/telnetd.c |
| 84 | ++++ b/telnetd/telnetd.c |
| 85 | +@@ -527,7 +527,7 @@ telnetd_setup (int fd) |
| 86 | + signal (SIGTTOU, SIG_IGN); |
| 87 | + #endif |
| 88 | + |
| 89 | +- signal (SIGCHLD, cleanup); |
| 90 | ++ signal (SIGCHLD, cleanup_sighandler); |
| 91 | + } |
| 92 | + |
| 93 | + int |
| 94 | +diff --git a/telnetd/telnetd.h b/telnetd/telnetd.h |
| 95 | +index ce90fbc..8bac120 100644 |
| 96 | +--- a/telnetd/telnetd.h |
| 97 | ++++ b/telnetd/telnetd.h |
| 98 | +@@ -326,6 +326,7 @@ extern void add_slc (char func, char flag, cc_t val); |
| 99 | + extern void check_slc (void); |
| 100 | + extern void change_slc (char func, char flag, cc_t val); |
| 101 | + |
| 102 | ++extern void cleanup_sighandler (int); |
| 103 | + extern void cleanup (int); |
| 104 | + extern void clientstat (int, int, int); |
| 105 | + extern void copy_termbuf (); |
| 106 | +-- |
| 107 | +1.7.9.5 |
| 108 | + |
0 commit comments