Skip to content

Linux Secret Service store never scrubs plaintext credentials from managed memory (Windows and macOS do) #160

Description

@matt-edmondson

What's wrong

WindowsCredentialStore.cs and MacOsCredentialStore.cs both load the stored secret into a native/pinned byte buffer and deserialize it via CredentialSerialization.DeserializeAndScrub(blob) (both at line 60), which per CredentialSerialization.cs zeroes the plaintext bytes after use.

LinuxSecretServiceCredentialStore.cs does not do this:

  • TryLoad (lines 58-67): converts the native secret straight into a managed string via Marshal.PtrToStringUTF8(passwordPtr), then calls CredentialSerialization.DeserializeFromString(value) — the non-scrubbing string-based path, not DeserializeAndScrub.
  • Save (lines 75-93): builds the plaintext JSON as a managed string via CredentialSerialization.SerializeToString(credential) and passes it directly to secret_password_store_sync.

Why it matters

.NET strings are immutable, so once the plaintext credential exists as a managed string, it cannot be actively zeroed — it just sits on the GC heap until eventually collected (and may be copied during compaction in the meantime). This is precisely the exposure CredentialSerialization.cs (lines ~68-75) documents as the reason DeserializeAndScrub/NativeSecretBuffer exist in the first place, and it's the same class of issue previously fixed for Windows in issue #144 (Windows buffer scrubbing). That fix was never applied to the Linux store, so on Linux specifically, every credential loaded from or saved to the Secret Service passes through plaintext managed strings that linger in memory (visible to crash dumps, memory-scanning tools, or a GC heap dump) for an unbounded time after use.

Suggested fix

Route the Linux store through the same byte-array + scrub discipline as Windows/macOS:

  • TryLoad: use secret_password_lookup_binary_sync (or marshal the UTF-8 bytes directly rather than via PtrToStringUTF8) into a byte[]/native buffer, then call CredentialSerialization.DeserializeAndScrub.
  • Save: serialize via CredentialSerialization.Serialize to a byte[], pass that to the native call, and call CredentialSerialization.Zero (or equivalent) on the buffer afterward.

If libsecret's synchronous API genuinely offers no byte-array entry point and a managed string is unavoidable, at minimum document that limitation explicitly in the store's doc comment (it currently reads as if scrubbing were platform-uniform) so it's a documented, deliberate gap rather than an inconsistency discovered by inspection.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions