Skip to content

Fix native memory leak in Keychain.GetKeychainPath error path#178

Merged
rmarinho merged 2 commits intomainfrom
op-fix-keychain-getpath-leak
May 11, 2026
Merged

Fix native memory leak in Keychain.GetKeychainPath error path#178
rmarinho merged 2 commits intomainfrom
op-fix-keychain-getpath-leak

Conversation

@rmarinho
Copy link
Copy Markdown
Member

@rmarinho rmarinho commented May 4, 2026

Summary

In Keychain.GetKeychainPath, Marshal.AllocHGlobal allocates a 1 KB native buffer for the keychain path. When SecKeychainGetPath returns a non-OK status, the exception thrown at line 436 bypasses the Marshal.FreeHGlobal call, leaking the buffer. In long-lived processes (e.g., IDE plugins) that retry keychain access after transient failures, each failure leaks 1 KB of unmanaged memory.

Changes

Wrapped the SecKeychainGetPath call and result handling in a try/finally block, moving Marshal.FreeHGlobal(pathBuffer) into the finally clause. This ensures the native buffer is freed on both success and error paths.

Verification

  • dotnet build Xamarin.MacDev.sln — 0 warnings, 0 errors (TreatWarningsAsErrors enabled)
  • dotnet test tests/tests.csproj -f net10.0 — all tests pass
  • dotnet format --verify-no-changes — formatting correct on changed file

Wrap SecKeychainGetPath call and result handling in try/finally to ensure
Marshal.FreeHGlobal(pathBuffer) executes even when an exception is thrown
on non-OK status. Previously, the 1 KB native buffer leaked on every
failed call.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an unmanaged-memory leak in Xamarin.MacDev.Keychain.GetKeychainPath() by ensuring the native buffer allocated for SecKeychainGetPath is always released, including when the native call fails and an exception is thrown. This is a targeted reliability fix in the keychain interop layer.

Changes:

  • Wrapped the SecKeychainGetPath call in a try/finally.
  • Moved Marshal.FreeHGlobal(pathBuffer) into the finally block so cleanup runs on success and failure.
  • Kept the existing success-path behavior unchanged while closing the native leak on error paths.

Comment thread Xamarin.MacDev/Keychain.cs
…Path

Replace unmanaged allocation with a pinned managed byte array as suggested
by rolfbjarne. This eliminates the need for try/finally cleanup since the
GC handles the buffer lifetime.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment on lines 431 to +437
uint bufferLength = 1024;
var pathBuffer = Marshal.AllocHGlobal ((int) bufferLength);
var status = SecKeychainGetPath (keychainPtr, out bufferLength, pathBuffer);
var pathBuffer = new byte [bufferLength];
OSStatus status;
unsafe {
fixed (byte* pathPtr = pathBuffer)
status = SecKeychainGetPath (keychainPtr, out bufferLength, (IntPtr) pathPtr);
}
@rmarinho rmarinho merged commit fa33bf8 into main May 11, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants