Skip to content

Commit deed9c3

Browse files
authored
Fix range check / buffer overflow in ExpressionHasher.Append(char, char) (#8811)
1 parent 23758e8 commit deed9c3

File tree

2 files changed

+139
-35
lines changed

2 files changed

+139
-35
lines changed

src/GreenDonut/src/GreenDonut.Data/Internal/ExpressionHasher.cs

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ public ExpressionHasher()
2929
_initialSize = _buffer.Length;
3030
}
3131

32+
internal int BufferSize => _buffer.Length;
33+
internal int InitialBufferSize => _initialSize;
34+
3235
public ExpressionHasher Add(Expression expression)
3336
{
3437
Visit(expression);
@@ -50,8 +53,7 @@ public ExpressionHasher Add<T>(SortDefinition<T> sortDefinition)
5053

5154
public ExpressionHasher Add(char c)
5255
{
53-
Append(c);
54-
Append(';');
56+
Append(c, ';');
5557
return this;
5658
}
5759

@@ -211,12 +213,10 @@ private void Append(int i)
211213
{
212214
int written;
213215

214-
while (!Utf8Formatter.TryFormat(i, _buffer.AsSpan()[_start..], out written))
216+
var span = _buffer.AsSpan()[_start..];
217+
while (!Utf8Formatter.TryFormat(i, span, out written))
215218
{
216-
var newBuffer = ArrayPool<byte>.Shared.Rent(_buffer.Length * 2);
217-
_buffer.AsSpan()[.._start].CopyTo(newBuffer);
218-
ArrayPool<byte>.Shared.Return(_buffer);
219-
_buffer = newBuffer;
219+
span = ExpandRollingBufferCapacity(_start);
220220
}
221221

222222
_start += written;
@@ -262,23 +262,17 @@ private void Append(char s)
262262
{
263263
if (_start == _buffer.Length)
264264
{
265-
var newBuffer = ArrayPool<byte>.Shared.Rent(_buffer.Length * 2);
266-
_buffer.AsSpan().CopyTo(newBuffer);
267-
ArrayPool<byte>.Shared.Return(_buffer);
268-
_buffer = newBuffer;
265+
ExpandBufferCapacity();
269266
}
270267

271268
_buffer[_start++] = (byte)s;
272269
}
273270

274271
private void Append(char a, char b)
275272
{
276-
if (_start + 1 == _buffer.Length)
273+
if (_start + 1 >= _buffer.Length)
277274
{
278-
var newBuffer = ArrayPool<byte>.Shared.Rent(_buffer.Length * 2);
279-
_buffer.AsSpan().CopyTo(newBuffer);
280-
ArrayPool<byte>.Shared.Return(_buffer);
281-
_buffer = newBuffer;
275+
ExpandBufferCapacity();
282276
}
283277

284278
_buffer[_start++] = (byte)a;
@@ -293,11 +287,7 @@ private void Append(string s)
293287

294288
while (!Encoding.UTF8.TryGetBytes(chars, span, out written))
295289
{
296-
var newBuffer = ArrayPool<byte>.Shared.Rent(_buffer.Length * 2);
297-
_buffer.AsSpan()[.._start].CopyTo(newBuffer);
298-
ArrayPool<byte>.Shared.Return(_buffer);
299-
_buffer = newBuffer;
300-
span = _buffer.AsSpan()[_start..];
290+
span = ExpandRollingBufferCapacity(_start);
301291
}
302292

303293
_start += written;
@@ -310,11 +300,7 @@ private void Append(ReadOnlySpan<char> s)
310300

311301
while (!Encoding.UTF8.TryGetBytes(s, span, out written))
312302
{
313-
var newBuffer = ArrayPool<byte>.Shared.Rent(_buffer.Length * 2);
314-
_buffer.AsSpan()[.._start].CopyTo(newBuffer);
315-
ArrayPool<byte>.Shared.Return(_buffer);
316-
_buffer = newBuffer;
317-
span = _buffer.AsSpan()[_start..];
303+
span = ExpandRollingBufferCapacity(_start);
318304
}
319305

320306
_start += written;
@@ -324,10 +310,7 @@ private void Append(byte b)
324310
{
325311
if (_start == _buffer.Length)
326312
{
327-
var newBuffer = ArrayPool<byte>.Shared.Rent(_buffer.Length * 2);
328-
_buffer.AsSpan().CopyTo(newBuffer);
329-
ArrayPool<byte>.Shared.Return(_buffer);
330-
_buffer = newBuffer;
313+
ExpandBufferCapacity();
331314
}
332315

333316
_buffer[_start++] = b;
@@ -339,13 +322,26 @@ private void Append(ReadOnlySpan<byte> span)
339322

340323
while (!span.TryCopyTo(bufferSpan))
341324
{
342-
var newBuffer = ArrayPool<byte>.Shared.Rent(_buffer.Length * 2);
343-
_buffer.AsSpan()[.._start].CopyTo(newBuffer);
344-
ArrayPool<byte>.Shared.Return(_buffer);
345-
_buffer = newBuffer;
346-
bufferSpan = _buffer.AsSpan()[_start..];
325+
bufferSpan = ExpandRollingBufferCapacity(_start);
347326
}
348327

349328
_start += span.Length;
350329
}
330+
331+
private void ExpandBufferCapacity()
332+
{
333+
var newBuffer = ArrayPool<byte>.Shared.Rent(_buffer.Length * 2);
334+
_buffer.AsSpan().CopyTo(newBuffer);
335+
ArrayPool<byte>.Shared.Return(_buffer);
336+
_buffer = newBuffer;
337+
}
338+
339+
private Span<byte> ExpandRollingBufferCapacity(int bufferIndex)
340+
{
341+
var newBuffer = ArrayPool<byte>.Shared.Rent(_buffer.Length * 2);
342+
_buffer.AsSpan()[..bufferIndex].CopyTo(newBuffer);
343+
ArrayPool<byte>.Shared.Return(_buffer);
344+
_buffer = newBuffer;
345+
return _buffer.AsSpan()[bufferIndex..];
346+
}
351347
}

src/GreenDonut/test/GreenDonut.Data.Tests/Internal/ExpressionHasherTests.cs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,114 @@ public static void Simple_Predicate()
9696
Assert.Equal("76892c282824bfdfe59e135a298c926e", hash);
9797
}
9898

99+
[Fact]
100+
public static void BufferResize_Add_Char()
101+
{
102+
// arrange
103+
var hasher = new ExpressionHasher();
104+
var initialBufferSize = hasher.InitialBufferSize;
105+
106+
// act
107+
for (var i = 0; i < initialBufferSize / 2; i++)
108+
{
109+
hasher.Add('a');
110+
}
111+
112+
Assert.Equal(initialBufferSize, hasher.BufferSize);
113+
hasher.Add('b');
114+
115+
// assert
116+
Assert.Equal(initialBufferSize * 2, hasher.BufferSize);
117+
}
118+
119+
[Fact]
120+
public static void BufferResize_Add_ReadOnlyCharSpan()
121+
{
122+
// arrange
123+
var hasher = new ExpressionHasher();
124+
var initialBufferSize = hasher.InitialBufferSize;
125+
126+
// act
127+
hasher.Add(new string('a', initialBufferSize + 1));
128+
129+
// assert
130+
Assert.Equal(initialBufferSize * 2, hasher.BufferSize);
131+
}
132+
133+
[Fact]
134+
public static void BufferResize_Add_ReadOnlyByteSpan()
135+
{
136+
// arrange
137+
var hasher = new ExpressionHasher();
138+
var initialBufferSize = hasher.InitialBufferSize;
139+
140+
// act
141+
hasher.Add(Enumerable.Range(0, initialBufferSize + 1).Select(_ => (byte)1).ToArray());
142+
143+
// assert
144+
Assert.Equal(initialBufferSize * 2, hasher.BufferSize);
145+
}
146+
147+
[Fact]
148+
public static void BufferResize_Add_Expression()
149+
{
150+
// arrange
151+
var hasher = new ExpressionHasher();
152+
var initialBufferSize = hasher.InitialBufferSize;
153+
var expression = Expression.Lambda<Func<Entity1>>(Expression.Constant(new Entity1())); // translated to 8 bytes
154+
155+
// act
156+
var length = 0;
157+
while (length < initialBufferSize + 1)
158+
{
159+
hasher.Add(expression);
160+
length += 8;
161+
}
162+
163+
// assert
164+
Assert.Equal(initialBufferSize * 2, hasher.BufferSize);
165+
}
166+
167+
[Fact]
168+
public static void BufferResize_Add_QueryContext()
169+
{
170+
// arrange
171+
var hasher = new ExpressionHasher();
172+
var initialBufferSize = hasher.InitialBufferSize;
173+
var queryContext = new QueryContext<Entity1>(x => x); // translated to 32 bytes
174+
175+
// act
176+
var length = 0;
177+
while (length < initialBufferSize + 1)
178+
{
179+
hasher.Add(queryContext);
180+
length += 32;
181+
}
182+
183+
// assert
184+
Assert.Equal(initialBufferSize * 2, hasher.BufferSize);
185+
}
186+
187+
[Fact]
188+
public static void BufferResize_Add_SortDefinition()
189+
{
190+
// arrange
191+
var hasher = new ExpressionHasher();
192+
var initialBufferSize = hasher.InitialBufferSize;
193+
var sortDefinition = new SortDefinition<Entity1>().AddAscending(x => x.Name); // translated to 102 bytes
194+
195+
// act
196+
var length = 0;
197+
while (length < initialBufferSize + 1)
198+
{
199+
hasher.Add(sortDefinition);
200+
length += 102;
201+
}
202+
203+
// assert
204+
Assert.Equal(initialBufferSize * 2, hasher.BufferSize);
205+
}
206+
99207
public class Entity1
100208
{
101209
public string Name { get; set; } = null!;

0 commit comments

Comments
 (0)