Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix memory leak in Signature.decode() #6

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

burlindw
Copy link

@burlindw burlindw commented Aug 5, 2024

Signature.decode() followed by Signature.deinit() leaked memory.

My best guess for why this was happening is that there is a pointer somewhere in the internals of std.heap.ArenaAllocator that gets invalidated when the ArenaAllocator is copied from the function scope into the Signature and returned from Signature.decode(). However, I'm not familiar enough with ArenaAllocator's internals to be confident in that.

I solved the issue by removing the ArenaAllocator and using the supplied std.mem.Allocator directly. This required the following:

  • Move the Allocator.dupe() calls before of the struct initialization so that they may have their own errdefer'd calls to Allocator.free(). Strictly speaking, only the first errdefer is necessary, since there are no more opportunities for errors following the second call to Allocator.dupe(), but I've included both in case the code is rearranged in the future.
  • Replace the ArenaAllocator field in Signature with a plain Allocator.
  • Free the trusted_comment and untrusted_comment fields individually in Signature.deinit().

@burlindw
Copy link
Author

burlindw commented Aug 6, 2024

I dug into this some more and I've found that my initial assumption was wrong. Struct initialization happens field-by-field in the order that the fields are listed in the initializer, so in the original code

const sig = Signature{
    .arena = arena,
    .untrusted_comment = try arena.allocator().dupe(u8, untrusted_comment),
    .signature_algorithm = bin1[0..2].*,
    .key_id = bin1[2..10].*,
    .signature = bin1[10..74].*,
    .trusted_comment = try arena.allocator().dupe(u8, trusted_comment),
    .global_signature = bin2,
};

the arena local is copied into the arena field in the struct, then untrusted_comment local is duplicated using the original allocator. The arena local has never allocated before, so its internal linked list of nodes has null for its first element. The call to dupe() creates a new node in the local but the arena field still has null as its first node.

The simplest fix would be to initialize the arena field last, but that seems prone to being broken accidentally in the future. I think it would be better to move the calls to dupe() before the struct initialization. I'll update the pull request to this effect.

The fix no longer requires breaking changes to any part of the API.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant