diff --git a/internal/client/supervisor/windows/native_windows.go b/internal/client/supervisor/windows/native_windows.go index 9f61a04..f9b978c 100644 --- a/internal/client/supervisor/windows/native_windows.go +++ b/internal/client/supervisor/windows/native_windows.go @@ -25,10 +25,13 @@ import ( ) const ( - logon32LogonService = 5 - logon32ProviderDefault = 0 - securitySystemRID = "S-1-5-18" - disableMaxPrivilege = 0x1 + logon32LogonService = 5 + logon32ProviderDefault = 0 + securitySystemRID = "S-1-5-18" + securityLocalServiceRID = "S-1-5-19" + disableMaxPrivilege = 0x1 + mediumIntegrityRID = 0x2000 + highIntegrityRID = 0x3000 ) var ( @@ -596,6 +599,10 @@ func createRestrictedMediumToken(source winapi.Token) (winapi.Token, error) { _ = restricted.Close() return 0, errors.New("restricted active-user token remained elevated") } + if err := verifyTokenIntegrity(restricted, mediumIntegrityRID); err != nil { + _ = restricted.Close() + return 0, err + } return restricted, nil } @@ -615,27 +622,24 @@ func setMediumIntegrity(token winapi.Token) error { } func verifyUserToken(token winapi.Token, candidate *SessionCandidate, normal bool) error { - user, err := token.GetTokenUser() - if err != nil || user.User.Sid == nil { - if err != nil { - return err - } - return errors.New("active token has no user SID") - } - if user.User.Sid.String() != candidate.UserSID { - return errors.New("active token user SID changed during launch selection") - } - var sessionID uint32 - var returned uint32 - if err := winapi.GetTokenInformation(token, winapi.TokenSessionId, (*byte)(unsafe.Pointer(&sessionID)), uint32(unsafe.Sizeof(sessionID)), &returned); err != nil { - return err - } - if returned != uint32(unsafe.Sizeof(sessionID)) || sessionID != candidate.SessionID { - return errors.New("active token session changed during launch selection") + if err := verifyTokenIdentity(token, candidate.UserSID, candidate.SessionID); err != nil { + return fmt.Errorf("verify active token identity: %w", err) } if normal && token.IsElevated() { return errors.New("normal active-user token is elevated") } + if normal { + if err := verifyTokenIntegrity(token, mediumIntegrityRID); err != nil { + return fmt.Errorf("normal active-user token is not medium integrity: %w", err) + } + } else { + if !token.IsElevated() { + return errors.New("elevated active-user token is not elevated") + } + if err := verifyTokenIntegrity(token, highIntegrityRID); err != nil { + return fmt.Errorf("elevated active-user token is not high integrity: %w", err) + } + } return nil } @@ -678,15 +682,11 @@ func duplicateServiceTokenForSession(sessionID uint32) (winapi.Token, supervisor return 0, supervisor.EffectiveIdentity{}, errors.New("active SYSTEM token session did not read back as requested") } } - user, err := target.GetTokenUser() - if err != nil || user.User.Sid == nil || user.User.Sid.String() != securitySystemRID { + if err := verifyTokenIdentity(target, securitySystemRID, sessionID); err != nil { _ = target.Close() - if err != nil { - return 0, supervisor.EffectiveIdentity{}, err - } - return 0, supervisor.EffectiveIdentity{}, errors.New("duplicated service token is not LocalSystem") + return 0, supervisor.EffectiveIdentity{}, fmt.Errorf("duplicated service token is invalid: %w", err) } - return target, supervisor.EffectiveIdentity{Context: string(ContextLocalSystem), SessionID: sessionID, UserSID: user.User.Sid.String(), Elevated: true, Integrity: "system"}, nil + return target, supervisor.EffectiveIdentity{Context: string(ContextLocalSystem), SessionID: sessionID, UserSID: securitySystemRID, Elevated: true, Integrity: "system"}, nil } // enableTokenPrivilege enables one privilege only for the short operation @@ -733,9 +733,90 @@ func logonLocalService() (winapi.Token, error) { } return 0, syscall.GetLastError() } + if err := verifyTokenIdentity(token, securityLocalServiceRID, 0); err != nil { + _ = token.Close() + return 0, fmt.Errorf("LocalService token failed identity verification: %w", err) + } return token, nil } +func verifyTokenIdentity(token winapi.Token, expectedSID string, expectedSession uint32) error { + user, err := token.GetTokenUser() + if err != nil || user.User.Sid == nil { + if err != nil { + return err + } + return errors.New("token has no user SID") + } + if user.User.Sid.String() != expectedSID { + return fmt.Errorf("token user SID %q does not match %q", user.User.Sid.String(), expectedSID) + } + actualSession, err := tokenInformationUint32(token, winapi.TokenSessionId) + if err != nil { + return err + } + if actualSession != expectedSession { + return fmt.Errorf("token session %d does not match %d", actualSession, expectedSession) + } + tokenType, err := tokenInformationUint32(token, winapi.TokenType) + if err != nil { + return err + } + if tokenType != winapi.TokenPrimary { + return fmt.Errorf("token type %d is not primary", tokenType) + } + return nil +} + +func tokenInformationUint32(token winapi.Token, class uint32) (uint32, error) { + buffer, err := tokenInformation(token, class) + if err != nil { + return 0, err + } + if len(buffer) != int(unsafe.Sizeof(uint32(0))) { + return 0, errors.New("token information has unexpected size") + } + return *(*uint32)(unsafe.Pointer(&buffer[0])), nil +} + +func tokenInformation(token winapi.Token, class uint32) ([]byte, error) { + var returned uint32 + err := winapi.GetTokenInformation(token, class, nil, 0, &returned) + if returned == 0 && err != nil && !errors.Is(err, winapi.ERROR_INSUFFICIENT_BUFFER) { + return nil, err + } + if returned == 0 { + return nil, errors.New("token information returned an empty buffer") + } + buffer := make([]byte, returned) + if err := winapi.GetTokenInformation(token, class, &buffer[0], returned, &returned); err != nil { + return nil, err + } + if returned > uint32(len(buffer)) { + return nil, errors.New("token information length changed during query") + } + return buffer[:returned], nil +} + +func verifyTokenIntegrity(token winapi.Token, minimumRID uint32) error { + buffer, err := tokenInformation(token, winapi.TokenIntegrityLevel) + if err != nil { + return err + } + if len(buffer) < int(unsafe.Sizeof(winapi.Tokenmandatorylabel{})) { + return errors.New("token integrity information is truncated") + } + label := (*winapi.Tokenmandatorylabel)(unsafe.Pointer(&buffer[0])) + if label.Label.Sid == nil || label.Label.Sid.SubAuthorityCount() == 0 { + return errors.New("token integrity SID is missing") + } + level := label.Label.Sid.SubAuthority(uint32(label.Label.Sid.SubAuthorityCount()) - 1) + if level < minimumRID { + return fmt.Errorf("token integrity level 0x%x is below 0x%x", level, minimumRID) + } + return nil +} + func (manager *execSupervisor) Signal(ctx context.Context, process supervisor.Process, signal supervisor.SignalKind) (supervisor.SignalOutcome, error) { if process == nil { return supervisor.SignalOutcome{}, ErrProcessNotFound