Skip to content

Commit a3c664d

Browse files
pethinN-Olbert
andauthored
Fix range check / buffer overflow in ExpressionHasher.Append(char, char) (#8998)
Co-authored-by: NOlbert <[email protected]>
1 parent 74ad56b commit a3c664d

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().Slice(_start), out written))
216+
var span = _buffer.AsSpan().Slice(_start);
217+
while (!Utf8Formatter.TryFormat(i, span, out written))
215218
{
216-
var newBuffer = ArrayPool<byte>.Shared.Rent(_buffer.Length * 2);
217-
_buffer.AsSpan().Slice(0, _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().Slice(0, _start).CopyTo(newBuffer);
298-
ArrayPool<byte>.Shared.Return(_buffer);
299-
_buffer = newBuffer;
300-
span = _buffer.AsSpan().Slice(_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().Slice(0, _start).CopyTo(newBuffer);
315-
ArrayPool<byte>.Shared.Return(_buffer);
316-
_buffer = newBuffer;
317-
span = _buffer.AsSpan().Slice(_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().Slice(0, _start).CopyTo(newBuffer);
344-
ArrayPool<byte>.Shared.Return(_buffer);
345-
_buffer = newBuffer;
346-
bufferSpan = _buffer.AsSpan().Slice(_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().Slice(0, bufferIndex).CopyTo(newBuffer);
343+
ArrayPool<byte>.Shared.Return(_buffer);
344+
_buffer = newBuffer;
345+
return _buffer.AsSpan().Slice(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
@@ -97,6 +97,114 @@ public static void Simple_Predicate()
9797
Assert.Equal("76892c282824bfdfe59e135a298c926e", hash);
9898
}
9999

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

0 commit comments

Comments
 (0)