Validate collection sizes before allocating (#1848)

Validate cases where (in particular) a few bytes of server data determines the size of
a collection allocated, and throw if it's not possible based on the size of the buffer
that the collection is purported to be contained in.
This commit is contained in:
Rob Hague
2026-10-19 19:52:37 +02:00
committed by GitHub
parent 293bd03c79
commit b741550e8e
6 changed files with 87 additions and 20 deletions
+3 -6
View File
@@ -371,12 +371,9 @@ namespace Renci.SshNet.Common
/// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is greater than the internal buffer size.</exception>
internal byte[] ReadBytes(int length)
{
var data = new byte[length];
var bytesRead = Read(data, 0, length);
if (bytesRead < length)
{
throw new ArgumentOutOfRangeException(nameof(length), string.Format(CultureInfo.InvariantCulture, "The requested length ({0}) is greater than the actual number of bytes read ({1}).", length, bytesRead));
}
var data = GetRemainingBuffer().Slice(0, length).ToArray();
Position += length;
return data;
}
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Renci.SshNet.Common;
namespace Renci.SshNet.Sftp.Responses
{
internal sealed class SftpNameResponse : SftpResponse
@@ -29,6 +32,13 @@ namespace Renci.SshNet.Sftp.Responses
base.LoadData();
Count = ReadUInt32();
// Each entry is at least two uints in size.
if (Count > (DataStream.Length - DataStream.Position) / 8)
{
throw new SshException(string.Format(CultureInfo.InvariantCulture, "Invalid SSH_FXP_NAME count: {0}.", Count));
}
Files = new KeyValuePair<string, SftpFileAttributes>[Count];
for (var i = 0; i < Count; i++)
+10 -2
View File
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -820,8 +821,15 @@ namespace Renci.SshNet.Sftp
if ((flag & SSH_FILEXFER_ATTR_EXTENDED) == SSH_FILEXFER_ATTR_EXTENDED)
{
var extendedCount = (int)stream.ReadUInt32();
extensions = new Dictionary<string, string>(extendedCount);
var extendedCount = stream.ReadUInt32();
// Each extension is at least two string lengths.
if (extendedCount > (stream.Length - stream.Position) / 8)
{
throw new SshException(string.Format(CultureInfo.InvariantCulture, "Invalid extended attribute count: {0}.", extendedCount));
}
extensions = new Dictionary<string, string>((int)extendedCount);
for (var i = 0; i < extendedCount; i++)
{
var extensionName = stream.ReadString(Encoding.UTF8);
@@ -102,23 +102,15 @@ namespace Renci.SshNet.Tests.Classes.Common
}
[TestMethod]
public void ReadBytes_Length_LengthIsGreaterThanNumberOfBytesAvailable()
[DataRow(4)]
[DataRow(int.MaxValue)]
public void ReadBytes_Length_LengthIsGreaterThanNumberOfBytesAvailable(int length)
{
MySshData sshData = new MySshData();
sshData.Load(new byte[] { 0x01, 0x0d, 0x0a }, 0, 3);
try
{
sshData.ReadBytes(4);
Assert.Fail();
}
catch (ArgumentOutOfRangeException ex)
{
Assert.AreEqual(typeof(ArgumentOutOfRangeException), ex.GetType());
Assert.IsNull(ex.InnerException);
Assert.AreEqual("length", ex.ParamName);
}
Assert.Throws<ArgumentException>(() => sshData.ReadBytes(length));
}
[TestMethod]
@@ -0,0 +1,47 @@
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Common;
using Renci.SshNet.Sftp.Responses;
namespace Renci.SshNet.Tests.Classes.Sftp.Responses
{
[TestClass]
public class SftpNameResponseTest
{
[TestMethod]
public void Load()
{
var target = new SftpNameResponse(3, Encoding.UTF8);
target.Load([
0, 0, 0, 7, // response id
0, 0, 0, 1, // count
0, 0, 0, 1, (byte)'a', // filename
0, 0, 0, 0, // longname
0, 0, 0, 0, // attribute flags
]);
Assert.AreEqual(7u, target.ResponseId);
Assert.HasCount(1, target.Files);
Assert.AreEqual("a", target.Files[0].Key);
}
[TestMethod]
public void Load_CountExceedsData_Throws()
{
var target = new SftpNameResponse(3, Encoding.UTF8);
var ex = Assert.ThrowsExactly<SshException>(() => target.Load([
0, 0, 0, 7, // response id
0xFF, 0xFF, 0xFF, 0xFF, // count
0, 0, 0, 1, (byte)'a', // filename
0, 0, 0, 0, // longname
0, 0, 0, 0, // attribute flags
]));
Assert.AreEqual("Invalid SSH_FXP_NAME count: 4294967295.", ex.Message);
}
}
}
@@ -44,6 +44,19 @@ namespace Renci.SshNet.Tests.Classes.Sftp
Assert.AreEqual(isNamedPipe, attributes.IsNamedPipe);
}
[TestMethod]
public void FromBytes_ExtendedCountExceedsData_Throws()
{
var ex = Assert.ThrowsExactly<SshException>(() => SftpFileAttributes.FromBytes([
0x80, 0, 0, 0, // flags: extended
0x7F, 0xFF, 0xFF, 0xFF, // extended count
0, 0, 0, 1, (byte)'a', // extension name
0, 0, 0, 1, (byte)'b', // extension data
]));
Assert.AreEqual("Invalid extended attribute count: 2147483647.", ex.Message);
}
[TestMethod]
public void FromBytesGetBytes()
{