Attempt to make audio and speech discovery more robust.
This commit is contained in:
1 parent
a27f274099
commit
dc945ff392
21 files changed
+1314
-60
No files matched your search
@@ -11,6 +11,8 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -102,10 +104,6 @@ func spawn_arguments(ctx context.Context, endpoint serverAddress) ([]string, err
|
||||
}
|
||||
|
||||
func spawn_server(ctx context.Context, endpoint serverAddress) error {
|
||||
// Never create a root-owned server in the invoking user's runtime directory.
|
||||
if uid := os.Getenv("SUDO_UID"); uid != "" && uid != strconv.Itoa(os.Geteuid()) {
|
||||
return fmt.Errorf("start Speech Dispatcher as the invoking user before running Fenrir with sudo")
|
||||
}
|
||||
spawnContext, cancel := context.WithTimeout(ctx, commandTimeout)
|
||||
defer cancel()
|
||||
args, err := spawn_arguments(spawnContext, endpoint)
|
||||
@@ -117,6 +115,24 @@ func spawn_server(ctx context.Context, endpoint serverAddress) error {
|
||||
command = "speech-dispatcher"
|
||||
}
|
||||
process := exec.CommandContext(spawnContext, command, args...)
|
||||
if uid := os.Getenv("SUDO_UID"); uid != "" && uid != strconv.Itoa(os.Geteuid()) {
|
||||
// Drop credentials only in the child. Never run a root-owned daemon in
|
||||
// another user's session or change the reader's device privileges.
|
||||
if os.Geteuid() != 0 {
|
||||
return fmt.Errorf("cannot start Speech Dispatcher as the invoking user without root privileges")
|
||||
}
|
||||
account, err := user.LookupId(uid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("look up invoking user for Speech Dispatcher: %w", err)
|
||||
}
|
||||
groups, err := account.GroupIds()
|
||||
if err != nil {
|
||||
return fmt.Errorf("look up invoking user's groups: %w", err)
|
||||
}
|
||||
if err := configure_sudo_spawn(process, account, groups); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
process.WaitDelay = 100 * time.Millisecond
|
||||
output, err := process.CombinedOutput()
|
||||
if err != nil {
|
||||
@@ -124,3 +140,48 @@ func spawn_server(ctx context.Context, endpoint serverAddress) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func configure_sudo_spawn(process *exec.Cmd, account *user.User, groups []string) error {
|
||||
uid, uidErr := strconv.ParseUint(account.Uid, 10, 32)
|
||||
gid, gidErr := strconv.ParseUint(account.Gid, 10, 32)
|
||||
if uidErr != nil || gidErr != nil || uid == 0 || !filepath.IsAbs(account.HomeDir) {
|
||||
return fmt.Errorf("invalid invoking user for Speech Dispatcher")
|
||||
}
|
||||
credentials := &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
|
||||
for _, group := range groups {
|
||||
id, err := strconv.ParseUint(group, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid invoking user's group: %w", err)
|
||||
}
|
||||
credentials.Groups = append(credentials.Groups, uint32(id))
|
||||
}
|
||||
process.SysProcAttr = &syscall.SysProcAttr{Credential: credentials}
|
||||
process.Dir = account.HomeDir
|
||||
// Do not pass root's HOME, XDG paths, D-Bus address, or unrelated secrets
|
||||
// into a user-owned daemon. Preserve explicit audio choices and locale.
|
||||
process.Env = []string{"HOME=" + account.HomeDir, "USER=" + account.Username,
|
||||
"LOGNAME=" + account.Username, "PATH=/usr/local/bin:/usr/bin:/bin"}
|
||||
for _, entry := range os.Environ() {
|
||||
key, _, _ := strings.Cut(entry, "=")
|
||||
if key == "LANG" || strings.HasPrefix(key, "LC_") || key == "PULSE_SERVER" || key == "PULSE_COOKIE" {
|
||||
process.Env = append(process.Env, entry)
|
||||
}
|
||||
}
|
||||
runtimeDir := filepath.Join("/run/user", account.Uid)
|
||||
if candidate := os.Getenv("XDG_RUNTIME_DIR"); owned_directory(candidate, uint32(uid)) {
|
||||
runtimeDir = candidate
|
||||
}
|
||||
if owned_directory(runtimeDir, uint32(uid)) {
|
||||
process.Env = append(process.Env, "XDG_RUNTIME_DIR="+runtimeDir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func owned_directory(path string, uid uint32) bool {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil || !info.IsDir() {
|
||||
return false
|
||||
}
|
||||
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||
return ok && stat.Uid == uid
|
||||
}
|
||||
Reference in new issue
Block a user