mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-10-26 05:59:37 +00:00
Limit the length of SCP control lines
ReadString accumulated bytes until a line feed with no upper bound, so a server could exhaust client memory by never sending one. Control lines and error messages are now limited to 64 KiB; a longer line throws ScpException.
This commit is contained in:
@@ -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
|
||||
|
||||
/// <summary>
|
||||
/// The maximum length, in bytes and excluding the terminating line feed, of a control line
|
||||
/// or error message received from the server.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies that the length of a control line received from the server is bounded.
|
||||
/// </summary>
|
||||
[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<ScpException>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user