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