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 LibGit2 securezero! issue #24731

Merged
merged 3 commits into from
Nov 25, 2017
Merged

Fix LibGit2 securezero! issue #24731

merged 3 commits into from
Nov 25, 2017

Conversation

omus
Copy link
Member

@omus omus commented Nov 23, 2017

Another reminder of the dangers with securezero!. When running on 32-bit machines the GitCredential finalizer was causing copies of Strings to get wiped out. Fixes this issue:

Error During Test at /buildworker/worker/package_linux32/build/usr/share/julia/test/libgit2.jl:2387
  Got an exception of type ErrorException outside of a @test
  Could not locate challenge: "Password for 'https://[email protected]':". Process output found:
  """
  Username for 'https://github.com' [julia]:
  ERROR: ArgumentError: embedded NULs are not allowed in C strings: "Password for 'https://\0\0\0\0\[email protected]':"
  ...
  """

@omus omus added the libgit2 The libgit2 library or the LibGit2 stdlib module label Nov 23, 2017
@omus
Copy link
Member Author

omus commented Nov 23, 2017

cc: @staticfloat

@omus
Copy link
Member Author

omus commented Nov 23, 2017

Here's a minimal reproduction of the issue:

julia> git_cred = LibGit2.GitCredential("https", "localhost", nothing, "bob")
Base.LibGit2.GitCredential(Nullable{String}("https"), Nullable{String}("localhost"), Nullable{String}(), Nullable{String}("bob"), Nullable{String}(), true)

julia> u = Base.get(git_cred.username, "")  # Should be using `deepcopy` here
"bob"

julia> git_cred = nothing

julia> gc()

julia> u  # Was intended to be used later
"\0\0\0"

@ararslan
Copy link
Member

Could you add the reproducing example as a test?

@ararslan ararslan added bugfix This change fixes an existing bug needs tests Unit tests are required for this change labels Nov 23, 2017
@omus
Copy link
Member Author

omus commented Nov 23, 2017

Could you add the reproducing example as a test?

I should be able to write a test that verifies that garbage collection of the GitCredential in the callback doesn't result in the content of the UserPasswordCredentials also being wiped.

@StefanKarpinski
Copy link
Member

I’m not sure if this is evidence that securezero! is a good idea or a bad one.

@omus
Copy link
Member Author

omus commented Nov 23, 2017

I’m not sure if this is evidence that securezero! is a good idea or a bad one.

I think the ideal version of this would be to only wipe the string once all references to it have been lost. Wiping a string upon the first reference being dropped is the core issue.

@stevengj
Copy link
Member

I thought that the consensus in #23232 was to not use a String at all, and just use a Vector{UInt8} for this data?

@omus
Copy link
Member Author

omus commented Nov 24, 2017

I'll be using be switching to Vector{UInt8} instead of String within SecureString for #24738. The current iteration of the code uses String.

@omus
Copy link
Member Author

omus commented Nov 24, 2017

AppVeyor and Travis CI Mac failures look unrelated.

@omus omus removed the needs tests Unit tests are required for this change label Nov 24, 2017
@omus
Copy link
Member Author

omus commented Nov 24, 2017

Any other comments? I'll merge this later today.

test/libgit2.jl Outdated
# Triggering a garbage collection will cause the internal GitCredential
# used in `authenticate_userpass` to be securely wiped but this should
# not cause the payload credential to be wiped (#24731).
gc()
Copy link
Member

@stevengj stevengj Nov 24, 2017

Choose a reason for hiding this comment

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

Wouldn't it be better to add a manual finalize() call in authenticate_userpass when it is done with the internal GitCredential?

Copy link
Member Author

Choose a reason for hiding this comment

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

I can add an explicit securezero! call in authenticate_userpass but this gc() will still be required to ensure that the memory has been duplicated.

Copy link
Member

Choose a reason for hiding this comment

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

What do you mean by gc ensuring that memory is duplicated?

Copy link
Member Author

@omus omus Nov 25, 2017

Choose a reason for hiding this comment

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

What do you mean by gc ensuring that memory is duplicated?

The original issue was that in authenticate_userpass sensitive information was being loaded into git_cred and then shallow copied into a UserPasswordCredentials credential. Since the data was being shallow copied once the git_cred was garbage collected (and then finalized) the data stored within the cred would also be securely wiped.

The gc() in this test case ensures that the git_cred is consistently garbage collected (this was issue was previously only cropping up on 32-bit systems) which will cause the git_cred data to be wiped out. Since the git_cred information is now deep copied to cred the secure wiping of git_cred should not impact the data within cred.

Copy link
Member Author

Choose a reason for hiding this comment

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

Adding a manual finalize() call would cause the git_cred memory to be wiped sooner (definitely good idea) and would also make sure that cred and git_cred data is independent. My concern with removing the gc() call and only having a finalize() call only is that the conditions to reproduce this issue exist within the source code and not within the tests.

Potentially a future developer could remove deepcopy calls and the finalize calls and the tests would still pass as long as garbage collection hasn't happened to run.

Copy link
Member Author

@omus omus Nov 25, 2017

Choose a reason for hiding this comment

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

With all of that said having a manual gc() call in a test case is a strange thing to do. If we do consider it bad practise we can go with just using finalize() and hope only me or stevengj modify this code in the future ;)

Copy link
Member

Choose a reason for hiding this comment

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

Just having it in a test case is fine.

Copy link
Member Author

Choose a reason for hiding this comment

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

@stevengj Good to know. I'll merge as is, without the gc call, so we can get this resolved.

@omus omus force-pushed the cv/libgit2-securezero-fix branch from 472f2be to b548c8a Compare November 25, 2017 02:01
@omus
Copy link
Member Author

omus commented Nov 25, 2017

I squashed the commits and added an explicit securezero! call inside of authenticate_userpass.

@omus
Copy link
Member Author

omus commented Nov 25, 2017

After writing my speech on leaving in the gc() call I decided it was more important just to get this fix merged.

@omus omus merged commit 6fa2047 into master Nov 25, 2017
@omus omus deleted the cv/libgit2-securezero-fix branch November 25, 2017 14:53
# Use `deepcopy` to ensure zeroing the `git_cred` doesn't also zero the `cred`s copy
cred.user = deepcopy(Base.get(git_cred.username, ""))
cred.pass = deepcopy(Base.get(git_cred.password, ""))
securezero!(git_cred)
revised = true
Copy link
Member

Choose a reason for hiding this comment

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

It seems like we should also call securezero!(cred) at the end of the authenticate_userpass function. The function returns the result of git_cred_userpass_plaintext_new, but this function makes its own copy of the password.

Copy link
Member Author

@omus omus Nov 25, 2017

Choose a reason for hiding this comment

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

We can't run securezero!(cred) at the end of authenticate_userpass as the credential information is still needed to be used in the approve(::CredentialPayload) and the reject(::CredentialPayload) methods. We should be able to run securezero! on the credentials at the end of the approve/reject methods but I'll need to add in some extra logic to skip zeroing the data when running the test cases.

I was planning to do this in a future PR.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I see, cred is actually still available via get(p.credential).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bugfix This change fixes an existing bug libgit2 The libgit2 library or the LibGit2 stdlib module
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants