init ee/currentprocess, centralize current process query APIs, fix unprivileged desktop runner - #2804
init ee/currentprocess, centralize current process query APIs, fix unprivileged desktop runner#2804brhoades wants to merge 7 commits into
Conversation
ccd4df0 to
c9254dc
Compare
| // Querying console users on Windows requires privilege, other platforms | ||
| // use session information to establish process configuration even when ran | ||
| // unprivileged | ||
| if runtime.GOOS == "windows" && !r.elevated { | ||
| consoleUsers = []string{r.currentUid} |
There was a problem hiding this comment.
I don't love this, but an alternative distracts from my changeset: extract the loop's body below and call it for !r.elevated on all platforms. I can do it if preferred.
There was a problem hiding this comment.
I am fine with this -- since the launcher parent process being unprivileged is a new case (i.e. we always currently expect the parent process to be privileged), we can define whatever behavior we want here.
There was a problem hiding this comment.
The root check was harmful in some cases. When ran as a systemd user unit (which seems plausible) or tmux (which is probably just me), the process won't have DISPLAY.
Also worth noting that userEnvVars below worked fine unprivileged on my box.
c9254dc to
f92c99f
Compare
| if !windows.GetCurrentProcessToken().IsElevated() { | ||
| if elevated, err := currentprocess.IsElevated(); err != nil { | ||
| // historical behavior treats failure to check as not elevated | ||
| wc.slogger.Log(context.TODO(), slog.LevelError, "failed to check if process is elevated", "err", err) | ||
| return false |
There was a problem hiding this comment.
The historical behavior here is from IsElevated() which treats a failure to get token info as unprivileged:
// IsElevated returns whether the current token is elevated from a UAC perspective.
func (token Token) IsElevated() bool {
var isElevated uint32
var outLen uint32
err := GetTokenInformation(token, TokenElevation, (*byte)(unsafe.Pointer(&isElevated)), uint32(unsafe.Sizeof(isElevated)), &outLen)
if err != nil {
return false
}
return outLen == uint32(unsafe.Sizeof(isElevated)) && isElevated != 0
}If written today, I'd rather we fail to configure watchdog when unprivileged than not configure watchdog when privileged and a Windows API misbehaves.
Any opinions?
edd16a0 to
9e8bfeb
Compare
Signed-off-by: Billy J Rhoades II <billy.rhoades@agilebits.com>
…alculation Signed-off-by: Billy J Rhoades II <billy.rhoades@agilebits.com>
Signed-off-by: Billy J Rhoades II <billy.rhoades@agilebits.com>
9e8bfeb to
6b48b14
Compare
A slight refactor lets us bail earlier and runAs only in specific cases. Signed-off-by: Billy J Rhoades II <billy.rhoades@agilebits.com>
Signed-off-by: Billy J Rhoades II <billy.rhoades@agilebits.com>
Signed-off-by: Billy J Rhoades II <billy.rhoades@agilebits.com>
It should be fine -- you should be able to test by generating a flare and confirming k2 consumes it and displays it appropriately on the flare page.
Yes, this is a good spot for it. In general, new code lives in |
RebeccaMahany
left a comment
There was a problem hiding this comment.
LGTM, one small documentation request
| // Querying console users on Windows requires privilege, other platforms | ||
| // use session information to establish process configuration even when ran | ||
| // unprivileged | ||
| if runtime.GOOS == "windows" && !r.elevated { | ||
| consoleUsers = []string{r.currentUid} |
There was a problem hiding this comment.
I am fine with this -- since the launcher parent process being unprivileged is a new case (i.e. we always currently expect the parent process to be privileged), we can define whatever behavior we want here.
| if !windows.GetCurrentProcessToken().IsElevated() { | ||
| if elevated, err := currentprocess.IsElevated(); err != nil { | ||
| // historical behavior treats failure to check as not elevated | ||
| wc.slogger.Log(context.TODO(), slog.LevelError, "failed to check if process is elevated", "err", err) | ||
| return false |
Why are you reading this:
I promised in #2800 to port and generalize process
IsElevatedlogic. It's done. I used it to fix unprivileged Windows desktop runner detection and found a few weird bits to tweak.Summary:
ee/currentprocessand which has cross-platformIsElevated() (bool, error)andUid()flare,doctor, windows syslogger, desktop runner, and launcher options parsing.I've left comments on the review for areas I'm least happy about or context.
Fixes:
user.CurrentUser()are cached, so were permanently fatal to the runner.errors.Is(err, NoExplorerProcessError{})was always false.Known remaining bugs:
Testing
I ran the general launcher privileged and unprivileged on all three platforms. It detected permissions when it had them or when it didn't. Logs available, but I didn't attach them since I'm worried they may have credentials in them.
On Windows, that led me to discover that unprivileged users can't query console users at all which was fatal:
json failure log
```json { "time": "2026-08-26T20:36:33.257766Z", "level": "ERROR", "source": { "function": "github.com/kolide/launcher/v2/ee/desktop/runner.(*DesktopUsersProcessesRunner).Execute", "file": "/home/billy/work/launcher/ee/desktop/runner/runner.go", "line": 271 }, "msg": "could not run console user desktop process", "component": "desktop_runner", "err": "getting console users: getting logon session data for LUID: Access is denied." } ```Also worth noting that unprivileged users have a complicated set of requirements to spawn a process to their own user's graphical session:
json failure log
```json { "time": "2026-09-11T15:47:16.7741552Z", "level": "ERROR", "source": { "function": "github.com/kolide/launcher/v2/ee/desktop/runner.(*DesktopUsersProcessesRunner).Execute", "fil e": "/home/billy/work/launcher/ee/desktop/runner/runner.go", "line": 300 }, "msg": "could not run console user desktop process", "component": "desktop_runner", "err": "spawning new desktop user process for DESKTOP-1P62ADB\\billy: running desktop command as user: fork/exec C:\\Users\\billy\\kolide\\launcher-edd16a0c-dirty-change-token-bind.exe: A required privilege is not held by the client." } ```Questions
I assumed we can add flare fields without any changes in k2. I also figured it'd be clearer to add an error key than to push it all into the old field. Is that right?Isee/currentprocessthe right folder/shape for a common API here? I didn't see clear distinction between pkg and ee.