pkgsrc-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: pkg/60563: exposed sshd w/o password-login causes sshd-auth looping



The following reply was made to PR pkg/60563; it has been noted by GNATS.

From: Havard Eidnes <he%uninett.no@localhost>
To: gnats-bugs%NetBSD.org@localhost
Cc: 
Subject: Re: pkg/60563: exposed sshd w/o password-login causes sshd-auth
 looping
Date: Sat, 03 Oct 2026 20:51:36 +0200 (CEST)

 Hm,
 
 it doesn't look like the fix to net/openssh which gave us
 10.5p1nb1 resolves this issue correctly.
 
 The issue is reportedly triggered by just connecting to the sshd
 port, and not giving any input, so no "ultra-malicious motives"
 are needed.
 
 What this will cause is sshd-auth will enter a tight loop where
 it tests via poll() that the FD is *writable*, and then do read()
 on the FD, and getting EAGAIN because nothing is possible to read
 because nothing is and the FD is in non-blocking mode, and the
 cycle repeats.  It will repeat this cycle until the self-imposed
 10 minutes CPU time resource limit expires.
 
 
 The pattern the code uses is
 
 In kex_exchange_identification() the code does (among other
 things):
 
                         len = atomicio(read, ssh_packet_get_connection_in(ssh),
                             &c, 1);
 
 In other words, it tries to pass "read" as a function pointer.
 atomicio() in atomicio.c is
 
 size_t
 atomicio(ssize_t (*f) (int, void *, size_t), int fd, void *_s, size_t n)
 {
         return atomicio6(f, fd, _s, n, NULL, NULL);
 }
 
 and then inside atomicio6() we find:
 
         pfd.events = f == read ? POLLIN : POLLOUT;
 ...
         while (n > pos) {
                 res = (f) (fd, s + pos, n - pos);
                 switch (res) {
                 case -1:
                         if (errno == EINTR) {
                                 /* possible SIGALARM, update callback */
                                 if (cb != NULL && cb(cb_arg, 0) == -1) {
                                         errno = EINTR;
                                         return pos;
                                 }
                                 continue;
                         } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
                                 (void)poll(&pfd, 1, -1);
                                 continue;
                         }
 
 Now, the issue is that pfd.events in that first conditional ends
 up as POLLOUT, not POLLIN:
 
 #define POLLIN          0x0001
 #define POLLPRI         0x0002
 #define POLLOUT         0x0004
 
 even though the code invocation from
 kex_exchange_identification() is passing "read" as the first
 argument:
 
 (gdb) where
 #0  0x00007b8ff90473fa in read () from /usr/lib/libc.so.12
 #1  0x00007b8ff8c08579 in read () from /usr/lib/libpthread.so.1
 #2  0x00000000448723f1 in atomicio6 (f=f@entry=0x44882cd0 <read>, fd=5, 
     _s=_s@entry=0x7f7ffffdae2f, n=n@entry=1, cb=cb@entry=0x0, 
     cb_arg=cb_arg@entry=0x0) at atomicio.c:56
 #3  0x00000000448724c5 in atomicio (f=f@entry=0x44882cd0 <read>, 
     fd=<optimized out>, _s=_s@entry=0x7f7ffffdae2f, n=n@entry=1)
     at atomicio.c:88
 #4  0x0000000044885f11 in kex_exchange_identification (
     ssh=ssh@entry=0x7b8ffb03d000, timeout_ms=<optimized out>, 
     timeout_ms@entry=-1, version_addendum=<optimized out>) at kex.c:1322
 #5  0x00000000448b662d in do_ssh2_kex (ssh=0x7b8ffb03d000) at sshd-auth.c:787
 #6  main (ac=<optimized out>, av=<optimized out>) at sshd-auth.c:735
 (gdb) 
 (gdb) down
 #3  0x00000000448724c5 in atomicio (f=f@entry=0x44882cd0 <read>, 
     fd=<optimized out>, _s=_s@entry=0x7f7ffffdae2f, n=n@entry=1)
     at atomicio.c:88
 88              return atomicio6(f, fd, _s, n, NULL, NULL);
 (gdb) down
 #2  0x00000000448723f1 in atomicio6 (f=f@entry=0x44882cd0 <read>, fd=5, 
     _s=_s@entry=0x7f7ffffdae2f, n=n@entry=1, cb=cb@entry=0x0, 
     cb_arg=cb_arg@entry=0x0) at atomicio.c:56
 56                      res = (f) (fd, s + pos, n - pos);
 (gdb) p f
 $7 = (ssize_t (*)(int, void *, size_t)) 0x44882cd0 <read>
 (gdb) p read
 $8 = {ssize_t (int, void *, size_t)} 0x44872150 <read>
 (gdb) 
 (gdb) p pfd
 $9 = {fd = 5, events = 4, revents = 4}
 (gdb) 
 (gdb) up
 #3  0x00000000448724c5 in atomicio (f=f@entry=0x44882cd0 <read>, 
     fd=<optimized out>, _s=_s@entry=0x7f7ffffdae2f, n=n@entry=1)
     at atomicio.c:88
 88              return atomicio6(f, fd, _s, n, NULL, NULL);
 (gdb) up
 #4  0x0000000044885f11 in kex_exchange_identification (
     ssh=ssh@entry=0x7b8ffb03d000, timeout_ms=<optimized out>, 
     timeout_ms@entry=-1, version_addendum=<optimized out>) at kex.c:1322
 1322                            len = atomicio(read, ssh_packet_get_connection_in(ssh),
 (gdb) 
 
 Those two $7 and $8 are *not* the same, while the code appears to
 assume that they will be in this case.
 
 The $9 result shows that pfd.events is POLLOUT.  But that's not
 what's wanted to test for, it should here be testing whether the
 FD is readable (POLLIN), not whether it's writable.
 
 Now... __SSP_FORTIFY_LEVEL may play into this.  Not sure what it
 is by default, but possibly and probably not 0, so read() doesn't
 get defined via this entry in <unistd.h>:
 
 #if __SSP_FORTIFY_LEVEL == 0
 ssize_t  read(int, void *, size_t);
 #endif
 
 but instead via <ssp/unistd.h> as
 
 __ssp_redirect0(ssize_t, read, (int __fd, void *__buf, size_t __len), \
     (__fd, __buf, __len));
 
 and <ssp/ssp.h> has
 
 #define __ssp_redirect0(rtype, fun, args, call) \
     __ssp_redirect_raw(rtype, fun, fun, args, call, 1, __ssp_bos0)
 
 and
 
 #define __ssp_redirect_raw(rtype, fun, symbol, args, call, cond, bos) \
 rtype __ssp_real_(fun) args __RENAME(symbol); \
 __ssp_inline rtype fun args __RENAME(__ssp_protected_ ## fun); \
 __ssp_inline rtype fun args { \
         if (cond) \
                 __ssp_check(__buf, __len, bos); \
         return __ssp_real_(fun) call; \
 }
 
 but ... is that messing things up for the openssh code in this
 case?
 
 There is also the issue that the address taken may not point to
 the "real" function but to an entry in the procedure linkage
 table(?)  But why is the value different as seen from the two
 different functions?  I beleive I've not understood why the
 pattern used by openssh fails.
 
 Any hints about how to do this "correctly"?
 



Home | Main Index | Thread Index | Old Index