Skip to content

[Enhancement]: About - VerifyAuthenticode accepts a binary signed by any publisher #1674

Description

@laurentiu021

Problem

UpdateService.VerifyAuthenticode (Services/UpdateService.cs:410-438) extracts the signer certificate and returns true after only logging cert.Subject (lines 421-422). There is no subject/thumbprint pinning and no chain build. So once SysManager is signed, a binary signed by ANY certificate - including an attacker's own self-issued one - passes this gate identically to a legitimately signed build. The same codebase already contains the correct pattern for a third-party binary: SpeedTestService.VerifyOoklaSignature (Services/SpeedTestService.cs:443-484) pins the subject (line 452: !cert.Subject.Contains("Ookla") -> delete + throw), then explicitly builds an X509Chain with RevocationMode.Online (lines 463-471) because, in its own comment at line 466-467, "Subject alone is forgeable (anyone can issue a self-signed 'Ookla' cert)". The asymmetry is stark: the downloaded Ookla CLI gets pinned-subject + full chain + revocation + fail-closed deletion, while SysManager's own self-replacing, potentially-elevated update binary gets a log line. Grepped for any pinning constant (expectedSigner|signerSubject|CN=laurentiu|pinnedSubject|ExpectedSubject) -> no matches. Grepped UpdateServiceAuthenticodeTests.cs for Subject|signer|publisher -> no matches; its four tests only cover unsigned/tiny/empty/MZ-header files.

Proposed solution

Restructure VerifyAuthenticode into a two-state contract now, while it is cheap: (a) no embedded signature -> allowed, log it, as today (correct while builds are unsigned); (b) signature present -> pin it the way VerifyOoklaSignature does - build an X509Chain with RevocationMode.Online and compare the signer against a pinned expected subject/thumbprint constant, failing closed on mismatch. Introduce the pinned value as a single named constant that is empty/inactive until a certificate exists, so switching signing on is a one-line change rather than a security redesign under time pressure. Add the missing negative tests to SysManager.Tests/UpdateServiceAuthenticodeTests.cs: a signed-but-wrong-publisher file must be rejected. Reference SpeedTestService.cs:443-484 in the code comment so the two paths stay visibly symmetric.

Rationale

This is the one place where the trust posture is pre-wired to fail the moment the maintainer's biggest planned trust upgrade lands. Today the exposure is bounded, because SHA256 (verified over a held deny-write handle) is the real gate and the no-signature branch is the live one. But the day a certificate arrives, the natural assumption will be "we sign now, so the signature check protects us" - and it will not, silently. Fixing it before signing ships costs one function and two tests; fixing it after means auditing a security control while also managing a certificate rollout. The codebase already proves the maintainer knows the correct pattern, so this is closing an internal inconsistency, not inventing a subsystem.

Evidence

Read Services/UpdateService.cs:391-438 in full: line 396 private const int CryptENoMatch = unchecked((int)0x80092009);, line 421 Serilog.Log.Information("Update binary is Authenticode-signed: {Subject}", cert.Subject); immediately followed by return true; at 422 - no subject comparison, no X509Chain anywhere in the method. Read Services/SpeedTestService.cs:438-484 for contrast: subject pin at 452, chain build at 463-471, TryDeleteExe + throw on both failure paths. Grepped for pinning identifiers across SysManager/ excluding obj/ -> exit 1 (none). Read SysManager.Tests/UpdateServiceAuthenticodeTests.cs in full: four tests, all path-shape cases (UnsignedFile->true, TinyUnsignedFile->true, EmptyFile->false, RandomBinaryContent->true); grep for Subject/signer/publisher -> exit 1.

Risk / trade-off

Adding a chain build with online revocation introduces a network dependency and latency into the install path, and could fail-closed on an offline machine - so the chain check must apply only in the signature-present branch (never blocking today's unsigned flow) and needs a considered offline policy. Over-tightening could brick self-update for users who legitimately obtained a build signed under a future, rotated certificate, so the pinned value should tolerate certificate renewal (pin on subject/organization rather than a single thumbprint, or maintain a small allowed set).

Affected area

About


Effort: M | priority: value 3/5, fit 4/5
Identified during the trust, distribution and reach audit audit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions