Files
ctrld/metadata.go
Cuong Manh Le 40c68a13a1 fix(metadata): detect login user via logname when running under sudo
On Darwin 26.2+, sudo no longer preserves SUDO_USER, LOGNAME, and USER
(CVE-2025-43416), so env-based detection fails. Use the logname(1)
command on Unix first, then fall back to environment variables and
user.Current() so the real login user is still reported correctly.
2026-03-03 14:25:11 +07:00

94 lines
2.6 KiB
Go

package ctrld
import (
"context"
"os"
"os/exec"
"os/user"
"runtime"
"strings"
"github.com/cuonglm/osinfo"
"github.com/Control-D-Inc/ctrld/internal/system"
)
const (
metadataOsKey = "os"
metadataChassisTypeKey = "chassis_type"
metadataChassisVendorKey = "chassis_vendor"
metadataUsernameKey = "username"
metadataDomainOrWorkgroupKey = "domain_or_workgroup"
metadataDomainKey = "domain"
)
var (
chassisType string
chassisVendor string
)
// SystemMetadata collects system and user-related SystemMetadata and returns it as a map.
func SystemMetadata(ctx context.Context) map[string]string {
m := make(map[string]string)
oi := osinfo.New()
m[metadataOsKey] = oi.String()
if chassisType == "" && chassisVendor == "" {
if ci, err := system.GetChassisInfo(); err == nil {
chassisType, chassisVendor = ci.Type, ci.Vendor
}
}
m[metadataChassisTypeKey] = chassisType
m[metadataChassisVendorKey] = chassisVendor
m[metadataUsernameKey] = currentLoginUser(ctx)
m[metadataDomainOrWorkgroupKey] = partOfDomainOrWorkgroup(ctx)
domain, err := system.GetActiveDirectoryDomain()
if err != nil {
ProxyLogger.Load().Debug().Err(err).Msg("Failed to get active directory domain name")
}
m[metadataDomainKey] = domain
return m
}
// currentLoginUser attempts to find the actual login user, even if the process is running as root.
func currentLoginUser(ctx context.Context) string {
// On Darwin 26.2+, sudo no longer preserves SUDO_USER, LOGNAME, USER etc., so we cannot
// rely on environment variables when running under sudo. See CVE-2025-43416.
// We use the logname(1) command on Unix, which reports the login name from the session
// (e.g. utmp); there is no portable syscall equivalent in Go, so we exec logname.
if runtime.GOOS != "windows" {
if name := runLogname(ctx); name != "" {
return name
}
}
// Fallback: env vars (still set on older systems or when not using sudo)
if u := os.Getenv("SUDO_USER"); u != "" {
return u
}
if u := os.Getenv("LOGNAME"); u != "" {
return u
}
if u := os.Getenv("USER"); u != "" {
return u
}
currentUser, err := user.Current()
if err != nil {
ProxyLogger.Load().Debug().Err(err).Msg("Failed to get current user")
return "unknown"
}
return currentUser.Username
}
// runLogname runs the logname(1) command and returns the trimmed output, or "" on failure.
func runLogname(ctx context.Context) string {
cmd := exec.CommandContext(ctx, "logname")
out, err := cmd.Output()
if err != nil {
ProxyLogger.Load().Debug().Err(err).Msg("Failed to run logname")
return ""
}
return strings.TrimSpace(string(out))
}