Skip to content

Commit 7715391

Browse files
rzikmwfurtam11bartonjs
authored
Add optional hooks for debugging OpenSSL memory allocations (#111539)
* add hooks to debug OpenSSL memory * opensslshim * 1.x * 1.0.1 * android * Collections * unsafe * build * feedback * update * feedback * feedback * feedback * fix build * gcc * gcc * Update src/native/libs/System.Security.Cryptography.Native/openssl.c Co-authored-by: Adeel Mujahid <[email protected]> * Move init to Interop.Crypto * Fix data race on GetIncrementalAllocations * Use modern tuple type * Fix typo * Move functionality to separate file * Revert unnecessary changes * WIP tracking in native code * Improvements * Reintroduce rw lock * Add readme * Fix build * Code review feedback * code review changes - cont. * More code review feedback * Expose the "API" via system.security.cryptography * Fix infinite link list traversal * Fix memory accounting for realloc * Refactoring * Improve comments * Improve Readme * Simplify implementation * Don't use CRYPTO_atomic_add * Readme fixes * Fix build * Fix compilation, attempt 2 * Fix build - attemt no 3 * Apply suggestions from code review Co-authored-by: Jeremy Barton <[email protected]> * Feedback --------- Co-authored-by: wfurt <[email protected]> Co-authored-by: Adeel Mujahid <[email protected]> Co-authored-by: Jeremy Barton <[email protected]>
1 parent 8a22b87 commit 7715391

File tree

10 files changed

+478
-0
lines changed

10 files changed

+478
-0
lines changed

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Crypto.cs

+50
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33

44
using System;
55
using System.Diagnostics;
6+
using System.Globalization;
67
using System.Runtime.InteropServices;
78
using System.Security.Cryptography.X509Certificates;
89
using Microsoft.Win32.SafeHandles;
910

11+
using TrackedAllocationDelegate = System.Action<System.IntPtr, ulong, System.IntPtr, int>;
12+
1013
internal static partial class Interop
1114
{
1215
internal static partial class Crypto
@@ -164,5 +167,52 @@ internal static byte[] GetDynamicBuffer<THandle>(NegativeSizeReadMethod<THandle>
164167

165168
return bytes;
166169
}
170+
171+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetMemoryUse")]
172+
private static partial void GetMemoryUse(ref long memoryUse, ref long allocationCount);
173+
174+
internal static long GetOpenSslAllocatedMemory()
175+
{
176+
long used = 0;
177+
long count = 0;
178+
GetMemoryUse(ref used, ref count);
179+
return used;
180+
}
181+
182+
internal static long GetOpenSslAllocationCount()
183+
{
184+
long used = 0;
185+
long count = 0;
186+
GetMemoryUse(ref used, ref count);
187+
return count;
188+
}
189+
190+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_EnableMemoryTracking")]
191+
internal static unsafe partial void EnableMemoryTracking(int enable);
192+
193+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_ForEachTrackedAllocation")]
194+
private static unsafe partial void ForEachTrackedAllocation(delegate* unmanaged<IntPtr, ulong, char*, int, IntPtr, void> callback, IntPtr ctx);
195+
196+
internal static unsafe void ForEachTrackedAllocation(TrackedAllocationDelegate callback)
197+
{
198+
ForEachTrackedAllocation(&MemoryTrackingCallback, (IntPtr)(&callback));
199+
}
200+
201+
[UnmanagedCallersOnly]
202+
private static unsafe void MemoryTrackingCallback(IntPtr ptr, ulong size, char* file, int line, IntPtr ctx)
203+
{
204+
TrackedAllocationDelegate callback = *(TrackedAllocationDelegate*)ctx;
205+
callback(ptr, size, (IntPtr)file, line);
206+
}
207+
208+
internal static unsafe void EnableMemoryTracking()
209+
{
210+
EnableMemoryTracking(1);
211+
}
212+
213+
internal static unsafe void DisableMemoryTracking()
214+
{
215+
EnableMemoryTracking(0);
216+
}
167217
}
168218
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# System.Security.Cryptography.Native
2+
3+
This folder contains C# bindings for native shim (libSystem.Security.Cryptography.Native.OpenSsl.so), shimming functionality provided by the OpenSSL library.
4+
5+
## Memory allocation hooks
6+
7+
One extra feature exposed by the native shim is tracking of memory used by
8+
OpenSSL by hooking the memory allocation routines via
9+
`CRYPTO_set_mem_functions`.
10+
11+
The functionality is enabled by setting
12+
`DOTNET_OPENSSL_MEMORY_DEBUG` to 1. This environment
13+
variable must be set before launching the program (calling
14+
`Environment.SetEnvironmentVariable` at the start of the program is not
15+
sufficient). The diagnostic API is not officially exposed and needs to be
16+
accessed via private reflection on the `Interop.Crypto` type located in the
17+
`System.Security.Cryptography` assembly. On this type, you can use following static
18+
methods:
19+
20+
- `int GetOpenSslAllocatedMemory()`
21+
- Gets the total amount of memory allocated by OpenSSL
22+
- `int GetOpenSslAllocationCount()`
23+
- Gets the number of allocations made by OpenSSL
24+
- `void EnableMemoryTracking()`/`void DisableMemoryTracking()`
25+
- toggles tracking of individual live allocations via internal data
26+
structures. I.e. will keep track of live memory allocated since the start of
27+
tracking.
28+
- `void ForEachTrackedAllocation(Action<IntPtr, ulong, IntPtr, int> callback)`
29+
- Accepts an callback and calls it for each allocation performed since the
30+
last `EnableMemoryTracking` call. The order of reported information does not
31+
correspond to the order of allocation. This method holds an internal lock
32+
which prevents other threads from allocating any memory from OpenSSL.
33+
- Callback parameters are
34+
- IntPtr - The pointer to the allocated object
35+
- ulong - size of the allocation in bytes
36+
- IntPtr - Pointer to a null-terminated string (`const char*`) containing the name of the file from which the allocation was made.
37+
- int - line number within the file specified by the previous parameter where the allocation was called from.
38+
39+
The debug functionality brings some overhead (header for each allocation,
40+
locks/synchronization during each allocation) and may cause performance penalty.
41+
42+
### Example usage
43+
44+
```cs
45+
// all above mentioned APIs are accessible via "private reflection"
46+
BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Static;
47+
var cryptoInterop = typeof(RandomNumberGenerator).Assembly.GetTypes().First(t => t.Name == "Crypto");
48+
49+
// enable tracking, this clears up any previously tracked allocations
50+
cryptoInterop.InvokeMember("EnableMemoryTracking", flags, null, null, null);
51+
52+
// do some work that includes OpenSSL
53+
HttpClient client = new HttpClient();
54+
await client.GetAsync("https://www.microsoft.com");
55+
56+
// stop tracking (this step is optional)
57+
cryptoInterop.InvokeMember("DisableMemoryTracking", flags, null, null, null);
58+
59+
using var process = Process.GetCurrentProcess();
60+
Console.WriteLine($"Bytes known to GC [{GC.GetTotalMemory(false)}], process working set [{process.WorkingSet64}]");
61+
Console.WriteLine("OpenSSL - currently allocated memory: {0} B", cryptoInterop.InvokeMember("GetOpenSslAllocatedMemory", flags, null, null, null));
62+
Console.WriteLine("OpenSSL - total allocations since start: {0}", cryptoInterop.InvokeMember("GetOpenSslAllocationCount", flags, null, null, null));
63+
64+
Dictionary<(IntPtr file, int line), ulong> allAllocations = new();
65+
Action<IntPtr, ulong, IntPtr, int> callback = (ptr, size, namePtr, line) =>
66+
{
67+
CollectionsMarshal.GetValueRefOrAddDefault(allAllocations, (namePtr, line), out _) += size;
68+
};
69+
cryptoInterop.InvokeMember("ForEachTrackedAllocation", flags, null, null, [callback]);
70+
71+
Console.WriteLine("Total allocated OpenSSL memory by location");
72+
foreach (var ((filenameptr, line), total) in allAllocations.OrderByDescending(kvp => kvp.Value).Take(10))
73+
{
74+
string filename = Marshal.PtrToStringUTF8(filenameptr);
75+
Console.WriteLine($"{total:N0} B from {filename}:{line}");
76+
}
77+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<linker>
2+
<assembly fullname="System.Security.Cryptography">
3+
<type fullname="Interop/Crypto">
4+
<!-- Debug only APIs to be reachable only via private reflection -->
5+
<method name="GetOpenSslAllocationCount" />
6+
<method name="GetOpenSslAllocatedMemory" />
7+
<method name="ForEachTrackedAllocation" />
8+
<method name="EnableMemoryTracking" />
9+
<method name="DisableMemoryTracking" />
10+
</type>
11+
</assembly>
12+
</linker>

src/native/libs/System.Security.Cryptography.Native/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ include_directories(${OPENSSL_INCLUDE_DIR})
2121
set(NATIVECRYPTO_SOURCES
2222
apibridge.c
2323
apibridge_30.c
24+
memory_debug.c
2425
openssl.c
2526
pal_asn1.c
2627
pal_bignum.c

src/native/libs/System.Security.Cryptography.Native/apibridge.h

+6
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,9 @@ int32_t local_X509_get_version(const X509* x509);
6565
int32_t local_X509_up_ref(X509* x509);
6666
typedef void (*SSL_CTX_keylog_cb_func)(const SSL *ssl, const char *line);
6767
void local_SSL_CTX_set_keylog_callback(SSL_CTX *ctx, SSL_CTX_keylog_cb_func cb);
68+
69+
typedef void *(*CRYPTO_malloc_fn)(size_t num, const char *file, int line);
70+
typedef void *(*CRYPTO_realloc_fn)(void *addr, size_t num, const char *file, int line);
71+
typedef void (*CRYPTO_free_fn)(void *addr, const char *file, int line);
72+
73+
int CRYPTO_set_mem_functions(CRYPTO_malloc_fn malloc_fn, CRYPTO_realloc_fn realloc_fn, CRYPTO_free_fn free_fn);

src/native/libs/System.Security.Cryptography.Native/entrypoints.c

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
// Include System.Security.Cryptography.Native headers
77
#include "openssl.h"
8+
#include "memory_debug.h"
89
#include "pal_asn1.h"
910
#include "pal_bignum.h"
1011
#include "pal_bio.h"
@@ -188,6 +189,9 @@ static const Entry s_cryptoNative[] =
188189
DllImportEntry(CryptoNative_GetECKeyParameters)
189190
DllImportEntry(CryptoNative_GetMaxMdSize)
190191
DllImportEntry(CryptoNative_GetMemoryBioSize)
192+
DllImportEntry(CryptoNative_GetMemoryUse)
193+
DllImportEntry(CryptoNative_EnableMemoryTracking)
194+
DllImportEntry(CryptoNative_ForEachTrackedAllocation)
191195
DllImportEntry(CryptoNative_GetObjectDefinitionByName)
192196
DllImportEntry(CryptoNative_GetOcspRequestDerSize)
193197
DllImportEntry(CryptoNative_GetPkcs7Certificates)

0 commit comments

Comments
 (0)