1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 09:27:59 +00:00
putty-source/ssh2userauth-server.c
Simon Tatham cd97b7e7ea Account for packet queues in ssh_sendbuffer().
Ever since I reworked the SSH code to have multiple internal packet
queues, there's been a long-standing FIXME in ssh_sendbuffer() saying
that we ought to include the data buffered in those queues as part of
reporting how much data is buffered on standard input.

Recently a user reported that 'proftpd', or rather its 'mod_sftp'
add-on that implements an SFTP-only SSH server, exposes a bug related
to that missing piece of code. The xfer_upload system in sftp.c starts
by pushing SFTP write messages into the SSH code for as long as
sftp_sendbuffer() (which ends up at ssh_sendbuffer()) reports that not
too much data is buffered locally. In fact what happens is that all
those messages end up on the packet queues between SSH protocol
layers, so they're not counted by sftp_sendbuffer(), so we just keep
going until there's some other reason to stop.

Usually the reason we stop is because we've filled up the SFTP
channel's SSH-layer window, so we need the server to send us a
WINDOW_ADJUST before we're allowed to send any more data. So we return
to the main event loop and start waiting for reply packets. And when
the window is moderate (e.g. OpenSSH currently seems to present about
2MB), this isn't really noticeable.

But proftpd presents the maximum-size window of 2^32-1 bytes, and as a
result we just keep shovelling more and more packets into the internal
packet queues until PSFTP has grown to 4GB in size, and only then do
we even return to the event loop and start actually sending them down
the network. Moreover, this happens again at rekey time, because while
a rekey is in progress, ssh2transport stops emptying the queue of
outgoing packets sent by its higher layer - so, again, everything just
keeps buffering up somewhere that sftp_sendbuffer can't see it.

But this commit fixes it! Each PacketProtocolLayer now provides a
vtable method for asking how much data it currently has queued. Most
of them share a default implementation which just returns the newly
added total_size field from their pq_out; the exception is
ssh2transport, which also has to account for data queued in its higher
layer. And ssh_sendbuffer() adds that on to the quantity it already
knew about in other locations, to give a more realistic idea of the
currently buffered data.
2020-02-05 19:34:29 +00:00

376 lines
12 KiB
C

/*
* Packet protocol layer for the server side of the SSH-2 userauth
* protocol (RFC 4252).
*/
#include <assert.h>
#include "putty.h"
#include "ssh.h"
#include "sshbpp.h"
#include "sshppl.h"
#include "sshcr.h"
#include "sshserver.h"
#ifndef NO_GSSAPI
#include "sshgssc.h"
#include "sshgss.h"
#endif
struct ssh2_userauth_server_state {
int crState;
PacketProtocolLayer *transport_layer, *successor_layer;
ptrlen session_id;
AuthPolicy *authpolicy;
const SshServerConfig *ssc;
ptrlen username, service, method;
unsigned methods, this_method;
bool partial_success;
AuthKbdInt *aki;
PacketProtocolLayer ppl;
};
static void ssh2_userauth_server_free(PacketProtocolLayer *);
static void ssh2_userauth_server_process_queue(PacketProtocolLayer *);
static const struct PacketProtocolLayerVtable ssh2_userauth_server_vtable = {
ssh2_userauth_server_free,
ssh2_userauth_server_process_queue,
NULL /* get_specials */,
NULL /* special_cmd */,
NULL /* want_user_input */,
NULL /* got_user_input */,
NULL /* reconfigure */,
ssh_ppl_default_queued_data_size,
"ssh-userauth",
};
static void free_auth_kbdint(AuthKbdInt *aki)
{
int i;
if (!aki)
return;
sfree(aki->title);
sfree(aki->instruction);
for (i = 0; i < aki->nprompts; i++)
sfree(aki->prompts[i].prompt);
sfree(aki->prompts);
sfree(aki);
}
PacketProtocolLayer *ssh2_userauth_server_new(
PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy,
const SshServerConfig *ssc)
{
struct ssh2_userauth_server_state *s =
snew(struct ssh2_userauth_server_state);
memset(s, 0, sizeof(*s));
s->ppl.vt = &ssh2_userauth_server_vtable;
s->successor_layer = successor_layer;
s->authpolicy = authpolicy;
s->ssc = ssc;
return &s->ppl;
}
void ssh2_userauth_server_set_transport_layer(PacketProtocolLayer *userauth,
PacketProtocolLayer *transport)
{
struct ssh2_userauth_server_state *s =
container_of(userauth, struct ssh2_userauth_server_state, ppl);
s->transport_layer = transport;
}
static void ssh2_userauth_server_free(PacketProtocolLayer *ppl)
{
struct ssh2_userauth_server_state *s =
container_of(ppl, struct ssh2_userauth_server_state, ppl);
if (s->successor_layer)
ssh_ppl_free(s->successor_layer);
free_auth_kbdint(s->aki);
sfree(s);
}
static PktIn *ssh2_userauth_server_pop(struct ssh2_userauth_server_state *s)
{
return pq_pop(s->ppl.in_pq);
}
static void ssh2_userauth_server_add_session_id(
struct ssh2_userauth_server_state *s, strbuf *sigdata)
{
if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) {
put_datapl(sigdata, s->session_id);
} else {
put_stringpl(sigdata, s->session_id);
}
}
static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl)
{
struct ssh2_userauth_server_state *s =
container_of(ppl, struct ssh2_userauth_server_state, ppl);
PktIn *pktin;
PktOut *pktout;
crBegin(s->crState);
s->session_id = ssh2_transport_get_session_id(s->transport_layer);
if (s->ssc->banner.ptr) {
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_BANNER);
put_stringpl(pktout, s->ssc->banner);
put_stringz(pktout, ""); /* language tag */
pq_push(s->ppl.out_pq, pktout);
}
while (1) {
crMaybeWaitUntilV((pktin = ssh2_userauth_server_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_USERAUTH_REQUEST) {
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
"expecting USERAUTH_REQUEST, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx, pktin->type));
return;
}
s->username = get_string(pktin);
s->service = get_string(pktin);
s->method = get_string(pktin);
if (!ptrlen_eq_string(s->service, s->successor_layer->vt->name)) {
/*
* Unconditionally reject authentication for any service
* other than the one we're going to hand over to.
*/
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE);
put_stringz(pktout, "");
put_bool(pktout, false);
pq_push(s->ppl.out_pq, pktout);
continue;
}
s->methods = auth_methods(s->authpolicy);
s->partial_success = false;
if (ptrlen_eq_string(s->method, "none")) {
s->this_method = AUTHMETHOD_NONE;
if (!(s->methods & s->this_method))
goto failure;
if (!auth_none(s->authpolicy, s->username))
goto failure;
} else if (ptrlen_eq_string(s->method, "password")) {
bool changing;
ptrlen password, new_password, *new_password_ptr;
s->this_method = AUTHMETHOD_PASSWORD;
if (!(s->methods & s->this_method))
goto failure;
changing = get_bool(pktin);
password = get_string(pktin);
if (changing) {
new_password = get_string(pktin);
new_password_ptr = &new_password;
} else {
new_password_ptr = NULL;
}
int result = auth_password(s->authpolicy, s->username,
password, new_password_ptr);
if (result == 2) {
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ);
put_stringz(pktout, "Please change your password");
put_stringz(pktout, ""); /* language tag */
pq_push(s->ppl.out_pq, pktout);
continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */
} else if (result != 1) {
goto failure;
}
} else if (ptrlen_eq_string(s->method, "publickey")) {
bool has_signature, success;
ptrlen algorithm, blob, signature;
const ssh_keyalg *keyalg;
ssh_key *key;
strbuf *sigdata;
s->this_method = AUTHMETHOD_PUBLICKEY;
if (!(s->methods & s->this_method))
goto failure;
has_signature = get_bool(pktin);
algorithm = get_string(pktin);
blob = get_string(pktin);
if (!auth_publickey(s->authpolicy, s->username, blob))
goto failure;
keyalg = find_pubkey_alg_len(algorithm);
if (!keyalg)
goto failure;
key = ssh_key_new_pub(keyalg, blob);
if (!key)
goto failure;
if (!has_signature) {
ssh_key_free(key);
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK);
put_stringpl(pktout, algorithm);
put_stringpl(pktout, blob);
pq_push(s->ppl.out_pq, pktout);
continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */
}
sigdata = strbuf_new();
ssh2_userauth_server_add_session_id(s, sigdata);
put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST);
put_stringpl(sigdata, s->username);
put_stringpl(sigdata, s->service);
put_stringpl(sigdata, s->method);
put_bool(sigdata, has_signature);
put_stringpl(sigdata, algorithm);
put_stringpl(sigdata, blob);
signature = get_string(pktin);
success = ssh_key_verify(key, signature,
ptrlen_from_strbuf(sigdata));
ssh_key_free(key);
strbuf_free(sigdata);
if (!success)
goto failure;
} else if (ptrlen_eq_string(s->method, "keyboard-interactive")) {
int i, ok;
unsigned n;
s->this_method = AUTHMETHOD_KBDINT;
if (!(s->methods & s->this_method))
goto failure;
do {
s->aki = auth_kbdint_prompts(s->authpolicy, s->username);
if (!s->aki)
goto failure;
pktout = ssh_bpp_new_pktout(
s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_REQUEST);
put_stringz(pktout, s->aki->title);
put_stringz(pktout, s->aki->instruction);
put_stringz(pktout, ""); /* language tag */
put_uint32(pktout, s->aki->nprompts);
for (i = 0; i < s->aki->nprompts; i++) {
put_stringz(pktout, s->aki->prompts[i].prompt);
put_bool(pktout, s->aki->prompts[i].echo);
}
pq_push(s->ppl.out_pq, pktout);
crMaybeWaitUntilV(
(pktin = ssh2_userauth_server_pop(s)) != NULL);
if (pktin->type != SSH2_MSG_USERAUTH_INFO_RESPONSE) {
ssh_proto_error(
s->ppl.ssh, "Received unexpected packet when "
"expecting USERAUTH_INFO_RESPONSE, type %d (%s)",
pktin->type,
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
s->ppl.bpp->pls->actx, pktin->type));
return;
}
n = get_uint32(pktin);
if (n != s->aki->nprompts) {
ssh_proto_error(
s->ppl.ssh, "Received %u keyboard-interactive "
"responses after sending %u prompts",
n, s->aki->nprompts);
return;
}
{
ptrlen *responses = snewn(s->aki->nprompts, ptrlen);
for (i = 0; i < s->aki->nprompts; i++)
responses[i] = get_string(pktin);
ok = auth_kbdint_responses(s->authpolicy, responses);
sfree(responses);
}
free_auth_kbdint(s->aki);
s->aki = NULL;
} while (ok == 0);
if (ok <= 0)
goto failure;
} else {
goto failure;
}
/*
* If we get here, we've successfully completed this
* authentication step.
*/
if (auth_successful(s->authpolicy, s->username, s->this_method)) {
/*
* ... and it was the last one, so we're completely done.
*/
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_SUCCESS);
pq_push(s->ppl.out_pq, pktout);
break;
} else {
/*
* ... but another is required, so fall through to
* generation of USERAUTH_FAILURE, having first refreshed
* the bit mask of available methods.
*/
s->methods = auth_methods(s->authpolicy);
}
s->partial_success = true;
failure:
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE);
{
strbuf *list = strbuf_new();
if (s->methods & AUTHMETHOD_NONE)
add_to_commasep(list, "none");
if (s->methods & AUTHMETHOD_PASSWORD)
add_to_commasep(list, "password");
if (s->methods & AUTHMETHOD_PUBLICKEY)
add_to_commasep(list, "publickey");
if (s->methods & AUTHMETHOD_KBDINT)
add_to_commasep(list, "keyboard-interactive");
put_stringsb(pktout, list);
}
put_bool(pktout, s->partial_success);
pq_push(s->ppl.out_pq, pktout);
}
/*
* Finally, hand over to our successor layer, and return
* immediately without reaching the crFinishV: ssh_ppl_replace
* will have freed us, so crFinishV's zeroing-out of crState would
* be a use-after-free bug.
*/
{
PacketProtocolLayer *successor = s->successor_layer;
s->successor_layer = NULL; /* avoid freeing it ourself */
ssh_ppl_replace(&s->ppl, successor);
return; /* we've just freed s, so avoid even touching s->crState */
}
crFinishV;
}