SecureString.Unix.cs 11 KB

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