diff --git a/src/Renci.SshNet/ScpClient.cs b/src/Renci.SshNet/ScpClient.cs
index 20704502..a630bb18 100644
--- a/src/Renci.SshNet/ScpClient.cs
+++ b/src/Renci.SshNet/ScpClient.cs
@@ -5,6 +5,9 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
+#if NET
+using System.Runtime.InteropServices;
+#endif
using System.Text.RegularExpressions;
using System.Threading;
@@ -86,6 +89,12 @@ namespace Renci.SshNet
private static readonly Regex TimestampRegex = new Regex(TimestampPattern, RegexOptions.Compiled);
#endif
+ ///
+ /// The maximum length, in bytes and excluding the terminating line feed, of a control line
+ /// or error message received from the server.
+ ///
+ internal const int MaxControlLineLength = 64 * 1024;
+
private static readonly byte[] SuccessConfirmationCode = { 0 };
private static readonly byte[] ErrorConfirmationCode = { 1 };
@@ -753,18 +762,25 @@ namespace Renci.SshNet
while (b != SshNet.Session.LineFeed)
{
+ if (buffer.Count == MaxControlLineLength)
+ {
+ throw new ScpException(string.Format("The server sent a line that exceeds the maximum length of {0} bytes.", MaxControlLineLength));
+ }
+
buffer.Add((byte)b);
b = ReadByte(stream);
}
-
- var readBytes = buffer.ToArray();
-
+#if NET
+ var line = ConnectionInfo.Encoding.GetString(CollectionsMarshal.AsSpan(buffer));
+#else
+ var line = ConnectionInfo.Encoding.GetString(buffer.ToArray(), 0, buffer.Count);
+#endif
if (hasError)
{
- throw new ScpException(ConnectionInfo.Encoding.GetString(readBytes, 0, readBytes.Length));
+ throw new ScpException(line);
}
- return ConnectionInfo.Encoding.GetString(readBytes, 0, readBytes.Length);
+ return line;
}
///
diff --git a/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_ControlLineTooLong.cs b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_ControlLineTooLong.cs
new file mode 100644
index 00000000..ed9bd6df
--- /dev/null
+++ b/test/Renci.SshNet.Tests/Classes/ScpClientTest_Download_ControlLineTooLong.cs
@@ -0,0 +1,54 @@
+using System;
+using System.IO;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Renci.SshNet.Common;
+using Renci.SshNet.Tests.Common;
+
+namespace Renci.SshNet.Tests.Classes
+{
+ ///
+ /// Verifies that the length of a control line received from the server is bounded.
+ ///
+ [TestClass]
+ public class ScpClientTest_Download_ControlLineTooLong
+ {
+ [TestMethod]
+ public void LineLongerThanMaximum_ShouldThrowScpException()
+ {
+ var transcript = new ScpServerTranscript()
+ .Raw("C0644 1 " + new string('a', ScpClient.MaxControlLineLength))
+ .Raw("\n");
+
+ using (var destination = new MemoryStream())
+ {
+ var result = transcript.Run(client => client.Download("/remote/file", destination), closeChannel: true);
+
+ Assert.IsTrue(result.Completed);
+ Assert.IsInstanceOfType(result.Exception);
+ Assert.Contains("maximum length", result.Exception.Message, StringComparison.Ordinal);
+ }
+ }
+
+ [TestMethod]
+ public void LineOfMaximumLength_ShouldBeAccepted()
+ {
+ const string Header = "C0644 1 ";
+
+ var transcript = new ScpServerTranscript()
+ .Line(Header + new string('a', ScpClient.MaxControlLineLength - Header.Length))
+ .Raw("x")
+ .Raw([0]);
+
+ using (var destination = new MemoryStream())
+ {
+ var result = transcript.Run(client => client.Download("/remote/file", destination), closeChannel: true);
+
+ Assert.IsTrue(result.Completed);
+ Assert.IsNull(result.Exception, result.Exception?.ToString());
+ Assert.AreEqual(1, destination.Length);
+ }
+ }
+ }
+}