diff --git a/internal/client/supervisor/windows/native_exec.go b/internal/client/supervisor/windows/native_exec.go index ffb15c2..73ad781 100644 --- a/internal/client/supervisor/windows/native_exec.go +++ b/internal/client/supervisor/windows/native_exec.go @@ -252,7 +252,7 @@ func (process *execProcess) startReaders(maxChunk uint64, remove func()) { }() } -func materializeWrapper(directory string, wrapper Wrapper, now time.Time) (string, func(), error) { +func materializeWrapper(directory string, wrapper Wrapper, now time.Time, secure func(string) error) (string, func(), error) { if directory == "" { return "", nil, ErrInvalidWorkingDirectory } @@ -272,6 +272,12 @@ func materializeWrapper(directory string, wrapper Wrapper, now time.Time) (strin cleanup() return "", nil, err } + if secure != nil { + if err := secure(temporaryName); err != nil { + cleanup() + return "", nil, err + } + } if _, err := temporary.Write(wrapper.Bytes); err != nil { cleanup() return "", nil, err @@ -379,7 +385,7 @@ func (manager *execSupervisor) startPortable(ctx context.Context, spec superviso if err != nil { return nil, err } - wrapperPath, cleanup, err = materializeWrapper(spec.WorkingDirectory, wrapper, manager.options.Now()) + wrapperPath, cleanup, err = materializeWrapper(spec.WorkingDirectory, wrapper, manager.options.Now(), nil) if err != nil { return nil, err } diff --git a/internal/client/supervisor/windows/native_windows.go b/internal/client/supervisor/windows/native_windows.go index dbe28a3..5bb933e 100644 --- a/internal/client/supervisor/windows/native_windows.go +++ b/internal/client/supervisor/windows/native_windows.go @@ -87,10 +87,7 @@ func (manager *execSupervisor) Start(ctx context.Context, spec supervisor.StartS if err != nil { return nil, err } - wrapperPath, cleanup, err := materializeWrapper(spec.WorkingDirectory, wrapper, manager.options.Now()) - if err != nil { - return nil, err - } + var cleanup func() fail := func(cause error) (supervisor.Process, error) { if cleanup != nil { cleanup() @@ -103,6 +100,12 @@ func (manager *execSupervisor) Start(ctx context.Context, spec supervisor.StartS return fail(err) } defer token.Close() + wrapperPath, cleanup, err := materializeWrapper(spec.WorkingDirectory, wrapper, manager.options.Now(), func(path string) error { + return secureWrapperFile(path, identity.UserSID) + }) + if err != nil { + return fail(err) + } baseEnvironment, err := token.Environ(false) if err != nil { return fail(fmt.Errorf("build token environment: %w", err)) @@ -264,6 +267,50 @@ func verifyExecutable(path string) error { return nil } +// secureWrapperFile replaces the inherited directory ACL with a protected +// DACL. The service writes the wrapper before this call; afterward only +// LocalSystem and the selected effective token SID can read it. This is done +// after token selection so active-user commands do not depend on inherited +// broad ProgramData permissions. +func secureWrapperFile(path, effectiveSID string) error { + systemSID, err := winapi.StringToSid(securitySystemRID) + if err != nil { + return err + } + effective := systemSID + if effectiveSID != "" && effectiveSID != securitySystemRID { + effective, err = winapi.StringToSid(effectiveSID) + if err != nil { + return err + } + } + entries := []winapi.EXPLICIT_ACCESS{{ + AccessPermissions: winapi.GENERIC_ALL, + AccessMode: winapi.SET_ACCESS, + Trustee: winapi.TRUSTEE{ + TrusteeForm: winapi.TRUSTEE_IS_SID, + TrusteeType: winapi.TRUSTEE_IS_WELL_KNOWN_GROUP, + TrusteeValue: winapi.TrusteeValueFromSID(systemSID), + }, + }} + if effective != systemSID { + entries = append(entries, winapi.EXPLICIT_ACCESS{ + AccessPermissions: winapi.GENERIC_READ, + AccessMode: winapi.SET_ACCESS, + Trustee: winapi.TRUSTEE{ + TrusteeForm: winapi.TRUSTEE_IS_SID, + TrusteeType: winapi.TRUSTEE_IS_USER, + TrusteeValue: winapi.TrusteeValueFromSID(effective), + }, + }) + } + acl, err := winapi.ACLFromEntries(entries, nil) + if err != nil { + return err + } + return winapi.SetNamedSecurityInfo(path, winapi.SE_FILE_OBJECT, winapi.OWNER_SECURITY_INFORMATION|winapi.DACL_SECURITY_INFORMATION|winapi.PROTECTED_DACL_SECURITY_INFORMATION, systemSID, nil, acl, nil) +} + func createStandardPipes() (*os.File, *os.File, *os.File, *os.File, *os.File, *os.File, error) { security := &winapi.SecurityAttributes{Length: uint32(unsafe.Sizeof(winapi.SecurityAttributes{})), InheritHandle: 1} var stdinReadHandle, stdinWriteHandle winapi.Handle @@ -559,7 +606,7 @@ func openTokenForAttempt(contextName ExecutionContext, candidate *SessionCandida if err != nil { return 0, supervisor.EffectiveIdentity{}, err } - return token, supervisor.EffectiveIdentity{Context: string(ContextLocalService), Elevated: false, Integrity: "medium"}, nil + return token, supervisor.EffectiveIdentity{Context: string(ContextLocalService), UserSID: securityLocalServiceRID, Elevated: false, Integrity: "medium"}, nil case ContextLocalSystem: return duplicateServiceToken() default: