Fix possible overflow in SafeBuffer.Initialize.
[platform/upstream/coreclr.git] / src / System.Private.CoreLib / shared / System / Runtime / InteropServices / SafeBuffer.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 /*============================================================
6 **
7 ** Purpose: Unsafe code that uses pointers should use
8 ** SafePointer to fix subtle lifetime problems with the
9 ** underlying resource.
10 **
11 ===========================================================*/
12
13 // Design points:
14 // *) Avoid handle-recycling problems (including ones triggered via
15 // resurrection attacks) for all accesses via pointers.  This requires tying
16 // together the lifetime of the unmanaged resource with the code that reads
17 // from that resource, in a package that uses synchronization to enforce
18 // the correct semantics during finalization.  We're using SafeHandle's
19 // ref count as a gate on whether the pointer can be dereferenced because that
20 // controls the lifetime of the resource.
21 //
22 // *) Keep the penalties for using this class small, both in terms of space
23 // and time.  Having multiple threads reading from a memory mapped file
24 // will already require 2 additional interlocked operations.  If we add in
25 // a "current position" concept, that requires additional space in memory and
26 // synchronization.  Since the position in memory is often (but not always)
27 // something that can be stored on the stack, we can save some memory by
28 // excluding it from this object.  However, avoiding the need for
29 // synchronization is a more significant win.  This design allows multiple
30 // threads to read and write memory simultaneously without locks (as long as
31 // you don't write to a region of memory that overlaps with what another
32 // thread is accessing).
33 //
34 // *) Space-wise, we use the following memory, including SafeHandle's fields:
35 // Object Header  MT*  handle  int bool bool <2 pad bytes> length
36 // On 32 bit platforms: 24 bytes.  On 64 bit platforms: 40 bytes.
37 // (We can safe 4 bytes on x86 only by shrinking SafeHandle)
38 //
39 // *) Wrapping a SafeHandle would have been a nice solution, but without an
40 // ordering between critical finalizable objects, it would have required
41 // changes to each SafeHandle subclass to opt in to being usable from a
42 // SafeBuffer (or some clever exposure of SafeHandle's state fields and a
43 // way of forcing ReleaseHandle to run even after the SafeHandle has been
44 // finalized with a ref count > 1).  We can use less memory and create fewer
45 // objects by simply inserting a SafeBuffer into the class hierarchy.
46 //
47 // *) In an ideal world, we could get marshaling support for SafeBuffer that
48 // would allow us to annotate a P/Invoke declaration, saying this parameter
49 // specifies the length of the buffer, and the units of that length are X.
50 // P/Invoke would then pass that size parameter to SafeBuffer.
51 //     [DllImport(...)]
52 //     static extern SafeMemoryHandle AllocCharBuffer(int numChars);
53 // If we could put an attribute on the SafeMemoryHandle saying numChars is
54 // the element length, and it must be multiplied by 2 to get to the byte
55 // length, we can simplify the usage model for SafeBuffer.
56 //
57 // *) This class could benefit from a constraint saying T is a value type
58 // containing no GC references.
59
60 // Implementation notes:
61 // *) The Initialize method must be called before you use any instance of
62 // a SafeBuffer.  To avoid race conditions when storing SafeBuffers in statics,
63 // you either need to take a lock when publishing the SafeBuffer, or you
64 // need to create a local, initialize the SafeBuffer, then assign to the
65 // static variable (perhaps using Interlocked.CompareExchange).  Of course,
66 // assignments in a static class constructor are under a lock implicitly.
67
68 using System;
69 using System.Diagnostics;
70 using System.Runtime.CompilerServices;
71 using Internal.Runtime.CompilerServices;
72 using Microsoft.Win32.SafeHandles;
73
74 namespace System.Runtime.InteropServices
75 {
76     public abstract unsafe class SafeBuffer : SafeHandleZeroOrMinusOneIsInvalid
77     {
78         // Steal UIntPtr.MaxValue as our uninitialized value.
79         private static readonly UIntPtr Uninitialized = (UIntPtr.Size == 4) ?
80             ((UIntPtr)uint.MaxValue) : ((UIntPtr)ulong.MaxValue);
81
82         private UIntPtr _numBytes;
83
84         protected SafeBuffer(bool ownsHandle) : base(ownsHandle)
85         {
86             _numBytes = Uninitialized;
87         }
88
89         /// <summary>
90         /// Specifies the size of the region of memory, in bytes.  Must be
91         /// called before using the SafeBuffer.
92         /// </summary>
93         /// <param name="numBytes">Number of valid bytes in memory.</param>
94         [CLSCompliant(false)]
95         public void Initialize(ulong numBytes)
96         {
97             if (IntPtr.Size == 4 && numBytes > uint.MaxValue)
98                 throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_AddressSpace);
99
100             if (numBytes >= (ulong)Uninitialized)
101                 throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_UIntPtrMax);
102
103             _numBytes = (UIntPtr)numBytes;
104         }
105
106         /// <summary>
107         /// Specifies the size of the region in memory, as the number of
108         /// elements in an array.  Must be called before using the SafeBuffer.
109         /// </summary>
110         [CLSCompliant(false)]
111         public void Initialize(uint numElements, uint sizeOfEachElement)
112         {
113             if (sizeOfEachElement == 0)
114             {
115                 _numBytes = (UIntPtr)0;
116             }
117             else
118             {
119                 if (IntPtr.Size == 4 && numElements > uint.MaxValue / sizeOfEachElement)
120                     throw new ArgumentOutOfRangeException("numBytes", SR.ArgumentOutOfRange_AddressSpace);
121
122                 if (numElements >= (ulong)Uninitialized / sizeOfEachElement)
123                     throw new ArgumentOutOfRangeException(nameof(numElements), SR.ArgumentOutOfRange_UIntPtrMax);
124
125                 _numBytes = checked((UIntPtr)(numElements * sizeOfEachElement)); 
126             }
127         }
128
129         /// <summary>
130         /// Specifies the size of the region in memory, as the number of
131         /// elements in an array.  Must be called before using the SafeBuffer.
132         /// </summary>
133         [CLSCompliant(false)]
134         public void Initialize<T>(uint numElements) where T : struct
135         {
136             Initialize(numElements, AlignedSizeOf<T>());
137         }
138
139         // Callers should ensure that they check whether the pointer ref param
140         // is null when AcquirePointer returns.  If it is not null, they must
141         // call ReleasePointer.  This method calls DangerousAddRef
142         // & exposes the pointer. Unlike Read, it does not alter the "current
143         // position" of the pointer.  Here's how to use it:
144         //
145         // byte* pointer = null;
146         // try {
147         //     safeBuffer.AcquirePointer(ref pointer);
148         //     // Use pointer here, with your own bounds checking
149         // }
150         // finally {
151         //     if (pointer != null)
152         //         safeBuffer.ReleasePointer();
153         // }
154         //
155         // Note: If you cast this byte* to a T*, you have to worry about
156         // whether your pointer is aligned.  Additionally, you must take
157         // responsibility for all bounds checking with this pointer.
158         /// <summary>
159         /// Obtain the pointer from a SafeBuffer for a block of code,
160         /// with the express responsibility for bounds checking and calling
161         /// ReleasePointer later to ensure the pointer can be freed later.
162         /// This method either completes successfully or throws an exception 
163         /// and returns with pointer set to null.
164         /// </summary>
165         /// <param name="pointer">A byte*, passed by reference, to receive
166         /// the pointer from within the SafeBuffer.  You must set
167         /// pointer to null before calling this method.</param>
168         [CLSCompliant(false)]
169         public void AcquirePointer(ref byte* pointer)
170         {
171             if (_numBytes == Uninitialized)
172                 throw NotInitialized();
173
174             pointer = null;
175
176             bool junk = false;
177             DangerousAddRef(ref junk);
178             pointer = (byte*)handle;
179         }
180
181         public void ReleasePointer()
182         {
183             if (_numBytes == Uninitialized)
184                 throw NotInitialized();
185
186             DangerousRelease();
187         }
188
189         /// <summary>
190         /// Read a value type from memory at the given offset.  This is
191         /// equivalent to:  return *(T*)(bytePtr + byteOffset);
192         /// </summary>
193         /// <typeparam name="T">The value type to read</typeparam>
194         /// <param name="byteOffset">Where to start reading from memory.  You
195         /// may have to consider alignment.</param>
196         /// <returns>An instance of T read from memory.</returns>
197         [CLSCompliant(false)]
198         public T Read<T>(ulong byteOffset) where T : struct
199         {
200             if (_numBytes == Uninitialized)
201                 throw NotInitialized();
202
203             uint sizeofT = SizeOf<T>();
204             byte* ptr = (byte*)handle + byteOffset;
205             SpaceCheck(ptr, sizeofT);
206
207             // return *(T*) (_ptr + byteOffset);
208             T value = default;
209             bool mustCallRelease = false;
210             try
211             {
212                 DangerousAddRef(ref mustCallRelease);
213
214                 fixed (byte* pStructure = &Unsafe.As<T, byte>(ref value))
215                     Buffer.Memmove(pStructure, ptr, sizeofT);
216             }
217             finally
218             {
219                 if (mustCallRelease)
220                     DangerousRelease();
221             }
222             return value;
223         }
224
225         [CLSCompliant(false)]
226         public void ReadArray<T>(ulong byteOffset, T[] array, int index, int count)
227             where T : struct
228         {
229             if (array == null)
230                 throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
231             if (index < 0)
232                 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
233             if (count < 0)
234                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
235             if (array.Length - index < count)
236                 throw new ArgumentException(SR.Argument_InvalidOffLen);
237
238             if (_numBytes == Uninitialized)
239                 throw NotInitialized();
240
241             uint sizeofT = SizeOf<T>();
242             uint alignedSizeofT = AlignedSizeOf<T>();
243             byte* ptr = (byte*)handle + byteOffset;
244             SpaceCheck(ptr, checked((ulong)(alignedSizeofT * count)));
245
246             bool mustCallRelease = false;
247             try
248             {
249                 DangerousAddRef(ref mustCallRelease);
250
251                 if (count > 0)
252                 {
253                     unsafe
254                     {
255                         fixed (byte* pStructure = &Unsafe.As<T, byte>(ref array[index]))
256                         {
257                             for (int i = 0; i < count; i++)
258                                 Buffer.Memmove(pStructure + sizeofT * i, ptr + alignedSizeofT * i, sizeofT);
259                         }
260                     }
261                 }
262             }
263             finally
264             {
265                 if (mustCallRelease)
266                     DangerousRelease();
267             }
268         }
269
270         /// <summary>
271         /// Write a value type to memory at the given offset.  This is
272         /// equivalent to:  *(T*)(bytePtr + byteOffset) = value;
273         /// </summary>
274         /// <typeparam name="T">The type of the value type to write to memory.</typeparam>
275         /// <param name="byteOffset">The location in memory to write to.  You
276         /// may have to consider alignment.</param>
277         /// <param name="value">The value type to write to memory.</param>
278         [CLSCompliant(false)]
279         public void Write<T>(ulong byteOffset, T value) where T : struct
280         {
281             if (_numBytes == Uninitialized)
282                 throw NotInitialized();
283
284             uint sizeofT = SizeOf<T>();
285             byte* ptr = (byte*)handle + byteOffset;
286             SpaceCheck(ptr, sizeofT);
287
288             // *((T*) (_ptr + byteOffset)) = value;
289             bool mustCallRelease = false;
290             try
291             {
292                 DangerousAddRef(ref mustCallRelease);
293
294                 fixed (byte* pStructure = &Unsafe.As<T, byte>(ref value))
295                     Buffer.Memmove(ptr, pStructure, sizeofT);
296             }
297             finally
298             {
299                 if (mustCallRelease)
300                     DangerousRelease();
301             }
302         }
303
304         [CLSCompliant(false)]
305         public void WriteArray<T>(ulong byteOffset, T[] array, int index, int count)
306             where T : struct
307         {
308             if (array == null)
309                 throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
310             if (index < 0)
311                 throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum);
312             if (count < 0)
313                 throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
314             if (array.Length - index < count)
315                 throw new ArgumentException(SR.Argument_InvalidOffLen);
316
317             if (_numBytes == Uninitialized)
318                 throw NotInitialized();
319
320             uint sizeofT = SizeOf<T>();
321             uint alignedSizeofT = AlignedSizeOf<T>();
322             byte* ptr = (byte*)handle + byteOffset;
323             SpaceCheck(ptr, checked((ulong)(alignedSizeofT * count)));
324
325             bool mustCallRelease = false;
326             try
327             {
328                 DangerousAddRef(ref mustCallRelease);
329
330                 if (count > 0)
331                 {
332                     unsafe
333                     {
334                         fixed (byte* pStructure = &Unsafe.As<T, byte>(ref array[index]))
335                         {
336                             for (int i = 0; i < count; i++)
337                                 Buffer.Memmove(ptr + alignedSizeofT * i, pStructure + sizeofT * i, sizeofT);
338                         }
339                     }
340                 }
341             }
342             finally
343             {
344                 if (mustCallRelease)
345                     DangerousRelease();
346             }
347         }
348
349         /// <summary>
350         /// Returns the number of bytes in the memory region.
351         /// </summary>
352         [CLSCompliant(false)]
353         public ulong ByteLength
354         {
355             get
356             {
357                 if (_numBytes == Uninitialized)
358                     throw NotInitialized();
359
360                 return (ulong)_numBytes;
361             }
362         }
363
364         /* No indexer.  The perf would be misleadingly bad.  People should use
365          * AcquirePointer and ReleasePointer instead.  */
366
367         private void SpaceCheck(byte* ptr, ulong sizeInBytes)
368         {
369             if ((ulong)_numBytes < sizeInBytes)
370                 NotEnoughRoom();
371             if ((ulong)(ptr - (byte*)handle) > ((ulong)_numBytes) - sizeInBytes)
372                 NotEnoughRoom();
373         }
374
375         private static void NotEnoughRoom()
376         {
377             throw new ArgumentException(SR.Arg_BufferTooSmall);
378         }
379
380         private static InvalidOperationException NotInitialized()
381         {
382             return new InvalidOperationException(SR.InvalidOperation_MustCallInitialize);
383         }
384
385         /// <summary>
386         /// Returns the size that SafeBuffer (and hence, UnmanagedMemoryAccessor) reserves in the unmanaged buffer for each element of an array of T. This is not the same
387         /// value that sizeof(T) returns! Since the primary use case is to parse memory mapped files, we cannot change this algorithm as this defines a de-facto serialization format.
388         /// Throws if T contains GC references.
389         /// </summary>
390         internal static uint AlignedSizeOf<T>() where T : struct
391         {
392             uint size = SizeOf<T>();
393             if (size == 1 || size == 2)
394             {
395                 return size;
396             }
397
398             return (uint)(((size + 3) & (~3)));
399         }
400
401         /// <summary>
402         /// Returns same value as sizeof(T) but throws if T contains GC references.
403         /// </summary>
404         internal static uint SizeOf<T>() where T : struct
405         {
406             if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
407                 throw new ArgumentException(SR.Argument_NeedStructWithNoRefs);
408
409             return (uint)Unsafe.SizeOf<T>();
410         }
411     }
412 }