Skip to content

Consider using SafeHandle in Libgit2Object instead of void* #2111

Open
@EgorBo

Description

@EgorBo

We've had a couple of bug reports against dotnet/runtime with rarely-reproduceable crashes coming from libgit2sharp. We believe that one possible reason could be an interop anti-pattern in Libgit2Object - void*/IntPtr representation of a native handle that can be freed in finalizer. E.g. in this case the finalizer in Libgit2Object may end up calling native free here (and other overloads).

The reason why it's called an anti-pattern can be explained by a short repro in this issue: dotnet/runtime#103522 and a general solution is to use SafeHandle for such handles. Also, see https://learn.microsoft.com/en-us/dotnet/standard/native-interop/best-practices

Reproduction steps

Expected behavior

Actual behavior

Version of LibGit2Sharp (release number or SHA1)

Operating system(s) tested; .NET runtime tested

Activity

christian-clausen

christian-clausen commented on Oct 22, 2024

@christian-clausen

We are seeing System.ExecutionEngineException also in dotnet 9 RC2 (9.0.100-rc.2.24474.11) when running our app in debug mode.

bording

bording commented on Nov 21, 2024

@bording
Member

@EgorBo Thanks for raising the issue. I've converted Libgit2Object to derive from SafeHandleZeroOrMinusOneIsInvalid in #2127 which if I'm understanding everything properly should be enough to avoid the problem.

jkotas

jkotas commented on Nov 21, 2024

@jkotas

@bording In order to benefit from SafeHandles reliability and security guarantees, the code has to AddRef/Release around all places where it operates on the underlying raw handle. It does not look like that the conversions of SafeHandle to IntPtr like https://github.com/libgit2/libgit2sharp/pull/2127/files#diff-f210a201602dfd11231de2914b00197fa6ae2dd165e88d9f602fbfc950c98082R111 do proper AddRef/Release. As far as a I can tell, the code has the same problems as before since it is not using SafeHandle correctly.

bording

bording commented on Nov 21, 2024

@bording
Member

@jkotas Can you point me to documentation that explains what I need to change? The docs didn't seem to explain anything regarding "proper usage".

bording

bording commented on Nov 21, 2024

@bording
Member

@jkotas If the derived handle type usage is wrapped in a using statement, is it still also required to pair up DangerousAddRef/DangerousRelease calls as well?

For example, this method is using this implicit operator to access the underlying IntPtr, but it's called from this method which wraps the ReferenceHandle in a using statement.

You're saying that's not enough and it also needs to be wrapped in DangerousAddRef/DangerousRelease calls?

jkotas

jkotas commented on Nov 21, 2024

@jkotas

If the calling method ensures lifetime like in your example, additional DangerousAddRef / DangerousRelease is not strictly required. Unless the code is perf sensitive, it is easier to do DangerousAddRef / DangerousRelease everywhere. If it is perf sensitive and you really want to avoid the extra cost DangerousAddRef / DangerousRelease, it can use a comment to make it clear that the DangerousGetHandle is safe.

Also, another common pattern is leave it to the P/Invoke marshaller to auto-generate the DangerousAddRef / DangerousRelease for you by using the SafeHandle in the P/Invoke signature. For example, change

internal static extern unsafe int git_branch_delete(
git_reference* reference);
to int git_branch_delete(ReferenceHandle reference);, so that you do not have to write the DangerousAddRef / DangerousRelease manually.

rickbrew

rickbrew commented on Dec 7, 2024

@rickbrew

I use a disposable struct and an extension method to help with this https://gist.github.com/rickbrew/0c1643b5186f8ac061be092de7a9a40a

SafeHandle handle = ...;

using (SafeHandleValue handleValue = handle.UseValue())
{
    SomeNativeMethod(handleValue.Value); // or omit .Value and use the implicit cast to `IntPtr` / `nint`
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @EgorBo@bording@jkotas@rickbrew@christian-clausen

        Issue actions

          Consider using SafeHandle in Libgit2Object instead of void* · Issue #2111 · libgit2/libgit2sharp