diff options
author | djm@openbsd.org <djm@openbsd.org> | 2018-12-27 03:25:24 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2018-12-27 14:38:22 +1100 |
commit | 0a843d9a0e805f14653a555f5c7a8ba99d62c12d (patch) | |
tree | 481f36e9fd1918be5449e369a97c086a1a8d2432 /kex.c | |
parent | 434b587afe41c19391821e7392005068fda76248 (diff) |
upstream: move client/server SSH-* banners to buffers under
ssh->kex and factor out the banner exchange. This eliminates some common code
from the client and server.
Also be more strict about handling \r characters - these should only
be accepted immediately before \n (pointed out by Jann Horn).
Inspired by a patch from Markus Schmidt.
(lots of) feedback and ok markus@
OpenBSD-Commit-ID: 1cc7885487a6754f63641d7d3279b0941890275b
Diffstat (limited to 'kex.c')
-rw-r--r-- | kex.c | 294 |
1 files changed, 267 insertions, 27 deletions
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: kex.c,v 1.142 2018/12/07 03:39:40 djm Exp $ */ | 1 | /* $OpenBSD: kex.c,v 1.143 2018/12/27 03:25:25 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. | 3 | * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. |
4 | * | 4 | * |
@@ -25,19 +25,25 @@ | |||
25 | 25 | ||
26 | #include "includes.h" | 26 | #include "includes.h" |
27 | 27 | ||
28 | 28 | #include <sys/types.h> | |
29 | #include <errno.h> | ||
29 | #include <signal.h> | 30 | #include <signal.h> |
30 | #include <stdarg.h> | 31 | #include <stdarg.h> |
31 | #include <stdio.h> | 32 | #include <stdio.h> |
32 | #include <stdlib.h> | 33 | #include <stdlib.h> |
33 | #include <string.h> | 34 | #include <string.h> |
35 | #include <unistd.h> | ||
36 | #include <poll.h> | ||
34 | 37 | ||
35 | #ifdef WITH_OPENSSL | 38 | #ifdef WITH_OPENSSL |
36 | #include <openssl/crypto.h> | 39 | #include <openssl/crypto.h> |
37 | #include <openssl/dh.h> | 40 | #include <openssl/dh.h> |
38 | #endif | 41 | #endif |
39 | 42 | ||
43 | #include "ssh.h" | ||
40 | #include "ssh2.h" | 44 | #include "ssh2.h" |
45 | #include "atomicio.h" | ||
46 | #include "version.h" | ||
41 | #include "packet.h" | 47 | #include "packet.h" |
42 | #include "compat.h" | 48 | #include "compat.h" |
43 | #include "cipher.h" | 49 | #include "cipher.h" |
@@ -578,32 +584,20 @@ kex_input_kexinit(int type, u_int32_t seq, struct ssh *ssh) | |||
578 | return SSH_ERR_INTERNAL_ERROR; | 584 | return SSH_ERR_INTERNAL_ERROR; |
579 | } | 585 | } |
580 | 586 | ||
581 | int | 587 | struct kex * |
582 | kex_new(struct ssh *ssh, char *proposal[PROPOSAL_MAX], struct kex **kexp) | 588 | kex_new(void) |
583 | { | 589 | { |
584 | struct kex *kex; | 590 | struct kex *kex; |
585 | int r; | ||
586 | 591 | ||
587 | *kexp = NULL; | 592 | if ((kex = calloc(1, sizeof(*kex))) == NULL || |
588 | if ((kex = calloc(1, sizeof(*kex))) == NULL) | 593 | (kex->peer = sshbuf_new()) == NULL || |
589 | return SSH_ERR_ALLOC_FAIL; | 594 | (kex->my = sshbuf_new()) == NULL || |
590 | if ((kex->peer = sshbuf_new()) == NULL || | 595 | (kex->client_version = sshbuf_new()) == NULL || |
591 | (kex->my = sshbuf_new()) == NULL) { | 596 | (kex->server_version = sshbuf_new()) == NULL) { |
592 | r = SSH_ERR_ALLOC_FAIL; | ||
593 | goto out; | ||
594 | } | ||
595 | if ((r = kex_prop2buf(kex->my, proposal)) != 0) | ||
596 | goto out; | ||
597 | kex->done = 0; | ||
598 | kex->flags = KEX_INITIAL; | ||
599 | kex_reset_dispatch(ssh); | ||
600 | ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit); | ||
601 | r = 0; | ||
602 | *kexp = kex; | ||
603 | out: | ||
604 | if (r != 0) | ||
605 | kex_free(kex); | 597 | kex_free(kex); |
606 | return r; | 598 | return NULL; |
599 | } | ||
600 | return kex; | ||
607 | } | 601 | } |
608 | 602 | ||
609 | void | 603 | void |
@@ -642,6 +636,9 @@ kex_free(struct kex *kex) | |||
642 | { | 636 | { |
643 | u_int mode; | 637 | u_int mode; |
644 | 638 | ||
639 | if (kex == NULL) | ||
640 | return; | ||
641 | |||
645 | #ifdef WITH_OPENSSL | 642 | #ifdef WITH_OPENSSL |
646 | DH_free(kex->dh); | 643 | DH_free(kex->dh); |
647 | #ifdef OPENSSL_HAS_ECC | 644 | #ifdef OPENSSL_HAS_ECC |
@@ -654,9 +651,9 @@ kex_free(struct kex *kex) | |||
654 | } | 651 | } |
655 | sshbuf_free(kex->peer); | 652 | sshbuf_free(kex->peer); |
656 | sshbuf_free(kex->my); | 653 | sshbuf_free(kex->my); |
654 | sshbuf_free(kex->client_version); | ||
655 | sshbuf_free(kex->server_version); | ||
657 | free(kex->session_id); | 656 | free(kex->session_id); |
658 | free(kex->client_version_string); | ||
659 | free(kex->server_version_string); | ||
660 | free(kex->failed_choice); | 657 | free(kex->failed_choice); |
661 | free(kex->hostkey_alg); | 658 | free(kex->hostkey_alg); |
662 | free(kex->name); | 659 | free(kex->name); |
@@ -664,11 +661,24 @@ kex_free(struct kex *kex) | |||
664 | } | 661 | } |
665 | 662 | ||
666 | int | 663 | int |
664 | kex_ready(struct ssh *ssh, char *proposal[PROPOSAL_MAX]) | ||
665 | { | ||
666 | int r; | ||
667 | |||
668 | if ((r = kex_prop2buf(ssh->kex->my, proposal)) != 0) | ||
669 | return r; | ||
670 | ssh->kex->flags = KEX_INITIAL; | ||
671 | kex_reset_dispatch(ssh); | ||
672 | ssh_dispatch_set(ssh, SSH2_MSG_KEXINIT, &kex_input_kexinit); | ||
673 | return 0; | ||
674 | } | ||
675 | |||
676 | int | ||
667 | kex_setup(struct ssh *ssh, char *proposal[PROPOSAL_MAX]) | 677 | kex_setup(struct ssh *ssh, char *proposal[PROPOSAL_MAX]) |
668 | { | 678 | { |
669 | int r; | 679 | int r; |
670 | 680 | ||
671 | if ((r = kex_new(ssh, proposal, &ssh->kex)) != 0) | 681 | if ((r = kex_ready(ssh, proposal)) != 0) |
672 | return r; | 682 | return r; |
673 | if ((r = kex_send_kexinit(ssh)) != 0) { /* we start */ | 683 | if ((r = kex_send_kexinit(ssh)) != 0) { /* we start */ |
674 | kex_free(ssh->kex); | 684 | kex_free(ssh->kex); |
@@ -1043,3 +1053,233 @@ dump_digest(char *msg, u_char *digest, int len) | |||
1043 | sshbuf_dump_data(digest, len, stderr); | 1053 | sshbuf_dump_data(digest, len, stderr); |
1044 | } | 1054 | } |
1045 | #endif | 1055 | #endif |
1056 | |||
1057 | /* | ||
1058 | * Send a plaintext error message to the peer, suffixed by \r\n. | ||
1059 | * Only used during banner exchange, and there only for the server. | ||
1060 | */ | ||
1061 | static void | ||
1062 | send_error(struct ssh *ssh, char *msg) | ||
1063 | { | ||
1064 | char *crnl = "\r\n"; | ||
1065 | |||
1066 | if (!ssh->kex->server) | ||
1067 | return; | ||
1068 | |||
1069 | if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), | ||
1070 | msg, strlen(msg)) != strlen(msg) || | ||
1071 | atomicio(vwrite, ssh_packet_get_connection_out(ssh), | ||
1072 | crnl, strlen(crnl)) != strlen(crnl)) | ||
1073 | error("%s: write: %.100s", __func__, strerror(errno)); | ||
1074 | } | ||
1075 | |||
1076 | /* | ||
1077 | * Sends our identification string and waits for the peer's. Will block for | ||
1078 | * up to timeout_ms (or indefinitely if timeout_ms <= 0). | ||
1079 | * Returns on 0 success or a ssherr.h code on failure. | ||
1080 | */ | ||
1081 | int | ||
1082 | kex_exchange_identification(struct ssh *ssh, int timeout_ms, | ||
1083 | const char *version_addendum) | ||
1084 | { | ||
1085 | int remote_major, remote_minor, mismatch; | ||
1086 | size_t len, i, n; | ||
1087 | int r, expect_nl; | ||
1088 | u_char c; | ||
1089 | struct sshbuf *our_version = ssh->kex->server ? | ||
1090 | ssh->kex->server_version : ssh->kex->client_version; | ||
1091 | struct sshbuf *peer_version = ssh->kex->server ? | ||
1092 | ssh->kex->client_version : ssh->kex->server_version; | ||
1093 | char *our_version_string = NULL, *peer_version_string = NULL; | ||
1094 | char *cp, *remote_version = NULL; | ||
1095 | |||
1096 | /* Prepare and send our banner */ | ||
1097 | sshbuf_reset(our_version); | ||
1098 | if (version_addendum != NULL && *version_addendum == '\0') | ||
1099 | version_addendum = NULL; | ||
1100 | if ((r = sshbuf_putf(our_version, "SSH-%d.%d-%.100s%s%s\r\n", | ||
1101 | PROTOCOL_MAJOR_2, PROTOCOL_MINOR_2, SSH_VERSION, | ||
1102 | version_addendum == NULL ? "" : " ", | ||
1103 | version_addendum == NULL ? "" : version_addendum)) != 0) { | ||
1104 | error("%s: sshbuf_putf: %s", __func__, ssh_err(r)); | ||
1105 | goto out; | ||
1106 | } | ||
1107 | |||
1108 | if (atomicio(vwrite, ssh_packet_get_connection_out(ssh), | ||
1109 | sshbuf_mutable_ptr(our_version), | ||
1110 | sshbuf_len(our_version)) != sshbuf_len(our_version)) { | ||
1111 | error("%s: write: %.100s", __func__, strerror(errno)); | ||
1112 | r = SSH_ERR_SYSTEM_ERROR; | ||
1113 | goto out; | ||
1114 | } | ||
1115 | if ((r = sshbuf_consume_end(our_version, 2)) != 0) { /* trim \r\n */ | ||
1116 | error("%s: sshbuf_consume_end: %s", __func__, ssh_err(r)); | ||
1117 | goto out; | ||
1118 | } | ||
1119 | our_version_string = sshbuf_dup_string(our_version); | ||
1120 | if (our_version_string == NULL) { | ||
1121 | error("%s: sshbuf_dup_string failed", __func__); | ||
1122 | r = SSH_ERR_ALLOC_FAIL; | ||
1123 | goto out; | ||
1124 | } | ||
1125 | debug("Local version string %.100s", our_version_string); | ||
1126 | |||
1127 | /* Read other side's version identification. */ | ||
1128 | for (n = 0; ; n++) { | ||
1129 | if (n >= SSH_MAX_PRE_BANNER_LINES) { | ||
1130 | send_error(ssh, "No SSH identification string " | ||
1131 | "received."); | ||
1132 | error("%s: No SSH version received in first %u lines " | ||
1133 | "from server", __func__, SSH_MAX_PRE_BANNER_LINES); | ||
1134 | r = SSH_ERR_INVALID_FORMAT; | ||
1135 | goto out; | ||
1136 | } | ||
1137 | sshbuf_reset(peer_version); | ||
1138 | expect_nl = 0; | ||
1139 | for (i = 0; ; i++) { | ||
1140 | if (timeout_ms > 0) { | ||
1141 | r = waitrfd(ssh_packet_get_connection_in(ssh), | ||
1142 | &timeout_ms); | ||
1143 | if (r == -1 && errno == ETIMEDOUT) { | ||
1144 | send_error(ssh, "Timed out waiting " | ||
1145 | "for SSH identification string."); | ||
1146 | error("Connection timed out during " | ||
1147 | "banner exchange"); | ||
1148 | r = SSH_ERR_CONN_TIMEOUT; | ||
1149 | goto out; | ||
1150 | } else if (r == -1) { | ||
1151 | error("%s: %s", | ||
1152 | __func__, strerror(errno)); | ||
1153 | r = SSH_ERR_SYSTEM_ERROR; | ||
1154 | goto out; | ||
1155 | } | ||
1156 | } | ||
1157 | |||
1158 | len = atomicio(read, ssh_packet_get_connection_in(ssh), | ||
1159 | &c, 1); | ||
1160 | if (len != 1 && errno == EPIPE) { | ||
1161 | error("%s: Connection closed by remote host", | ||
1162 | __func__); | ||
1163 | r = SSH_ERR_CONN_CLOSED; | ||
1164 | goto out; | ||
1165 | } else if (len != 1) { | ||
1166 | error("%s: read: %.100s", | ||
1167 | __func__, strerror(errno)); | ||
1168 | r = SSH_ERR_SYSTEM_ERROR; | ||
1169 | goto out; | ||
1170 | } | ||
1171 | if (c == '\r') { | ||
1172 | expect_nl = 1; | ||
1173 | continue; | ||
1174 | } | ||
1175 | if (c == '\n') | ||
1176 | break; | ||
1177 | if (c == '\0' || expect_nl) { | ||
1178 | error("%s: banner line contains invalid " | ||
1179 | "characters", __func__); | ||
1180 | goto invalid; | ||
1181 | } | ||
1182 | if ((r = sshbuf_put_u8(peer_version, c)) != 0) { | ||
1183 | error("%s: sshbuf_put: %s", | ||
1184 | __func__, ssh_err(r)); | ||
1185 | goto out; | ||
1186 | } | ||
1187 | if (sshbuf_len(peer_version) > SSH_MAX_BANNER_LEN) { | ||
1188 | error("%s: banner line too long", __func__); | ||
1189 | goto invalid; | ||
1190 | } | ||
1191 | } | ||
1192 | /* Is this an actual protocol banner? */ | ||
1193 | if (sshbuf_len(peer_version) > 4 && | ||
1194 | memcmp(sshbuf_ptr(peer_version), "SSH-", 4) == 0) | ||
1195 | break; | ||
1196 | /* If not, then just log the line and continue */ | ||
1197 | if ((cp = sshbuf_dup_string(peer_version)) == NULL) { | ||
1198 | error("%s: sshbuf_dup_string failed", __func__); | ||
1199 | r = SSH_ERR_ALLOC_FAIL; | ||
1200 | goto out; | ||
1201 | } | ||
1202 | /* Do not accept lines before the SSH ident from a client */ | ||
1203 | if (ssh->kex->server) { | ||
1204 | error("%s: client sent invalid protocol identifier " | ||
1205 | "\"%.256s\"", __func__, cp); | ||
1206 | free(cp); | ||
1207 | goto invalid; | ||
1208 | } | ||
1209 | debug("%s: banner line %zu: %s", __func__, n, cp); | ||
1210 | free(cp); | ||
1211 | } | ||
1212 | peer_version_string = sshbuf_dup_string(peer_version); | ||
1213 | if (peer_version_string == NULL) | ||
1214 | error("%s: sshbuf_dup_string failed", __func__); | ||
1215 | /* XXX must be same size for sscanf */ | ||
1216 | if ((remote_version = calloc(1, sshbuf_len(peer_version))) == NULL) { | ||
1217 | error("%s: calloc failed", __func__); | ||
1218 | r = SSH_ERR_ALLOC_FAIL; | ||
1219 | goto out; | ||
1220 | } | ||
1221 | |||
1222 | /* | ||
1223 | * Check that the versions match. In future this might accept | ||
1224 | * several versions and set appropriate flags to handle them. | ||
1225 | */ | ||
1226 | if (sscanf(peer_version_string, "SSH-%d.%d-%[^\n]\n", | ||
1227 | &remote_major, &remote_minor, remote_version) != 3) { | ||
1228 | error("Bad remote protocol version identification: '%.100s'", | ||
1229 | peer_version_string); | ||
1230 | invalid: | ||
1231 | send_error(ssh, "Invalid SSH identification string."); | ||
1232 | r = SSH_ERR_INVALID_FORMAT; | ||
1233 | goto out; | ||
1234 | } | ||
1235 | debug("Remote protocol version %d.%d, remote software version %.100s", | ||
1236 | remote_major, remote_minor, remote_version); | ||
1237 | ssh->compat = compat_datafellows(remote_version); | ||
1238 | |||
1239 | mismatch = 0; | ||
1240 | switch (remote_major) { | ||
1241 | case 2: | ||
1242 | break; | ||
1243 | case 1: | ||
1244 | if (remote_minor != 99) | ||
1245 | mismatch = 1; | ||
1246 | break; | ||
1247 | default: | ||
1248 | mismatch = 1; | ||
1249 | break; | ||
1250 | } | ||
1251 | if (mismatch) { | ||
1252 | error("Protocol major versions differ: %d vs. %d", | ||
1253 | PROTOCOL_MAJOR_2, remote_major); | ||
1254 | send_error(ssh, "Protocol major versions differ."); | ||
1255 | r = SSH_ERR_NO_PROTOCOL_VERSION; | ||
1256 | goto out; | ||
1257 | } | ||
1258 | |||
1259 | if (ssh->kex->server && (ssh->compat & SSH_BUG_PROBE) != 0) { | ||
1260 | logit("probed from %s port %d with %s. Don't panic.", | ||
1261 | ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), | ||
1262 | peer_version_string); | ||
1263 | r = SSH_ERR_CONN_CLOSED; /* XXX */ | ||
1264 | goto out; | ||
1265 | } | ||
1266 | if (ssh->kex->server && (ssh->compat & SSH_BUG_SCANNER) != 0) { | ||
1267 | logit("scanned from %s port %d with %s. Don't panic.", | ||
1268 | ssh_remote_ipaddr(ssh), ssh_remote_port(ssh), | ||
1269 | peer_version_string); | ||
1270 | r = SSH_ERR_CONN_CLOSED; /* XXX */ | ||
1271 | goto out; | ||
1272 | } | ||
1273 | if ((ssh->compat & SSH_BUG_RSASIGMD5) != 0) { | ||
1274 | logit("Remote version \"%.100s\" uses unsafe RSA signature " | ||
1275 | "scheme; disabling use of RSA keys", remote_version); | ||
1276 | } | ||
1277 | /* success */ | ||
1278 | r = 0; | ||
1279 | out: | ||
1280 | free(our_version_string); | ||
1281 | free(peer_version_string); | ||
1282 | free(remote_version); | ||
1283 | return r; | ||
1284 | } | ||
1285 | |||