Fail fast when no console session exists and keep the SAS value type

This commit is contained in:
Viktor Liu
2026-08-06 10:40:16 +02:00
parent 4984e8efef
commit 78bc6560b8
2 changed files with 24 additions and 3 deletions

View File

@@ -88,6 +88,11 @@ const (
// 1. Active session with a user logged in (RDP user in session ≥2)
// 2. Active session without a user (console at login screen)
// 3. Console session ID
//
// noActiveSession is what the WTS APIs report when no session is attached to
// the console, which is the state a headless or freshly booted machine sits in.
const noActiveSession = uint32(0xFFFFFFFF)
func getActiveSessionID() uint32 {
var sessionInfo uintptr
var count uint32
@@ -402,7 +407,7 @@ const (
)
func newSessionManager() *sessionManager {
m := &sessionManager{sessionID: ^uint32(0), done: make(chan struct{})}
m := &sessionManager{sessionID: noActiveSession, done: make(chan struct{})}
if h, err := createKillOnCloseJob(); err != nil {
log.Warnf("create job object for vnc-agent (orphan agents possible after crash): %v", err)
} else {
@@ -498,6 +503,16 @@ func (m *sessionManager) Resolve(ctx context.Context) (string, string, uint32, e
return socketPath, token, 0, nil
}
// With no session on the console there is nothing to wait for: the
// manager will not spawn an agent, so say so now rather than after the
// deadline, and say it with the error the caller turns into a message
// about nobody being logged in. Queried live instead of read from the
// manager, whose sessionID starts out as the same sentinel and would
// make this fire during the first tick after startup.
if getActiveSessionID() == noActiveSession {
return "", "", 0, errNoConsoleUser
}
select {
case <-ctx.Done():
return "", "", 0, ctx.Err()
@@ -622,7 +637,7 @@ func (m *sessionManager) scheduleNextSpawn(exitCode uint32, lifetime time.Durati
// window has elapsed. Returns false to permanently stop the manager when the
// service lacks the privileges needed to spawn cross-session.
func (m *sessionManager) maybeSpawnAgent(sid uint32) bool {
if m.agentProc != 0 || sid == 0xFFFFFFFF || !time.Now().After(m.nextSpawnAt) {
if m.agentProc != 0 || sid == noActiveSession || !time.Now().After(m.nextSpawnAt) {
return true
}

View File

@@ -112,8 +112,14 @@ func enableSoftwareSAS() {
defer sasStateMu.Unlock()
if !savedSASState.captured {
prev, _, err := key.GetIntegerValue("SoftwareSASGeneration")
prev, valType, err := key.GetIntegerValue("SoftwareSASGeneration")
switch {
case err == nil && valType != registry.DWORD:
// Restoring writes a DWORD, so anything else would come back with a
// different type than the administrator set, even when the number
// itself fits.
log.Warnf("SoftwareSASGeneration has registry type %d rather than DWORD, leaving it alone", valType)
return
case err == nil && prev > math.MaxUint32:
// A DWORD is what the policy takes, so a wider value is not ours to
// narrow and restore.