protected void Lock(byte *address, long sizeToLock, TransactionState state) { var lockTaken = false; try { if (Sodium.Lock(address, (UIntPtr)sizeToLock) == 0) { return; } if (DoNotConsiderMemoryLockFailureAsCatastrophicError) { return; } if (PlatformDetails.RunningOnPosix == false) { // when running on linux we can't do anything from within the process, so let's avoid the locking entirely Monitor.Enter(WorkingSetIncreaseLocker, ref lockTaken); } TryHandleFailureToLockMemory(address, sizeToLock); } finally { if (lockTaken) { Monitor.Exit(WorkingSetIncreaseLocker); } } }
protected void TryHandleFailureToLockMemory(byte *addressToLock, long sizeToLock) { using var currentProcess = Process.GetCurrentProcess(); if (PlatformDetails.RunningOnPosix == false) { var retries = 10; while (retries > 0) { // From: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686234(v=vs.85).aspx // "The maximum number of pages that a process can lock is equal to the number of pages in its minimum working set minus a small overhead" // let's increase the max size of memory we can lock by increasing the MinWorkingSet. On Windows, that is available for all users var nextWorkingSetSize = GetNearestFileSize(currentProcess.MinWorkingSet.ToInt64() + sizeToLock); if (nextWorkingSetSize > int.MaxValue && PlatformDetails.Is32Bits) { nextWorkingSetSize = int.MaxValue; } // Minimum working set size must be less than or equal to the maximum working set size. // Let's increase the max as well. if (nextWorkingSetSize > (long)currentProcess.MaxWorkingSet) { try { currentProcess.MaxWorkingSet = new IntPtr(nextWorkingSetSize); } catch (Exception e) { throw new InsufficientMemoryException( $"Need to increase the min working set size from {(long)currentProcess.MinWorkingSet:#,#;;0} bytes to {nextWorkingSetSize:#,#;;0} bytes but the max working set size was too small: {(long)currentProcess.MaxWorkingSet:#,#;;0}. " + $"Failed to increase the max working set size so we can lock {sizeToLock:#,#;;0} bytes for {FileName}. With encrypted " + "databases we lock some memory in order to avoid leaking secrets to disk. Treating this as a catastrophic error " + "and aborting the current operation.", e); } } try { currentProcess.MinWorkingSet = new IntPtr(nextWorkingSetSize); } catch (Exception e) { throw new InsufficientMemoryException( $"Failed to increase the min working set size to {nextWorkingSetSize:#,#;;0} bytes so we can lock {sizeToLock:#,#;;0} bytes for {FileName}. With encrypted " + "databases we lock some memory in order to avoid leaking secrets to disk. Treating this as a catastrophic error " + "and aborting the current operation.", e); } if (Sodium.Lock(addressToLock, (UIntPtr)sizeToLock) == 0) { return; } // let's retry, since we increased the WS, but other thread might have locked the memory retries--; } } var msg = $"Unable to lock memory for {FileName} with size {sizeToLock:#,#;;0} bytes), with encrypted databases we lock some memory in order to avoid leaking secrets to disk. Treating this as a catastrophic error and aborting the current operation.{Environment.NewLine}"; if (PlatformDetails.RunningOnPosix) { msg += $"The admin may configure higher limits using: 'sudo prlimit --pid {currentProcess.Id} --memlock={sizeToLock}' to increase the limit. (It's recommended to do that as part of the startup script){Environment.NewLine}"; } else { msg += $"Already tried to raise the the process min working set to {currentProcess.MinWorkingSet.ToInt64():#,#;;0} bytes but still got a failure.{Environment.NewLine}"; } msg += "This behavior is controlled by the 'Security.DoNotConsiderMemoryLockFailureAsCatastrophicError' setting (expert only, modifications of this setting is not recommended)."; throw new InsufficientMemoryException(msg); }