Skip to content

Darling install tree: verify shipped binaries after the lock, and narrow the service account's Modify to pg-runtime (#4038 review residuals) #4043

Description

@erikdarlingdata

Residuals from the round-1 security review of #4038 (#4034, locking the install folder). #4038 closes WRITE access, and now locks before anything runs from the tree, both on install and before an upgrade's overlay. It also normalizes the logon account and reports junctions. Two things a lock can't do remain, and each needs a design call.

1. A file swapped before the lock survives it (Medium residual)

Between extracting the zip (the folder inherits Authenticated Users: Modify from C:\) and install-darling.ps1 step 1b2, a local user can replace the service exe, a DLL, or a pg-runtime binary. The lock stops FURTHER writes; it never checks CONTENTS, so a planted binary loads as the service account on first start. #4038 narrows the window to extract-then-run and documents extracting into a fresh folder and running the script straight away. The window itself remains.

Options:

  • Authenticode: after the lock, require a valid signature from our publisher on the service exe and our own DLLs. Release zips are SignPath-signed; dev and nightly builds are not, so an unsigned build would need a flag. That doesn't cover third-party or pg-runtime binaries.
  • Hash manifest: a SHA-256 manifest verified after the lock. It has to be trusted from outside the extracted tree (embedded in the signed exe, or verified against the release's published checksums), since a planted binary can come with a planted manifest.
  • Script-driven extraction: the script extracts the zip itself into a folder it has already locked. That changes the install flow; the upgrade path already verifies a -Source zip's SHA-256.

2. The service account holds Modify on the whole tree (Low, defence in depth)

Only pg-runtime and pg-runtime-prev extraction needs the service to write into the tree; the store, logs and log-hash key live under ProgramData. A compromised service or postgres process could rewrite the service exe for persistence.

Fix: grant the service read and execute on the root, and Modify only on pre-created pg-runtime and pg-runtime-prev folders.
Risk: if the service writes anywhere else in the tree, it breaks at start, which is tier-1 breakage. Every write site needs auditing first, then a test on a real service install.

Activity

  1. erikdarlingdata commented on Sep 23, 2026

    @erikdarlingdata
    OwnerAuthor

    Disposition (backtrack of #3898-#4043, 2026-09-23): work order 4 of 6, tier 1 residual (the #4038 review's Medium residual plus a Low); needs one design call. Recommendation: (1) after the lock, verify the Authenticode signature of our own signed binaries on release builds, and only warn on an unsigned dev or nightly build; third-party and pg-runtime binaries stay covered by extracting fresh (README). (2) Narrow the service account's Modify to pg-runtime only after auditing every write the service makes in the tree, with a real-service install test, since a wrong narrowing stops collection. #4038 already locks before anything runs, so both are hardening, not an open hole.

  2. erikdarlingdata commented on Sep 23, 2026

    @erikdarlingdata
    OwnerAuthor

    Revised recommendation for part 1. The earlier one (Authenticode checks after the lock) is withdrawn: it doesn't close the window it targets.

    • A local user who can replace the service exe or a DLL between extraction and step 1b2 can just as easily replace install-darling.ps1, and the admin then runs it elevated.
    • A signature check run BY that script only catches an attacker who leaves the script alone. The same goes for a hash manifest the script verifies.
    • The trust root is where the admin extracts the zip, not anything inside the tree.

    Recommended instead:

    1. Check the tree's ACL first, before the lock. At the very start, before step 1b2, the script inspects the tree's ACL. It looks for any Allow ACE granting write, append, delete, change-permissions or take-ownership to a principal beyond SYSTEM, Administrators, TrustedInstaller and the installing admin. The typical case is Authenticated Users: Modify inherited from C:\. If it finds one:
      • it names the principals;
      • it explains that files may have been replaced before the lock;
      • it refuses unless -AcceptWritableExtraction is passed. That switch exists for dev loops and says in its help that it accepts the risk.
      • It points to C:\Program Files\<product>, or a folder only the admin can write, as the place to extract.
      • This fires on every risky install, attack or not, so it fixes the habit that creates the window. It doesn't depend on an attacker having left the script untouched.
    2. Docs: the README and install doc recommend extracting under C:\Program Files, which non-admins can't write, and say why.
    3. Upgrade path: unchanged. It already verifies a -Source zip's SHA-256 and locks before the overlay.
    4. Part 2 is unchanged: narrow the service account's Modify to pg-runtime only after auditing every write the service makes in the tree. It's verified with a real-service install on a test box, never on a monitored or dev-in-use host.
  3. added
    in-progressActively being worked by a local session or its agents (PR open or in flight)
    on Sep 23, 2026
  4. erikdarlingdata commented on Sep 23, 2026

    @erikdarlingdata
    OwnerAuthor

    Part 2 (narrowing the service account's Modify) is split to #4052 and handed to client-site. This issue closes when #4050 merges to dev.

  5. erikdarlingdata commented on Sep 23, 2026

    @erikdarlingdata
    OwnerAuthor

    Fixed by #4050, merged to dev at 19:01Z. It ships with the next release.

  6. removed
    in-progressActively being worked by a local session or its agents (PR open or in flight)
    on Sep 23, 2026
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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions