Skip to content

fix(agent): verify DACL and owner of package manager binaries before elevated execution - #1889

Draft
Vladyslav Nikonov (vnikonov-devolutions) wants to merge 2 commits into
masterfrom
vnikonov-devolutions-dgw-434-package-broker-elevation-securit
Draft

fix(agent): verify DACL and owner of package manager binaries before elevated execution#1889
Vladyslav Nikonov (vnikonov-devolutions) wants to merge 2 commits into
masterfrom
vnikonov-devolutions-dgw-434-package-broker-elevation-securit

Conversation

@vnikonov-devolutions

Copy link
Copy Markdown
Contributor

Before running a package-manager executable with an elevated (SYSTEM) token, the Agent package broker now verifies that the binary is owned by SYSTEM or the built-in Administrators group and that its DACL does not grant write access to non-admin users. Without this check, a standard user could plant or overwrite a package-manager binary (for example via a user-writable PATH entry) and have it executed with elevated rights, resulting in local privilege escalation.

The verification is fail-closed and enforced at every elevated executable resolution point: directly spawned binaries (dotnet.exe, trusted PowerShell hosts) as well as winget and Chocolatey binaries that are embedded into generated batch scripts. Non-elevated per-user executions are unaffected, so user-scope installs (pip virtualenvs, ~/.cargo, ~/.bun, and so on) keep working as before.

Issue: DGW-434

Extract the owner/DACL verification logic from the policy file check
into reusable helpers so the same admin-only-writability validation can
be applied to other security-sensitive files:

- verify_admin_only_writable(file, subject) with subject-aware errors
- verify_admin_only_writable_path(path, subject), fail-closed on open
- verify_elevated_executable_security(path, requires_elevation), a
  no-op unless the executable is about to run elevated

Issue: DGW-434

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…elevated execution

Before running a package-manager executable with an elevated (SYSTEM)
token, verify that the binary is owned by SYSTEM or built-in
Administrators and that its DACL does not grant write access to
non-admin users. This prevents privilege escalation where a standard
user plants or overwrites a package-manager binary (e.g. via a
user-writable PATH entry) and gets it executed with elevated rights.

The check is enforced at every elevated executable resolution point:

- resolve_executable / create_process for directly spawned binaries
  (dotnet.exe, trusted PowerShell hosts)
- resolve_winget_executable and resolve_trusted_chocolatey_executable,
  whose binaries are embedded into generated batch scripts and never
  reach create_process as the resolved executable

Non-elevated (per-user) executions are unaffected, keeping user-scope
installs (pip venvs, ~/.cargo, ~/.bun, etc.) working as before.

Issue: DGW-434

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

Let maintainers know that an action is required on their side

  • Add the label release-required Please cut a new release (Devolutions Gateway, Devolutions Agent, Jetsocat, PowerShell module) when you request a maintainer to cut a new release (Devolutions Gateway, Devolutions Agent, Jetsocat, PowerShell module)

  • Add the label release-blocker Follow-up is required before cutting a new release if a follow-up is required before cutting a new release

  • Add the label publish-required Please publish libraries (`Devolutions.Gateway.Utils`, OpenAPI clients, etc) when you request a maintainer to publish libraries (Devolutions.Gateway.Utils, OpenAPI clients, etc.)

  • Add the label publish-blocker Follow-up is required before publishing libraries if a follow-up is required before publishing libraries

@vnikonov-devolutions

Copy link
Copy Markdown
Contributor Author

Implementation notes:

  • The existing policy-file owner/DACL verification in broker/policy_security.rs was generalized into subject-aware helpers: verify_admin_only_writable(file, subject), verify_admin_only_writable_path(path, subject), and verify_elevated_executable_security(path, requires_elevation) (a no-op when not elevated). verify_policy_file_security is now a thin wrapper, so policy files and elevated executables share the exact same fail-closed logic (owner must be SYSTEM/Administrators; no write/delete ACEs for non-admin SIDs; NULL DACL and unsupported ACE types rejected).
  • requires_elevation is computed once in the executor (elevation == Elevated || scope == Machine) and threaded through run_plan -> prepare_* -> create_process/resolve_executable, which check the resolved binary before spawning.
  • winget and Chocolatey need dedicated checks in resolve_winget_executable / resolve_trusted_chocolatey_executable: their binaries are embedded into generated temp batch scripts and the process actually spawned is System32 cmd.exe, so the generic create_process check would never see them.
  • The check intentionally gates on elevation only. Verifying unconditionally would break legitimate per-user installs (pip venvs, ~/.cargo, ~/.bun) whose binaries live in user-writable locations. Only winget, Chocolatey, dotnet, and machine-scope PowerShell can run elevated; all other managers reject elevation at the builder level (vcpkg/bun also at the executor level), which the security audit confirmed.
  • Tests: 8 new tests set real DACLs (Everyone full-control) on temp files via win-api-wrappers and assert elevated resolution fails while non-elevated resolution succeeds, covering the generic path plus winget and Chocolatey script generation. Full broker suite (189 tests), workspace clippy with -D warnings, and nightly fmt all pass.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds security validation for package-manager executables before elevated Windows execution.

Changes:

  • Generalizes file owner/DACL validation.
  • Applies validation to direct and script-embedded executables.
  • Adds elevated/non-elevated ACL tests.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
policy_security.rs Generalizes security verification.
broker/mod.rs Exposes the security module internally.
windows/process.rs Validates resolved executables.
windows/mod.rs Propagates elevation state and validates WinGet/Chocolatey paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +127 to +129
pub(crate) fn verify_admin_only_writable_path(path: &Path, subject: &str) -> anyhow::Result<()> {
let file = File::open(path).with_context(|| format!("failed to open {subject} at {}", path.display()))?;
verify_admin_only_writable(&file, subject)
Comment on lines +139 to +145
pub(crate) fn verify_elevated_executable_security(path: &Path, requires_elevation: bool) -> anyhow::Result<()> {
if !requires_elevation {
return Ok(());
}

let subject = format!("elevated package-manager executable '{}'", path.display());
verify_admin_only_writable_path(path, &subject)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants