-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Synopsis: OpenSSH CBC cipher mode plain text disclosure vulnerability
NetBSD versions: 5.0
Thanks to: Martin Albrecht, Kenny Paterson and Gaven Watson
Reported in NetBSD Security Advisory: NetBSD-SA2009-005

Index: crypto/dist/ssh/cipher.c
===================================================================
RCS file: /cvsroot/src/crypto/dist/ssh/cipher.c,v
retrieving revision 1.21
diff -u -r1.21 cipher.c
- --- cipher.c	23 Jun 2008 14:51:31 -0000	1.21
+++ cipher.c	29 Jun 2009 22:41:39 -0000
@@ -62,38 +62,39 @@
 	u_int	block_size;
 	u_int	key_len;
 	u_int	discard_len;
+	u_int	cbc_mode;
 	const EVP_CIPHER	*(*evptype)(void);
 } ciphers[] = {
- -	{ "none",		SSH_CIPHER_NONE, 8, 0, 0, EVP_enc_null },
- -	{ "des",		SSH_CIPHER_DES, 8, 8, 0, EVP_des_cbc },
- -	{ "3des",		SSH_CIPHER_3DES, 8, 16, 0, evp_ssh1_3des },
- -	{ "blowfish",		SSH_CIPHER_BLOWFISH, 8, 32, 0, evp_ssh1_bf },
- -
- -	{ "3des-cbc",		SSH_CIPHER_SSH2, 8, 24, 0, EVP_des_ede3_cbc },
- -	{ "blowfish-cbc",	SSH_CIPHER_SSH2, 8, 16, 0, EVP_bf_cbc },
- -	{ "cast128-cbc",	SSH_CIPHER_SSH2, 8, 16, 0, EVP_cast5_cbc },
- -	{ "arcfour",		SSH_CIPHER_SSH2, 8, 16, 0, EVP_rc4 },
- -	{ "arcfour128",		SSH_CIPHER_SSH2, 8, 16, 1536, EVP_rc4 },
- -	{ "arcfour256",		SSH_CIPHER_SSH2, 8, 32, 1536, EVP_rc4 },
- -	{ "aes128-cbc",		SSH_CIPHER_SSH2, 16, 16, 0, EVP_aes_128_cbc },
- -	{ "aes192-cbc",		SSH_CIPHER_SSH2, 16, 24, 0, EVP_aes_192_cbc },
- -	{ "aes256-cbc",		SSH_CIPHER_SSH2, 16, 32, 0, EVP_aes_256_cbc },
+	{ "none",		SSH_CIPHER_NONE, 8, 0, 0, 0, EVP_enc_null },
+	{ "des",		SSH_CIPHER_DES, 8, 8, 0, 1, EVP_des_cbc },
+	{ "3des",		SSH_CIPHER_3DES, 8, 16, 0, 1, evp_ssh1_3des },
+	{ "blowfish",		SSH_CIPHER_BLOWFISH, 8, 32, 0, 0, evp_ssh1_bf },
+
+	{ "3des-cbc",		SSH_CIPHER_SSH2, 8, 24, 0, 1, EVP_des_ede3_cbc },
+	{ "blowfish-cbc",	SSH_CIPHER_SSH2, 8, 16, 0, 1, EVP_bf_cbc },
+	{ "cast128-cbc",	SSH_CIPHER_SSH2, 8, 16, 0, 1, EVP_cast5_cbc },
+	{ "arcfour",		SSH_CIPHER_SSH2, 8, 16, 0, 0, EVP_rc4 },
+	{ "arcfour128",		SSH_CIPHER_SSH2, 8, 16, 1536, 0, EVP_rc4 },
+	{ "arcfour256",		SSH_CIPHER_SSH2, 8, 32, 1536, 0, EVP_rc4 },
+	{ "aes128-cbc",		SSH_CIPHER_SSH2, 16, 16, 0, 1, EVP_aes_128_cbc },
+	{ "aes192-cbc",		SSH_CIPHER_SSH2, 16, 24, 0, 1, EVP_aes_192_cbc },
+	{ "aes256-cbc",		SSH_CIPHER_SSH2, 16, 32, 0, 1, EVP_aes_256_cbc },
 	{ "rijndael-cbc@lysator.liu.se",
- -				SSH_CIPHER_SSH2, 16, 32, 0, EVP_aes_256_cbc },
+				SSH_CIPHER_SSH2, 16, 32, 0, 1, EVP_aes_256_cbc },
 #ifdef AES_CTR_MT
- -	{ "aes128-ctr",		SSH_CIPHER_SSH2, 16, 16, 0, evp_aes_ctr_mt },
- -	{ "aes192-ctr",		SSH_CIPHER_SSH2, 16, 24, 0, evp_aes_ctr_mt },
- -	{ "aes256-ctr",		SSH_CIPHER_SSH2, 16, 32, 0, evp_aes_ctr_mt },
+	{ "aes128-ctr",		SSH_CIPHER_SSH2, 16, 16, 0, 0, evp_aes_ctr_mt },
+	{ "aes192-ctr",		SSH_CIPHER_SSH2, 16, 24, 0, 0, evp_aes_ctr_mt },
+	{ "aes256-ctr",		SSH_CIPHER_SSH2, 16, 32, 0, 0, evp_aes_ctr_mt },
 #else
- -	{ "aes128-ctr",		SSH_CIPHER_SSH2, 16, 16, 0, evp_aes_128_ctr },
- -	{ "aes192-ctr",		SSH_CIPHER_SSH2, 16, 24, 0, evp_aes_128_ctr },
- -	{ "aes256-ctr",		SSH_CIPHER_SSH2, 16, 32, 0, evp_aes_128_ctr },
+	{ "aes128-ctr",		SSH_CIPHER_SSH2, 16, 16, 0, 0, evp_aes_128_ctr },
+	{ "aes192-ctr",		SSH_CIPHER_SSH2, 16, 24, 0, 0, evp_aes_128_ctr },
+	{ "aes256-ctr",		SSH_CIPHER_SSH2, 16, 32, 0, 0, evp_aes_128_ctr },
 #endif
 #ifdef ACSS
- -	{ "acss@openssh.org",	SSH_CIPHER_SSH2, 16, 5, 0, EVP_acss },
+	{ "acss@openssh.org",	SSH_CIPHER_SSH2, 16, 5, 0, 0, EVP_acss },
 #endif
 
- -	{ NULL,			SSH_CIPHER_INVALID, 0, 0, 0, NULL }
+	{ NULL,			SSH_CIPHER_INVALID, 0, 0, 0, 0, NULL }
 };
 
 #ifndef ACSS
@@ -121,6 +122,12 @@
 }
 
 u_int
+cipher_is_cbc(const Cipher *c)
+{
+	return (c->cbc_mode);
+}
+
+u_int
 cipher_mask_ssh1(int client)
 {
 	u_int mask = 0;
Index: crypto/dist/ssh/cipher.h
===================================================================
RCS file: /cvsroot/src/crypto/dist/ssh/cipher.h,v
retrieving revision 1.2
diff -u -r1.2 cipher.h
- --- cipher.h	28 Sep 2006 21:22:14 -0000	1.2
+++ cipher.h	29 Jun 2009 22:41:40 -0000
@@ -82,6 +82,7 @@
 void	 cipher_set_key_string(CipherContext *, Cipher *, const char *, int);
 u_int	 cipher_blocksize(const Cipher *);
 u_int	 cipher_keylen(const Cipher *);
+u_int	 cipher_is_cbc(const Cipher *);
 
 u_int	 cipher_get_number(const Cipher *);
 void	 cipher_get_keyiv(CipherContext *, u_char *, u_int);
Index: crypto/dist/ssh/packet.c
===================================================================
RCS file: /cvsroot/src/crypto/dist/ssh/packet.c,v
retrieving revision 1.31
diff -u -r1.31 packet.c
- --- packet.c	16 Feb 2009 20:53:54 -0000	1.31
+++ packet.c	29 Jun 2009 22:41:41 -0000
@@ -84,6 +84,8 @@
 #define DBG(x)
 #endif
 
+#define PACKET_MAX_SIZE (256 * 1024)
+
 /*
  * This variable contains the file descriptors used for communicating with
  * the other side.  connection_in is used for reading; connection_out for
@@ -160,6 +162,10 @@
 /* roundup current message to extra_pad bytes */
 static u_char extra_pad = 0;
 
+/* XXX discard incoming data after MAC error */
+static u_int packet_discard = 0;
+static Mac *packet_discard_mac = NULL;
+
 struct packet {
 	TAILQ_ENTRY(packet) next;
 	u_char type;
@@ -209,6 +215,36 @@
 		packet_timeout_ms = timeout * count * 1000;
 }
 
+static void
+packet_stop_discard(void)
+{
+	if (packet_discard_mac) {
+		char buf[1024];
+		
+		memset(buf, 'a', sizeof(buf));
+		while (buffer_len(&incoming_packet) < PACKET_MAX_SIZE)
+			buffer_append(&incoming_packet, buf, sizeof(buf));
+		(void) mac_compute(packet_discard_mac,
+		    p_read.seqnr,
+		    buffer_ptr(&incoming_packet),
+		    PACKET_MAX_SIZE);
+	}
+	logit("Finished discarding for %.200s", get_remote_ipaddr());
+	cleanup_exit(255);
+}
+
+static void
+packet_start_discard(Enc *enc, Mac *mac, u_int packet_length, u_int discard)
+{
+	if (!cipher_is_cbc(enc->cipher))
+		packet_disconnect("Packet corrupt");
+	if (packet_length != PACKET_MAX_SIZE && mac && mac->enabled)
+		packet_discard_mac = mac;
+	if (buffer_len(&input) >= discard)
+		packet_stop_discard();
+	packet_discard = discard - buffer_len(&input);
+}
+
 /* Returns 1 if remote host is connected via socket, 0 if not. */
 
 int
@@ -1126,6 +1162,9 @@
 	Mac *mac   = NULL;
 	Comp *comp = NULL;
 
+	if (packet_discard)
+		return SSH_MSG_NONE;
+
 	if (newkeys[MODE_IN] != NULL) {
 		enc  = &newkeys[MODE_IN]->enc;
 		mac  = &newkeys[MODE_IN]->mac;
@@ -1147,11 +1186,15 @@
 		    block_size);
 		cp = buffer_ptr(&incoming_packet);
 		packet_length = get_u32(cp);
- -		if (packet_length < 1 + 4 || packet_length > 256 * 1024) {
+		if (packet_length < 1 + 4 || packet_length > PACKET_MAX_SIZE) {
 #ifdef PACKET_DEBUG
 			buffer_dump(&incoming_packet);
 #endif
- -			packet_disconnect("Bad packet length %u.", packet_length);
+			logit("Bad packet length %-10u.",
+			    packet_length);
+			packet_start_discard(enc, mac, packet_length,
+			    PACKET_MAX_SIZE);
+			return SSH_MSG_NONE;
 		}
 		DBG(debug("input: packet len %u", packet_length+4));
 		buffer_consume(&input, block_size);
@@ -1160,9 +1203,13 @@
 	need = 4 + packet_length - block_size;
 	DBG(debug("partial packet %d, need %d, maclen %d", block_size,
 	    need, maclen));
- -	if (need % block_size != 0)
- -		fatal("padding error: need %d block %d mod %d",
+	if (need % block_size != 0) {
+		logit("padding error: need %d block %d mod %d",
 		    need, block_size, need % block_size);
+		packet_start_discard(enc, mac, packet_length,
+		    PACKET_MAX_SIZE - block_size);
+		return SSH_MSG_NONE;
+	}
 	/*
 	 * check if the entire packet has been received and
 	 * decrypt into incoming_packet
@@ -1184,11 +1231,19 @@
 		macbuf = mac_compute(mac, p_read.seqnr,
 		    buffer_ptr(&incoming_packet),
 		    buffer_len(&incoming_packet));
- -		if (memcmp(macbuf, buffer_ptr(&input), mac->mac_len) != 0)
- -			packet_disconnect("Corrupted MAC on input.");
+		if (memcmp(macbuf, buffer_ptr(&input), mac->mac_len) != 0) {
+			logit("Corrupted MAC on input.");
+			if (need > PACKET_MAX_SIZE)
+				fatal("internal error need %d", need);
+			packet_start_discard(enc, mac, packet_length,
+			    PACKET_MAX_SIZE - need);
+			return SSH_MSG_NONE;
+		}
+				
 		DBG(debug("MAC #%d ok", p_read.seqnr));
 		buffer_consume(&input, mac->mac_len);
 	}
+	/* XXX now it's safe to use fatal/packet_disconnect */
 	if (seqnr_p != NULL)
 		*seqnr_p = p_read.seqnr;
 	if (++p_read.seqnr == 0)
@@ -1321,6 +1376,13 @@
 void
 packet_process_incoming(const char *buf, u_int len)
 {
+	if (packet_discard) {
+		keep_alive_timeouts = 0; /* ?? */
+		if (len >= packet_discard)
+			packet_stop_discard();
+		packet_discard -= len;
+		return;
+	}
 	buffer_append(&input, buf, len);
 }
 
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (NetBSD)

iQIcBAEBAgAGBQJKSoQoAAoJEAZJc6xMSnBuWuAQAImaNvEk3CEhlWntklbTU7Eg
Igc/86dgqKQ5v8t9iFaU34e+o67Sqr/TKUnXGwXE9TarmOBwL88u/cU6Ed4pld6m
FdN5L+NKa16DAL3ll+k16EocY7hZLy0gwHgNhNvvVYZOumMI//dWZMMI1kt8+8/K
kAvuRAjzMH8xSTCSkyusrG9QDLqvL45zWnRkIQF/gaEvK6n+hIg+QqSAj+58NHlV
7Zxl3kEqY8eIYeqB4S3XhIAwIwk412NWSQ02u41u4bLeeDAOTkTeIn1uH5G4hBRE
h25YA1mm7YiritL/FEEiWzgZSHGA7zouIbDEYvZZavmZ7eePHaU9IHyuOLLfaTTD
VotM+1efMTRymFV7xe6CQbkEIqdJVUcdscYXt7iAbX07WzHVHxBxL+MWSwmM/IFq
oDesMtSw8OfinF7k/HBvb9TCHUQN6PZUtxD3jfMhxhLO/qvzWr3iXRmpH1kttaEs
baXK0e8tGp/THe27OlDJ993JcaG33JNEMF+bqQatoAyyGNtD9GR2fmSwunDtEf2v
AQyPbkHsN7dlG42W6WJTOX6hVLqQkcIAu9GZYr8/51o3QVsLMaI7kV8qAxQAUcss
kbSw5BfP70kYiN2gcZGhl4G12y5sU68H486JJ2wqC5Y8eW2XL9dcHlrWOyvpM0N2
NenU6wAjxSoa3pvAbgL9
=VBEC
-----END PGP SIGNATURE-----