-
-
Notifications
You must be signed in to change notification settings - Fork 441
core: fix security and concurrency issues found in a backend audit #2805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ package greeter | |
| import ( | ||
| "fmt" | ||
| "os" | ||
| "os/exec" | ||
| "path/filepath" | ||
| "strings" | ||
| "syscall" | ||
|
|
@@ -93,18 +94,105 @@ func resolveSessionExecInDirs(sessionID string, dirs []string) (string, error) { | |
| return "", fmt.Errorf("session desktop file %q was not found", id) | ||
| } | ||
|
|
||
| // tokenizeExecLine splits a Desktop Entry Exec= value into argv, honoring | ||
| // basic single/double quoting and backslash escapes, but never invokes a | ||
| // shell. Session .desktop files can live in a user-writable directory | ||
| // (~/.local/share/wayland-sessions), so shell metacharacters in Exec= | ||
| // must never reach an actual shell -- they're just literal argv text here. | ||
| func tokenizeExecLine(execLine string) ([]string, error) { | ||
| var tokens []string | ||
| var cur strings.Builder | ||
| hasCur := false | ||
| runes := []rune(execLine) | ||
| i := 0 | ||
| for i < len(runes) { | ||
| c := runes[i] | ||
| switch { | ||
| case c == ' ' || c == '\t': | ||
| if hasCur { | ||
| tokens = append(tokens, cur.String()) | ||
| cur.Reset() | ||
| hasCur = false | ||
| } | ||
| i++ | ||
| case c == '\'': | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe XDG spec dictates single quotes shouldnt be treated as a quote, but a reserved character |
||
| hasCur = true | ||
| i++ | ||
| for i < len(runes) && runes[i] != '\'' { | ||
| cur.WriteRune(runes[i]) | ||
| i++ | ||
| } | ||
| if i >= len(runes) { | ||
| return nil, fmt.Errorf("unterminated single quote") | ||
| } | ||
| i++ | ||
| case c == '"': | ||
| hasCur = true | ||
| i++ | ||
| for i < len(runes) && runes[i] != '"' { | ||
| if runes[i] == '\\' && i+1 < len(runes) { | ||
| if next := runes[i+1]; next == '"' || next == '\\' || next == '$' || next == '`' { | ||
| cur.WriteRune(next) | ||
| i += 2 | ||
| continue | ||
| } | ||
| } | ||
| cur.WriteRune(runes[i]) | ||
| i++ | ||
| } | ||
| if i >= len(runes) { | ||
| return nil, fmt.Errorf("unterminated double quote") | ||
| } | ||
| i++ | ||
| case c == '\\' && i+1 < len(runes): | ||
| hasCur = true | ||
| cur.WriteRune(runes[i+1]) | ||
| i += 2 | ||
| default: | ||
| hasCur = true | ||
| cur.WriteRune(c) | ||
| i++ | ||
| } | ||
| } | ||
| if hasCur { | ||
| tokens = append(tokens, cur.String()) | ||
| } | ||
| return tokens, nil | ||
| } | ||
|
|
||
| func LaunchSessionByID(sessionID string) error { | ||
| execLine, err := ResolveSessionExec(sessionID) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| execLine = strings.TrimSpace(stripDesktopExecCodes(execLine)) | ||
| execLine = strings.TrimSpace(execLine) | ||
| if execLine == "" { | ||
| return fmt.Errorf("session %q has an empty Exec command", sessionID) | ||
| } | ||
|
|
||
| tokens, err := tokenizeExecLine(execLine) | ||
| if err != nil { | ||
| return fmt.Errorf("session %q has an invalid Exec command: %w", sessionID, err) | ||
| } | ||
|
|
||
| argv := make([]string, 0, len(tokens)) | ||
| for _, tok := range tokens { | ||
| if strings.HasPrefix(tok, "%") { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would maybe reference quickshell code DeskopEntries for all of this stuff, to ensure its following the XDG specification |
||
| continue // field codes (%f/%U/etc.) -- sessions take no file args | ||
| } | ||
| argv = append(argv, tok) | ||
| } | ||
| if len(argv) == 0 { | ||
| return fmt.Errorf("session %q has an empty Exec command", sessionID) | ||
| } | ||
|
|
||
| resolved, err := exec.LookPath(argv[0]) | ||
| if err != nil { | ||
| return fmt.Errorf("session %q command %q not found: %w", sessionID, argv[0], err) | ||
| } | ||
|
|
||
| env := append(os.Environ(), "XDG_SESSION_TYPE=wayland") | ||
| return syscall.Exec("/bin/sh", []string{"sh", "-c", "exec " + execLine}, env) | ||
| return syscall.Exec(resolved, argv, env) | ||
| } | ||
|
|
||
| func LaunchSessionFromMemory(cacheDir, homeDir string) error { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a security fix since the user is already authenticated at this point, but maybe a spec correctness change