SecureString.Unix.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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. using System.Diagnostics;
  5. using System.Runtime;
  6. using System.Runtime.InteropServices;
  7. using System.Text;
  8. namespace System.Security
  9. {
  10. // SecureString attempts to provide a defense-in-depth solution.
  11. //
  12. // On Windows, this is done with several mechanisms:
  13. // 1. keeping the data in unmanaged memory so that copies of it aren't implicitly made by the GC moving it around
  14. // 2. zero'ing out that unmanaged memory so that the string is reliably removed from memory when done with it
  15. // 3. encrypting the data while it's not being used (it's unencrypted to manipulate and use it)
  16. //
  17. // On Unix, we do 1 and 2, but we don't do 3 as there's no CryptProtectData equivalent.
  18. public sealed partial class SecureString
  19. {
  20. private UnmanagedBuffer _buffer;
  21. internal SecureString(SecureString str)
  22. {
  23. // Allocate enough space to store the provided string
  24. EnsureCapacity(str._decryptedLength);
  25. _decryptedLength = str._decryptedLength;
  26. // Copy the string into the newly allocated space
  27. if (_decryptedLength > 0)
  28. {
  29. UnmanagedBuffer.Copy(str._buffer, _buffer, (ulong)(str._decryptedLength * sizeof(char)));
  30. }
  31. }
  32. private unsafe void InitializeSecureString(char* value, int length)
  33. {
  34. // Allocate enough space to store the provided string
  35. EnsureCapacity(length);
  36. _decryptedLength = length;
  37. if (length == 0)
  38. {
  39. return;
  40. }
  41. // Copy the string into the newly allocated space
  42. byte* ptr = null;
  43. try
  44. {
  45. _buffer.AcquirePointer(ref ptr);
  46. Buffer.MemoryCopy(value, ptr, _buffer.ByteLength, (ulong)(length * sizeof(char)));
  47. }
  48. finally
  49. {
  50. if (ptr != null)
  51. {
  52. _buffer.ReleasePointer();
  53. }
  54. }
  55. }
  56. private void DisposeCore()
  57. {
  58. if (_buffer != null && !_buffer.IsInvalid)
  59. {
  60. _buffer.Dispose();
  61. _buffer = null;
  62. }
  63. }
  64. private void ClearCore()
  65. {
  66. _decryptedLength = 0;
  67. _buffer.Clear();
  68. }
  69. private unsafe void AppendCharCore(char c)
  70. {
  71. // Make sure we have enough space for the new character, then write it at the end.
  72. EnsureCapacity(_decryptedLength + 1);
  73. _buffer.Write((ulong)(_decryptedLength * sizeof(char)), c);
  74. _decryptedLength++;
  75. }
  76. private unsafe void InsertAtCore(int index, char c)
  77. {
  78. // Make sure we have enough space for the new character, then shift all of the characters above it and insert it.
  79. EnsureCapacity(_decryptedLength + 1);
  80. byte* ptr = null;
  81. try
  82. {
  83. _buffer.AcquirePointer(ref ptr);
  84. ptr += index * sizeof(char);
  85. long bytesToShift = (_decryptedLength - index) * sizeof(char);
  86. Buffer.MemoryCopy(ptr, ptr + sizeof(char), bytesToShift, bytesToShift);
  87. *((char*)ptr) = c;
  88. ++_decryptedLength;
  89. }
  90. finally
  91. {
  92. if (ptr != null)
  93. {
  94. _buffer.ReleasePointer();
  95. }
  96. }
  97. }
  98. private unsafe void RemoveAtCore(int index)
  99. {
  100. // Shift down all values above the specified index, then null out the empty space at the end.
  101. byte* ptr = null;
  102. try
  103. {
  104. _buffer.AcquirePointer(ref ptr);
  105. ptr += index * sizeof(char);
  106. long bytesToShift = (_decryptedLength - index - 1) * sizeof(char);
  107. Buffer.MemoryCopy(ptr + sizeof(char), ptr, bytesToShift, bytesToShift);
  108. *((char*)(ptr + bytesToShift)) = (char)0;
  109. --_decryptedLength;
  110. }
  111. finally
  112. {
  113. if (ptr != null)
  114. {
  115. _buffer.ReleasePointer();
  116. }
  117. }
  118. }
  119. private void SetAtCore(int index, char c)
  120. {
  121. // Overwrite the character at the specified index
  122. _buffer.Write((ulong)(index * sizeof(char)), c);
  123. }
  124. internal unsafe IntPtr MarshalToBSTRCore()
  125. {
  126. int length = _decryptedLength;
  127. IntPtr ptr = IntPtr.Zero;
  128. IntPtr result = IntPtr.Zero;
  129. byte* bufferPtr = null;
  130. try
  131. {
  132. _buffer.AcquirePointer(ref bufferPtr);
  133. int resultByteLength = (length + 1) * sizeof(char);
  134. ptr = Marshal.AllocBSTR(length);
  135. Buffer.MemoryCopy(bufferPtr, (byte*)ptr, resultByteLength, length * sizeof(char));
  136. result = ptr;
  137. }
  138. finally
  139. {
  140. // If we failed for any reason, free the new buffer
  141. if (result == IntPtr.Zero && ptr != IntPtr.Zero)
  142. {
  143. RuntimeImports.RhZeroMemory(ptr, (UIntPtr)(length * sizeof(char)));
  144. Marshal.FreeBSTR(ptr);
  145. }
  146. if (bufferPtr != null)
  147. {
  148. _buffer.ReleasePointer();
  149. }
  150. }
  151. return result;
  152. }
  153. internal unsafe IntPtr MarshalToStringCore(bool globalAlloc, bool unicode)
  154. {
  155. int length = _decryptedLength;
  156. byte* bufferPtr = null;
  157. IntPtr stringPtr = IntPtr.Zero, result = IntPtr.Zero;
  158. try
  159. {
  160. _buffer.AcquirePointer(ref bufferPtr);
  161. if (unicode)
  162. {
  163. int resultLength = (length + 1) * sizeof(char);
  164. stringPtr = globalAlloc ? Marshal.AllocHGlobal(resultLength) : Marshal.AllocCoTaskMem(resultLength);
  165. Buffer.MemoryCopy(
  166. source: bufferPtr,
  167. destination: (byte*)stringPtr.ToPointer(),
  168. destinationSizeInBytes: resultLength,
  169. sourceBytesToCopy: length * sizeof(char));
  170. *(length + (char*)stringPtr) = '\0';
  171. }
  172. else
  173. {
  174. int resultLength = Encoding.UTF8.GetByteCount((char*)bufferPtr, length) + 1;
  175. stringPtr = globalAlloc ? Marshal.AllocHGlobal(resultLength) : Marshal.AllocCoTaskMem(resultLength);
  176. int encodedLength = Encoding.UTF8.GetBytes((char*)bufferPtr, length, (byte*)stringPtr, resultLength);
  177. Debug.Assert(encodedLength + 1 == resultLength, $"Expected encoded length to match result, got {encodedLength} != {resultLength}");
  178. *(resultLength - 1 + (byte*)stringPtr) = 0;
  179. }
  180. result = stringPtr;
  181. }
  182. finally
  183. {
  184. // If there was a failure, such that result isn't initialized,
  185. // release the string if we had one.
  186. if (stringPtr != IntPtr.Zero && result == IntPtr.Zero)
  187. {
  188. RuntimeImports.RhZeroMemory(stringPtr, (UIntPtr)(length * sizeof(char)));
  189. MarshalFree(stringPtr, globalAlloc);
  190. }
  191. if (bufferPtr != null)
  192. {
  193. _buffer.ReleasePointer();
  194. }
  195. }
  196. return result;
  197. }
  198. // -----------------------------
  199. // ---- PAL layer ends here ----
  200. // -----------------------------
  201. private void EnsureCapacity(int capacity)
  202. {
  203. // Make sure the requested capacity doesn't exceed SecureString's defined limit
  204. if (capacity > MaxLength)
  205. {
  206. throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_Capacity);
  207. }
  208. // If we already have enough space allocated, we're done
  209. if (_buffer != null && (capacity * sizeof(char)) <= (int)_buffer.ByteLength)
  210. {
  211. return;
  212. }
  213. // We need more space, so allocate a new buffer, copy all our data into it,
  214. // and then swap the new for the old.
  215. UnmanagedBuffer newBuffer = UnmanagedBuffer.Allocate(capacity * sizeof(char));
  216. if (_buffer != null)
  217. {
  218. UnmanagedBuffer.Copy(_buffer, newBuffer, _buffer.ByteLength);
  219. _buffer.Dispose();
  220. }
  221. _buffer = newBuffer;
  222. }
  223. /// <summary>SafeBuffer for managing memory meant to be kept confidential.</summary>
  224. private sealed class UnmanagedBuffer : SafeBuffer
  225. {
  226. internal UnmanagedBuffer() : base(true) { }
  227. internal static UnmanagedBuffer Allocate(int bytes)
  228. {
  229. Debug.Assert(bytes >= 0);
  230. UnmanagedBuffer buffer = new UnmanagedBuffer();
  231. buffer.SetHandle(Marshal.AllocHGlobal(bytes));
  232. buffer.Initialize((ulong)bytes);
  233. return buffer;
  234. }
  235. internal unsafe void Clear()
  236. {
  237. byte* ptr = null;
  238. try
  239. {
  240. AcquirePointer(ref ptr);
  241. RuntimeImports.RhZeroMemory((IntPtr)ptr, (UIntPtr)ByteLength);
  242. }
  243. finally
  244. {
  245. if (ptr != null)
  246. {
  247. ReleasePointer();
  248. }
  249. }
  250. }
  251. internal static unsafe void Copy(UnmanagedBuffer source, UnmanagedBuffer destination, ulong bytesLength)
  252. {
  253. if (bytesLength == 0)
  254. {
  255. return;
  256. }
  257. byte* srcPtr = null, dstPtr = null;
  258. try
  259. {
  260. source.AcquirePointer(ref srcPtr);
  261. destination.AcquirePointer(ref dstPtr);
  262. Buffer.MemoryCopy(srcPtr, dstPtr, destination.ByteLength, bytesLength);
  263. }
  264. finally
  265. {
  266. if (dstPtr != null)
  267. {
  268. destination.ReleasePointer();
  269. }
  270. if (srcPtr != null)
  271. {
  272. source.ReleasePointer();
  273. }
  274. }
  275. }
  276. protected override unsafe bool ReleaseHandle()
  277. {
  278. Marshal.FreeHGlobal(handle);
  279. return true;
  280. }
  281. }
  282. }
  283. }