Skip to content

Commit ab006e1

Browse files
committed
Harden ArrayBuilder
Since we're using the ArrayPool, it's really essential that we prevent use-after-free bugs. I'm currently tracking one down.
1 parent 89bf584 commit ab006e1

File tree

2 files changed

+34
-0
lines changed

2 files changed

+34
-0
lines changed

src/Components/Components/test/Rendering/ArrayBuilderTest.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Buffers;
56
using System.Linq;
67
using Xunit;
@@ -261,6 +262,7 @@ public void Dispose_NonEmptyBufferIsReturned()
261262
Assert.Single(ArrayPool.ReturnedBuffers);
262263
var returnedBuffer = Assert.Single(ArrayPool.ReturnedBuffers);
263264
Assert.Same(buffer, returnedBuffer);
265+
Assert.NotSame(builder.Buffer, buffer); // Prevents use after free
264266
}
265267

266268
[Fact]
@@ -281,6 +283,21 @@ public void DoubleDispose_DoesNotReturnBufferTwice()
281283
Assert.Same(buffer, returnedBuffer);
282284
}
283285

286+
[Fact]
287+
public void Dispose_ThrowsOnReuse()
288+
{
289+
// Arrange
290+
var builder = CreateArrayBuilder();
291+
builder.Append(1);
292+
var buffer = builder.Buffer;
293+
294+
builder.Dispose();
295+
Assert.Single(ArrayPool.ReturnedBuffers);
296+
297+
// Act & Assert
298+
Assert.Throws<ObjectDisposedException>(() => builder.Append(1));
299+
}
300+
284301
[Fact]
285302
public void UnusedBufferIsReturned_OnResize()
286303
{

src/Components/Shared/src/ArrayBuilder.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,16 @@ public void Clear()
160160

161161
private void GrowBuffer(int desiredCapacity)
162162
{
163+
// When we dispose, we set the count back to zero and return the array.
164+
//
165+
// If someone tries to do something that would require non-zero storage then
166+
// this is a use-after-free. Throwing here is an easy way to prevent that without
167+
// introducing overhead to every method.
168+
if (_disposed)
169+
{
170+
ThrowObjectDisposedException();
171+
}
172+
163173
var newCapacity = Math.Max(desiredCapacity, _minCapacity);
164174
Debug.Assert(newCapacity > _items.Length);
165175

@@ -188,12 +198,19 @@ public void Dispose()
188198
{
189199
_disposed = true;
190200
ReturnBuffer();
201+
_items = Empty;
202+
_itemsInUse = 0;
191203
}
192204
}
193205

194206
private static void ThrowIndexOutOfBoundsException()
195207
{
196208
throw new ArgumentOutOfRangeException("index");
197209
}
210+
211+
private static void ThrowObjectDisposedException()
212+
{
213+
throw new ObjectDisposedException(objectName: null);
214+
}
198215
}
199216
}

0 commit comments

Comments
 (0)