-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix capacity checks in BufferExtensions #504
Conversation
…by invalidcast issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
besides the one I commented GetWindowsRuntimeBuffer(this MemoryStream underlyingStream, int positionInStream, int length)
also has a <=
operator which is supicious candidate for throwing when retrieving a zero sized buffer at end of memory stream capacity
The other fixes look like I would expect, but its late for me and I'll do a more thorough review tomorrow.
Of course test cases also would be useful, to ensure that doing zero sized operations to/from the end of a buffer works the same as in the middle of a buffer.
cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs
Outdated
Show resolved
Hide resolved
cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs
Show resolved
Hide resolved
After some thought, I don't think we should support indices that are equal to the length of the buffer or array, as this is technically out of memory bounds. You mentioned the invariant was Please provide an example scenario / code that you believe should work that doesn't |
You are mistaken and mixing up the concepts of element access and span access. A span of length zero is valid at the end of a buffer, for example If you do not like the
This is incorrect, The full mathematical invariant for spans is From this invariant the individual checks are derived, needing four checks in total instead of three since you have
The invariant for indexed slot access you have in mind is
You can of course solve the currently present bugs by special casing zero, but its a bit inelegant. Its not my codebase so I'm not arguing over it, if you find it easier to reason about special casing zero instead of reasoning about the invariants (which I agree can be hard if you aren't used to it) then feel free to do so. Maintainability of the codebase is more important than elegance, and the more people can easily understand it, the better. Note though, that in the previous PR the early-exit on zero count did appear in a spot where some argument checks were skipped, i.e. when special casing zero without reordering the other checks you may accidently change the semantics and no longer throw on some invalid arguments. Not critical to me personally, since the arguments are invalid anyways, just mentioning it because it may prevent detecting bugs in callers.
I'll make the list whith scenarios that don't work, will take some time and should be ready later today. |
Thank you for your reply!
This example doesn't quite make sense to me because you are using List as the example data-structure, which can grow to arbitrary size; arrays and buffers have fixed lengths. I am concerned that if we change the
If ending is exclusive shouldn't it be I am writing some test cases now to exemplify my concerns. == |
I think you misunderstood the example, the buffers would be an implementation detail of implementing such a list. Immutable lists and strings do not grow at all, yet they have such an API. It is common to implement file editors by having a tree of buffers, and range based APIs are the fundamental building block there. Having to code around the defect of the framework not allowing zero length ranges at end of a buffer is just annoying. Its possible to easily work around, I have done so in the past, but you have to litter the callers with checks that wouldn't be necessary otherwise. Or did you ever see anyone doing length checks when calling
No, that would be if If you need to make yourself familiar with range indexing first, take the string API as an example, I've created a bunch of test cases, not covering the previous PR, but instead what is still looking wrong in the current source. I've also included examples for other APIs which have correct range checking. Its really just these extensions that are broken because their range checks are wrong. Everyone else seems to have gotten it right. test cases failingusing System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.VisualStudio.TestTools.UnitTesting;
// add 'System.Memory' nuget package for span support
namespace UnitTestProject1
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void CopyArrayToBufferWithZeroCountAtEnd()
{
byte[] array = { 0xA1, 0xA2, 0xA3 };
var buffer = WindowsRuntimeBuffer.Create(10);
array.CopyTo(3, buffer, 10, 0);
}
[TestMethod]
public void CopyArrayToBufferWithZeroCountAtEnd_WorksWithArrays()
{
byte[] array = { 0xA1, 0xA2, 0xA3 };
byte[] buffer = new byte[10];
Array.Copy(array, 3, buffer, 10, 0);
}
[TestMethod]
public void CopyBufferToArrayWithZeroCountAtEnd()
{
byte[] array = { 0xA1, 0xA2, 0xA3 };
var buffer = array.AsBuffer();
var target = new byte[10];
buffer.CopyTo(3, target, 10, 0);
}
[TestMethod]
public void CopyBufferToArrayWithZeroCountAtEnd_WorksWithSpans()
{
byte[] array = { 0xA1, 0xA2, 0xA3 };
byte[] buffer = new byte[10];
array.AsSpan(3).CopyTo(buffer.AsSpan(10, 0));
}
[TestMethod]
public void CopyBufferToBufferWithZeroCountAtEnd()
{
byte[] array = { 0xA1, 0xA2, 0xA3 };
var sourceBuffer = array.AsBuffer();
var targetBuffer = WindowsRuntimeBuffer.Create(10);
sourceBuffer.CopyTo(3, targetBuffer, 10, 0);
}
[TestMethod]
public void CopyBufferToBufferWithZeroCountAtEnd_WorksWithSpans()
{
byte[] array = { 0xA1, 0xA2, 0xA3 };
var sourceBuffer = array.AsSpan();
var targetBuffer = new byte[10].AsSpan();
sourceBuffer.Slice(3).CopyTo(targetBuffer.Slice(10, 0));
}
[TestMethod]
public void BufferToArrayWithZeroCountAtEnd()
{
byte[] array = { 0xA1, 0xA2, 0xA3 };
var result = array.AsBuffer().ToArray(3, 0);
Assert.IsNotNull(result);
Assert.AreEqual(0, result.Length);
}
[TestMethod]
public void BufferToArrayWithZeroCountAtEnd_WorksWithSpans()
{
byte[] array = { 0xA1, 0xA2, 0xA3 };
var result = array.AsSpan().Slice(3, 0).ToArray();
Assert.IsNotNull(result);
Assert.AreEqual(0, result.Length);
}
[TestMethod]
public void BufferToArrayWithZeroCountAtEnd_WorksInLinq()
{
byte[] array = { 0xA1, 0xA2, 0xA3 };
var result = array.ToList().Skip(3).Take(0).ToArray();
Assert.IsNotNull(result);
Assert.AreEqual(0, result.Length);
}
[TestMethod]
public void GetWindowsRuntimeBufferWithZeroCountAtEnd()
{
byte[] array = { 0xA1, 0xA2, 0xA3 };
var stream = new MemoryStream(array);
var result = stream.GetWindowsRuntimeBuffer(3, 0);
Assert.IsNotNull(result);
Assert.AreEqual(0, result.Length);
}
[TestMethod]
public void GetWindowsRuntimeBufferWithZeroCountAtEnd_WorksWithSpan()
{
byte[] array = { 0xA1, 0xA2, 0xA3 };
var stream = new MemoryStream(array);
var result = stream.ToArray().AsSpan(3, 0).ToArray();
Assert.IsNotNull(result);
Assert.AreEqual(0, result.Length);
}
}
} |
cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs
Outdated
Show resolved
Hide resolved
There is an unrelated bug I discovered while reviewing, which is actually security relevant because it can corrupt memory, should this get a separate issue? Its not covered by #334 (which wants to reduce checks, not add new checks). Obviously it can just be fixed in this PR. |
I made the change, the check was already present in the CopyTo for IBuffer to IBuffer, strangely enough. Nice catch |
@j0shuams did you forget to push? I'm not seeing the changes. |
@weltkante I am about to, I was comparing the unit tests I have been developing with the ones you provided to make sure I was handling proper cases. |
cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs
Outdated
Show resolved
Hide resolved
@weltkante thank you for your attention on this PR - much appreciated |
@j0shuams is this PR ready for review? |
cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs
Outdated
Show resolved
Hide resolved
cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs
Outdated
Show resolved
Hide resolved
cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs
Outdated
Show resolved
Hide resolved
source.Length here too Refers to: cswinrt/strings/additions/Windows.Storage.Streams/WindowsRuntimeBufferExtensions.cs:424 in d721226. [](commit_id = d721226, deletion_comment = False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generally, shouldn't all bounds checks look at:
- Length for source, to ensure valid bytes are read
- Capacity for destination, to ensure sufficient space
@Scottj1s I'm not sure you can make a breaking change from |
We're already committed to breaking changes here, with the new conditions on params, beyond the the Length check. This code (and any client code) has to be able to rely on IBuffer.Length being stable. |
@weltkante thanks again for the invaluable feedback. we're going to run with this and will be on the lookout for any regressions. |
Making previously throwing cases non-throwing and handled correctly is IMHO less breaking than starting to throw exceptions in previously working cases, which is why I was pointing this out, but its your call. |
This PR adds support for 0 length slices of buffers and arrays for buffer extension methods. There was another issue found that showed an overflow vulnerability that has been fixed by adding a check. Unit tests have been added that verify taking 0 length slices of buffers and arrays works the same when done from the middle, beginning and end.
I also did these same tests with length 1 slices to ensure that errors would be thrown properly in the case that the selection would be out of bounds.
Right now most of these unit tests are have to be skipped until the fix for #497 gets merged.
I grabbed the change from that work (a new version of
IBufferByteAccess.cs
), and all tests withSkip
attribute pass when the new version is used. I left a note on the 497 page to remove the attribute when before merging.Closes #334