fix(installer): grant users permission to start the helper task

The task was registered correctly and pointed at the right binary — the app's
own log confirmed the match — but starting it failed:

  run: schtasks /Run failed (Some(1)):  ERROR: Access is denied.
  falling back to a direct elevated launch — this is the consent prompt

Registering a task and being allowed to start one are separate things, and I
had conflated them. The principal (BUILTIN\Users by SID, HighestAvailable)
decides who the task runs AS. Who may START it comes from the task's security
descriptor, and a task created by an elevated installer defaults to granting
execution to Administrators only. So the unprivileged GUI was refused and fell
back to prompting on every connect, exactly as before the installer existed.

This also explains why manual testing said the opposite: running the task by
hand happened from an elevated console, where it works, which pointed suspicion
at the app for several rounds.

Register-ScheduledTask cannot set a descriptor, so the hook now follows the
registration with a SetSecurityDescriptor call through the Task Scheduler COM
object: GA for Administrators and SYSTEM, GR+GX for BUILTIN\Users. A failure
there is reported on its own rather than being folded into the success message,
since the task would otherwise look registered while remaining unusable.
This commit is contained in:
ospab 2026-08-11 23:28:36 +03:00
parent 2887f1af8d
commit 732d0bf5ae
1 changed files with 23 additions and 1 deletions

View File

@ -49,7 +49,29 @@
Pop $R0 Pop $R0
${If} $R0 == 0 ${If} $R0 == 0
; Registering the task is not enough to make it usable. The principal above
; decides WHO THE TASK RUNS AS; the task's security descriptor decides who
; is allowed to START it, and they are not the same thing. A task created by
; an elevated installer defaults to a DACL granting execution to
; Administrators only, so the unprivileged GUI got
; schtasks /Run -> ERROR: Access is denied
; and fell back to prompting on every single connect. Running it by hand
; from an elevated console worked, which is what made this look for a while
; like the app was at fault.
;
; Register-ScheduledTask cannot set a descriptor, so this goes through the
; Task Scheduler COM object. GA for Administrators and SYSTEM, GR+GX —
; read and execute — for BUILTIN\Users (BU), which is what lets a normal
; user start it without being elevated.
DetailPrint "Granting users permission to start the task..."
nsExec::ExecToLog `powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "$$svc = New-Object -ComObject Schedule.Service; $$svc.Connect(); $$t = $$svc.GetFolder('\').GetTask('OSTP TUN Helper'); $$t.SetSecurityDescriptor('D:(A;;GA;;;BA)(A;;GA;;;SY)(A;;GRGX;;;BU)', 0)"`
Pop $R1
${If} $R1 == 0
DetailPrint "Helper task registered; connecting will not ask for consent." DetailPrint "Helper task registered; connecting will not ask for consent."
${Else}
DetailPrint "Task registered but its permissions could not be set (exit $R1)."
DetailPrint "Every connect will ask for consent."
${EndIf}
${Else} ${Else}
; Not fatal: the app still works, it just falls back to an elevated launch ; Not fatal: the app still works, it just falls back to an elevated launch
; that asks for consent on each connect. ; that asks for consent on each connect.