@@ -6,10 +6,12 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, ReadBuf};
66
77use crate :: Error ;
88
9+ const SSH_ID_BUF_SIZE : usize = 256 ;
10+
911/// The buffer to read the identification string (first line in the
1012/// protocol). Not sensitive data — just protocol version exchange.
1113struct ReadSshIdBuffer {
12- pub buf : Vec < u8 > ,
14+ pub buf : Box < [ u8 ; SSH_ID_BUF_SIZE ] > ,
1315 pub total : usize ,
1416 pub bytes_read : usize ,
1517 pub sshid_len : usize ,
@@ -22,9 +24,8 @@ impl ReadSshIdBuffer {
2224 }
2325
2426 pub fn new ( ) -> ReadSshIdBuffer {
25- let buf = vec ! [ 0 ; 256 ] ;
2627 ReadSshIdBuffer {
27- buf,
28+ buf : Box :: new ( [ 0 ; SSH_ID_BUF_SIZE ] ) ,
2829 sshid_len : 0 ,
2930 bytes_read : 0 ,
3031 total : 0 ,
@@ -172,3 +173,113 @@ impl<R: AsyncRead + Unpin> SshRead<R> {
172173 }
173174 }
174175}
176+
177+ #[ cfg( test) ]
178+ mod tests {
179+ use super :: * ;
180+ use std:: iter;
181+
182+ #[ tokio:: test]
183+ async fn test_ssh_id_openssh ( ) {
184+ let data = "SSH-2.0-OpenSSH_10.2\r \n " ;
185+ let mut read = SshRead :: new ( data. as_bytes ( ) ) ;
186+
187+ let received = read. read_ssh_id ( ) . await . unwrap ( ) ;
188+ assert_eq ! ( received, b"SSH-2.0-OpenSSH_10.2" ) ;
189+ }
190+
191+ #[ tokio:: test]
192+ async fn test_ssh_id_openssh_7_4 ( ) {
193+ let data = "SSH-2.0-OpenSSH_7.4\n " ;
194+ let mut read = SshRead :: new ( data. as_bytes ( ) ) ;
195+
196+ let received = read. read_ssh_id ( ) . await . unwrap ( ) ;
197+ assert_eq ! ( received, b"SSH-2.0-OpenSSH_7.4" ) ;
198+ }
199+
200+ #[ tokio:: test]
201+ async fn test_ssh_id_too_long ( ) {
202+ let data = String :: from_iter ( iter:: once ( "SSH-2.0-" ) . chain (
203+ iter:: repeat ( "A" ) . take ( 500 ) ) ) ;
204+ let mut read = SshRead :: new ( data. as_bytes ( ) ) ;
205+
206+ let received = read. read_ssh_id ( ) . await ;
207+ assert ! ( matches!( received. err( ) , Some ( Error :: Disconnect ) ) ) ;
208+ }
209+
210+ #[ tokio:: test]
211+ async fn test_ssh_id_empty ( ) {
212+ let data = "" ;
213+ let mut read = SshRead :: new ( data. as_bytes ( ) ) ;
214+
215+ let received = read. read_ssh_id ( ) . await ;
216+ assert ! ( matches!( received. err( ) , Some ( Error :: Disconnect ) ) ) ;
217+ }
218+
219+ #[ tokio:: test]
220+ async fn test_ssh_id_almost_empty_cr_nl ( ) {
221+ let data = "SSH-2.0-\n " ;
222+ let mut read = SshRead :: new ( data. as_bytes ( ) ) ;
223+
224+ let received = read. read_ssh_id ( ) . await . unwrap ( ) ;
225+ assert_eq ! ( received, b"SSH-2.0-" ) ;
226+ }
227+
228+ #[ tokio:: test]
229+ async fn test_ssh_id_almost_empty_nl ( ) {
230+ let data = "SSH-2.0-\n " ;
231+ let mut read = SshRead :: new ( data. as_bytes ( ) ) ;
232+
233+ let received = read. read_ssh_id ( ) . await . unwrap ( ) ;
234+ assert_eq ! ( received, b"SSH-2.0-" ) ;
235+ }
236+
237+ #[ tokio:: test]
238+ async fn test_ssh_id_newline ( ) {
239+ let data = "\n " ;
240+ let mut read = SshRead :: new ( data. as_bytes ( ) ) ;
241+
242+ let received = read. read_ssh_id ( ) . await ;
243+ assert ! ( matches!( received. err( ) , Some ( Error :: Disconnect ) ) ) ;
244+ }
245+
246+ #[ tokio:: test]
247+ async fn test_ssh_id_contains_cr ( ) {
248+ // A \r that isn't followed by \n has no special meaning
249+ let data = "SSH-2.0-OpenSSH\r 10.2\n " ;
250+ let mut read = SshRead :: new ( data. as_bytes ( ) ) ;
251+
252+ let received = read. read_ssh_id ( ) . await . unwrap ( ) ;
253+ assert_eq ! ( received, b"SSH-2.0-OpenSSH\r 10.2" ) ;
254+ }
255+
256+ #[ tokio:: test]
257+ async fn test_ssh_id_trailing_cr ( ) {
258+ // Verify this doesn't cause an out-of-bounds access when testing for \r\n
259+ let data = "SSH-2.0-OpenSSH_10.2\r " ;
260+ let mut read = SshRead :: new ( data. as_bytes ( ) ) ;
261+
262+ let received = read. read_ssh_id ( ) . await ;
263+ assert ! ( matches!( received. err( ) , Some ( Error :: Disconnect ) ) ) ;
264+ }
265+
266+ #[ tokio:: test]
267+ async fn test_ssh_id_nl_cr ( ) {
268+ // Like \r\n but backwards
269+ let data = "SSH-2.0-OpenSSH_10.2\n \r " ;
270+ let mut read = SshRead :: new ( data. as_bytes ( ) ) ;
271+
272+ let received = read. read_ssh_id ( ) . await . unwrap ( ) ;
273+ assert_eq ! ( received, b"SSH-2.0-OpenSSH_10.2" ) ;
274+ }
275+
276+ #[ tokio:: test]
277+ async fn test_ssh_id_nl_cr_nl ( ) {
278+ // Like \r\n but backwards, but also part of \r\n
279+ let data = "SSH-2.0-OpenSSH_10.2\n \r \n " ;
280+ let mut read = SshRead :: new ( data. as_bytes ( ) ) ;
281+
282+ let received = read. read_ssh_id ( ) . await . unwrap ( ) ;
283+ assert_eq ! ( received, b"SSH-2.0-OpenSSH_10.2" ) ;
284+ }
285+ }
0 commit comments