From 3d462a97e6f9c9eefdf8fcdbb435ed47a25cb2df Mon Sep 17 00:00:00 2001 From: djm Date: Mon, 2 Mar 2026 02:40:15 +0000 Subject: [PATCH] Move banner exchange to sshd-auth process Previously, exchange of the initial SSH- banners was performed by the privileged sshd-session monitor. This moves it to the unprivileged sshd-auth subprocess, removing ~200 LoC from the monitor's privileged attack surface. The monitor gains a new "setcompat" RPC to allow sshd-auth to inform it of bug compat flags picked up from the client's banner. feedback dtucker@, ok markus@ deraadt@ --- usr.bin/ssh/compat.c | 5 +++-- usr.bin/ssh/monitor.c | 28 +++++++++++++++++++++++++++- usr.bin/ssh/monitor.h | 3 ++- usr.bin/ssh/monitor_wrap.c | 17 ++++++++++++++++- usr.bin/ssh/monitor_wrap.h | 3 ++- usr.bin/ssh/packet.h | 5 +++-- usr.bin/ssh/sshd-auth.c | 10 +++++++++- usr.bin/ssh/sshd-session.c | 9 +-------- 8 files changed, 63 insertions(+), 17 deletions(-) diff --git a/usr.bin/ssh/compat.c b/usr.bin/ssh/compat.c index 31972a3e32e..9ebb9e5fcbc 100644 --- a/usr.bin/ssh/compat.c +++ b/usr.bin/ssh/compat.c @@ -1,4 +1,4 @@ -/* $OpenBSD: compat.c,v 1.127 2026/02/14 00:18:34 jsg Exp $ */ +/* $OpenBSD: compat.c,v 1.128 2026/03/02 02:40:15 djm Exp $ */ /* * Copyright (c) 1999, 2000, 2001, 2002 Markus Friedl. All rights reserved. * @@ -25,6 +25,7 @@ #include +#include #include #include @@ -41,7 +42,7 @@ compat_banner(struct ssh *ssh, const char *version) int i; static struct { char *pat; - int bugs; + uint32_t bugs; } check[] = { { "OpenSSH_2.*," "OpenSSH_3.0*," diff --git a/usr.bin/ssh/monitor.c b/usr.bin/ssh/monitor.c index c46cd57ee12..79a673b8d19 100644 --- a/usr.bin/ssh/monitor.c +++ b/usr.bin/ssh/monitor.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor.c,v 1.252 2026/02/08 19:54:31 dtucker Exp $ */ +/* $OpenBSD: monitor.c,v 1.253 2026/03/02 02:40:15 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -102,6 +102,7 @@ static struct sshbuf *child_state; /* Functions on the monitor that answer unprivileged requests */ int mm_answer_moduli(struct ssh *, int, struct sshbuf *); +int mm_answer_setcompat(struct ssh *, int, struct sshbuf *); int mm_answer_sign(struct ssh *, int, struct sshbuf *); int mm_answer_pwnamallow(struct ssh *, int, struct sshbuf *); int mm_answer_auth2_read_banner(struct ssh *, int, struct sshbuf *); @@ -139,6 +140,7 @@ static u_char *session_id2 = NULL; static pid_t monitor_child_pid; static int auth_attempted = 0; static int invalid_user = 0; +static int compat_set = 0; struct mon_table { enum monitor_reqtype type; @@ -164,6 +166,7 @@ struct mon_table mon_dispatch_proto20[] = { #ifdef WITH_OPENSSL {MONITOR_REQ_MODULI, MON_ONCE, mm_answer_moduli}, #endif + {MONITOR_REQ_SETCOMPAT, MON_ONCE, mm_answer_setcompat}, {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign}, {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow}, {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv}, @@ -246,6 +249,7 @@ monitor_child_preauth(struct ssh *ssh, struct monitor *pmonitor) /* Permit requests for state, moduli and signatures */ monitor_permit(mon_dispatch, MONITOR_REQ_STATE, 1); monitor_permit(mon_dispatch, MONITOR_REQ_MODULI, 1); + monitor_permit(mon_dispatch, MONITOR_REQ_SETCOMPAT, 1); monitor_permit(mon_dispatch, MONITOR_REQ_SIGN, 1); /* The first few requests do not require asynchronous access */ @@ -646,6 +650,20 @@ mm_answer_moduli(struct ssh *ssh, int sock, struct sshbuf *m) } #endif +int +mm_answer_setcompat(struct ssh *ssh, int sock, struct sshbuf *m) +{ + int r; + + debug3_f("entering"); + + if ((r = sshbuf_get_u32(m, &ssh->compat)) != 0) + fatal_fr(r, "parse"); + compat_set = 1; + + return (0); +} + int mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m) { @@ -661,6 +679,10 @@ mm_answer_sign(struct ssh *ssh, int sock, struct sshbuf *m) debug3_f("entering"); + /* Make sure the unpriv process sent the compat bits already */ + if (!compat_set) + fatal_f("state error: setcompat never called"); + if ((r = sshkey_froms(m, &pubkey)) != 0 || (r = sshbuf_get_string(m, &p, &datlen)) != 0 || (r = sshbuf_get_cstring(m, &alg, NULL)) != 0 || @@ -788,6 +810,10 @@ mm_answer_pwnamallow(struct ssh *ssh, int sock, struct sshbuf *m) debug3_f("entering"); + /* Make sure the unpriv process sent the compat bits already */ + if (!compat_set) + fatal_f("state error: setcompat never called"); + if (authctxt->attempt++ != 0) fatal_f("multiple attempts for getpwnam"); diff --git a/usr.bin/ssh/monitor.h b/usr.bin/ssh/monitor.h index a3304660de7..744d9e3a18e 100644 --- a/usr.bin/ssh/monitor.h +++ b/usr.bin/ssh/monitor.h @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor.h,v 1.27 2026/02/09 21:23:35 dtucker Exp $ */ +/* $OpenBSD: monitor.h,v 1.28 2026/03/02 02:40:15 djm Exp $ */ /* * Copyright 2002 Niels Provos @@ -39,6 +39,7 @@ enum monitor_reqtype { MONITOR_REQ_AUTHPASSWORD = 12, MONITOR_ANS_AUTHPASSWORD = 13, MONITOR_REQ_BSDAUTHQUERY = 14, MONITOR_ANS_BSDAUTHQUERY = 15, MONITOR_REQ_BSDAUTHRESPOND = 16, MONITOR_ANS_BSDAUTHRESPOND = 17, + MONITOR_REQ_SETCOMPAT = 18, MONITOR_REQ_KEYALLOWED = 22, MONITOR_ANS_KEYALLOWED = 23, MONITOR_REQ_KEYVERIFY = 24, MONITOR_ANS_KEYVERIFY = 25, MONITOR_REQ_KEYEXPORT = 26, diff --git a/usr.bin/ssh/monitor_wrap.c b/usr.bin/ssh/monitor_wrap.c index a2e56e11aff..311da0d6971 100644 --- a/usr.bin/ssh/monitor_wrap.c +++ b/usr.bin/ssh/monitor_wrap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor_wrap.c,v 1.145 2026/02/08 19:54:31 dtucker Exp $ */ +/* $OpenBSD: monitor_wrap.c,v 1.146 2026/03/02 02:40:15 djm Exp $ */ /* * Copyright 2002 Niels Provos * Copyright 2002 Markus Friedl @@ -251,6 +251,21 @@ mm_choose_dh(int min, int nbits, int max) } #endif +void +mm_sshkey_setcompat(struct ssh *ssh) +{ + struct sshbuf *m; + int r; + + debug3_f("entering"); + if ((m = sshbuf_new()) == NULL) + fatal_f("sshbuf_new failed"); + if ((r = sshbuf_put_u32(m, ssh->compat)) != 0) + fatal_fr(r, "assemble"); + + mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_SETCOMPAT, m); +} + int mm_sshkey_sign(struct ssh *ssh, struct sshkey *key, u_char **sigp, size_t *lenp, const u_char *data, size_t datalen, const char *hostkey_alg, diff --git a/usr.bin/ssh/monitor_wrap.h b/usr.bin/ssh/monitor_wrap.h index 988ce24d0fd..b404367795d 100644 --- a/usr.bin/ssh/monitor_wrap.h +++ b/usr.bin/ssh/monitor_wrap.h @@ -1,4 +1,4 @@ -/* $OpenBSD: monitor_wrap.h,v 1.53 2025/07/04 07:47:35 djm Exp $ */ +/* $OpenBSD: monitor_wrap.h,v 1.54 2026/03/02 02:40:15 djm Exp $ */ /* * Copyright 2002 Niels Provos @@ -46,6 +46,7 @@ int mm_is_monitor(void); #ifdef WITH_OPENSSL DH *mm_choose_dh(int, int, int); #endif +void mm_sshkey_setcompat(struct ssh *); int mm_sshkey_sign(struct ssh *, struct sshkey *, u_char **, size_t *, const u_char *, size_t, const char *, const char *, const char *, u_int compat); diff --git a/usr.bin/ssh/packet.h b/usr.bin/ssh/packet.h index 4eb10308c75..75cf71d7152 100644 --- a/usr.bin/ssh/packet.h +++ b/usr.bin/ssh/packet.h @@ -1,4 +1,4 @@ -/* $OpenBSD: packet.h,v 1.105 2026/02/08 17:50:49 dtucker Exp $ */ +/* $OpenBSD: packet.h,v 1.106 2026/03/02 02:40:15 djm Exp $ */ /* * Author: Tatu Ylonen @@ -18,6 +18,7 @@ #include +#include #include #include @@ -68,7 +69,7 @@ struct ssh { int dispatch_skip_packets; /* datafellows */ - int compat; + uint32_t compat; /* Lists for private and public keys */ TAILQ_HEAD(, key_entry) private_keys; diff --git a/usr.bin/ssh/sshd-auth.c b/usr.bin/ssh/sshd-auth.c index e29377cd794..fbb290fdaf7 100644 --- a/usr.bin/ssh/sshd-auth.c +++ b/usr.bin/ssh/sshd-auth.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd-auth.c,v 1.12 2026/02/11 17:05:32 dtucker Exp $ */ +/* $OpenBSD: sshd-auth.c,v 1.13 2026/03/02 02:40:15 djm Exp $ */ /* * SSH2 implementation: * Privilege Separation: @@ -768,6 +768,14 @@ do_ssh2_kex(struct ssh *ssh) free(hkalgs); + if ((r = kex_exchange_identification(ssh, -1, + options.version_addendum)) != 0) + sshpkt_fatal(ssh, r, "banner exchange"); + mm_sshkey_setcompat(ssh); /* tell monitor */ + + if ((ssh->compat & SSH_BUG_NOREKEY)) + debug("client does not support rekeying"); + /* start key exchange */ if ((r = kex_setup(ssh, myproposal)) != 0) fatal_r(r, "kex_setup"); diff --git a/usr.bin/ssh/sshd-session.c b/usr.bin/ssh/sshd-session.c index 6521dd97b85..309c0196935 100644 --- a/usr.bin/ssh/sshd-session.c +++ b/usr.bin/ssh/sshd-session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd-session.c,v 1.20 2026/02/09 21:38:14 dtucker Exp $ */ +/* $OpenBSD: sshd-session.c,v 1.21 2026/03/02 02:40:15 djm Exp $ */ /* * SSH2 implementation: * Privilege Separation: @@ -1132,13 +1132,6 @@ main(int ac, char **av) fatal("login grace time setitimer failed"); } - if ((r = kex_exchange_identification(ssh, -1, - options.version_addendum)) != 0) - sshpkt_fatal(ssh, r, "banner exchange"); - - if ((ssh->compat & SSH_BUG_NOREKEY)) - debug("client does not support rekeying"); - ssh_packet_set_nonblocking(ssh); /* allocate authentication context */