From 7b0156775c5dced99be3c71e519c54f8476ca39e Mon Sep 17 00:00:00 2001 From: brent saner Date: Mon, 21 Apr 2025 02:28:19 -0400 Subject: [PATCH] v1.13.0 ADDED: * Convenience functions to determine if a process is running in an elevated/dropped privileges context --- TODO | 7 +- funcs_idstate.go | 163 +++++++++++++++++++++++++++++++++++++++++++++++ funcs_linux.go | 50 +++++++++++++++ paths/TODO | 1 + types_linux.go | 53 +++++++++++++++ 5 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 funcs_idstate.go create mode 100644 funcs_linux.go create mode 100644 paths/TODO create mode 100644 types_linux.go diff --git a/TODO b/TODO index 7130ae8..fe955b6 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,9 @@ +- refactor the elevation detection stuff. I'm not terribly happy with it. + - password generator utility/library +-- incorporate with r00t2.io/pwgen -- incorporate with https://github.com/tredoe/osutil ? --- cli flag to dump flat hashes too ---- https://github.com/hlandau/passlib --- incoprporated separately; https://git.r00t2.io/r00t2/PWGen (import r00t2.io/pwgen) +-- cli flag to dump flat hashes too (https://github.com/hlandau/passlib and others soon in pwgen) - auger needs to be build-constrained to linux. diff --git a/funcs_idstate.go b/funcs_idstate.go new file mode 100644 index 0000000..e40a313 --- /dev/null +++ b/funcs_idstate.go @@ -0,0 +1,163 @@ +package sysutils + +// Checked consolidates all the provided checked functions. +func (i *IDState) Checked() (checked bool) { + + if i == nil { + return + } + + checked = i.uidsChecked && + i.gidsChecked && + i.sudoChecked && + i.ppidUidChecked && + i.ppidGidChecked + + return +} + +/* +IsReal consolidates all the elevation/dropped-privs checks into a single method. + +It will only return true if no sudo was detected and *all* UIDs/GIDs match. +*/ +func (i *IDState) IsReal(real bool) { + + if i == nil { + return + } + + real = true + + for _, b := range []bool{ + i.IsSuid(), + i.IsSgid(), + i.IsSudoUser(), + i.IsSudoGroup(), + } { + if b { + real = false + return + } + } + + return +} + +/* +IsSudoGroup is true if any of the group sudo env vars are set, +or the parent process has a different group (and is not PID 1). + +It will always return false if SudoChecked returns false oor PPIDGIDsChecked returns false. +*/ +func (i *IDState) IsSudoGroup() (sudo bool) { + + if i == nil || !i.sudoChecked || !i.ppidGidChecked { + return + } + + sudo = i.SudoEnvGroup || !i.PPIDGidMatch + + return +} + +/* +IsSudoUser is true if any of the user sudo env vars are set, +or the parent process has a different owner (and is not PID 1). + +It will always return false if SudoChecked returns false or PPIDUIDsChecked returns false. +*/ +func (i *IDState) IsSudoUser() (sudo bool) { + + if i == nil || !i.sudoChecked || !i.ppidUidChecked { + return + } + + sudo = i.SudoEnvUser || !i.PPIDUidMatch + + return +} + +// IsSuid is true if the RUID does not match EUID or SUID. It will always return false if UIDsChecked returns false. +func (i *IDState) IsSuid() (suid bool) { + + if i == nil || !i.uidsChecked { + return + } + + suid = i.RUID != i.EUID || i.RUID != i.SUID + + return +} + +// IsSgid is true if the RGID does not match EGID or SGID. It will always return false if GIDsChecked returns false. +func (i *IDState) IsSgid() (sgid bool) { + + if i == nil || !i.gidsChecked { + return + } + + sgid = i.RGID != i.EGID || i.RGID != i.SGID + + return +} + +// GIDsChecked is true if the GIDs presented can be trusted. +func (i *IDState) GIDsChecked() (checked bool) { + + if i == nil { + return + } + + checked = i.gidsChecked + + return +} + +// PPIDGIDsChecked is true if PPIDGidMatch can be trusted. +func (i *IDState) PPIDGIDsChecked() (checked bool) { + + if i == nil { + return + } + + checked = i.ppidGidChecked + + return +} + +// PPIDUIDsChecked is true if PPIDUidMatch can be trusted. +func (i *IDState) PPIDUIDsChecked() (checked bool) { + + if i == nil { + return + } + + checked = i.ppidUidChecked + + return +} + +// SudoChecked is true if SudoEnvVars can be trusted +func (i *IDState) SudoChecked() (checked bool) { + + if i == nil { + return + } + + checked = i.sudoChecked + + return +} + +// UIDsChecked is true if the UIDs presented can be trusted. +func (i *IDState) UIDsChecked() (checked bool) { + + if i == nil { + return + } + + checked = i.uidsChecked + + return +} diff --git a/funcs_linux.go b/funcs_linux.go new file mode 100644 index 0000000..8de6194 --- /dev/null +++ b/funcs_linux.go @@ -0,0 +1,50 @@ +package sysutils + +import ( + `fmt` + `os` + + `golang.org/x/sys/unix` + `r00t2.io/sysutils/envs` +) + +// GetIDState returns current ID/elevation information. An IDState should *not* be explicitly created/defined. +func GetIDState() (ids IDState) { + + var err error + + ids.RUID, ids.EUID, ids.SUID = unix.Getresuid() + ids.uidsChecked = true + ids.RGID, ids.EGID, ids.SGID = unix.Getresgid() + ids.gidsChecked = true + + ids.SudoEnvCmd = envs.HasEnv("SUDO_COMMAND") + ids.SudoEnvHome = envs.HasEnv("SUDO_HOME") + ids.SudoEnvGroup = envs.HasEnv("SUDO_GID") + ids.SudoEnvUser = envs.HasEnv("SUDO_UID") || envs.HasEnv("SUDO_USER") + if ids.SudoEnvCmd || ids.SudoEnvHome || ids.SudoEnvGroup || ids.SudoEnvUser { + ids.SudoEnvVars = true + } + ids.sudoChecked = true + + // PID 1 will *always* be root, so that can return a false positive for sudo. + if os.Getppid() != 1 { + ids.stat = new(unix.Stat_t) + if err = unix.Stat( + fmt.Sprintf("/proc/%d/stat", os.Getppid()), + ids.stat, + ); err != nil { + err = nil + } else { + ids.PPIDUidMatch = ids.RUID == int(ids.stat.Uid) + ids.ppidUidChecked = true + ids.PPIDGidMatch = ids.RGID == int(ids.stat.Gid) + ids.ppidGidChecked = true + } + } else { + ids.ppidUidChecked = true + ids.ppidGidChecked = true + } + + return +} diff --git a/paths/TODO b/paths/TODO new file mode 100644 index 0000000..dab3eff --- /dev/null +++ b/paths/TODO @@ -0,0 +1 @@ +- search criteria should *also* support a timestamp range (e.g. so a search can be restricted to both older than AND newer than; e.g. older than 00:00, newer than 01:00) diff --git a/types_linux.go b/types_linux.go new file mode 100644 index 0000000..2c69a7f --- /dev/null +++ b/types_linux.go @@ -0,0 +1,53 @@ +package sysutils + +import ( + `golang.org/x/sys/unix` +) + +/* +IDState collects information about the current running process. +It should only be used as returned from GetIDState(). +Its methods WILL return false information if any of these values are altered. + +FSUID/FSGID are not supported. +*/ +type IDState struct { + // RUID: Real UID + RUID int + // EUID: Effective UID + EUID int + // SUID: Saved Set UID + SUID int + // RGID: Real GID + RGID int + // EGID: Effective GID + EGID int + // SGID: Saved Set GID + SGID int + // SudoEnvUser is true if SUDO_USER or SUDO_UID is set. + SudoEnvUser bool + // SudoEnvGroup is true if SUDO_GID is set. + SudoEnvGroup bool + // SudoEnvCmd is true if SUDO_COMMAND is set. + SudoEnvCmd bool + // SudoEnvHome is true if SUDO_HOME is set. + SudoEnvHome bool + // SudoEnvVars is true if any of the "well-known" sudo environment variables are set. + SudoEnvVars bool + // PPIDUidMatch is true if the parent PID UID matches the current process UID (mismatch usually indicates sudo invocation). + PPIDUidMatch bool + // PPIDGidMatch is true if the parent PID GID matches the current process GID (mismatch usually indicates sudo invocation). + PPIDGidMatch bool + // uidsChecked is true if the RUID, EUID, and SUID have been populated. (They will be 0 if unset OR if root.) + uidsChecked bool + // gidsChecked is true if the RGID, EGID, and SGID have been populated. (They will be 0 if unset OR if root.) + gidsChecked bool + // sudoChecked is true if the SudoEnvVars is set. + sudoChecked bool + // ppidUidChecked is true if the PPIDUidMatch is set. + ppidUidChecked bool + // ppidGidChecked is true if the PPIDGidMatch is set. + ppidGidChecked bool + // stat holds the stat information for the parent PID. + stat *unix.Stat_t +}