From 772324247ab6f1ac459e29f4fc11fe7099404723 Mon Sep 17 00:00:00 2001 From: brent saner Date: Tue, 10 Jun 2025 11:32:07 -0400 Subject: [PATCH] v1.13.1 ADDED: * ispriv, which returns some information useful for determining if running with extra permissions, in sudo, etc. --- go.mod | 17 +- go.sum | 40 +++- ispriv/consts_nix.go | 14 ++ ispriv/doc_nix.go | 7 + ispriv/doc_windows.go | 7 + ispriv/funcs_nix.go | 68 ++++++ ispriv/funcs_procids_nix.go | 426 ++++++++++++++++++++++++++++++++++++ ispriv/funcs_windows.go | 60 +++++ ispriv/types_nix.go | 19 ++ 9 files changed, 649 insertions(+), 9 deletions(-) create mode 100644 ispriv/consts_nix.go create mode 100644 ispriv/doc_nix.go create mode 100644 ispriv/doc_windows.go create mode 100644 ispriv/funcs_nix.go create mode 100644 ispriv/funcs_procids_nix.go create mode 100644 ispriv/funcs_windows.go create mode 100644 ispriv/types_nix.go diff --git a/go.mod b/go.mod index 2752c9f..b991e19 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,19 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/djherbis/times v1.6.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - golang.org/x/sync v0.9.0 - golang.org/x/sys v0.26.0 + github.com/shirou/gopsutil/v4 v4.25.5 + golang.org/x/sync v0.15.0 + golang.org/x/sys v0.33.0 honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 - r00t2.io/goutils v1.7.1 + r00t2.io/goutils v1.8.1 +) + +require ( + github.com/ebitengine/purego v0.8.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect ) diff --git a/go.sum b/go.sum index 7e771f3..9a41992 100644 --- a/go.sum +++ b/go.sum @@ -3,17 +3,45 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc= +github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E= honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE= -r00t2.io/goutils v1.7.1 h1:Yzl9rxX1sR9WT0FcjK60qqOgBoFBOGHYKZVtReVLoQc= -r00t2.io/goutils v1.7.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk= +r00t2.io/goutils v1.8.1 h1:TQcUycPKsYn0QI4uCqb56utmvu/vVSxlblBg98iXStg= +r00t2.io/goutils v1.8.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk= r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o= diff --git a/ispriv/consts_nix.go b/ispriv/consts_nix.go new file mode 100644 index 0000000..703fa69 --- /dev/null +++ b/ispriv/consts_nix.go @@ -0,0 +1,14 @@ +//go:build unix + +package ispriv + +const ( + sudoEnvPfx string = "SUDO_" + sudoUidEnv string = sudoEnvPfx + "UID" + sudoGidEnv string = sudoEnvPfx + "GID" + sudoUnameEnv string = sudoEnvPfx + "USER" +) + +const ( + curLoginUidFile string = "/proc/self/loginuid" +) diff --git a/ispriv/doc_nix.go b/ispriv/doc_nix.go new file mode 100644 index 0000000..020ab56 --- /dev/null +++ b/ispriv/doc_nix.go @@ -0,0 +1,7 @@ +//go:build unix + +/* +ispriv provides functions and a method to determine if a process is being run SUID/SGID, under sudo, etc. +*/ + +package ispriv diff --git a/ispriv/doc_windows.go b/ispriv/doc_windows.go new file mode 100644 index 0000000..2353c74 --- /dev/null +++ b/ispriv/doc_windows.go @@ -0,0 +1,7 @@ +//go:build windows + +/* +ispriv provides functions on Windows to determine the currentl privilege status. +*/ + +package ispriv diff --git a/ispriv/funcs_nix.go b/ispriv/funcs_nix.go new file mode 100644 index 0000000..e470374 --- /dev/null +++ b/ispriv/funcs_nix.go @@ -0,0 +1,68 @@ +//go:build unix + +package ispriv + +import ( + `os` + + `github.com/shirou/gopsutil/v4/process` +) + +/* + GetProcIDs returns a ProcIDs from a given PID. An error will be raised if the process ID doesn't exist. + A negative value indicates "self" (see also GetProcIDsSelf). + + Note that if you are not EUID == 0 (root) or you/the sudo target user does not own the process, + the returning ProcIDs is HIGHLY LIKELY to be very inaccurate. +*/ +func GetProcIDs(pid int32) (p *ProcIDs, err error) { + + var proc ProcIDs + var ids []uint32 + + if pid < 0 { + pid = int32(os.Getpid()) + } + + if proc.proc, err = process.NewProcess(pid); err != nil { + return + } + if ids, err = proc.proc.Gids(); err != nil { + return + } + p.gids = &IdInfo{ + real: uint(ids[0]), + effective: uint(ids[1]), + savedSet: uint(ids[2]), + filesystem: nil, + } + if len(ids) == 4 { + p.gids.filesystem = new(uint) + *p.gids.filesystem = uint(ids[3]) + } + if ids, err = proc.proc.Uids(); err != nil { + return + } + p.uids = &IdInfo{ + real: uint(ids[0]), + effective: uint(ids[1]), + savedSet: uint(ids[2]), + filesystem: nil, + } + if len(ids) == 4 { + p.uids.filesystem = new(uint) + *p.uids.filesystem = uint(ids[3]) + } + + p = &proc + + return +} + +// GetProcIDsSelf returns a ProcIDs from the current process' PID. +func GetProcIDsSelf() (p *ProcIDs, err error) { + + p, err = GetProcIDs(int32(os.Getpid())) + + return +} diff --git a/ispriv/funcs_procids_nix.go b/ispriv/funcs_procids_nix.go new file mode 100644 index 0000000..cbe5cf0 --- /dev/null +++ b/ispriv/funcs_procids_nix.go @@ -0,0 +1,426 @@ +//go:build unix + +package ispriv + +import ( + `errors` + `os` + `os/user` + `strconv` + `strings` + + `github.com/shirou/gopsutil/v4/process` + `golang.org/x/sys/unix` + `r00t2.io/sysutils/envs` + `r00t2.io/sysutils/paths` +) + +// GetEffective returns the EUID/EGID. +func (p *ProcIDs) GetEffective() (euid, egid uint) { + + euid = p.uids.effective + egid = p.gids.effective + + return +} + +// GetFS returns the FSUID/FSGID. Not all platforms have this, in which case they'll be nil. +func (p *ProcIDs) GetFS() (fsuid, fsgid *uint) { + + if p.uids.filesystem != nil { + fsuid = new(uint) + *fsuid = *p.uids.filesystem + } + if p.gids.filesystem != nil { + fsgid = new(uint) + *fsgid = *p.gids.filesystem + } + + return +} + +/* + GetGids returms a set of a ProcIDs GIDs. + fs will be nil if unsupported on the platform. + If invoked with SGID, "savedSet" will be the SGID GID. +*/ +func (p *ProcIDs) GetGids() (real, effective, savedSet uint, fs *uint) { + + real = p.gids.real + effective = p.gids.effective + savedSet = p.gids.savedSet + if p.gids.filesystem != nil { + fs = new(uint) + *fs = *p.gids.filesystem + } + + return +} + +// GetReal returns the (R)UID/(R)GID. +func (p *ProcIDs) GetReal() (ruid, rgid uint) { + + ruid = p.uids.real + rgid = p.gids.real + + return +} + +// GetSaved returns the SUID/SGID. +func (p *ProcIDs) GetSaved() (suid, sgid uint) { + + suid = p.uids.savedSet + sgid = p.gids.savedSet + + return +} + +/* + GetUids returms a set of a ProcIDs UIDs. + fs will be nil if unsupported on the platform. + If invoked with SUID, "savedSet" will be the SUID UID. +*/ +func (p *ProcIDs) GetUids() (real, effective, savedSet uint, fs *uint) { + + real = p.uids.real + effective = p.uids.effective + savedSet = p.uids.savedSet + if p.uids.filesystem != nil { + fs = new(uint) + *fs = *p.uids.filesystem + } + + return +} + +/* + IsSGID returns true if the process is Set GID/SGID. + + Note that it will return false if invoked by a group with the same GID as an SGID that's set. +*/ +func (p *ProcIDs) IsSGID() (isSgid bool) { + + isSgid = p.gids.real != p.gids.savedSet + + return +} + +/* + IsSUID returns true if the process is Set UID/SUID. + + Note that it will return false if invoked by a user with the same UID as an SUID that's set. +*/ +func (p *ProcIDs) IsSUID() (isSuid bool) { + + isSuid = p.uids.real != p.uids.savedSet + + return +} + +/* + IsSudo does a very fast (and potentially inaccurate) evaluation of whether the process is running under sudo. + + DO NOT use this function for security-sensitive uses, fully accurate results, or critical implementations! + Use IsSudoWithConfidence instead for those cases. + IsSudo only does the most basic of checking, which can be easily and completely overridden by a non-privileged user. +*/ +func (p *ProcIDs) IsSudo() (isSudo bool) { + + // This is how every other Joe Blow does this. It's an extremely dumb way to do it. The caller has been warned. + for k, _ := range envs.GetEnvMap() { + if strings.HasPrefix(k, sudoEnvPfx) { + isSudo = true + return + } + } + + return +} + +/* + IsSudoDetailed returns true for a very fast evaluation of whether the process is running under sudo, + and information about that context. + (If isSudo is false, originalUid/originalGid will both be -1 and originalUser will be nil.) + + DO NOT use this function for security-sensitive uses, fully accurate results, or critical implementations! + Use IsSudoWithConfidenceDetailed instead for those cases. + IsSudoDetailed only does the most basic of checking, which can be easily and completely overridden by a non-privileged user. +*/ +func (p *ProcIDs) IsSudoDetailed() (isSudo bool, originalUid, originalGid int, originalUser *user.User, err error) { + + if originalUid, originalGid, originalUser, err = p.getSudoInfoEnv(); err != nil { + return + } + + if originalUid >= 0 || originalGid >= 0 || originalUser != nil { + isSudo = true + } + + return +} + +/* + IsSudoWithConfidence is like IsSudo, but is *much* more throrough. + + It not only returns isSudo, which is true if *any* indicators pass, + but also: + + * a confidence value (which indicates *how many* indicators *passed*) + * a maxConfidence value (which indicates how many indicators were *tested*) + * a score value (which is a float indicating overall confidence on a fixed and weighted scale; higher is more confident, 1.0 indicates 100% confidence) +*/ +func (p *ProcIDs) IsSudoWithConfidence() (isSudo bool, confidence, maxConfidence uint, score float64, err error) { + + // confidence/maxConfidence are not used directly; they're unweighted counters. + var scoreConf uint + var scoreMaxConf uint + + score = float64(scoreConf) / float64(scoreMaxConf) + + return +} + +/* + IsSudoWithConfidenceDetailed is like IsSudoDetailed, but is *much* more throrough. + + It not only returns the same results as IsSudoDetailed, but includes the same scoring values/system as IsSudoWithConfidence. +*/ +func (p *ProcIDs) IsSudoWithConfidenceDetailed() (isSudo bool, confidence, maxConfidence uint, score float64, originalUid, originalGid int, originalUser *user.User, err error) { + + var b []byte + var ok bool + var permErr bool + var envUid int + var envGid int + var scoreConf uint + var scoreMaxConf uint + var curUser *user.User + var envUser *user.User + var curUid uint64 + var fstat unix.Stat_t + var fsUid int + var procFiles []process.OpenFilesStat + var loginUidFile string = curLoginUidFile + + if curUser, err = user.Current(); err != nil { + return + } + if curUid, err = strconv.ParseUint(curUser.Uid, 10, 32); err != nil { + return + } + + if procFiles, err = p.proc.OpenFiles(); err != nil { + return + } + + // Env vars; only score 1x/each. + maxConfidence += 3 + scoreMaxConf += 3 + if envUid, envGid, envUser, err = p.getSudoInfoEnv(); err != nil { + return + } + originalUid, originalGid, originalUser = envUid, envGid, envUser + if envUid >= 0 { + confidence++ + scoreConf++ + } + if envGid >= 0 { + confidence++ + scoreConf++ + } + if envUser != nil { + confidence++ + scoreConf++ + } + + /* + TTY/PTY ownership. We (can) only check this if we're running in an interactive session. + + Typically this is done via (golang.org/x/term).IsTerminal(), + That pulls in a bunch of stuff I don't need, though, so I'll just replicate (...).IsTerminal() here; + it's just a wrapped single function call. + */ + // procFiles[0] is always STDIN. Whether it's a pipe, or TTY/PTY, or file, etc. + // (likewise, procFiles[1] is always STDOUT, procFiles[2] is always STDERR); however... + if _, err = unix.IoctlGetTermios(int(procFiles[0].Fd), unix.TCGETS); err == nil { + // Interactive + maxConfidence++ + // This is only worth 2. It's pretty hard to fake unless origin user is root, + // but it's ALSO usually set to the target user. + scoreMaxConf += 2 + fstat = unix.Stat_t{} + if err = unix.Fstat(int(procFiles[0].Fd), &fstat); err != nil { + return + } + if uint64(fstat.Uid) != curUid { + // This is a... *potential* indicator, if a lateral sudo was done (user1 => user2), + // or root used sudo to *drop* privs to a regular user. + // We mark it as a pass for confidence since it IS a terminal, and it's permission-related. + confidence++ + scoreConf += 2 + originalUid = int(fstat.Uid) + } + } else { + // err is OK; just means non-interactive. No counter or score/max score increase; basically a NO-OP. + err = nil + } + + // /proc/self/loginuid + // This is a REALLY good indicator. Probably the strongest next to reverse-walking the proc tree. It depends on PAM and auditd support, I think, + // BUT if it's present it's *really* really strong. + if ok, err = paths.RealPathExists(&loginUidFile); err != nil { + return + } + if ok { + maxConfidence++ + scoreMaxConf += 5 + if b, err = os.ReadFile(loginUidFile); err != nil { + return + } + if fsUid, err = strconv.Atoi(strings.TrimSpace(string(b))); err != nil { + return + } + if uint64(fsUid) != curUid { + confidence++ + scoreConf += 5 + originalUid = fsUid + } + } + + // proc tree reverse walking. + // This is, by far, the most reliable method. + // There are some valid conditions in which this would fail due to permissions + // (e.g. lateral sudo: user1 => user2), but if it's a permission error it's *probably* + // a lateral move anyways. + if isSudo, permErr, originalUid, originalGid, originalUser, err = p.revProcWalk(); err != nil { + return + } + maxConfidence++ + scoreMaxConf += 10 + if permErr { + confidence++ + scoreConf += 5 + } else if isSudo { + confidence++ + scoreConf += 10 + } + + score = float64(scoreConf) / float64(scoreMaxConf) + + return +} + +/* + getSudoInfoEnv returns env var driven sudo information. + + These are in no way guaranteed to be accurate as the user can remove or override them. +*/ +func (p *ProcIDs) getSudoInfoEnv() (uid, gid int, u *user.User, err error) { + + var ok bool + var val string + var envMap map[string]string = envs.GetEnvMap() + + uid = -1 + gid = -1 + + if val, ok = envMap[sudoUnameEnv]; ok { + if u, err = user.Lookup(val); err != nil { + return + } + } + if val, ok = envMap[sudoUidEnv]; ok { + if uid, err = strconv.Atoi(val); err != nil { + return + } + } + if val, ok = envMap[sudoGidEnv]; ok { + if gid, err = strconv.Atoi(val); err != nil { + return + } + } + + return +} + +/* + revProcWalk walks up the process tree ("proctree") until it either: + + * finds a process invoked with sudo (true) + * hits PID == 1 (false) + * hits a permission error (true-ish) +*/ +func (p *ProcIDs) revProcWalk() (sudoFound, isPermErr bool, origUid, origGid int, origUser *user.User, err error) { + + var cmd []string + var parent *ProcIDs + var parentPid int32 + var parentUname string + var parentUids []uint32 + var parentGids []uint32 + + origUid = -1 + origGid = -1 + + parent = p + for { + if parent == nil || parent.proc.Pid == 1 { + break + } + if cmd, err = parent.proc.CmdlineSlice(); err != nil { + if errors.Is(err, os.ErrPermission) { + isPermErr = true + err = nil + } + return + } + if cmd[0] == "sudo" { + sudoFound = true + if parentUname, err = parent.proc.Username(); err != nil { + if errors.Is(err, os.ErrPermission) { + isPermErr = true + err = nil + } + return + } + if parentUids, err = parent.proc.Uids(); err != nil { + if errors.Is(err, os.ErrPermission) { + isPermErr = true + err = nil + } + return + } + if parentGids, err = parent.proc.Gids(); err != nil { + if errors.Is(err, os.ErrPermission) { + isPermErr = true + err = nil + } + return + } + if origUser, err = user.Lookup(parentUname); err != nil { + return + } + origUid = int(parentUids[0]) + origGid = int(parentGids[0]) + } + if sudoFound { + break + } + if parentPid, err = parent.proc.Ppid(); err != nil { + if errors.Is(err, os.ErrPermission) { + isPermErr = true + err = nil + } + return + } + if parent, err = GetProcIDs(parentPid); err != nil { + if errors.Is(err, os.ErrPermission) { + isPermErr = true + err = nil + } + return + } + } + + return +} diff --git a/ispriv/funcs_windows.go b/ispriv/funcs_windows.go new file mode 100644 index 0000000..7eb44c4 --- /dev/null +++ b/ispriv/funcs_windows.go @@ -0,0 +1,60 @@ +//go:build windows + +package ispriv + +import ( + `golang.org/x/sys/windows` +) + +// IsAdmin returns true if currently running with Administrator privileges. +func IsAdmin() (admin bool, err error) { + + var sid *windows.SID + var tok windows.Token + + if err = windows.AllocateAndInitializeSid( + &windows.SECURITY_NT_AUTHORITY, // identAuth + 2, // subAuth + windows.SECURITY_BUILTIN_DOMAIN_RID, // subAuth0 + windows.DOMAIN_ALIAS_RID_ADMINS, // subAuth1 + 0, 0, 0, 0, 0, 0, // subAuth2-10 + &sid, // sid + ); err != nil { + return + } + defer windows.FreeSid(sid) + + tok = windows.Token(0) + if admin, err = tok.IsMember(sid); err != nil { + return + } + + return +} + +// IsElevated returns true if running in an elevated ("Run as Administrator") context. +func IsElevated() (elevated bool) { + + var tok windows.Token = windows.Token(0) + + elevated = tok.IsElevated() + + return +} + +/* + IsPrivileged indicates that the current security context is running both + with Administrator priviliges AND is elevated. +*/ +func IsPrivileged() (privileged bool, err error) { + + if privileged, err = IsAdmin(); err != nil { + return + } + + if privileged { + privileged = IsElevated() + } + + return +} diff --git a/ispriv/types_nix.go b/ispriv/types_nix.go new file mode 100644 index 0000000..6518a67 --- /dev/null +++ b/ispriv/types_nix.go @@ -0,0 +1,19 @@ +//go:build unix + +package ispriv + +import ( + `github.com/shirou/gopsutil/v4/process` +) + +type ProcIDs struct { + proc *process.Process + uids *IdInfo + gids *IdInfo +} +type IdInfo struct { + real uint + effective uint + savedSet uint + filesystem *uint +}